SQL menghitung partisi berbeda

10

Saya punya tabel dengan dua kolom, saya ingin menghitung nilai yang berbeda pada Col_B lebih dari (dikondisikan oleh) 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

Hasil yang diharapkan

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

Saya mencoba kode berikut

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

count (col_B berbeda) tidak berfungsi. Bagaimana saya bisa menulis ulang fungsi penghitungan untuk menghitung nilai yang berbeda?

sara92
sumber

Jawaban:

18

Beginilah cara saya melakukannya:

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;

The GROUP BYklausa berlebihan mengingat data yang disediakan dalam pertanyaan, tetapi mungkin memberi Anda rencana eksekusi yang lebih baik. Lihat T&J CROSS BERLAKU tindak lanjut menghasilkan gabungan luar .

Pertimbangkan memilih untuk permintaan peningkatan klausa OVER - klausa DISTINCT untuk fungsi agregat di situs umpan balik jika Anda ingin fitur itu ditambahkan ke SQL Server.

Erik Darling
sumber
6

Anda dapat menirunya dengan menggunakan dense_rank, dan kemudian memilih peringkat maksimum untuk setiap partisi:

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    

Anda harus mengecualikan nulls dari col_buntuk mendapatkan hasil yang sama dengan COUNT(DISTINCT).

Lennart
sumber
6

Ini, dengan cara tertentu, merupakan perluasan dari solusi Lennart , tetapi sangat jelek sehingga saya tidak berani menyarankannya sebagai hasil edit. Tujuannya di sini adalah untuk mendapatkan hasil tanpa tabel turunan. Mungkin tidak pernah ada kebutuhan untuk itu, dan dikombinasikan dengan keburukan dari permintaan, seluruh upaya itu mungkin tampak seperti usaha yang sia-sia. Saya masih ingin melakukan ini sebagai latihan, dan sekarang ingin membagikan hasil saya:

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
;

Bagian inti dari perhitungan adalah ini (dan pertama-tama saya ingin mencatat bahwa idenya bukan milik saya, saya belajar tentang trik ini di tempat lain):

  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

Ekspresi ini dapat digunakan tanpa perubahan apa pun jika nilai dalam Col_Bdijamin tidak pernah memiliki null. Namun, jika kolom memiliki nol, Anda harus memperhitungkannya, dan itulah tepatnya yang dimaksud dengan CASEekspresi itu. Ini membandingkan jumlah baris per partisi dengan jumlah Col_Bnilai per partisi. Jika angkanya berbeda, itu berarti bahwa beberapa baris memiliki nol Col_Bdan, oleh karena itu, perhitungan awal ( DENSE_RANK() ... + DENSE_RANK() - 1) perlu dikurangi dengan 1.

Perhatikan bahwa karena - 1ini adalah bagian dari formula inti, saya memilih untuk membiarkannya seperti itu. Namun, itu sebenarnya dapat dimasukkan ke dalam CASEekspresi, dalam upaya yang sia-sia untuk membuat seluruh solusi terlihat kurang jelek:

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
;

Demo langsung ini di logo dbfiddledb <> fiddle.uk dapat digunakan untuk menguji kedua variasi solusi.

Andriy M
sumber
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
kevinnwhat
sumber
1

Alternatif jika Anda alergi terhadap subqueries yang berkorelasi (jawaban Erik Darling) dan CTE (jawaban kevinnwhat) seperti saya.

Ketahuilah bahwa ketika nol dilemparkan ke dalam campuran, tidak ada yang bisa berfungsi seperti yang Anda inginkan. (Tapi itu cukup sederhana untuk memodifikasinya sesuai selera)

Kasus sederhana:

--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]
;

Sama seperti di atas, tetapi dengan komentar tentang apa yang harus diubah untuk penanganan nol:

--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)
*/
ap55
sumber