Как создать" пустые " агрегированные результаты в SQL

Я пытаюсь уточнить SQL-запрос, чтобы мои отчеты выглядели лучше. Мой запрос считывает данные из одной таблицы, группирует по нескольким colums и вычисляет некоторые агрегированные поля (подсчеты и суммы).

SELECT A, B, C, COUNT(*), SUM(D) FROM T
GROUP BY A, B, C
ORDER BY A, B, C

теперь предположим, что столбцы B и C являются определенными постоянными строками, например, B может быть 'B1' или 'B2', может быть 'C1' или 'C2'. Итак, пример resultset:

A  | B  | C  | COUNT(*) | SUM(D)
--------------------------------
A1 | B1 | C1 |       34 |   1752
A1 | B1 | C2 |        4 |    183
A1 | B2 | C1 |      199 |   8926
A1 | B2 | C2 |       56 |   2511
A2 | B1 | C2 |        6 |     89
A2 | B2 | C2 |       12 |    231
A3 | B1 | C1 |       89 |    552
...

как вы можете видеть, для 'A1' у меня есть все четыре возможных (B, C) комбинации, но это не так для 'A2'. Мой вопрос: как я могу генерировать также сводные строки для комбинации (B, C), не присутствующей, по сути, в данной таблице? То есть, как я могу напечатать, например, и эти строки:

A  | B  | C  | COUNT(*) | SUM(D)
--------------------------------
A2 | B1 | C1 |        0 |      0
A2 | B2 | C1 |        0 |      0

единственное решение, которое я вижу, - создать некоторые вспомогательные таблицы со всеми значениями (B, C), а затем сделать правое внешнее соединение с этой таблицей aux. Но я ищу более чистый способ...

спасибо всем.

3 ответов


вспомогательная таблица не должна быть реальной таблицей, она может быть общим табличным выражением - по крайней мере, если вы можете получить все возможные значения (или все, что вас интересует) из самой таблицы. Используя запрос @Bob Jarvis для генерации всех возможных комбинаций, вы можете сделать что-то вроде:

WITH CTE AS (
    SELECT * FROM (SELECT DISTINCT a FROM T)
    JOIN (SELECT DISTINCT b, c FROM T) ON (1 = 1)
)
SELECT CTE.A, CTE.B, CTE.C,
    SUM(CASE WHEN T.A IS NULL THEN 0 ELSE 1 END), NVL(SUM(T.D),0)
FROM CTE
LEFT JOIN T ON T.A = CTE.A AND T.B = CTE.B AND T.C = CTE.C
GROUP BY CTE.A, CTE.B, CTE.C
ORDER BY CTE.A, CTE.B, CTE.C;

Если у вас есть фиксированные значения, которые могут не быть в таблице, то это немного сложнее (или уродливее в любом случае, и становится хуже с более возможными значениями):

WITH CTE AS (
    SELECT * FROM (SELECT DISTINCT a FROM T)
    JOIN (SELECT 'B1' AS B FROM DUAL
        UNION ALL SELECT 'B2' FROM DUAL) ON (1 = 1)
    JOIN (SELECT 'C1' AS C FROM DUAL
        UNION ALL SELECT 'C2' FROM DUAL) ON (1 = 1)
)
SELECT CTE.A, CTE.B, CTE.C,
    SUM(CASE WHEN T.A IS NULL THEN 0 ELSE 1 END), NVL(SUM(T.D),0)
FROM CTE
LEFT JOIN T ON T.A = CTE.A AND T.B = CTE.B AND T.C = CTE.C
GROUP BY CTE.A, CTE.B, CTE.C
ORDER BY CTE.A, CTE.B, CTE.C;

но вы должны присоединиться к что-то что знает о недостающих ценностей. Если та же логика нужна в другом месте, и у вас есть фиксированные значения, то постоянная таблица может быть чище - обслуживание может потребоваться в любом случае. Вы также можете рассмотреть конвейерную функцию в качестве суррогатной таблицы, но, возможно, зависит от томов.


дело в том, что если у вас нет определенной комбинации в вашей базе данных, как бы двигатель знал, чтобы включить эту комбинацию в результаты? Чтобы иметь все комбинации в результатах, вам нужно иметь все доступные комбинации-будь то в главной таблице или в какой-либо другой таблице, используемой для ссылки. Например, вы можете создать другую таблицу R с такими данными:

A  | B  | C  
------------
A1 | B1 | C1
A1 | B1 | C2
A1 | B2 | C1
A1 | B2 | C2
A2 | B1 | C1
A2 | B1 | C2
A2 | B2 | C1
A2 | B2 | C2
A3 | B1 | C1
A3 | B1 | C2
A3 | B1 | C1
A3 | B2 | C2
...

и тогда ваш запрос будет выглядеть следующим образом:

SELECT r.*, COUNT(t.d), coalesce(SUM(t.d), 0)
FROM r LEFT OUTER JOIN t on (r.a=t.a and r.b=t.b and r.c=t.c)
GROUP BY r.a, r.b, r.c
ORDER BY r.a, r.b, r.c

это верните вам набор, как вы хотите с 0 | 0 для комбинации, которая не существует в главной таблице. Обратите внимание, что это возможно только в том случае, если вы знаете все возможные комбинации, которые хотите включить, что может быть не всегда так.

если, с другой стороны, ваши A, B, C являются числовыми значениями, и вы просто хотите включить все числа в диапазон, тогда может быть другой способ справиться с этим, что-то вроде этого:

SELECT a.n, b.n, c.n, COUNT(t.d), coalesce(SUM(t.d), 0)
FROM (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_a CONNECT BY LEVEL <= end_a) a,
     (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_b CONNECT BY LEVEL <= end_b) b,
     (SELECT (rownum) "n" FROM DUAL WHERE LEVEL >= start_c CONNECT BY LEVEL <= end_c) c,
     t
WHERE a.n = t.a(+) AND b.n = t.b(+) AND c.n = t.c(+)
GROUP BY a.n, b.n, c.n
ORDER BY a.n, b.n, c.n

(у меня нет экземпляра Oracle для тестирования это, так что это скорее несколько обоснованная догадка, чем что-либо еще.)

суть в том, что двигатель должен знать, что включать в окончательные результаты - так или иначе.


вероятно, есть более красивые способы сделать это, но следующее должно заставить вас начать к тому, что вы хотите:

SELECT * FROM 
  (SELECT DISTINCT a FROM T)
JOIN
  (SELECT DISTINCT b, c FROM T)
  ON (1 = 1)
ORDER BY a, b, c

это даст вам все комбинации, которые существуют B и C, вместе со всеми A, которые существуют, подобно

A1  B1  C1
A1  B1  C2
A1  B2  C1
A1  B2  C2
A2  B1  C1
A2  B1  C2
A2  B2  C1
A2  B2  C2

поделиться и наслаждаться.