2010-03-30 3 views
2

Мне нужно усреднить некоторые значения в строке -wise fashion, а не столбец -wise fashion. (Если бы я делал средний по столбцам, я мог бы просто использовать avg()). Мое конкретное применение этого требует, чтобы я игнорировал NULL при усреднении. Это довольно простая логика, но в SQL это кажется ужасно трудным. Есть ли элегантный способ делать мои расчеты?Вычисление нескольких столбцов в SQLite3

Я использую 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 

Coalesce каждое значение 0, если NULL:

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 

Но это кажется намного больше программ, чем это должен взять. Есть ли элегантный способ сделать это, о котором я не знаю?

Update:

Если я что-то недоразумение, avg() не будет работать для этого.Пример того, что я хотел бы сделать:

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

Выход:

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

Я думаю, что БД в вашей форме не нормируется, поэтому нет простого способа манипулировать данными с помощью «набора». – munissor

+0

Я обновил свой ответ, чтобы отразить проблемы вашего обновления с помощью 'AVG'. – Welbog

+0

Что касается проблем нормализации, то как данные существуют в текущей базе данных. (Я не проектировал это - серьезно, 144 + столбцы?), Но я должен выбирать свои битвы.) Я мог бы просто укусить пулю и написать что-то, чтобы нормализовать это, чтобы обработать. –

ответ

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 

даст вам то, что вы хотите SUM сольются в 0, если нуль и COUNT не будет считаться NULLs. (желающий SQLite3 соответствует).

EDIT: проверено соответствие http://www.sqlite.org/lang_aggfunc.html и SQLite; если sum() будет переполняться, вы можете использовать total().

Также, если вы не нормализуете свой дизайн таблиц, а затем меняйте нормализацию мнений (и всякий раз, когда вы видите столбцы с цифрами в своих именах, поднимите красный флаг), у вас не будет элегантный SQL.

+0

В итоге я столкнулся с чем-то похожим на то, что вы предложили. (Для других это было '(coalesce (# {question}, 0) + ...)/((случай, когда # {question}> 0 then 1 else 0 end) + ...)'.) Я бы как нормализовать, но на данном этапе это не стоит - это, вероятно, будет в последний раз, когда мы когда-либо будем смотреть на эти данные. (Некоторое время я возвращался и вперед и решил, что в этом случае лучшим вариантом является взлом). –

4

AVG уже игнорирует аннулирует и делает то, что вы хотите:

ср() функция возвращает среднее значение всех не-NULL X внутри группы. Значения String и BLOB, которые не выглядят как числа, интерпретируются как 0. Результат avg() всегда является значением с плавающей точкой, если в нем присутствует хотя бы один не-NULL-вход, даже если все входы являются целыми числами. Результат avg() равен NULL тогда и только тогда, когда нет не-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 а не в среднем по всему миру:

SELECT ID, AVG(value) 
FROM Table 
WHERE question IN (1,2) 
GROUP BY ID 
+0

Если я не понимаю, 'avg()' решает другую проблему. Я обновляю вопрос. –

+0

Он решает другую проблему, поэтому я предлагаю вам преобразовать вашу проблему в ту, что 'AVG' решает, раскручивая таблицу. – Welbog

+0

А, я пропустил это. Спасибо за обновление - похоже, что я искал. –

1

Используйте отдельную таблицу для хранения результатов опроса для разных вопросов (при условии, что q из-за вопроса). Что-то вроде следующих

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

После этого вы можете запустить запрос как

SELECT avg(Rating) WHERE SurveyId=? 
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 столбцов в их собственный стол, с одним вопросом в строке и ссылками на опрос. У вас было бы соотношение между 1 и многими отношениями между опросом и вопросом.

+2

'AVG' определяется стандартом SQL, чтобы игнорировать NULL при вычислении среднего значения столбца. Более того, документация для SQLite четко указывает, что 'AVG' игнорирует NULL, поэтому даже если бы это был не стандарт, он все равно будет применяться здесь. Поэтому, пожалуйста, прекратите распространять дезинформацию, как это. – Welbog

+1

Исправлен ответ, как вы сказали, работает AVG(). –

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 - вы хотите дубликаты, если они существуют.