Сложная группировка - проблема дизайна / производительности - PullRequest
4 голосов
/ 11 августа 2011

ВНИМАНИЕ: Это один БОЛЬШОЙ вопрос


У меня проблема с дизайном, которая началась просто, но на одном этапе роста меня полностью ошарашило.

У простой версии реальности есть хорошая плоская таблица фактов ...
Все имена были изменены, чтобы защитить невинных

CREATE TABLE raw_data (
  tier0_id INT, tier1_id  INT, tier2_id INT, tier3_id INT,
  metric0  INT, metric1   INT, metric2  INT, metric3  INT
)

tierID относятся к объектам в дереве фиксированной глубины. Например, бизнес-иерархия.

Метрики - это просто показатели производительности, такие как количество пойманных лягушек или выпущенных голубей.

В отчете любезно пользователь выбирает что-то вроде следующего:

  • 34 и 55 tier0_id - показаны отдельно
  • все tier1_id - сгруппированы вместе
  • все tier2_id - сгруппированы вместе
  • все tier3_id - показаны отдельно
  • метрики 2 и 3

Это дает мне следующий тип запроса:

SELECT
  CASE WHEN @t0_grouping = 1 THEN NULL ELSE tier0_id END AS tier0_id,
  CASE WHEN @t1_grouping = 1 THEN NULL ELSE tier1_id END AS tier1_id,
  CASE WHEN @t2_grouping = 1 THEN NULL ELSE tier2_id END AS tier2_id,
  CASE WHEN @t3_grouping = 1 THEN NULL ELSE tier3_id END AS tier3_id,
  SUM(metric2) AS metric2, SUM(metric3) AS metric3
FROM
  raw_data
INNER JOIN tier0_values ON tier0_values.id = raw_data.tier0_id OR tier0_values.id IS NULL
INNER JOIN tier1_values ON tier1_values.id = raw_data.tier1_id OR tier1_values.id IS NULL
INNER JOIN tier2_values ON tier2_values.id = raw_data.tier2_id OR tier2_values.id IS NULL
INNER JOIN tier3_values ON tier3_values.id = raw_data.tier3_id OR tier3_values.id IS NULL
GROUP BY
  CASE WHEN @t0_grouping = 1 THEN NULL ELSE tier0_id END,
  CASE WHEN @t1_grouping = 1 THEN NULL ELSE tier1_id END,
  CASE WHEN @t2_grouping = 1 THEN NULL ELSE tier2_id END,
  CASE WHEN @t3_grouping = 1 THEN NULL ELSE tier3_id END

Это хороший гибрид динамического SQL и параметризованных запросов. И да, я знаю, но SQL-CE заставляет людей делать странные вещи. Кроме того, это можно привести в порядок по мере внесения следующих изменений ...


Отныне нам нужно иметь возможность включать NULL в разные уровни. Это будет означать «относится ко ВСЕМ сущностям этого уровня».

Например, со следующими очень упрощенными данными:

Activity    WorkingTime    ActiveTime    BusyTime

   1            0m             10m          0m
   2            0m             15m          0m
   3            0m             20m          0m
  NULL         60m              0m         45m

WorkingTime никогда не применяется к действию, поэтому все значения идут с NULL ID. Но ActiveTime конкретно относится к конкретному виду деятельности, поэтому он имеет законный идентификатор. BusyTime также против действия NULL, потому что это совокупность всех ActiveTime.

Если нужно отчитаться по этим данным, значения NULL - всегда - включаются в каждую строку, потому что NULL - означает - «относится ко всему». Данные будут выглядеть как ...

Activity    WorkingTime    ActiveTime    BusyTime   (BusyOnOtherActivities)

   1           60m             10m         45m            (45-10 = 35m)
   2           60m             15m         45m            (45-15 = 30m)
   3           60m             20m         45m            (45-20 = 25m)

  1&2          60m             25m         45m            (45-25 = 20m)
  1&3          60m             30m         45m            (45-30 = 15m)
  2&3          60m             35m         45m            (45-35 = 10m)

  ALL          60m             45m         45m            (45-45 =  0m)



