DISTINCTを使用して可能なパーティション関数COUNT()OVER


88

次のように、個別のNumUsersの現在の合計を取得するために、次のように記述しようとしています。

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

管理スタジオはこれについてあまり満足していないようです。DISTINCTキーワードを削除するとエラーは消えますが、明確なカウントにはなりません。

DISTINCTパーティション関数内では不可能のようです。個別のカウントを見つけるにはどうすればよいですか?相関サブクエリなど、より従来の方法を使用しますか?

これをもう少し詳しく見てみると、これらのOVER関数は、現在のSQL-Server合計を計算するために使用できないという点で、Oracleとは異なる動作をする可能性があります。

ここSQLfiddleにライブの例を追加しました。ここでは、パーティション関数を使用して現在の合計を計算しようとしています。


2
COUNTORDER BY代わりにPARTITION BY、2008年には明確に定義されていません。私はそれがあなたにそれをまったく持たせていることに驚いています。ドキュメントによるORDER BYと、集計関数のは許可されていません。
damien_The_Unbeliever 2012年

うん-私はいくつかのオラクル機能と混同していると思います。これらの現在の合計と実行カウントはもう少し複雑になります
なぜtheq 2012年

回答:


177

を使用して非常に簡単な解決策があります dense_rank()

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

これにより、まさにあなたが求めていたものが得られます。各月内の個別のUserAccountKeyの数。


23
注意すべきことの1つdense_rank()は、NULLをカウントしますが、カウントCOUNT(field) OVERしないことです。このため、ソリューションで使用することはできませんが、それでもかなり賢いと思います。
bf2020 2014年

1
しかし、私は毎年数か月にわたって個別のユーザーアカウントキーの現在の合計を探しています:これがどのようにそれに答えるかわからないのですか?
whytheq 2016

4
@ bf2020、にNULL値が含まれる可能性がある場合は、UserAccountKey次の用語を追加する必要があります-MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)。アイデアは、以下のLarsRönnbäckによる回答から取られています。基本的に、UserAccountKeyNULL値がある場合は、NULLをカウントする1ため、結果から余分な値を引く必要がありますDENSE_RANK
ウラジーミルバラノフ2017年

1
@ahsteeleありがとうございます、あなたは私の心を吹き飛ばし、私の問題を解決しました
HenriqueDonati19年

ここではdense_rank、ウィンドウ関数にフレームがある場合にこのソリューションを使用する方法について説明します。SQL Serverが許可されていませんdense_rank:窓枠で使用stackoverflow.com/questions/63527035/...
K4M

6

ネクロマンシング:

DENSE_RANKを介してMAXを使用してPARTITIONBYでCOUNTDISTINCTをエミュレートするのは比較的簡単です。

;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

注:
これは、問題のフィールドがNULL不可のフィールドであることを前提としています。
フィールドに1つ以上のNULLエントリがある場合は、1を引く必要があります。


5

上記のDavidと同様のソリューションを使用しますが、一部の行をカウントから除外する必要がある場合は、さらに工夫を加えています。これは、[UserAccountKey]がnullになることはないことを前提としています。

-- 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フィドルはここにあります。


1
あなたのアイデアは、元の式を([Include]あなたが答えで話していることの複雑さなしに)dense_rank()仕事で作るために使用することUserAccountKeyができますNULL。この項を式に追加します -MAX(CASE WHEN UserAccountKey IS NULL THEN 1 ELSE 0 END) OVER (PARTITION BY Mth)
ウラジーミルバラノフ2017年

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は、最善のオプションだと思います。


かっこいいありがとう。私が試みるOUTERAPPLYオプションを特徴とするこのSO回答を見つけました。その答えでループするUPDATEアプローチを見たことがありますか...それはかなり遠く、明らかに高速です。2012年は生活が楽になります-それはOracleのストレートコピーですか?
whytheq 2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.