web-dev-qa-db-ja.com

SQL Serverのロールアップ付きピボット

カーソルを使用して動的テーブルを作成し、それらの列を使用してSQL Server 2008でデータを集計することはできますか?

たとえば、次の表を見てください。

CREATE TABLE Billing (
    BillingId BIGINT IDENTITY,
    SubscriptionId BIGINT,
        ExternalServiceName VARCHAR(50),
        BillYear INT NOT NULL,
    BillMonth INT NOT NULL
);

INSERT INTO Billing (BillingId, SubscriptionId, ExternalServiceName,
                     BillYear, BillMonth)
VALUES (1, 1, 'Dogs', 2018, 4),
       (2, 2, 'Cats', 2018, 4),
       (3, 1, 'Dogs', 2018, 5),
       (4, 2, 'Cats', 2018, 5);

CREATE TABLE BillingData (
    BillingDataId INT IDENTITY PRIMARY KEY,
    BillingId INT NOT NULL,
    Feature VARCHAR(50) NOT NULL,
    Usage INT NOT NULL,
    Measurement VARCHAR(50),
    Cost NUMERIC(18,2) NOT NULL
);

INSERT INTO BillingData(BillingId, Feature, Usage, Measurement, Cost)
VALUES (1, 'Walks', 25, 'walks', 200.32),
       (1, 'Baths', 5, 'baths', 251.32),
       (2, 'Litter change', 53, 'changes', 110.21),
       (2, 'Groom', 25, 'brushings', 123),
       (2, 'Scratching', 213, 'clipping', 123),
       (3, 'Pilling', 11, 'medicate', 10),
       (4, 'Groom', 5, 'brushings', 50),
       (4, 'Exercise', 1, 'run', 25.12),
       (1, 'Walks', 500, 'walks', 12351.31),
       (1, 'Baths', 53, 'baths', 1235),
       (2, 'Baths', 53, 'baths', 1235); 

私ができることは、この形式でテーブルを作成することです

 +-------------+---------+---------+-----------------+---------+--------------+---------+----------+
 | [BillingId] | [Walks] | [Baths] | [Litter change] | [Groom] | [Scratching] | [Usage] | [Cost]   |
 +-------------+---------+---------+-----------------+---------+--------------+---------+----------+
 | 1           | 525     | 58      | 0               | 0       | 0            | 583     | 14037.95 |
 | 2           | 0       | 53      | 53              | 25      | 213          | 344     | 1591.21  |
 +-------------+---------+---------+-----------------+---------+--------------+---------+----------+

これを達成するために私が考えた唯一の方法は、垂直テーブルを集計することでした。

次のクエリのようなことをすることによって

SELECT MAX(BillingId), MAX(Feature), SUM(Usage), MAX(Measurement), SUM(Cost) 
FROM BillingData;

ただし、特にBillingDataが月によって異なる場合があるため、これらの列を動的にBillingテーブルに結合する必要があります。例えば:

SELECT DISTINCT Feature FROM BillingData WHERE BillYear=2018 AND BillMonth=5;

とは違う

SELECT DISTINCT Feature FROM BillingData WHERE BillYear=2018 and BillMonth=4;

したがって、BillingId、Walks、Baths、Litter change、Groom、Scratching、Usage、Costの列は4月に適していますが、5月の列はBillingId、Pilling、Groom、Exercise、Usage、およびCostのみです。

ここではピボットテーブルが必要だと思いますが、月ごとに列を変える必要があるため、動的にする必要があるのではないかと思います。

これを行う最善の方法はわかりません。いくつかの助けをいただければ幸いです。

3
Mark D

これは PIVOT で実行でき、動的に実行できますが、動的に実行する前に、静的またはハードを使用して必要な結果を取得する必要がありますでコード化されたバージョンのクエリを使用して、動的SQLに変換します。

SQL Server 2008を使用していて、UsageCostの両方の合計列が必要なため、最初に sum(<your column) over(...) から見ていきます。 。これにより、データをピボットする前に1つのステップでデータを集約できます。

静的バージョンを取得するには、まず次のようなクエリから始めます。

_select 
    b.BillingId,
    bd.Feature,
    bd.Usage,
    TotalUsage = sum(bd.Usage) over(partition by bd.BillingId),
    TotalCost = sum(bd.Cost) over(partition by bd.BillingId)
from Billing b
inner join BillingData bd
    on b.BillingId = bd.BillingId
where b.BillYear = 2018 and b.BillMonth = 4
_

SQLフィドルを参照 。このクエリは、ピボットしたい基本的なデータを取得します。

_| BillingId |       Feature | Usage | TotalUsage | TotalCost |
|-----------|---------------|-------|------------|-----------|
|         1 |         Walks |    25 |        583 |  14037.95 |
|         1 |         Baths |     5 |        583 |  14037.95 |
|         1 |         Walks |   500 |        583 |  14037.95 |
|         1 |         Baths |    53 |        583 |  14037.95 |
|         2 |         Baths |    53 |        344 |   1591.21 |
|         2 | Litter change |    53 |        344 |   1591.21 |
|         2 |         Groom |    25 |        344 |   1591.21 |
|         2 |    Scratching |   213 |        344 |   1591.21 |
_

