9

SQLクエリでデータを範囲でグループ化する方法に関するSOに関する多くの質問を見てきました。

GROUP BY正確なシナリオはさまざまですが、それぞれの一般的な根本的な問題は、列内の個々の値ではなく、値の範囲でグループ化することです。つまり、データベース テーブルに格納しているよりも精度の低い粒度でグループ化することです。

これは、ヒストグラム、カレンダー表現、ピボット テーブル、その他の特注のレポート出力などを生成するときに、現実の世界で頻繁に発生します。

いくつかのサンプルデータ (無関係なテーブル):

|      OrderHistory       |       |         Staff        |                
---------------------------       ------------------------
|    Date    |  Quantity  |       |   Age     |   Name   |
---------------------------       ------------------------       
|01-Jul-2012 |     2      |       |    19     |   Barry  |
|02-Jul-2012 |     5      |       |    53     |   Nigel  |
|08-Jul-2012 |     1      |       |    29     |   Donna  |
|10-Jul-2012 |     3      |       |    26     |   James  |
|14-Jul-2012 |     4      |       |    44     |   Helen  |
|17-Jul-2012 |     2      |       |    49     |   Wendy  |
|28-Jul-2012 |     6      |       |    62     |   Terry  |
---------------------------       ------------------------

ここDateで、テーブルの列を使用してOrderHistory、週ごと、つまり 7 日間の範囲でグループ化するとします。Staffまたは、 10 歳の範囲にグループ化することもできます。

|       Week      |  QtyCount  |        |  AgeGroup | NameCount |         
--------------------------------        -------------------------
|01-Jul to 07-Jul |     7      |        |   10-19   |    1      |
|08-Jul to 14-Jul |     8      |        |   20-29   |    2      | 
|15-Jul to 21-Jul |     2      |        |   30-39   |    0      |
|22-Jul to 28-Jul |     6      |        |   40-49   |    2      |
--------------------------------        |   50-59   |    1      |
                                        |   60-69   |    1      |
                                        -------------------------

GROUP BY DateそしてGROUP BY Age、それを自分で行うことはありません。

私が見る最も一般的な答え (どれも一貫して「正しい」と投票されていません) は、次の 1 つ以上を使用することです。

  • CASEステートメントの束、グループごとに 1 つ
  • グループごとに異なる句を使用した一連のUNIONクエリWHERE
  • 私はSQL Serverを扱っているのでPIVOT()UNPIVOT()
  • サブセレクト、一時テーブル、またはビュー構造を使用した 2 段階のクエリ

そのようなクエリを処理するための確立された一般的なパターンはありますか?

4

7 に答える 7

3

ファクトテーブルディメンションテーブルなど、いくつかのディメンションモデリング手法を使用できます。注文履歴は、DateディメンションへのDateKey外部キー関係を持つファクトテーブルとして機能できます。日付ディメンションには、次のようなスキーマを含めることができます。

日付の次元

日付テーブルには、N年までのデータが事前に入力されていることに注意してください。

上記の例を使用して、結果を取得するためのサンプルクエリを次に示します。

select CalendarWeek, sum(Quantity)
from OrderHistory a
join DimDate b
    on a.DateKey = b.DateKey
group by CalendarWeek

スタッフテーブルの場合、年齢の代わりに誕生日キーを保存し、クエリに年齢と範囲を計算させることができます。

これがSQLフィドルです

日付ディメンションの作成スクリプトはここから取得されました。

于 2012-07-17T19:56:26.433 に答える
2

よくあることですが、この SQL の問題では、構成に複数のパターンを使用する必要があります。

この場合、使用できる2つは次のとおりです

  • NTILE
  • 数字の表

NTITLEを使用して、一定数のグループを作成できます。ただし、グループの各メンバーを表すわけではないため、数値テーブルも使用する必要があります。SQL Server を使用しているため、シミュレートする必要がないので簡単です。

これはスタッフの問題の例です

WITH g as (
SELECT 
     NTILE(6) OVER (ORDER BY number) grp, 
     NUMBER
FROM 
    master..spt_values
WHERE 
    TYPE = 'P'
and number >=10 and number <=69
)
SELECT 
      CAST(min(g.number) as varchar) + ' - ' + 
      CAST(max(g.number) as varchar) AgeGroup ,
      COUNT(s.age) NameCount
FROM 
     g
     LEFT JOIN Staff s
     ON g.NUMBER = s.Age
GROUP BY 
    grp

デモ

これを日付に適用することも、日々の操作が必要なだけです

于 2012-07-17T17:19:39.060 に答える
1

年齢(または日付)とそれに対応する範囲だけの新しい小さなテーブルで、年齢(または日付)を外部キーとして扱うことはできませんか?結合ステートメントは、AgeGroupsを含む列を持つ新しいテーブルを提供できます。新しいテーブルでは、標準のgroup-byメソッドを使用できます。

グループ化のために新しいテーブルを作成するのは無謀に思えますが、プログラムで作成するのは簡単であり、caseステートメントやwhere句よりも保守(または削除して再作成)する方が簡単だと思います。このクエリの結果が1回限りの場合、使い捨てのsqlステートメントがおそらく最適に機能しますが、私の方法は長期的な使用に最も適していると思います。

