Bölme İşlevi COUNT () OVER DISTINCT kullanılarak mümkün


90

Devam eden toplam farklı NumUsers elde etmek için aşağıdakileri yazmaya çalışıyorum, şöyle ki:

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

Yönetim stüdyosu bundan pek memnun görünmüyor. DISTINCTAnahtar kelimeyi kaldırdığımda hata kayboluyor , ancak daha sonra ayrı bir sayı olmayacak.

DISTINCTbölüm işlevlerinde mümkün görünmüyor. Farklı sayımı nasıl bulabilirim? İlişkili alt sorgu gibi daha geleneksel bir yöntem mi kullanıyorum?

Buna biraz daha bakarsak, belki bu OVERişlevler , SQL-Serverçalışan toplamları hesaplamak için kullanılamadıkları için Oracle'dan farklı çalışır.

Burada SQLfiddle'a , hareketli toplamı hesaplamak için bir bölümleme işlevi kullanmaya çalıştığım bir canlı örnek ekledim .


2
COUNTile ORDER BYyerine PARTITION BY2008 yılında hatalı tanımlanmış Ben bunu hiç have it izin verdi şaşırttı. Başına belgeler , bir izin yok ORDER BYbir toplama işlevi için.
Damien_The_Unbeliever

evet - bazı oracle işlevleriyle kafamı karıştırdığımı düşünüyorum; bu
değişen

Yanıtlar:


180

Kullanarak çok basit bir çözüm var dense_rank()

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

Bu size tam olarak ne istediğinizi verecektir: Her ay içindeki farklı UserAccountKeys sayısı.


23
Dikkat edilmesi gereken bir şey, dense_rank()NULL'leri sayarken, saymamasıdır COUNT(field) OVER. Bu yüzden çözümümde kullanamıyorum ama yine de oldukça akıllıca olduğunu düşünüyorum.
bf2020

1
Ancak, her yılın ayları boyunca değişen toplam farklı kullanıcı hesap anahtarları arıyorum: Bunun bunu nasıl yanıtladığından emin değil misiniz?
whytheq

4
olamazmış @ bf2020, NULLdeğerleri UserAccountKey, o zaman bu terim eklemeniz gerekir: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth). Fikir, aşağıdaki LarsRönnbäck tarafından verilen cevaptan alınmıştır. Esasen, eğer değerler varsa UserAccountKey, sonuçtan NULLfazladan çıkarmanız gerekir 1, çünkü DENSE_RANKNULL'lar sayılır.
Vladimir Baranov

Burada dense_rank, pencere işlevinin bir çerçevesi olduğunda bu çözümü kullanmanın bir tartışması . SQL Server, dense_rankbir pencere çerçevesiyle kullanılmasına izin vermiyor : stackoverflow.com/questions/63527035/…
K4M

6

Büyüleyici:

DENSE_RANK üzerinden MAX ile PARTITION BY üzerinden COUNT DISTINCT taklit etmek göreceli olarak basittir:

;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

Not:
Bu, söz konusu alanların NON-nullable alanlar olduğunu varsayar.
Alanlarda bir veya daha fazla NULL girişi varsa, 1'i çıkarmanız gerekir.


5

Yukarıdaki David'inkine benzer bir çözüm kullanıyorum , ancak bazı satırların sayımın dışında bırakılması gerekiyorsa ek bir bükülme ile. Bu, [UserAccountKey] değerinin hiçbir zaman boş olmadığını varsayar.

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

Genişletilmiş bir örneğe sahip bir SQL Fiddle burada bulunabilir.


1
Fikriniz, orijinal formülü ( [Include]cevabınızda bahsettiğiniz karmaşıklıklar olmadan ) olabildiğince dense_rank()işe yaramak UserAccountKeyiçin kullanılabilir NULL. Formülüne Bu terimi ekleyin: -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth).
Vladimir Baranov

5

Bunu SQL-Server 2008R2'de yapmanın tek yolunun ilişkili bir alt sorgu veya bir dış uygulama kullanmak olduğunu düşünüyorum:

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;

Bu, önerdiğiniz sözdizimini kullanarak SQL-Server 2012'de yapılabilir :

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

Ancak, kullanımına DISTINCThala izin verilmiyor, bu nedenle DISTINCT gerekliyse ve / veya yükseltme bir seçenek değilse, bence OUTER APPLYen iyi seçeneğiniz


Oh, teşekkürler. Deneyeceğim DIŞ UYGULAMA seçeneğini içeren bu SO cevabını buldum . Bu cevapta döngüsel GÜNCELLEME yaklaşımını gördünüz mü ... oldukça uzak ve görünüşe göre hızlı. 2012'de hayat daha kolay olacak - bu düz bir Oracle kopyası mı?
whytheq
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.