Hàm phân vùng COUNT () OVER có thể sử dụng DISTINCT


88

Tôi đang cố gắng viết những điều sau để có được tổng số NumUser khác nhau đang chạy, như sau:

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

Quản lý studio có vẻ không quá hài lòng về điều này. Lỗi sẽ biến mất khi tôi xóa DISTINCTtừ khóa, nhưng sau đó nó sẽ không phải là một số lượng riêng biệt.

DISTINCTdường như không khả thi trong các chức năng phân vùng. Làm cách nào để tìm số lượng riêng biệt? Tôi có sử dụng một phương pháp truyền thống hơn chẳng hạn như một truy vấn con tương quan không?

Xem xét vấn đề này xa hơn một chút, có thể các OVERhàm này hoạt động khác với Oracle theo cách mà chúng không thể được sử dụng SQL-Serverđể tính toán tổng đang chạy.

Tôi đã thêm một ví dụ trực tiếp ở đây trên SQLfiddle nơi tôi cố gắng sử dụng hàm phân vùng để tính tổng số đang chạy.


2
COUNTvới ORDER BYthay vì PARTITION BYlà không rõ ràng vào năm 2008. Tôi ngạc nhiên là nó cho phép bạn có nó. Theo tài liệu , bạn không được phép sử ORDER BYdụng một hàm tổng hợp.
Damien_The_Un Believer

vâng - nghĩ rằng tôi đang bối rối với một số chức năng tiên tri; những hoạt động tổng số và chạy tội sẽ là một chút tham gia nhiều hơn
whytheq

Câu trả lời:


177

Có một giải pháp rất đơn giản là sử dụng dense_rank()

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

Điều này sẽ cung cấp cho bạn chính xác những gì bạn đang yêu cầu: Số lượng UserAccountKeys riêng biệt trong mỗi tháng.


23
Một điều cần cẩn thận dense_rank()là nó sẽ tính NULL trong khi COUNT(field) OVERthì không. Tôi không thể sử dụng nó trong giải pháp của mình vì điều này nhưng tôi vẫn nghĩ nó khá thông minh.
bf2020,

1
Nhưng tôi đang tìm kiếm tổng số các khóa tài khoản sử dụng riêng biệt đang chạy qua các tháng mỗi năm: không chắc điều này trả lời như thế nào?
whytheq

4
@ bf2020, nếu có thể có NULLgiá trị trong UserAccountKey, sau đó bạn cần phải thêm thuật ngữ này: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). Ý tưởng được trích từ câu trả lời của LarsRönnbäck dưới đây. Về cơ bản, nếu UserAccountKeyNULLgiá trị, bạn cần phải trừ thêm 1từ kết quả, vì DENSE_RANKtính NULL.
Vladimir Baranov

1
@ahsteele cảm ơn bạn, bạn đã thổi bay tâm trí của tôi và giải quyết vấn đề của tôi
Henrique Donati

Đây là một cuộc thảo luận về việc sử dụng dense_rankgiải pháp này khi chức năng cửa sổ có một khung. SQL Server không cho phép dense_ranksử dụng với khung cửa sổ: stackoverflow.com/questions/63527035/…
K4M

6

Necromancing:

Tương đối đơn giản để mô phỏng COUNT DISTINCT trên PARTITION BY với MAX qua 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

Lưu ý:
Điều này giả sử các trường được đề cập là trường KHÔNG có giá trị vô hiệu.
Nếu có một hoặc nhiều mục nhập NULL trong các trường, bạn cần trừ đi 1.


5

Tôi sử dụng một giải pháp tương tự như giải pháp của David ở trên, nhưng với một phần bổ sung nếu một số hàng sẽ bị loại trừ khỏi số lượng. Điều này giả định rằng [UserAccountKey] không bao giờ rỗng.

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

Bạn có thể tìm thấy SQL Fiddle với một ví dụ mở rộng tại đây.


1
Ý tưởng của bạn có thể được sử dụng để tạo ra công thức ban đầu (không phức tạp [Include]mà bạn đang đề cập trong câu trả lời của mình) với dense_rank()công việc khi UserAccountKeycó thể NULL. Thêm thuật ngữ này để công thức: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov

5

Tôi nghĩ rằng cách duy nhất để thực hiện việc này trong SQL-Server 2008R2 là sử dụng truy vấn con tương quan hoặc áp dụng bên ngoài:

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;

Điều này có thể được thực hiện trong SQL-Server 2012 bằng cú pháp bạn đã đề xuất:

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

Tuy nhiên, việc sử dụng DISTINCTvẫn không được phép, vì vậy nếu DISTINCT là bắt buộc và / hoặc nếu nâng cấp không phải là một tùy chọn thì tôi nghĩ OUTER APPLYlà lựa chọn tốt nhất của bạn


mát mẻ cảm ơn bạn. Tôi đã tìm thấy câu trả lời SO này có tùy chọn ÁP DỤNG NGOÀI TRỜI mà tôi sẽ thử. Bạn đã thấy cách tiếp cận UPDATE lặp lại trong câu trả lời đó chưa ... nó khá xa và có vẻ nhanh. Cuộc sống sẽ dễ dàng hơn vào năm 2012 - đó có phải là một bản sao của Oracle không?
whytheq
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.