Как я могу эффективно транспонировать и суммировать группу столбцов в SQL? - PullRequest
0 голосов
/ 12 февраля 2020

Допустим, у меня есть некоторые данные, которые выглядят так:

+--------+-----------+-------------+----------+------------+
| person | red_apple | green_apple | red_pear | green_pear |
+--------+-----------+-------------+----------+------------+
| bill   | 4         | 1           | 1        | 4          |
| bill   | 0         | 1           | 2        | 0          |
| jill   | 2         | 1           | 1        | 4          |
| jill   | 0         | 0           | 2        | 0          |
+--------+-----------+-------------+----------+------------+

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

+--------+-------+-------+-------+
| person | color | fruit | value |
+--------+-------+-------+-------+
| bill   | red   | apple | 4     |
| jill   | red   | apple | 2     |
| bill   | green | apple | 2     |
| jill   | green | apple | 1     |
| bill   | red   | pear  | 3     |
| jill   | red   | pear  | 3     |
| bill   | green | pear  | 4     |
| jill   | green | pear  | 4     |
+--------+-------+-------+-------+

Как я могу сделать такую ​​вещь? Допустим, на практике на самом деле гораздо больше фруктов, цветов и людей, чем я перечислил для этого простого примера. Я пытаюсь решить две проблемы: 1) сделать SQL не волосатым зверем для чтения и обслуживания, и 2) сделать запрос эффективным, так как фактическая базовая таблица велика.

Лучшее, что у меня есть пока что что-то вроде этого, с использованием макросов:

DEFINE MACRO GROUP_SUM (
  SELECT
    person,
    "$1" as color,
    "$2" as fruit,
    SUM($1_$2) as value
  FROM source_table
  GROUP BY 1, 2
);

DEFINE MACRO ALL_COLORS $GROUP_SUM(red,$1) UNION ALL $GROUP_SUM(green,$1);

SELECT
  person,
  color,
  fruit,
  SUM(value) as value
FROM
  ($ALL_COLORS(apple)
   UNION ALL $ALL_COLORS(pear))
GROUP BY 1, 2, 3;

Это нормально с точки зрения ремонтопригодности, но когда мы получаем 5 или 10 цветов и фруктов, я предполагаю, исходя из количества времени, которое требуется для выполнить запрос и мои предположения о том, как работают подзапросы, и мы в конечном итоге повторно сканируем таблицу несколько раз.

Есть ли способ повысить эффективность этого запроса?

Ответы [ 2 ]

1 голос
/ 12 февраля 2020

Попробуйте выполнить дезагрегацию и агрегирование один раз:

select person, color, fruit, sum(value)
from ((select person, 'red' as color, 'apple' as fruit, red_apple as value
       from t
      ) union all
      (select person, 'green' as color, 'apple' as fruit, green_apple
       from t
      ) union all
      (select person, 'red' as color, 'pear' as fruit, red_pear
       from t
      ) union all
      (select person, 'green' as color, 'pear' as fruit, green_pear
       from t
      )
     ) ap
group by person, color, fruit;

В зависимости от ваших данных, это должно быть быстрее, чем три отдельных агрегации.

Могут быть более быстрые методы, в зависимости от вашей базы данных.

0 голосов
/ 12 февраля 2020

Ниже для BigQuery Standard SQL

#standardSQL
SELECT 
  person,
  REGEXP_EXTRACT(SPLIT(kv, ':')[OFFSET(0)], r'"(.+)_') AS color, 
  REGEXP_EXTRACT(SPLIT(kv, ':')[OFFSET(0)], r'_(.+)"') AS fruit, 
  SUM(CAST(SPLIT(kv, ':')[OFFSET(1)] AS INT64)) AS value 
FROM `project.dataset.table` t,
UNNEST(REGEXP_EXTRACT_ALL(TO_JSON_STRING(t), r',(".+?":.+?)')) kv
GROUP BY person, color, fruit   

, если применить к образцу данных в вашем вопросе - результат

enter image description here

Вы можете выполнить тестирование с помощью CTE, созданной на основе данных образца, как показано в примере ниже

#standardSQL
WITH `project.dataset.table` AS (
  SELECT 'bill' person, 4 red_apple, 1 green_apple, 1 red_pear, 4 green_pear UNION ALL
  SELECT 'bill', 0, 1, 2, 0 UNION ALL
  SELECT 'jill', 2, 1, 1, 4 UNION ALL
  SELECT 'jill', 0, 0, 2, 0 
)
SELECT 
  person,
  REGEXP_EXTRACT(SPLIT(kv, ':')[OFFSET(0)], r'"(.+)_') AS color, 
  REGEXP_EXTRACT(SPLIT(kv, ':')[OFFSET(0)], r'_(.+)"') AS fruit, 
  SUM(CAST(SPLIT(kv, ':')[OFFSET(1)] AS INT64)) AS value 
FROM `project.dataset.table` t,
UNNEST(REGEXP_EXTRACT_ALL(TO_JSON_STRING(t), r',(".+?":.+?)')) kv
GROUP BY person, color, fruit
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...