2017-11-24 3 views
0

テーブルから集計を作成したいが、解決策を見つけることができない。変数を基にした複製によるtsqlグループ化

例テーブル:その時点で既存の一人一人のために

DECLARE @MyTable TABLE(person INT, the_date date, the_value int) 
INSERT INTO @MyTable VALUES 
(1,'2017-01-01', 10), 
(1,'2017-02-01', 5), 
(1,'2017-03-01', 5), 
(1,'2017-04-01', 10), 
(1,'2017-05-01', 2), 
(2,'2017-04-01', 10), 
(2,'2017-05-01', 10), 
(2,'2017-05-01', 0), 
(3,'2017-01-01', 2) 

、私は最後のx(@months_back)の値を平均化したいいくつかの開始日(@start_date)与えヶ月:

DECLARE @months_back int, @start_date date 
set @months_back = 3 
set @start_date = '2017-05-01' 

SELECT person, avg(the_value) as avg_the_value 
FROM @MyTable 
where the_date <= @start_date and the_date >= dateadd(month, [email protected]_back, @start_date) 
group by person 

これは機能します。私は今再び同じことをしたいが、開始日から数ヶ月(@month_skip)をスキップする。そして、私はそれらの2つのテーブルを一緒に結合したい。次に、この日からもう一度@month_skip月をスキップして同じことをしたいと思います。指定された日付(@min_date)にスキップするまで、これをやり続けます。

@MyTable結果がでなければなりません上記の変数とテーブルを使用して
DECLARE @months_back int, @month_skip int, @start_date date, @min_date date 
set @months_back = 3 
set @month_skip = 2 
set @start_date = '2017-05-01' 
set @min_date = '2017-03-01' 

person | avg_the_value 
1  | 5 
2  | 6 
1  | 6 
3  | 2 

つだけのスキップが@min_dateが2ヶ月前であるので、ここで作られているが、私は行うことができるようにしたいと思います@min_dateに基づいて複数のスキップが行われます。

この例の表はシンプルですが、実際の列は自動的に多くの列が作成されるため、結果の表のスキームを宣言しなければならない表変数を使用することは現実的ではありません。

私は関連する質問Hereを尋ねましたが、この問題の回答を得ることはできませんでした。

日付(例えば、2017年5月1日)から開始し、バック@months_backヶ月を見て、日付の範囲を定義します。

+0

なぜ人1はあなたの期待される出力に2つの結果を持っていますか? 'GROUP BY person'を使用している場合は、1行になります。あなたは説明できますか? – Larnu

+0

@Larnu beacase私は、クエリの例の複数の結果を結合したいと思います。予想される出力では、人1の最初の行は2017-05-01および3ヶ月前の平均値で、2番目の値は2017-03-01および3ヶ月前の値です – Jarnald

+0

どのバージョンのSQL Serverを使用していますか?一部のバージョンでは、これを実行するのに役立つグループ化とウィンドウ/インターバル機能にアクセスできます。 – lyrisey

答えて

0

それは次のようであるあなたが何をしようとしているように聞こえます。たとえば、3カ月前に戻ると、2017-02-01から2017-05-01までの範囲を定義しています。

この範囲を定義した後、開始日に戻り、new開始日を定義して、@month_skip月に戻ります。たとえば、最初の開始日が2017-05-01の場合は、2か月後にスキップして2017-03-01の新しい開始日を与えます。

私たちはこの新しい開始日を取って、対応する日付の範囲を定義します(上記のように)。これにより、範囲2016-12-01〜2017-03-01が生成されます。

指定された最小の日付によって、必要に応じて我々は、我々はのための計算をしたい日付範囲のリストを生成するために、これを繰り返します、これらの期間のそれぞれについて

2017-03-01 through 2017-05-01 
2016-12-01 through 2017-03-01 
... etc ... 

人を見て、平均値を計算しますその価値の

以下のクエリは、値を取って前の値を計算するのではなく、数値表を使用して区間のオフセットを計算します。これは、開始日と終了日を決定するために使用されます。各間隔/期間。このクエリはSQL Server 2008 R2を使用して作成されたもので、将来のバージョンと互換性があります。

/* Table, data, variable declarations */ 

DECLARE @MyTable TABLE(person INT, the_date date, the_value int) 
INSERT INTO @MyTable VALUES 
(1,'2017-01-01', 10), 
(1,'2017-02-01', 5), 
(1,'2017-03-01', 5), 
(1,'2017-04-01', 10), 
(1,'2017-05-01', 2), 
(2,'2017-04-01', 10), 
(2,'2017-05-01', 10), 
(2,'2017-05-01', 0), 
(3,'2017-01-01', 2) 


DECLARE @months_back int, @month_skip int, @start_date date, @min_date date 
set @months_back = 3 
set @month_skip = 2 
set @start_date = '2017-05-01' 
set @min_date = '2017-01-01' 


