Підрахунок SQL відрізняється від розділу


10

У мене є таблиця з двома стовпцями, я хочу підрахувати різні значення Col_B над (обумовленими) Col_A.

MyTable

Col_A | Col_B 
A     | 1
A     | 1
A     | 2
A     | 2
A     | 2
A     | 3
b     | 4
b     | 4
b     | 5

Очікуваний результат

Col_A   | Col_B | Result
A       | 1     | 3
A       | 1     | 3
A       | 2     | 3
A       | 2     | 3
A       | 2     | 3
A       | 3     | 3
b       | 4     | 2
b       | 4     | 2
b       | 5     | 2

Я спробував наступний код

select *, 
count (distinct col_B) over (partition by col_A) as 'Result'
from MyTable

count (чіткий col_B) не працює. Як я можу переписати функцію підрахунку для підрахунку різних значень?

Відповіді:


18

Ось як я це зробив:

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

Цей GROUP BYпункт є зайвим, враховуючи дані, надані у запитанні, але може дати вам кращий план виконання. Див. Подальші запитання Q & A CROSS APPLY виробляє зовнішнє з'єднання .

Подумайте про голосування за запит на покращення пропозиції OVER - пункт DISTINCT для сукупних функцій на сайті зворотного зв’язку, якщо ви хочете, щоб ця функція була додана до SQL Server.


6

Ви можете емулювати його за допомогою dense_rank, а потім вибрати максимальний ранг для кожного розділу:

select col_a, col_b, max(rnk) over (partition by col_a)
from (
    select col_a, col_b
        , dense_rank() over (partition by col_A order by col_b) as rnk 
    from #mytable
) as t    

Вам потрібно буде виключити будь-які нулі, col_bщоб отримати ті самі результати, що і COUNT(DISTINCT).


6

Це, певним чином, розширення до рішення Леннарта , але воно настільки потворне, що я не смію пропонувати це як редагування. Мета тут - отримати результати без похідної таблиці. Ніколи в цьому не може бути потреби, і в поєднанні з неподобством запиту все починання може здатися марним зусиллям. Я все-таки хотів це зробити як вправу, і зараз хотів би поділитися своїм результатом:

SELECT
  Col_A,
  Col_B,
  DistinctCount = DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
                + DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
                - 1
                - CASE COUNT(Col_B) OVER (PARTITION BY Col_A)
                  WHEN COUNT(  *  ) OVER (PARTITION BY Col_A)
                  THEN 0
                  ELSE 1
                  END
FROM
  dbo.MyTable
;

Основна частина розрахунку полягає в цьому (і я, перш за все, хотів би зазначити, що ідея не моя, я дізнався про цю хитрість в інших місцях):

  DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
+ DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
- 1

Цей вираз може бути використаний без будь-яких змін, якщо значення в Col_Bгарантовано ніколи не матимуть нулів. Однак, якщо стовпець може мати нулі, вам потрібно це врахувати, і саме для цього CASEє вираз. Він порівнює кількість рядків на розділ з кількістю Col_Bзначень на розділ. Якщо цифри відрізняються, це означає, що деякі рядки мають нульове значення, Col_Bі, отже, початковий обчислення ( DENSE_RANK() ... + DENSE_RANK() - 1) потрібно зменшити на 1.

Зауважте, що оскільки - 1це частина основної формули, я вирішив залишити її так. Однак він може бути фактично включений у CASEвираз, у марній спробі зробити все рішення менш потворним:

SELECT
  Col_A,
  Col_B,
  DistinctCount = DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B ASC )
                + DENSE_RANK() OVER (PARTITION BY Col_A ORDER BY Col_B DESC)
                - CASE COUNT(Col_B) OVER (PARTITION BY Col_A)
                  WHEN COUNT(  *  ) OVER (PARTITION BY Col_A)
                  THEN 1
                  ELSE 2
                  END
FROM
  dbo.MyTable
;

Цей демонстраційнийdbfiddle логотип файл на db <> fiddle.uk можна використовувати для тестування обох варіантів рішення.


2
create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)


;with t1 as (

select t.Col_A,
       count(*) cnt
 from (
    select Col_A,
           Col_B,
           count(*) as ct
      from #MyTable
     group by Col_A,
              Col_B
  ) t
  group by t.Col_A
 )

select a.*,
       t1.cnt
  from #myTable a
  join t1
    on a.Col_A = t1.Col_a

1

Альтернативно, якщо у вас м'яка алергія на відповідні запити (відповідь Еріка Дарлінга) та CTE (відповідь Кевіна), як я.

Майте на увазі, що коли нулі кидаються в суміш, жодне з них не може працювати так, як ви хотіли б. (але змінити їх за смаком досить просто)

Простий випадок:

--ignore the existence of nulls
SELECT [mt].*, [Distinct_B].[Distinct_B]
FROM #MyTable AS [mt]

INNER JOIN(
    SELECT [Col_A], COUNT(DISTINCT [Col_B]) AS [Distinct_B]
    FROM #MyTable
    GROUP BY [Col_A]
) AS [Distinct_B] ON
    [mt].[Col_A] = [Distinct_B].[Col_A]
;

Те саме, що вище, але з коментарями щодо того, що потрібно змінити для нульової обробки:

--customizable null handling
SELECT [mt].*, [Distinct_B].[Distinct_B]
FROM #MyTable AS [mt]

INNER JOIN(
    SELECT 

    [Col_A],

    (
        COUNT(DISTINCT [Col_B])
        /*
        --uncomment if you also want to count Col_B NULL
        --as a distinct value
        +
        MAX(
            CASE
                WHEN [Col_B] IS NULL
                THEN 1
                ELSE 0
            END
        )
        */
    )
    AS [Distinct_B]

    FROM #MyTable
    GROUP BY [Col_A]
) AS [Distinct_B] ON
    [mt].[Col_A] = [Distinct_B].[Col_A]
/*
--uncomment if you also want to include Col_A when it's NULL
OR
([mt].[Col_A] IS NULL AND [Distinct_B].[Col_A] IS NULL)
*/
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.