Суммируйте отдельные повторяющиеся строки - PullRequest
0 голосов
/ 08 октября 2019

Сводка tl / dr : 3 таблицы с иерархическими отношениями, числовое поле на среднем уровне, нуждаются в сумме этого числа без дублирования из-за более низкого уровня, ища альтернативу с использованием функций OLAPв DB2.

Это несколько пересматривает эти две темы ( SUM (DISTINCT), основанные на других столбцах и Суммарные значения, основанные на различных направляющих ) - но я сталкиваюськак отдельная тема, потому что мне интересно, есть ли способ сделать это с помощью функций OLAP.

Я работаю в DB2. Сценарий (не фактические таблицы, из-за конфиденциальности клиента):

   Table: NEIGHBORHOOD, field NEIGHBORHOOD_NAME
   Table: HOUSEHOLD, fields NEIGHBORHOOD_NAME, HOUSEHOLD_NAME, and HOUSEHOLD_INCOME
   Table: HOUSEHOLD_MEMBER, fields HOUSEHOLD_NAME, PERSON_NAME

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

 Shady Acres, 123 Shady Lane, 25000, Jane
 Shady Acres, 123 Shady Lane, 25000, Mary
 Shady Acres, 123 Shady Lane, 25000, Robert
 Shady Acres, 126 Shady Lane, 15000, George
 Shady Acres, 126 Shady Lane, 15000, Tom
 Shady Acres, 126 Shady Lane, 15000, Betsy
 Shady Acres, 126 Shady Lane, 15000, Timmy

Если я хочу

    Shady Acres, 123 Shady Lane, 25000, 3  (household income, count of members)
    Shady Acres, 125 Shady Lane, 15000, 4

, это не проблема:

SELECT N.NEIGHBORHOOD_NAME, H.HOUSEHOLD_NAME, H.HOUSEHOLD_INCOME, count(1)
from NEIGHBORHOOD N join HOUSEHOLD H on N.HOUSEHOLD_NAME = H.HOUSEHOLD_NAME
join HOUSEHOLD_MEMBER M on H.HOUSEHOLD_NAME = M.HOUSEHOLD_NAME
group by N.NEIGHBORHOOD_NAME, H.HOUSEHOLD_NAME, H.HOUSEHOLD_INCOME

Однако, если я хочу

   Shady Acres, 2, 40000, 7 (i.e. neighborhood, number of households, sum of income, count of members)

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

Лучшее, что я получил на данный момент, это

select NEIGHBORHOOD.NEIGHBORHOOD_NAME,
count(distinct HOUSEHOLD.HOUSEHOLD_NAME) household_Count,
sum(distinct HOUSEHOLD.HOUSEHOLD_INCOME) total_income,
count(1) household_members group by N.NEIGHBORHOOD_NAME

Это не сработает, если у вас, конечно, два домохозяйства с одинаковым доходом. Я был искренне удивлен, что «сумма (отличная)» даже работала, так как это просто не имеет смысла для меня.

Я попытался

sum(household_income) over (partition by household.household_name) 

, и он выдал ошибку:

Выражение, начинающееся с«HOUSEHOLD_INCOME», указанный в предложении SELECT, предложении HAVING или ORDERПредложение BY не указано в предложении GROUP BY илиВ предложении SELECT, предложении HAVING или предложении ORDER BYс функцией столбца и без указания предложения GROUP BY. SQLCODE = -119, SQLSTATE = 42803, DRIVER = 4 .19 .56

Попытка добавить в группу HOUSEHOLD_INCOME или HOUSEHOLD_NAME приводит к неверным результатам, поскольку мы не хотим разбивать их по этим полям.

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

Ответы [ 4 ]

0 голосов
/ 09 октября 2019

Следующий запрос возвращает нужный вам результат:

WITH 
  NEIGHBORHOOD(NEIGHBORHOOD_NAME) AS 