Надеюсь, этот пример имеет смысл, потому что на самом деле это многоуровневая иерархия (согласно исходному примеру), и на каждом уровне допускаются значения NULL. Поэтому я попробую пример с 3 уровнями ...

t0_id  |  t1_id  |  t2_id   |   m1  |  m2  |  m3  |  m4  |  m5
    1         3        10   |    0     10      0      0      0
    1         4        10   |    0     15      0      0      0
    1         5        10   |    0     20      0      0      0
    1      NULL        10   |   60      0     45      0      0
    2         3        10   |    0      5      0      0      0
    2         5        10   |    0     10      0      0      0
    2         6        10   |    0     15      0      0      0
    2      NULL        10   |   50      0     30      0      0
    1         3        11   |    0      7      0      0      0
    1         4        11   |    0      8      0      0      0
    1         5        11   |    0      9      0      0      0
    1      NULL        11   |   30      0     24      0      0
    2         3        11   |    0      8      0      0      0
    2         5        11   |    0     10      0      0      0
    2         6        11   |    0     12      0      0      0
    2      NULL        11   |   40      0     30      0      0
 NULL      NULL        10   |    0      0      0     60      0
 NULL      NULL        11   |    0      0      0     60      0
 NULL      NULL      NULL   |    0      0      0      0      2

Это дало бы много-много разных возможных выходных записей в отчете, но вот несколько примеров ...

t0_id  |  t1_id  |  t2_id   |   m1  |  m2  |  m3  |  m4  |  m5

    1         3        10   |   60     10     45     60      2
    1         4        10   |   60     15     45     60      2
    1         5        10   |   60     20     45     60      2

    2         3        10   |   50      5     30     60      2
    2         5        10   |   50     10     30     60      2
    2         6        10   |   50     15     30     60      2

    1       ALL        10   |   60     45     45     60      2
    2       ALL        10   |   50     30     30     60      2

  ALL         3        10   |  110     15     75     60      2
  ALL         4        10   |   60     15     45     60      2
  ALL         5        10   |  110     30     75     60      2
  ALL         6        10   |   50     15     30     60      2

  ALL         3       ALL   |  180     30    129    120      2
  ALL         4       ALL   |   90     23     69    120      2
  ALL         5       ALL   |  180     49    129    120      2
  ALL         6       ALL   |   90     27     60    120      2

  ALL       ALL        10   |  110    129    129     60      2
  ALL       ALL        11   |   70    129    129     60      2
  ALL       ALL       ALL   |  180    129    129    120      2

    1       3&4       ALL   |   90     40     69    120      2
  ALL       3&4       ALL   |  180     53    129    120      2


Как бы беспорядочно это ни объяснялось, в моей голове есть полный и логичный смысл. Я понимаю, о чем спрашивают, но, судя по всему, я не могу написать запрос для этого, который не требует мучительных затрат времени.

Итак, как бы вы написали такой запрос и / или реорганизовали схему?

Я ценю, что люди будут спрашивать примеры того, что я сделал до сих пор, но я очень хочу сначала услышать неиспорченные идеи и советы других людей;)

1 Ответ

0 голосов
/ 11 августа 2011

Проблема больше похожа на деятельность по нормализации. Я бы начал с нормализации таблицы на что-то вроде: (вам может понадобиться еще несколько полей идентификации в зависимости от вашего использования)

CREATE TABLE raw_data (
    rawData_ID INT,
    Activity_id INT, 
    metric0  INT)

Я бы создал таблицу уровня, которая выглядит примерно так: (tierplan допускает несколько группировок. Если tier_id не имеет родительского элемента, к которому нужно свернуть, тогда tierparent_id равен NULL. Это позволяет использовать рекурсию в запросе.)

CREATE TABLE tiers (
    tierplan_id INT,
    tier_id INT,
    tierparent_id INT)

Наконец, я бы создал таблицу, которая связывает уровни и действия примерно так:

CREATE TABLE ActivTiers (
    Activplan_id INT, --id on the table
    tierplan_id INT,  --tells what tierplan the raw_data falls under
    rawdata_id INT)   --this allows the ActivityId to be payload instead of identifier.

Запросы на это должны быть "не слишком сложными".

...