/* Common table expression to build list of Integers */ 
/* reference http://www.itprotoday.com/software-development/build-numbers-table-you-need if you want more info */ 

declare @end_int bigint = 50 
; WITH IntegersTableFill (ints) AS 
    (
    SELECT 
    CAST(0 AS BIGINT) AS 'ints' 
    UNION ALL 

SELECT (T.ints + 1) AS 'ints' 
    FROM IntegersTableFill T 
    WHERE ints <= (
     CASE 
     WHEN (@end_int <= 32767) THEN @end_int 
     ELSE 32767 
     END 
     ) 
) 

/* What we're going to do is define a series of periods. 
    These periods have a start date and an end date, and will simplify grouping 
    (in place of the calculate-and-union approach) 
    */ 


/* Now, we start defining the periods 
    @months_Back_start defines the end of the range we need to calculate for. 
    @month_skip defines the amount of time we have to jump back for each period 

*/ 

/* Using the number table we defined above and the data in our variables, calculate start and end dates */ 

,periodEndDates as 
    (

    select ints as Period 
    ,DATEADD(month, -(@months_back*ints), @start_date) as endOfPeriod 
    from IntegersTableFill itf 
) 

,periodStartDates as 
    (

    select * 
    ,DATEADD(month, -(@month_skip), endOfPeriod) as startOfPeriod 

    from periodEndDates 
) 

,finalPeriodData as 
(
    select (period) as period, startOfPeriod, endOfPeriod from periodStartDates 

) 

/* Link the entries in our original data to the periods they fall into */ 
/* NOTE: The join criteria originally specified allows values to fall into multiple periods. 
    You may want to fix this? 
*/ 

,periodTableJoin as 
(
select * from finalPeriodData fpd 
inner join @MyTable mt 
    on mt.the_date >= fpd.startOfPeriod 
    and mt.the_date <= fpd.endOfPeriod 
    and mt.the_date >= @min_date 
    and mt.the_date <= @start_date 
) 

/* Calculate averages, grouping by period and person */ 

,periodValueAggregate as 
(
select person, avg(the_value) as avg_the_value from 
periodTableJoin 
group by period, person 
) 

select * from periodValueAggregate 
0

私が提案する方法は、反復ではなく、設定に基づいています。 (私はあなたの問題に厳密に従っているわけではありませんが、矛盾を解消することができます) 本質的に、カレンダーを興味のある期間に分割することを検討しています。ピリオドはすべて同じ幅で連続しています。 これは、カレンダーテーブルを作成し、コードに示すように除算を使用してピリオドをマークすることを提案します。

DECLARE @CalStart   DATE = '2017-01-01' 
     ,@CalEnd   DATE = '2018-01-01' 
     ,@CalWindowSize  INT  = 2 

;WITH Numbers AS 
(
    SELECT TOP (DATEDIFF(MONTH, @CalStart, @CalEnd)) N = CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS INT) - 1 
    FROM syscolumns 
) 
SELECT CalWindow = N/@CalWindowSize 
     ,CalDate = DATEADD(MONTH, N, @CalStart) 
FROM Numbers 

正しく設定したら、関心のあるウィンドウを表すカレンダーが必要です。

personだけでなく、CalWindowでもこのカレンダーをデータセットとグループに貼り付ける必要があります。

DECLARE @MyTable TABLE(person INT, the_date date, the_value int) 
INSERT INTO @MyTable VALUES 
(1,'2017-01-01', 10), 
(1,'2017-02-01', 5), 
(1,'2017-03-01', 5), 
(1,'2017-04-01', 10), 
(1,'2017-05-01', 2), 
(2,'2017-04-01', 10), 
(2,'2017-05-01', 10), 
(2,'2017-05-01', 0), 
(3,'2017-01-01', 2) 

---------------------------------- 
-- Build Calendar 
---------------------------------- 
DECLARE @CalStart   DATE = '2017-01-01' 
     ,@CalEnd   DATE = '2018-01-01' 
     ,@CalWindowSize  INT  = 2 

;WITH Numbers AS 
(
    SELECT TOP (DATEDIFF(MONTH, @CalStart, @CalEnd)) N = CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS INT) - 1 
    FROM syscolumns 
) 
,Calendar AS 
(
    SELECT CalWindow = N/@CalWindowSize 
      ,CalDate = DATEADD(MONTH, N, @CalStart) 
    FROM Numbers 
) 
SELECT TB.Person 
     ,AVG(TB.the_value) 
FROM @MyTable TB 
JOIN Calendar CL ON TB.the_date = CL.CalDate 
GROUP BY CL.CalWindow, TB.person 

私はあなたの問題を理解しています。

関連する問題