于 2012-07-17T16:23:50.670 に答える
1

OVER句とそれに関連する句を見てください:PARTITION BY、ROW、RANGE .. ..

関連するウィンドウ関数が適用される前に、行セットの分割と順序を決定します。つまり、OVER句は、クエリ結果セット内のウィンドウまたはユーザー指定の行セットを定義します。次に、ウィンドウ関数はウィンドウ内の各行の値を計算します。関数でOVER句を使用して、移動平均、累積集計、現在の合計、またはグループごとの上位Nの結果などの集計値を計算できます。

于 2012-07-17T16:25:06.203 に答える
1

最初の例では、間隔が規則的であるため、関数を使用するだけで目的の結果を得ることができます。以下は、必要に応じてデータを取得する例です。最初のクエリは、最初の列を日付形式で保持します (SQL 以外の形式で処理する方法が望ましいです)。2 番目のクエリは、文字列変換を行います。

DECLARE @OrderHistory TABLE (Date DATE, Quantity INT)
INSERT @OrderHistory VALUES 
    ('20120701', 2), ('20120702', 5), ('20120708', 1), ('20120710', 3), 
    ('20120714', 4), ('20120717', 2), ('20120728', 6)

SET DATEFIRST 7

SELECT  DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date) AS WeekStart,
        SUM(Quantity) AS Quantity
FROM    @OrderHistory
GROUP BY DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date)

SELECT  WeekStart,
        SUM(Quantity) AS Quantity
FROM    @OrderHistory
        CROSS APPLY 
        (   SELECT  CONVERT(VARCHAR(6), DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date), 6) + ' to ' + 
                    CONVERT(VARCHAR(6), DATEADD(DAY, 7 - DATEPART(WEEKDAY, Date), Date), 6) AS WeekStart
        ) ws
GROUP BY WeekStart

以下を使用して、年齢グループに対して同様のことができます。

SELECT  CAST(FLOOR(Age / 10.0) * 10 AS INT)

ただし、このグループのデータがないため、これは 30 ~ 39 では失敗します。

この問題に関する私のスタンスは、一時テーブルを使用して 1 回限りのクエリを実行している場合、cte または case ステートメントは問題なく機能するはずです。これは、小さなデータ セットで同じクエリを再利用することにも拡張する必要があります。

ただし、グループを再利用する可能性が高い場合、または大量のデータを参照している場合は、範囲が定義され、必要な列にインデックスが適用された永続的なテーブルを作成します。これは、OLAP でディメンションを作成する基本です。

于 2012-07-17T16:48:53.017 に答える
1

数年前、Oracle DB で次のようにしました。

  1. Sessions と Ranges の 2 つのテーブルがありました。範囲には、セッションを参照する外部キーがありました。
  2. SQL を実行する必要があるときは、セッションに新しいレコードを作成し、そのセッションを参照するいくつかの新しいレコードを範囲に作成しました。
  3. 私たちの SQL は、セッションによるフィルターを使用して範囲を結合しました。
    select sum(t.Value), r.Name
    データテーブル t から
    Join Ranges r on (r.Session = ? and r.Start t.MyDate)
    r.Name でグループ化
  1. 結果が得られた後、そのレコードをセッションから削除し、カスケードによって削除された範囲からレコードを削除しました。
  2. 異常な状況 (強制終了されたプロセスなど) の場合にリークされたジャンク レコードからセッションをパージするデーモン ジョブがありました。

これは完璧に機能しました。その時以来、Oracle は新しい SQL 句を追加しており、代わりにそれらを使用できる可能性があります。しかし、他の RDBMS では、これは依然として有効な方法です。

もう 1 つの方法は、GET_YEAR_BY_DATE または GET_QUARTER_BY_DATE または GET_WEEK_BY_DATE などの多数の関数を作成することです (これらの関数は、対応する期間の開始日を返します。たとえば、任意の日付が年の開始日を返します)。そして、それらでグループ化します:

select sum(Value), GET_YEAR_BY_DATE(MyDate) from DataTable
group by GET_YEAR_BY_DATE(MyDate)
于 2012-07-17T16:38:41.773 に答える
1

このジャンルで私のお気に入りのケースは、トランザクションを会計四半期または会計年度ごとにグループ化する必要がある場合です。さまざまな企業の会計四半期または会計年度の境界は、奇妙に接する可能性があります。

これを実装する私のお気に入りの方法は、日付の属性用に別のテーブルを作成することです。テーブルを「アルマナック」と呼びましょう。このテーブルの列の 1 つは会計四半期で、もう 1 つは会計年度です。この表の鍵はもちろん日付です。10 年分のデータで 3,650 行が埋まり、閏年の数行分が追加されます。次に、このテーブルを最初から作成できるプログラムが必要です。すべてのエンタープライズ カレンダー ルールは、この 1 つのプログラムに組み込まれています。

トランザクション データを会計四半期ごとにグループ化する必要がある場合は、このテーブルを日付で結合し、会計四半期ごとにグループ化します。

このパターンは、他の種類の範囲によるグループ化に拡張できると思いますが、自分で行ったことはありません。

于 2012-07-17T16:38:48.247 に答える