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гарантированно никогда не будут иметь нулевые значения. Однако, если столбец может иметь значения NULL, вам необходимо учесть это, и именно для этого и существует 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
;

Это живое демо на логотип dbfiddledb <> 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.