(
VALUES ('Shady Acres')
)
, HOUSEHOLD (NEIGHBORHOOD_NAME, HOUSEHOLD_NAME, HOUSEHOLD_INCOME) AS 
(
VALUES 
  ('Shady Acres', '123 Shady Lane', 25000)
, ('Shady Acres', '126 Shady Lane', 15000) 
)
, HOUSEHOLD_MEMBER (HOUSEHOLD_NAME, PERSON_NAME) AS
(
VALUES
      ('123 Shady Lane', 'Jane'  )
     ,('123 Shady Lane', 'Mary'  )
     ,('123 Shady Lane', 'Robert')
     ,('126 Shady Lane', 'George')
     ,('126 Shady Lane', 'Tom'   )
     ,('126 Shady Lane', 'Betsy' )
     ,('126 Shady Lane', 'Timmy' )    
)
, TMP AS 
(
SELECT 
  N.NEIGHBORHOOD_NAME
, CASE WHEN H.HOUSEHOLD_NAME = LAG(H.HOUSEHOLD_NAME) OVER (PARTITION BY N.NEIGHBORHOOD_NAME ORDER BY H.HOUSEHOLD_NAME) THEN 0 ELSE 1 END AS HOUSEHOLD_NAME_CHANGED
, H.HOUSEHOLD_INCOME
FROM NEIGHBORHOOD N
JOIN HOUSEHOLD H ON H.NEIGHBORHOOD_NAME = N.NEIGHBORHOOD_NAME
JOIN HOUSEHOLD_MEMBER M ON M.HOUSEHOLD_NAME = H.HOUSEHOLD_NAME
)
SELECT 
  NEIGHBORHOOD_NAME
, SUM(HOUSEHOLD_NAME_CHANGED) AS HOUSEHOLD_NAMES
, SUM(HOUSEHOLD_NAME_CHANGED * HOUSEHOLD_INCOME) AS HOUSEHOLD_INCOME
, COUNT(1) AS MEMBERS
FROM TMP
GROUP BY NEIGHBORHOOD_NAME;
0 голосов
/ 08 октября 2019

Другим вариантом может быть

with base (NEIGHBORHOOD_NAME,HOUSEHOLD_NAME, HOUSEHOLD_INCOME, HOUSEHOLD_MEMBER) as (
 values ('Shady Acres', '123 Shady Lane', 25000, 'Jane')
,( 'Shady Acres', '123 Shady Lane', 25000, 'Mary')
,( 'Shady Acres', '123 Shady Lane', 25000, 'Robert')
,( 'Shady Acres', '126 Shady Lane', 15000, 'George')
,( 'Shady Acres', '126 Shady Lane', 15000, 'Tom')
,( 'Shady Acres', '126 Shady Lane', 15000, 'Betsy')
,( 'Shady Acres', '126 Shady Lane', 15000, 'Timmy')
)
, temp as (
select NEIGHBORHOOD_NAME,HOUSEHOLD_NAME, HOUSEHOLD_INCOME, HOUSEHOLD_MEMBER
     , row_number() over (partition by NEIGHBORHOOD_NAME,HOUSEHOLD_NAME order by HOUSEHOLD_MEMBER) as rownum_asc
     , row_number() over (partition by NEIGHBORHOOD_NAME,HOUSEHOLD_NAME order by HOUSEHOLD_MEMBER desc) as rownum_desc
FROM base
)
SELECT NEIGHBORHOOD_NAME, sum(HOUSEHOLD_INCOME) as TOTAL_INCOME, sum(rownum_desc) as member_count

  FROM temp
 WHERE rownum_asc = 1
 GROUP BY NEIGHBORHOOD_NAME

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

Функции OLAP не уменьшают количество строк - это различие между мэрами и часто преимущество функций OLAP по сравнению с функциями GROUP BY и функциями столбцов. Но с вами уплощенный базовый стол необходим GROUP BY.

0 голосов
/ 09 октября 2019

Так что это было бы очень хакерским решением, которое будет работать до тех пор, пока вы не получите коллизий хеш-кода для ключа денормализованных / дублированных данных, и у вас не будет больше дубликатов, чем количество десятичных разрядов, которые я выдвинулЗначение HASH в стороне для SUM ()

WITH NEIGHBORHOOD(NEIGHBORHOOD_NAME) AS (VALUES ('Shady Acres'))
,   HOUSEHOLD (NEIGHBORHOOD_NAME, HOUSEHOLD_NAME, HOUSEHOLD_INCOME)
AS (VALUES 
   ('Shady Acres', '123 Shady Lane', 25000)
  ,('Shady Acres', '126 Shady Lane', 25000) 
  )
