私はこのような構造のテーブルがあります:
+-------+------------------+
| Value | Date |
+-------+------------------+
| 10 | 10/10/2010 10:00 |
| 11 | 10/10/2010 10:15 |
| 15 | 10/10/2010 10:30 |
| 15 | 10/10/2010 10:45 |
| 17 | 10/10/2010 11:00 |
| 18 | 10/10/2010 11:15 |
| 22 | 10/10/2010 11:30 |
| 30 | 10/10/2010 11:45 |
+-------+------------------+
現在、私はgroup byを使用して、min、max、avgを取得し、次のような1時間ごとのレポートを取得しています。
+-----+-----+-------+------------------+
| min | max | avg | Date |
+-----+-----+-------+------------------+
| 10 | 15 | 12.75 | 10/10/2010 10:00 |
| 17 | 30 | 21.75 | 10/10/2010 11:00 |
+-----+-----+-------+------------------+
各グループの最後の行と最初の行の値の差を計算して、次のようなものを生成する方法:
+-----+-----+-------+------+------------------+
| min | max | avg | diff | Date |
+-----+-----+-------+------+------------------+
| 10 | 15 | 12.75 | 5 | 10/10/2010 10:00 |
| 17 | 30 | 21.75 | 13 | 10/10/2010 11:00 |
+-----+-----+-------+------+------------------+
ありがとう。
diff
なしで結果を取得するために使用しているクエリを表示していません。私はそれがこのようなものであることを想定しています:
_SELECT
min = MIN(Value),
max = MAX(Value),
avg = AVG(Value), -- or, if Value is an int, like this, perhaps:
-- AVG(CAST(Value AS decimal(10,2))
Date = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM atable
GROUP BY
DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
;
_
また、firstおよびlastの意味を説明していません。この回答では、firstはグループの最初(Date
値による)を表し、同様にlastは- グループの最新。
diff
をスローする1つの方法は次のようになります。
最初に、2つの集計列、minDate
とmaxDate
を元のクエリに追加します。
_SELECT
min = MIN(Value),
max = MAX(Value),
avg = AVG(Value),
minDate = MIN(Date),
maxDate = MAX(Date),
Date = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM atable
GROUP BY
DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
;
_
次に、minDate
とmaxDate
で(個別に)元のテーブルに集計結果セットを結合し、対応するValue
sにアクセスします。
_SELECT
g.min,
g.max,
g.avg,
diff = last.Value - first.Value,
g.Date
FROM (
SELECT
min = MIN(Value),
max = MAX(Value),
avg = AVG(Value),
minDate = MIN(Date),
maxDate = MAX(Date),
Date = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM atable
GROUP BY
DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
) g
INNER JOIN atable first ON first.Date = g.minDate
INNER JOIN atable last ON last .Date = g.maxDate
;
_
上記では、Date
値(少なくとも対応する時間の最初または最後の値)が一意であると想定しています。そうしないと、出力の一部の時間に複数の行が表示されます。
SQL Server 2005以降のバージョンを使用している場合は、ウィンドウ集計関数MIN() OVER (...)
およびMAX() OVER (...)
を使用して、Value
またはminDate
のいずれかに対応するmaxDate
sを計算してから、あなたがおそらく今それをしている方法と同様の結果。具体的に私が話しているのは次のとおりです。
_WITH partitioned AS (
SELECT
Value,
Date,
GroupDate = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM atable
)
, firstlast AS (
SELECT
Value,
Date,
GroupDate,
FirstValue = CASE Date WHEN MIN(Date) OVER (PARTITION BY GroupDate) THEN Value END,
LastValue = CASE Date WHEN MAX(Date) OVER (PARTITION BY GroupDate) THEN Value END
FROM partitioned
)
SELECT
min = MIN(Value),
max = MAX(Value),
avg = AVG(Value), -- or, again, if Value is an int, cast it as a decimal or float
diff = MAX(LastValue) - MIN(FirstValue),
Date = GroupDate
FROM firstlast
GROUP BY
GroupDate
;
_
ご覧のとおり、最初の 共通テーブル式(CTE) は、すべての行を返し、計算列GroupDate
を追加するだけです。この列は、後でグループ化/パーティション化に使用されます。したがって、基本的にはグループ化式に名前を割り当てるだけです。これは、列が後で複数回参照されるため、クエリ全体の読みやすさ/保守性を向上させるために行われます。これは、最初のCTEが生成するものです。
_+-------+------------------+------------------+
| Value | Date | GroupDate |
+-------+------------------+------------------+
| 10 | 10/10/2010 10:00 | 10/10/2010 10:00 |
| 11 | 10/10/2010 10:15 | 10/10/2010 10:00 |
| 15 | 10/10/2010 10:30 | 10/10/2010 10:00 |
| 15 | 10/10/2010 10:45 | 10/10/2010 10:00 |
| 17 | 10/10/2010 11:00 | 10/10/2010 11:00 |
| 18 | 10/10/2010 11:15 | 10/10/2010 11:00 |
| 22 | 10/10/2010 11:30 | 10/10/2010 11:00 |
| 30 | 10/10/2010 11:45 | 10/10/2010 11:00 |
+-------+------------------+------------------+
_
2番目のCTEは、上記の結果にさらに2つの列を追加します。 window 集計関数MIN() OVER ...
とMAX() OVER ...
を使用してDate
と照合し、一致した場合、対応するValue
が別の列に返されます。 FirstValue
またはLastValue
:
_+-------+------------------+------------------+------------+-----------+
| Value | Date | GroupDate | FirstValue | LastValue |
+-------+------------------+------------------+------------+-----------+
| 10 | 10/10/2010 10:00 | 10/10/2010 10:00 | 10 | NULL |
| 11 | 10/10/2010 10:15 | 10/10/2010 10:00 | NULL | NULL |
| 15 | 10/10/2010 10:30 | 10/10/2010 10:00 | NULL | NULL |
| 15 | 10/10/2010 10:45 | 10/10/2010 10:00 | NULL | 15 |
| 17 | 10/10/2010 11:00 | 10/10/2010 11:00 | 17 | NULL |
| 18 | 10/10/2010 11:15 | 10/10/2010 11:00 | NULL | NULL |
| 22 | 10/10/2010 11:30 | 10/10/2010 11:00 | NULL | NULL |
| 30 | 10/10/2010 11:45 | 10/10/2010 11:00 | NULL | 30 |
+-------+------------------+------------------+------------+-----------+
_
この時点で、すべてが最終的な集計の準備ができています。 min
、max
、およびavg
列は以前と同じように集計され、集計されたdiff
から集計されたFirstValue
を減算することで、LastValue
を簡単に取得できるようになりました。上記の結果セットからわかるように、さまざまな関数を使用して、グループのFirstValue
とLastValue
を取得できます。これは、MIN
、MAX
、SUM
、AVG
のようになります。これらすべてのグループで値が1つしかないためです。
ただし、メインのSELECTは、ご覧のとおり、LastValue
ではなくMAX()
を、FirstValue
ではMIN()
をそれぞれ適用します。それは意図的なものです。この2番目の提案では、最初の提案とは異なり、Date
が一意である必要は実際にはないbut、minDate
またはmaxDate
のいずれかに複数のValue
が関連付けられている場合は、 FirstValue
またはLastValue
には、グループごとに複数の値が含まれ、次のようになります。
_+-------+------------------+------------------+------------+-----------+
| Value | Date | GroupDate | FirstValue | LastValue |
+-------+------------------+------------------+------------+-----------+
| 9 | 10/10/2010 10:00 | 10/10/2010 10:00 | 9 | NULL |
| 10 | 10/10/2010 10:00 | 10/10/2010 10:00 | 10 | NULL |
| 11 | 10/10/2010 10:15 | 10/10/2010 10:00 | NULL | NULL |
| 15 | 10/10/2010 10:30 | 10/10/2010 10:00 | NULL | NULL |
| 15 | 10/10/2010 10:45 | 10/10/2010 10:00 | NULL | 15 |
| 17 | 10/10/2010 11:00 | 10/10/2010 11:00 | 17 | NULL |
| 18 | 10/10/2010 11:15 | 10/10/2010 11:00 | NULL | NULL |
| 22 | 10/10/2010 11:30 | 10/10/2010 11:00 | NULL | NULL |
| 30 | 10/10/2010 11:45 | 10/10/2010 11:00 | NULL | 30 |
| 33 | 10/10/2010 11:45 | 10/10/2010 11:00 | NULL | 33 |
+-------+------------------+------------------+------------+-----------+
_
この状況では、最大の最後の値と最小の最初の値の違いを取るほうが自然だと思いました。ただし、ここで適用するルールをよく理解している必要があるため、それに応じてクエリを変更するだけです。
SQL Fiddleで両方のソリューションをテストできます。
更新
SQL Server 2012以降、 FIRST_VALUE および LAST_VALUE 関数を使用して、上記の最後のクエリでfirstlast
CTEのCASE式を次のように置き換えることもできます。
_FirstValue = FIRST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),
LastValue = LAST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
_
この場合、(メインSELECTで)FirstValue
およびLastValue
に対してMINまたはMAXを使用するかどうかは関係ありません。各__columnは、同じValue
グループのすべての行でまったく同じ値(最初または最後のGroupDate
)になります。したがって、MIN()
とMAX()
は、どちらの場合も同じ結果を返します。
実際、diff
をfirstlast
CTEで直接取得し、メインクエリで、MIN/MAXを使用して集計するか、GROUP BYに追加して、次のように集計せずに参照できます。
_WITH partitioned AS (
SELECT
Value,
Date,
GroupDate = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM atable
)
, firstlast AS (
SELECT
Value,
Date,
GroupDate,
diff = LAST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
- FIRST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM partitioned
)
SELECT
min = MIN(Value),
max = MAX(Value),
avg = AVG(Value),
diff,
Date = GroupDate
FROM firstlast
GROUP BY
GroupDate,
diff
;
_
さらに一歩進めると、メインのクエリの代わりに、対応するウィンドウ関数を使用して、min
でmax
、avg
、firstlast
を取得することもできます。
_min = MIN(Value) OVER (PARTITION BY GroupDate),
max = MAX(Value) OVER (PARTITION BY GroupDate),
avg = AVG(Value) OVER (PARTITION BY GroupDate),
_
これらの3つの追加の列と以前の変更により、firstlast
CTEは、例のように次のような行セットを返します。
_+-------+------------------+------------------+-----+-----+-------+------+
| Value | Date | GroupDate | min | max | avg | diff |
+-------+------------------+------------------+-----+-----+-------+------+
| 10 | 10/10/2010 10:00 | 10/10/2010 10:00 | 10 | 15 | 12.75 | 5 |
| 11 | 10/10/2010 10:15 | 10/10/2010 10:00 | 10 | 15 | 12.75 | 5 |
| 15 | 10/10/2010 10:30 | 10/10/2010 10:00 | 10 | 15 | 12.75 | 5 |
| 15 | 10/10/2010 10:45 | 10/10/2010 10:00 | 10 | 15 | 12.75 | 5 |
| 17 | 10/10/2010 11:00 | 10/10/2010 11:00 | 17 | 30 | 21.75 | 13 |
| 18 | 10/10/2010 11:15 | 10/10/2010 11:00 | 17 | 30 | 21.75 | 13 |
| 22 | 10/10/2010 11:30 | 10/10/2010 11:00 | 17 | 30 | 21.75 | 13 |
| 30 | 10/10/2010 11:45 | 10/10/2010 11:00 | 17 | 30 | 21.75 | 13 |
+-------+------------------+------------------+-----+-----+-------+------+
_
GroupDate
、min
、max
、avg
、およびdiff
(最終セットに実際に必要な列)が、同じグループに属するすべての行にわたって単純に繰り返されていることに注意してください。つまり、Value
とDate
を削除し、GroupDate
をDate
に名前変更して、列を少し並べ替え、結果のセットにDISTINCTを適用すると、最後のSELECTを削除できます。
_WITH partitioned AS (
SELECT
Value,
Date,
GroupDate = DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)
FROM
atable
)
SELECT DISTINCT
min = MIN(Value) OVER (PARTITION BY GroupDate),
max = MAX(Value) OVER (PARTITION BY GroupDate),
avg = AVG(Value) OVER (PARTITION BY GroupDate),
diff = LAST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
- FIRST_VALUE(Value) OVER (PARTITION BY GroupDate ORDER BY Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),
Date = GroupDate
FROM
partitioned
;
_
最後に、GroupDate
の計算を、min
、max
などが計算されるのと同じスコープに移動することもできます。そのためにCROSS APPLYを使用して、クエリを完全にネストする必要をなくすことができます。つまり、このようにして、partitioned
CTEを取り除くこともできます。 entireクエリは次のようになります。
_SELECT DISTINCT
min = MIN(t.Value) OVER (PARTITION BY x.GroupDate),
max = MAX(t.Value) OVER (PARTITION BY x.GroupDate),
avg = AVG(t.Value) OVER (PARTITION BY x.GroupDate),
diff = LAST_VALUE(t.Value) OVER (PARTITION BY x.GroupDate ORDER BY t.Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
- FIRST_VALUE(t.Value) OVER (PARTITION BY x.GroupDate ORDER BY t.Date ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),
Date = x.GroupDate
FROM
atable AS t
CROSS APPLY (SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, Date), 0)) AS x (GroupDate)
;
_
同じ結果を返します。あなたはそれをテストすることができます SQL Fiddle も。
現在実行していることを行い、列を追加します
max(value)-min(value) as diff