ฟังก์ชันพาร์ติชัน COUNT () เป็นไปได้โดยใช้ DISTINCT


90

ฉันพยายามเขียนสิ่งต่อไปนี้เพื่อให้ได้จำนวน NumUsers ที่แตกต่างกันออกไปทั้งหมดดังนี้:

NumUsers = COUNT(DISTINCT [UserAccountKey]) OVER (PARTITION BY [Mth])

สตูดิโอบริหารดูเหมือนจะไม่ค่อยมีความสุขกับเรื่องนี้ ข้อผิดพลาดจะหายไปเมื่อฉันลบDISTINCTคีย์เวิร์ด แต่จะไม่เป็นการนับเฉพาะ

DISTINCTดูเหมือนจะไม่เป็นไปได้ภายในฟังก์ชันพาร์ติชัน ฉันจะหาจำนวนที่แตกต่างได้อย่างไร ฉันใช้วิธีการแบบเดิมๆ เช่นการสืบค้นย่อยที่สัมพันธ์กันหรือไม่

เมื่อพิจารณาเพิ่มเติมอีกเล็กน้อยOVERฟังก์ชั่นเหล่านี้อาจทำงานแตกต่างจาก Oracle ในลักษณะที่ไม่สามารถใช้ในSQL-Serverการคำนวณผลรวมที่กำลังทำงานอยู่

ฉันได้เพิ่มตัวอย่างสดที่นี่ในSQLfiddleซึ่งฉันพยายามใช้ฟังก์ชันพาร์ติชันเพื่อคำนวณผลรวมที่กำลังทำงานอยู่


2
COUNTด้วยORDER BYแทนที่จะPARTITION BYเป็นป่วยกำหนดในปี 2008 ฉันประหลาดใจก็ปล่อยให้คุณมีมันเลย ตามเอกสารประกอบคุณไม่ได้รับอนุญาตให้ใช้ORDER BYสำหรับฟังก์ชันรวม
Damien_The_Unbeliever

ใช่ - คิดว่าฉันกำลังสับสนกับฟังก์ชันการทำงานของ oracle ผลรวมการวิ่งและการนับการวิ่งเหล่านี้จะมีส่วนเกี่ยวข้องมากขึ้นเล็กน้อย
whytheq

คำตอบ:


180

มีวิธีแก้ปัญหาที่ง่ายมากโดยใช้ dense_rank()

dense_rank() over (partition by [Mth] order by [UserAccountKey]) 
+ dense_rank() over (partition by [Mth] order by [UserAccountKey] desc) 
- 1

สิ่งนี้จะให้สิ่งที่คุณต้องการ: จำนวน UserAccountKeys ที่แตกต่างกันภายในแต่ละเดือน


23
สิ่งหนึ่งที่ต้องระวังdense_rank()คือมันจะนับ NULL ในขณะที่COUNT(field) OVERไม่นับ ฉันไม่สามารถใช้มันในโซลูชันของฉันได้เพราะเหตุนี้ แต่ฉันก็ยังคิดว่ามันค่อนข้างฉลาด
bf2020

1
แต่ฉันกำลังมองหารหัสการใช้งานที่แตกต่างกันทั้งหมดในช่วงหลายเดือนของแต่ละปี: ไม่แน่ใจว่าคำตอบนี้เป็นอย่างไร
whytheq

4
@ bf2020 ถ้าจะมีNULLค่าในแล้วคุณจะต้องเพิ่มคำนี้:UserAccountKey -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)แนวคิดนี้นำมาจากคำตอบของLarsRönnbäckด้านล่าง โดยพื้นฐานแล้วถ้าUserAccountKeyมีNULLค่าคุณจะต้องลบส่วนเกินออก1จากผลลัพธ์เนื่องจากDENSE_RANKนับเป็นค่า NULL
Vladimir Baranov

นี่คือการอภิปรายเกี่ยวกับการใช้dense_rankโซลูชันนี้เมื่อฟังก์ชันหน้าต่างมีกรอบ SQL Server ไม่อนุญาตให้dense_rankใช้กับกรอบหน้าต่าง: stackoverflow.com/questions/63527035/…
K4M

6

เนโครแมนซิ่ง:

การจำลอง COUNT DISTINCT เหนือ PARTITION BY MAX ผ่าน DENSE_RANK ทำได้ง่ายมาก:

;WITH baseTable AS
(
    SELECT 'RM1' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM1' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR2' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR3' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM2' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR1' AS ADR
    UNION ALL SELECT 'RM3' AS RM, 'ADR2' AS ADR
)
,CTE AS
(
    SELECT RM, ADR, DENSE_RANK() OVER(PARTITION BY RM ORDER BY ADR) AS dr 
    FROM baseTable
)
SELECT
     RM
    ,ADR

    ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY ADR) AS cnt1 
    ,COUNT(CTE.ADR) OVER (PARTITION BY CTE.RM) AS cnt2 
    -- Not supported
    --,COUNT(DISTINCT CTE.ADR) OVER (PARTITION BY CTE.RM ORDER BY CTE.ADR) AS cntDist
    ,MAX(CTE.dr) OVER (PARTITION BY CTE.RM ORDER BY CTE.RM) AS cntDistEmu 
FROM CTE

หมายเหตุ:
ถือว่าฟิลด์ที่เป็นปัญหาเป็นฟิลด์ NON-nullable
หากมีรายการ NULL อย่างน้อยหนึ่งรายการในช่องคุณต้องลบ 1


5

ฉันใช้วิธีแก้ปัญหาที่คล้ายกับของดาวิดข้างต้น แต่หากมีการบิดเพิ่มเติมหากควรแยกแถวบางแถวออกจากการนับ สิ่งนี้จะถือว่า [UserAccountKey] ไม่เป็นโมฆะ

-- subtract an extra 1 if null was ranked within the partition,
-- which only happens if there were rows where [Include] <> 'Y'
dense_rank() over (
  partition by [Mth] 
  order by case when [Include] = 'Y' then [UserAccountKey] else null end asc
) 
+ dense_rank() over (
  partition by [Mth] 
  order by case when [Include] = 'Y' then [UserAccountKey] else null end desc
)
- max(case when [Include] = 'Y' then 0 else 1 end) over (partition by [Mth])
- 1

SQL Fiddle พร้อมตัวอย่างเพิ่มเติมสามารถพบได้ที่นี่


1
ความคิดของคุณสามารถใช้ในการทำสูตรเดิม (ไม่มีความซับซ้อนของการ[Include]ที่คุณกำลังพูดถึงในคำตอบของคุณ) กับdense_rank()การทำงานเมื่อสามารถUserAccountKey NULLเพิ่มคำนี้ในสูตร: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov

5

ฉันคิดว่าวิธีเดียวในการทำสิ่งนี้ใน SQL-Server 2008R2 คือการใช้แบบสอบถามย่อยที่สัมพันธ์กันหรือใช้ภายนอก:

SELECT  datekey,
        COALESCE(RunningTotal, 0) AS RunningTotal,
        COALESCE(RunningCount, 0) AS RunningCount,
        COALESCE(RunningDistinctCount, 0) AS RunningDistinctCount
FROM    document
        OUTER APPLY
        (   SELECT  SUM(Amount) AS RunningTotal,
                    COUNT(1) AS RunningCount,
                    COUNT(DISTINCT d2.dateKey) AS RunningDistinctCount
            FROM    Document d2
            WHERE   d2.DateKey <= document.DateKey
        ) rt;

สิ่งนี้สามารถทำได้ในSQL-Server 2012โดยใช้ไวยากรณ์ที่คุณแนะนำ:

SELECT  datekey,
        SUM(Amount) OVER(ORDER BY DateKey) AS RunningTotal
FROM    document

อย่างไรก็ตามDISTINCTยังไม่อนุญาตให้ใช้ดังนั้นหากจำเป็นต้องใช้ DISTINCT และ / หรือหากการอัปเกรดไม่ใช่ตัวเลือกฉันคิดว่าOUTER APPLYเป็นตัวเลือกที่ดีที่สุดของคุณ


ขอบคุณมาก ฉันพบคำตอบ SOนี้ซึ่งมีตัวเลือก OUTER APPLY ซึ่งฉันจะลอง คุณเคยเห็นแนวทางการอัปเดตแบบวนซ้ำในคำตอบนั้นหรือไม่ ... มันค่อนข้างไกลและเร็วมาก ชีวิตจะง่ายขึ้นในปี 2012 - นั่นคือ Oracle copy หรือไม่?
whytheq
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.