В Redshift/Postgres, как подсчитывать строки, удовлетворяющие условию?
Я пытаюсь написать запрос, который подсчитывает только строки, соответствующие условию.
Например, в MySQL я бы написал это следующим образом:
SELECT
COUNT(IF(grade < 70), 1, NULL)
FROM
grades
ORDER BY
id DESC;
Однако, когда я пытаюсь сделать это в Redshift, он возвращает следующую ошибку:
ОШИБКА: функция if (boolean, integer, "unknown" ) не существует
Подсказка: никакая функция не соответствует указанному имени и типам аргументов. Возможно, вам придется добавлять явные типы.
Я проверил документацию для условных операторов, и нашел
NULLIF(value1, value2)
но он только сравнивает value1 и value2, и если такие значения равны, он возвращает null.
Я не мог найти простой оператор IF, и на первый взгляд я не мог найти способ делать то, что хочу.
Я попытался использовать выражение CASE, но я не получаю результаты, которые хочу:
SELECT
CASE
WHEN grade < 70 THEN COUNT(rank)
ELSE COUNT(rank)
END
FROM
grades
Вот так я хочу считать вещи:
и я ожидаю увидеть результаты:
+========+=========+======+===========+
| failed | average | good | excellent |
+========+=========+======+===========+
| 4 | 2 | 1 | 4 |
+========+=========+======+===========+
но я получаю следующее:
+========+=========+======+===========+
| failed | average | good | excellent |
+========+=========+======+===========+
| 11 | 11 | 11 | 11 |
+========+=========+======+===========+
Я надеюсь, что кто-то может указать мне в правильном направлении!
Если это поможет здесь несколько примеров информации
CREATE TABLE grades(
grade integer DEFAULT 0,
);
INSERT INTO grades(grade) VALUES(69, 50, 55, 60, 75, 70, 87, 100, 100, 98, 94);
Ответы
Ответ 1
Во-первых, проблема, с которой вы сталкиваетесь, заключается в том, что вы говорите: "Если класс меньше 70, значение этого выражения выражает счет (ранг). В противном случае значение этого выражения подсчитывается (ранг)." Таким образом, в любом случае вы всегда получаете одинаковое значение.
SELECT
CASE
WHEN grade < 70 THEN COUNT(rank)
ELSE COUNT(rank)
END
FROM
grades
count() учитывает только ненулевые значения, так что обычно шаблон, который вы увидите для выполнения того, что вы пытаетесь, таков:
SELECT
count(CASE WHEN grade < 70 THEN 1 END) as grade_less_than_70,
count(CASE WHEN grade >= 70 and grade < 80 THEN 1 END) as grade_between_70_and_80
FROM
grades
Таким образом, выражение case будет оцениваться только до 1, когда тестовое выражение истинно и в противном случае будет null. Затем count() будет считать только ненулевые экземпляры, т.е. Когда тестовое выражение истинно, что должно дать вам то, что вам нужно.
Изменить: в качестве примечания обратите внимание, что это точно так же, как и то, как вы изначально писали это с помощью count(if(test, true-value, false-value))
, только переписанным как count(case when test then true-value end)
(и null - это позиция в ложном значении, так как else
не был доставлен в корпус).
Изменить: postgres 9.4 был выпущен через несколько месяцев после этого первоначального обмена. В этой версии появились агрегированные фильтры, которые могут сделать сценарии, подобные этому, немного лучше и понятнее. Этот ответ по-прежнему получает некоторые случайные варианты, поэтому, если вы наткнулись на них и используете более новые postgres (т.е. 9.4+), вы можете рассмотреть эту эквивалентную версию:
SELECT
count(*) filter (where grade < 70) as grade_less_than_70,
count(*) filter (where grade >= 70 and grade < 80) as grade_between_70_and_80
FROM
grades
Ответ 2
Другой метод:
SELECT
sum(CASE WHEN grade < 70 THEN 1 else 0 END) as grade_less_than_70,
sum(CASE WHEN grade >= 70 and grade < 80 THEN 1 else 0 END) as grade_between_70_and_80
FROM
grades
Работает отлично, если вы хотите группировать подсчеты с помощью категориального столбца.
Ответ 3
Решение, данное @yieldsfalsehood, работает отлично:
SELECT
count(*) filter (where grade < 70) as grade_less_than_70,
count(*) filter (where grade >= 70 and grade < 80) as grade_between_70_and_80
FROM
grades
Но так как вы говорили о NULLIF(value1, value2)
, есть способ с nullif, который может дать тот же результат:
select count(nullif(grade < 70 ,true)) as failed from grades;
Ответ 4
Только красное смещение
Для ленивых печатных машин здесь COUNTIF
целочисленная версия приведения к сумме " COUNTIF
", построенная поверх ответа @user1509107:
SELECT
SUM((grade < 70)::INT) AS grade_less_than_70,
SUM((grade >= 70 AND grade < 80)::INT) AS grade_between_70_and_80
FROM
grades