2

我需要以逐行方式而不是逐方式平均一些值。(如果我要按列进行平均,我可以使用avg())。我对此的具体应用要求我在平均时忽略 NULL。这是非常简单的逻辑,但在 SQL 中似乎很难做到。有没有一种优雅的方式来做我的计算?

我正在使用 SQLite3,因为它的价值。

细节

如果您需要更多详细信息,这里有一个插图:

我有一张带有调查表的表格:

| q1 | q2    | q3    | ... | q144 |
|----|-------|-------|-----|------|
| 1  | 3     | 7     | ... | 2    |
| 4  | 2     | NULL  | ... | 1    |
| 5  | NULL  | 2     | ... | 3    |

(这些只是一些示例值和简单的列名。有效值为 1 到 7 和 NULL。)

我需要像这样计算一些平均值:

q7 + q33 + q38 + q40 + ... + q119 / 11 as domain_score_1
q10 + q11 + q34 + q35 + ... + q140 / 13 as domain_score_2
...
q2 + q5 + q13 + q25 + ... + q122 / 12 as domain_score_14

...但我需要根据非空值提取空值和平均值。所以,对于domain_score_1(有 11 项),我需要做:

Input:  3, 5, NULL, 7, 2, NULL, 3, 1, 5, NULL, 1

(3 + 5 + 7 + 2 + 3 + 1 + 5 + 1) / (11 - 3)
27 / 8
3.375

我正在考虑的一个简单算法是:

输入:

3, 5, NULL, 7, 2, NULL, 3, 1, 5, NULL, 1 

如果为 NULL,则将每个值合并为 0:

3, 5, 0, 7, 2, 0, 3, 1, 5, 0, 1

和:

27

通过将 > 0 的值转换为 1 并求和来获取非零的数量:

3, 5, 0, 7, 2, 0, 3, 1, 5, 0, 1
1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1
8

将这两个数字相除

27 / 8
3.375

但这似乎比这需要更多的编程。有没有一种我不知道的优雅方法?

更新:

除非我误解了什么,avg()否则不会为此工作。我想做的例子:

select avg(q7, q33, q38, ..., q119) from survey;

输出:

SQL error near line 3: wrong number of arguments to function avg()
4

5 回答 5

4

AVG已经忽略空值并执行您想要的操作:

avg() 函数返回组内所有非 NULL X 的平均值。看起来不像数字的字符串和 BLOB 值被解释为 0。 avg() 的结果始终是浮点值,只要至少有一个非 NULL 输入,即使所有输入都是整数。当且仅当没有非 NULL 输入时,avg() 的结果为 NULL。

来自http://www.sqlite.org/lang_aggfunc.html

因此,您可能可以获取每个域的值并将它们加载到另一个表中,然后在该表上运行平均值。或者,您也可以只旋转您的宽表并在其上计算平均值。


AVG适用于列,而不是行。因此,如果您不旋转您的表,您可以使用AVG并且不会遇到您面临的问题。让我们看一个小例子:

你有一张桌子,它看起来像这样:

ID  | q1  | q2  | q3
----------------------
1   | 1   | 2   | NULL
2   | NULL| 2   | 56

您想将 q1 和 q2 平均在一起,因为它们在同一个域中,但它们是单独的列,所以您不能。但是,如果您将表格更改为如下所示:

ID  | question | value
-----------------------
1   | 1        | 1
1   | 2        | 2
1   | 3        | NULL
2   | 1        | NULL
2   | 2        | 2
2   | 3        | 56

然后你可以很容易地取两个问题的平均值:

SELECT AVG(value)
FROM Table
WHERE question IN (1,2)

如果您想要每个 ID 的平均值而不是全局平均值,则可以按 ID 分组:

SELECT ID, AVG(value)
FROM Table
WHERE question IN (1,2)
GROUP BY ID
于 2010-03-30T15:37:39.087 回答
4

在标准 SQL 中

SELECT 
(SUM(q7)+SUM(q33)+SUM(q38)+SUM(q40)+..+SUM(q119))/
(COUNT(q7)+COUNT(q33)+COUNT(q38)+COUNT(q40)+..+COUNT(q119)) AS domain_score1 
FROM survey

会给你你想要的东西 如果 null 和 COUNT 不会计算 NULL,SUM 将合并为 0。(希望 SQLite3 符合)。

编辑:检查了http://www.sqlite.org/lang_aggfunc.html和 SQLite 符合;如果 sum() 将溢出,您可以使用 total() 代替。

此外,我支持重新规范化的意见,如果您不规范化表设计(并且每当您看到名称中带有数字的列都会引发红旗),您将不会拥有优雅的 SQL。

于 2010-03-30T16:06:01.077 回答
2

这将是一个可怕的查询,但你可以这样做:

SELECT AVG(q) FROM
((SELECT q7 AS q FROM survey) UNION ALL
(SELECT q33 FROM survey) UNION ALL
(SELECT q38 FROM survey) UNION ALL
...
(SELECT q119 FROM survey))

这会将您的列转换为行并使用该AVG()函数。

当然,您可能只希望将其用于特定的调查记录,因此不要忘记 WHERE 子句:

SELECT AVG(q) FROM
((SELECT q7 AS q FROM survey WHERE survey_id = 1) UNION ALL
(SELECT q33 FROM survey WHERE survey_id = 1) UNION ALL
(SELECT q38 FROM survey WHERE survey_id = 1) UNION ALL
...
(SELECT q119 FROM survey WHERE survey_id = 1))

如果您将 q 列标准化为它们自己的表,每行一个问题,并且引用回调查,那么您的时间会容易得多。您将在调查和问题之间建立一对多的关系。

于 2010-03-30T15:53:09.973 回答
1

使用单独的表来存储不同问题的调查分数(假设 q 是因为问题)。像下面这样的东西

SurveyTable(SurveyId, ...)
SurveyRatings(SurveyId, QuestionId, Rating)

之后,您可以运行查询

SELECT avg(Rating) WHERE SurveyId=?
于 2010-03-30T15:50:08.280 回答
0

采用:

SELECT AVG(x.answer)
  FROM (SELECT s.q7 AS answer
          FROM SURVEY s
        UNION ALL
        SELECT s.q33
          FROM SURVEY s
        UNION ALL    
       SELECT s.q38
         FROM SURVEY s
       ...
       UNION ALL
       SELECT s.q119
         FROM SURVEY s) x

不要使用UNION- 如果存在重复项,您需要重复项。

于 2010-03-30T16:19:24.897 回答