, HOUSEHOLD_MEMBER ( HOUSEHOLD_NAME, PERSON_NAME )
AS(VALUES
      ('123 Shady Lane', 'Jane'  )
     ,('123 Shady Lane', 'Mary'  )
     ,('123 Shady Lane', 'Robert')
     ,('126 Shady Lane', 'George')
     ,('126 Shady Lane', 'Tom'   )
     ,('126 Shady Lane', 'Betsy' )
     ,('126 Shady Lane', 'Timmy' )    
)
SELECT
    NEIGHBORHOOD_NAME
,   COUNT(DISTINCT HOUSEHOLD_NAME  )  AS HOUSEHOLD_COUNT
,   BIGINT(SUM(DISTINCT DECFLOAT(HOUSEHOLD_INCOME || '.000000' || ABS(HASH4(HOUSEHOLD_NAME))))) AS TOTAL_INCOME
,   COUNT(1)                          AS HOUSEHOLD_MEMBERS
FROM NEIGHBORHOOD N
JOIN HOUSEHOLD    H     USING ( NEIGHBORHOOD_NAME )
JOIN HOUSEHOLD_MEMBER M USING ( HOUSEHOLD_NAME )
GROUP BY N.NEIGHBORHOOD_NAME

, который возвращает

 NEIGHBORHOOD_NAME  HOUSEHOLD_COUNT     TOTAL_INCOME    HOUSEHOLD_MEMBERS
 -----------------  ---------------     ------------    -----------------
 Shady Acres                      2        50000                    7

Примечание. Я сделал доход двух домохозяйств одинаковым, чтобы доказать, что это решение работает в том, чтосценарий

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

0 голосов
/ 08 октября 2019

Я согласен, что это невозможно без подзапроса, если вы хотите использовать функции OLAP

Сопутствующий суб-выбор будет работать, но неэлегатен, неэффективен и скорее всего не тот, который вам нужен

WITH NEIGHBORHOOD(NEIGHBORHOOD_NAME) AS (VALUES ('Shady Acres'))
,   HOUSEHOLD (NEIGHBORHOOD_NAME, HOUSEHOLD_NAME, HOUSEHOLD_INCOME)
AS (VALUES 
   ('Shady Acres', '123 Shady Lane', 25000)
  ,('Shady Acres', '126 Shady Lane', 15000) 
  )
, HOUSEHOLD_MEMBER ( HOUSEHOLD_NAME, PERSON_NAME )
AS(VALUES
      ('123 Shady Lane', 'Jane'  )
     ,('123 Shady Lane', 'Mary'  )
     ,('123 Shady Lane', 'Robert')
     ,('126 Shady Lane', 'George')
     ,('126 Shady Lane', 'Tom'   )
     ,('126 Shady Lane', 'Betsy' )
     ,('126 Shady Lane', 'Timmy' )    
)
SELECT
    NEIGHBORHOOD_NAME
,   COUNT(DISTINCT HOUSEHOLD_NAME  )  AS HOUSEHOLD_COUNT
--,   SUM(DISTINCT   HOUSEHOLD_INCOME)  AS TOTAL_INCOME       -- not valid if two househols have the same income
--,   SUM(HOUSEHOLD_INCOME) OVER (PARTITION BY HOUSEHOLD_NAME)  -- not valid unless we GROUP BY HOUSEHOLD_NAME in the main body
,   SUM( (SELECT SUM(S.HOUSEHOLD_INCOME) FROM HOUSEHOLD S 
            WHERE S.HOUSEHOLD_NAME = H.HOUSEHOLD_NAME
            AND   M.PERSON_NAME = (SELECT MAX(SS.PERSON_NAME) 
                                   FROM HOUSEHOLD_MEMBER SS
                                   WHERE SS.HOUSEHOLD_NAME = H.HOUSEHOLD_NAME))
           )            AS TOTAL_INCOME
,   COUNT(1)                          AS HOUSEHOLD_MEMBERS
FROM NEIGHBORHOOD N
JOIN HOUSEHOLD    H     USING ( NEIGHBORHOOD_NAME )
JOIN HOUSEHOLD_MEMBER M USING ( HOUSEHOLD_NAME )
GROUP BY N.NEIGHBORHOOD_NAME

возврат

 NEIGHBORHOOD_NAME  HOUSEHOLD_COUNT     TOTAL_INCOME    HOUSEHOLD_MEMBERS
 -----------------  ---------------     ------------    -----------------
 Shady Acres                      2     40000           7
...