การนับ 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

นับ (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 ใช้ผลิตด้านนอกเข้าร่วม

พิจารณาการลงคะแนนสำหรับคำร้องขอการปรับปรุงข้อมากกว่า - ข้อ 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    

คุณจะต้องไม่รวม nulls ใด ๆ จากการที่จะได้รับผลเช่นเดียวกับcol_bCOUNT(DISTINCT)


6

นี่คือส่วนขยายของโซลูชันของ Lennartแต่ก็น่าเกลียดจนฉันไม่กล้าแนะนำว่าเป็นการแก้ไข เป้าหมายที่นี่คือการรับผลลัพธ์โดยไม่มีตารางที่ได้รับ อาจไม่จำเป็นสำหรับสิ่งนั้นและเมื่อรวมกับความน่าเกลียดของข้อความค้นหาความพยายามทั้งหมดอาจดูเหมือนเป็นความพยายามที่สูญเปล่า ฉันยังต้องการที่จะทำเช่นนี้เป็นแบบฝึกหัดและตอนนี้ต้องการที่จะแบ่งปันผลของฉัน:

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 อย่างไรก็ตามหากคอลัมน์สามารถมีค่า 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

ทางเลือกถ้าคุณแพ้อย่างอ่อนโยนต่อคำถามย่อยที่สัมพันธ์กัน (คำตอบของ Erik Darling) และ CTE (คำตอบของ kevinnwhat) อย่างฉัน

โปรดระวังว่าเมื่อมีการโยนทิ้งโมฆะในการผสมสิ่งเหล่านี้ไม่สามารถทำงานได้ตามที่คุณต้องการ (แต่มันค่อนข้างง่ายในการปรับเปลี่ยนเพื่อลิ้มรส)

กรณีง่าย ๆ :

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

เหมือนด้านบน แต่มีความคิดเห็นเกี่ยวกับสิ่งที่จะเปลี่ยนสำหรับการจัดการค่า null

--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)
*/
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.