BillingIdを含めて、最終的に新しい列に含めるFeaturesのそれぞれ、次にUsageTotalUsage、およびTotalCostBillingIdsum(<yourcolumn> over(partition by bd.BillingId)は、_GROUP BY_を使用せずに各アカウントの値を提供します。このデータを取得したら、PIVOT関数を適用できます。

_select 
    BillingId,
    Walks = IsNull(Walks, 0),
    Baths = IsNull(Baths, 0),
    [Litter Change] = IsNull([Litter Change], 0),
    Groom = IsNull(Groom, 0),
    Scratching = IsNull(Scratching, 0),
    Usage = TotalUsage,
    Cost = TotalCost
from
(
    select 
        b.BillingId,
        bd.Feature,
        bd.Usage,
        TotalUsage = sum(bd.Usage) over(partition by bd.BillingId),
        TotalCost = sum(bd.Cost) over(partition by bd.BillingId)
    from Billing b
    inner join BillingData bd
        on b.BillingId = bd.BillingId
    where b.BillYear = 2018 and b.BillMonth = 4
) x
pivot
(
    sum(Usage)
    for Feature in ([Walks], [Baths], [Litter Change], [Groom], [Scratching])
) piv;
_

SQLを参照Fiddle これにより結果が得られます。

_| BillingId | Walks | Baths | Litter Change | Groom | Scratching | Usage |     Cost |
|-----------|-------|-------|---------------|-------|------------|-------|----------|
|         1 |   525 |    58 |             0 |     0 |          0 |   583 | 14037.95 |
|         2 |     0 |    53 |            53 |    25 |        213 |   344 |  1591.21 |
_

探している最終結果が得られたので、クエリを動的SQLに変換することができます。これを行うには、Feature値として列にしたい値のリストを取得する必要があります。これは、必要なBillYearBillMonthを使用してテーブルにクエリを実行し、値を文字列に連結して、その列のリストを取得し、完全なSQL文字列を実行することによって行われます。完全なコードは次のようになります。

_DECLARE 
    @BillYear int = 2018,
    @BillMonth int = 4,
    @colsNull AS NVARCHAR(MAX),
    @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ', ' + QUOTENAME(bd.Feature)
                    from Billing b
                    inner join BillingData bd
                        on b.BillingId = bd.BillingId
                    where b.BillYear = @BillYear 
                        and b.BillMonth = @BillMonth
                    group by bd.Feature
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

select @colsNull = STUFF((SELECT ', IsNull(' + QUOTENAME(bd.Feature)+',0) as '+ QUOTENAME(bd.Feature)
                    from Billing b
                    inner join BillingData bd
                        on b.BillingId = bd.BillingId
                    where b.BillYear = @BillYear 
                        and b.BillMonth = @BillMonth
                    group by bd.Feature
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'');

set @query = N'SELECT BillingId, ' + @colsNUll + N', TotalUsage, TotalCost 
            from 
             (
                select 
                    b.BillingId,
                    bd.Feature,
                    bd.Usage,
                    TotalUsage = sum(bd.Usage) over(partition by bd.BillingId),
                    TotalCost = sum(bd.Cost) over(partition by bd.BillingId)
                from Billing b
                inner join BillingData bd
                    on b.BillingId = bd.BillingId
                where b.BillYear = '+cast(@BillYear as nvarchar(4))+N' 
                  and b.BillMonth = '+cast(@BillMonth as nvarchar(2))+N'
            ) x
            pivot 
            (
                sum(Usage)
                for Feature in (' + @cols + N')
            ) p '

exec sp_executesql @query;
_

SQLを参照Fiddle with Demo 。列には2つの変数があることに気づくでしょう-1つの_@cols_これはPIVOT関数、次に_@colsNull_これは最初のものと似ていますが、最終選択リストのnullsをゼロに置き換えます-必要がない場合は、これを使用しないようにできます。これを_BillingMonth = 4_に対して実行すると、静的バージョンと同じ結果が得られます。

_| BillingId | Baths | Groom | Litter change | Scratching | Walks | TotalUsage | TotalCost |
|-----------|-------|-------|---------------|------------|-------|------------|-----------|
|         1 |    58 |     0 |             0 |          0 |   525 |        583 |  14037.95 |
|         2 |    53 |    25 |            53 |        213 |     0 |        344 |   1591.21 |
_

次に、_BillingMonth = 5_を変更すると、クエリを変更せずに結果が得られます( Demo ):

_| BillingId | Exercise | Groom | Pilling | TotalUsage | TotalCost |
|-----------|----------|-------|---------|------------|-----------|
|         3 |        0 |     0 |      11 |         11 |        10 |
|         4 |        1 |     5 |       0 |          6 |     75.12 |
_
7
Taryn