sql >> データベース >  >> RDS >> Database

単純な集約からスライディング集約へのSQLでのデータ集約の技術

    SQLの旅を始めて、SQLでのデータの集計と、単純な集計やスライド式の集計を含む集計の種類を理解しましょう。

    集計にジャンプする前に、SQL全般、特に集計に関して、一部の開発者が見逃しがちな興味深い事実を検討する価値があります。

    この記事では、SQLはMicrosoftバージョンのSQLであり、標準のSQLよりも多くの機能を備えたT-SQLを指します。

    SQLの背後にある数学

    T-SQLは、厳密な数学ベースの言語ではありませんが、いくつかの堅実な数学の概念に基づいていることを理解することが非常に重要です。

    Itzik Ben-Ganによる著書「Microsoft_SQL_Server_2008_T_SQL_Fundamentals」によると、SQLは、リレーショナルデータベース管理システム(RDBMS)でデータをクエリおよび管理するように設計されています。

    リレーショナルデータベース管理システム自体は、2つの確かな数学的ブランチに基づいています。

    • 集合論
    • 述語論理

    集合論

    集合論は、その名前が示すように、集合に関する数学の一分野であり、明確な別個のオブジェクトのコレクションとも呼ばれます。

    要するに、集合論では、私たちは個々のアイテムを考えるのと同じように、物や物を全体として考えます。

    たとえば、本は明確に区別できるすべての本のセットであるため、その中のすべての本の詳細を取得するのに十分な本全体を取り上げます。

    述語論理

    述語論理は、変数の条件または値に応じてtrueまたはfalseを返すブール論理です。

    述語論理を使用して、整合性ルール(価格は0.00より大きくなければなりません)またはフィルターデータ(価格が10.00より大きくなければなりません)を適用できますが、T-SQLのコンテキストでは、次の3つの論理値があります。

    >
    1. 本当
    2. 誤り
    3. 不明(Null)

    これは次のように説明できます:

    述語の例は、「本の価格が10.00より大きい場合」です。

    数学についてはこれで十分ですが、この記事の後半で参照することに注意してください。

    SQLでのデータの集約が簡単な理由

    最も単純な形式でSQLにデータを集約することは、すべてを一度に合計について知ることです。

    たとえば、すべての顧客のリストとその詳細を含む顧客テーブルがある場合、顧客テーブルの集計データから、取得した顧客の総数を知ることができます。

    前に説明したように、セットは単一のアイテムと見なされるため、集計関数をテーブルに適用するだけで合計を取得できます。

    SQLは元々セットベースの言語であるため(前述のとおり)、他の言語と比較して、集計関数をSQLに適用する方が比較的簡単です。

    たとえば、データベース内のすべての製品のレコードを含む製品テーブルがある場合、ループで1つずつカウントするのではなく、すぐにカウント関数を製品テーブルに適用して、製品の総数を取得できます。

    データ集約レシピ

    SQLでデータを集約するには、少なくとも次のものが必要です。

    1. 集計すると意味のある列を持つデータ(テーブル)
    2. データに適用される集計関数

    サンプルデータの準備(表)

    3つのもの(列)を含む単純な注文テーブルの例を見てみましょう:

    1. 注文番号(OrderId)
    2. 注文日(OrderDate)
    3. 注文金額(TotalAmount)

    さらに先に進むために、AggregateSampleデータベースを作成しましょう:

    -- Create aggregate sample database 
    CREATE DATABASE AggregateSample
    

    次に、サンプルデータベースに次のように注文テーブルを作成します。

    -- Create order table in the aggregate sample database
    USE AggregateSample
    
    CREATE TABLE SimpleOrder
      (OrderId INT PRIMARY KEY IDENTITY(1,1),
      OrderDate DATETIME2,
      TotalAmount DECIMAL(10,2)
      )
    

    サンプルデータの入力

    1行追加してテーブルにデータを入力します:

    INSERT INTO dbo.SimpleOrder
    (
      OrderDate
     ,TotalAmount
    )
    VALUES
    (
      '20180101' -- OrderDate - datetime2
     ,20.50 -- TotalAmount - decimal(10, 2)
    );
    GO
    

    今すぐ表を見てみましょう:

    -- View order table 
    SELECT OrderId ,OrderDate ,TotalAmount FROM SimpleOrder
    

    この記事ではdbForgeStudiofor SQL Serverを使用しているため、SSMS(SQL Server Management Studio)で同じコードを実行した場合、出力の外観のみが異なる可能性があることに注意してください。スクリプトとその結果に関する限り、違いはありません。

    基本的な集計関数

    テーブルに適用できる基本的な集計関数は次のとおりです。

    1. 合計
    2. カウント
    3. 最小
    4. 最大
    5. 平均

    単一レコードテーブルの集約

    ここで興味深い質問は、「この場合のように行が1つしかない場合、テーブル内のデータ(レコード)を集計(合計またはカウント)できますか?」です。答えは「はい」です。あまり意味がありませんが、データがどのように集計の準備ができているかを理解するのに役立ちます。

    注文の総数を取得するには、テーブルでcount()関数を使用します。前述のように、SQLはセットベースの言語であり、操作をセットに適用できるため、集計関数をテーブルに適用するだけです。直接。

    -- Getting total number of orders placed so far
    SELECT COUNT(*) AS Total_Orders FROM SimpleOrder
    

    では、1つのレコードの最小、最大、平均の金額の注文についてはどうでしょうか。

    -- Getting order with minimum amount, maximum amount, average amount and total orders
    SELECT
      COUNT(*) AS Total_Orders
     ,MIN(TotalAmount) AS Min_Amount
     ,MAX(TotalAmount) AS Max_Amount
     ,AVG(TotalAmount) Average_Amount
    FROM SimpleOrder
    

    出力からわかるように、単一のレコードがある場合、最小、最大、および平均の量は同じであるため、単一のレコードに集計関数を適用することは可能ですが、同じ結果が得られます。

    集計されたデータを理解するには、少なくとも複数のレコードが必要です。

    複数のレコードテーブルの集約

    次のように、さらに4つのレコードを追加しましょう。

    INSERT INTO dbo.SimpleOrder
    (
      OrderDate
     ,TotalAmount
    )
    VALUES
    (
      '20180101' -- OrderDate - datetime2
     ,20.50 -- TotalAmount - decimal(10, 2)
    ),
    (
      '20180102' -- OrderDate - datetime2
     ,30.50 -- TotalAmount - decimal(10, 2)
    ),
    (
      '20180103' -- OrderDate - datetime2
     ,10.50 -- TotalAmount - decimal(10, 2)
    ),
    (
      '20180110' -- OrderDate - datetime2
     ,100.50 -- TotalAmount - decimal(10, 2)
    );
    
    GO
    

    表は次のようになります。

    ここで集計関数をテーブルに適用すると、良い結果が得られます:

    -- Getting order with minimum amount, maximum amount, average amount and total orders
    SELECT
      COUNT(*) AS Total_Orders
     ,MIN(TotalAmount) AS Min_Amount
     ,MAX(TotalAmount) AS Max_Amount
     ,AVG(TotalAmount) Average_Amount
    FROM SimpleOrder
    

    集合データのグループ化

    集計データを任意の列または列のセットでグループ化して、その列に基づいて集計を取得できます。

    たとえば、日付ごとの注文の総数を知りたい場合は、 次のようにGroupby句を使用して、テーブルを日付でグループ化する必要があります。

    -- Getting total orders per date
    SELECT
      OrderDate
     ,COUNT(*) AS Total_Orders
    FROM SimpleOrder
    GROUP BY OrderDate
    

    出力は次のとおりです。

    したがって、すべての注文金額の合計を確認したい場合は、 次のようにグループ化せずに、合計関数を合計金額列に適用するだけです。

    -- Sum of all the orders amount
    SELECT
      SUM(TotalAmount) AS Sum_of_Orders_Amount
    FROM SimpleOrder
    

    日付ごとの注文金額の合計を取得するには、次のように、上記のSQLステートメントに日付ごとのグループを追加するだけです。

    -- Sum of	all	the	orders amount per date
    SELECT
      OrderDate
     ,SUM(TotalAmount) AS Sum_of_Orders
    FROM SimpleOrder
    GROUP BY OrderDate
    

    データをグループ化せずに合計を取得する

    集計がすべてのテーブルを対象としている場合は、グループ化することなく、合計注文数、最大注文額、最小注文額、注文額の合計、平均注文額などの合計をすぐに取得できます。

    -- Getting order with minimum amount, maximum amount, average amount, sum of amount and total orders
    SELECT
      COUNT(*) AS Total_Orders
     ,MIN(TotalAmount) AS Min_Amount
     ,MAX(TotalAmount) AS Max_Amount
     ,AVG(TotalAmount) AS Average_Amount
     ,SUM(TotalAmount) AS Sum_of_Amount
    FROM SimpleOrder
    

    注文への顧客の追加

    テーブルに顧客を追加して、楽しみを追加しましょう。これを行うには、顧客の別のテーブルを作成し、顧客IDを注文テーブルに渡しますが、シンプルに保ち、データウェアハウススタイル(テーブルが非正規化されている)をモックするために、注文テーブルに顧客名列を次のように追加します:

    -- Adding CustomerName column and data to the order table
    ALTER TABLE SimpleOrder 
    ADD CustomerName VARCHAR(40) NULL 
      GO
      
    UPDATE SimpleOrder
    SET CustomerName = 'Eric'
    WHERE OrderId = 1
    GO
    
    UPDATE SimpleOrder
    SET CustomerName = 'Sadaf'
    WHERE OrderId = 2
    GO
    
    UPDATE SimpleOrder
    SET CustomerName = 'Peter'
    WHERE OrderId = 3
    GO
    
    UPDATE SimpleOrder
    SET CustomerName = 'Asif'
    WHERE OrderId = 4
    GO
    
    UPDATE SimpleOrder
    SET CustomerName = 'Peter'
    WHERE OrderId = 5
    GO
    

    顧客ごとの合計注文数の取得

    顧客ごとの合計注文数を取得する方法を推測できますか?次のように、customer(CustomerName)でグループ化し、集計関数count()をすべてのレコードに適用する必要があります。

    -- Total orders per customer
      SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder 
        GROUP BY CustomerName
    

    注文テーブルにさらに5つのレコードを追加する

    次に、次のように単純注文テーブルにさらに5行を追加します。

    -- Adding 5 more records to order table
    INSERT INTO SimpleOrder (OrderDate, TotalAmount, CustomerName)
      VALUES 
      ('01-Jan-2018', 70.50, 'Sam'),
      ('02-Jan-2018', 170.50, 'Adil'),
      ('03-Jan-2018',50.00,'Sarah'),
      ('04-Jan-2018',50.00,'Asif'),
      ('11-Jan-2018',50.00,'Peter')
    GO
    

    今すぐデータをご覧ください:

    -- Viewing order table after adding customer name and five more rows
    SELECT OrderId,CustomerName,OrderDate,TotalAmount FROM SimpleOrder 
    GO
    

    顧客あたりの合計注文数を最大注文数から最小注文数で並べ替える

    最大注文から最小注文でソートされた顧客あたりの合計注文数に関心がある場合は、次のようにこれを小さなステップに分割することはまったく悪い考えではありません。

    -- (1) Getting total orders
    SELECT COUNT(*) AS Total_Orders FROM SimpleOrder
    

    -- (2) Getting total orders per customer
    SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder
    GROUP BY CustomerName
    

    注文数を最大から最小に並べ替えるには、次のように、最後にcount()を指定したOrder By DESC(降順)句を使用する必要があります。

    -- (3) Getting total orders per customer from maximum to minimum orders
    SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder
    GROUP BY CustomerName
    ORDER BY COUNT(*) DESC
    

    日付ごとの合計注文数を最新の注文で最初に並べ替える

    上記の方法を使用すると、次のように、最初に最新の注文で並べ替えられた日付ごとの合計注文を見つけることができます。

    -- Getting total orders per date from most recent first
    SELECT CAST(OrderDate AS DATE) AS OrderDate,COUNT(*) AS Total_Orders FROM SimpleOrder
    GROUP BY OrderDate
    ORDER BY OrderDate DESC
    

    CAST関数は、日付部分のみを取得するのに役立ちます。出力は次のとおりです。

    意味がある限り、できるだけ多くの組み合わせを使用できます。

    アグリゲーションの実行

    データへの集計関数の適用に慣れてきたので、高度な形式の集計に移りましょう。そのような集計の1つに、実行中の集計があります。

    実行中の集計は、データセット全体ではなくデータのサブセットに適用される集計であり、データ上に小さなウィンドウを作成するのに役立ちます。

    これまで、すべての集計関数がテーブルのすべての行に適用され、注文日や顧客名などの列でグループ化できることを確認しましたが、集計を実行すると、全体をグループ化せずに集計関数を自由に適用できます。データセット。

    明らかに、これは、ウィンドウ関数と集計の実行に精通していないSQL初心者(または一部の開発者はこれを見落としている)にとっては少し奇妙なGroupBy句を使用せずに集計関数を適用できることを意味します。

    Windows on Data

    前述のように、実行中の集計はデータセットのサブセットに適用されます。つまり、データの小さなウィンドウに適用されます。

    ウィンドウは、セット内のセットまたはテーブル内のテーブルと考えてください。この場合のデータのウィンドウ処理の良い例は、異なる日付で行われた注文を含む注文テーブルがあるため、各日付が別々のウィンドウである場合、適用したのと同じ方法で各ウィンドウに集計関数を適用できるとしたらどうでしょうか。テーブル。

    注文テーブル(SimpleOrder)を注文日(OrderDate)で次のように並べ替えると、次のようになります。

    -- View order table sorted by order date
    SELECT so.OrderId
          ,so.OrderDate
          ,so.TotalAmount
          ,so.CustomerName FROM SimpleOrder so
      ORDER BY so.OrderDate
    

    集計を実行する準備ができているデータ上のWindowsは、以下を参照できます。

    これらのウィンドウまたはサブセットは、6つのミニ注文日ベースのテーブルと見なすことができ、これらの各ミニテーブルに集計を適用できます。

    OVER()句内でのパーティションの使用

    実行中の集計は、OVER()句内の「Partitionby」を使用してテーブルをパーティション化することで適用できます。

    たとえば、各日付がデータセットのサブテーブルまたはウィンドウであるなど、日付で注文テーブルを分割する場合は、注文日でデータを分割する必要があります。これは、COUNT(などの集計関数を使用して実現できます。 )OVER()を使用し、次のようにOVER()内でパーティションを作成します。

    -- Running Aggregation on Order table by partitioning by dates
    SELECT OrderDate, Total_Orders=COUNT(*) OVER(PARTITION BY OrderDate)  FROM SimpleOrder
    

    日付ウィンドウごとの現在の合計の取得(パーティション)

    集計を実行すると、集計スコープを定義されたウィンドウのみに制限するのに役立ち、次のようにウィンドウごとの現在の合計を取得できます。

    -- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per date window (partition by date)
    SELECT CAST (OrderDate AS DATE) AS OrderDate,
      Count=COUNT(*) OVER (PARTITION BY OrderDate),
      Min_Amount=MIN(TotalAmount) OVER (PARTITION BY OrderDate) ,
      Max_Amount=MAX(TotalAmount) OVER (PARTITION BY OrderDate) ,
      Average_Amount=AVG(TotalAmount) OVER (PARTITION BY OrderDate),
      Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY OrderDate)
      FROM SimpleOrder
    

    顧客ウィンドウ(パーティション)ごとの現在の合計の取得

    日付ウィンドウごとの現在の合計と同様に、次のように注文セット(テーブル)を小さな顧客のサブセット(パーティション)に分割することで、顧客ウィンドウごとの現在の合計を計算することもできます。

    -- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per customer window (partition by customer)
    SELECT CustomerName,
    CAST (OrderDate AS DATE) AS OrderDate,
      Count=COUNT(*) OVER (PARTITION BY CustomerName),
      Min_Amount=MIN(TotalAmount) OVER (PARTITION BY CustomerName) ,
      Max_Amount=MAX(TotalAmount) OVER (PARTITION BY CustomerName) ,
      Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName),
      Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY CustomerName)
      FROM SimpleOrder
      ORDER BY Count DESC,OrderDate
    

    スライディングアグリゲーション

    スライディングアグリゲーションは、ウィンドウ内のフレームに適用できるアグリゲーションです。つまり、ウィンドウ(パーティション)内でスコープをさらに狭めることを意味します。

    つまり、現在の合計は、テーブル内に作成するウィンドウ(サブセット)全体の合計(合計、平均、最小、最大、カウント)を示し、スライド式の合計は、合計(合計、平均、最小、最大、カウント)を示します。テーブルのウィンドウ(サブセット)内のフレーム(サブセットのサブセット)の場合。

    たとえば、(顧客ごとの)顧客に基づいてデータにウィンドウを作成すると、顧客「Peter」のウィンドウに3つのレコードがあり、すべての集計がこれらの3つのレコードに適用されていることがわかります。ここで、一度に2行のみのフレームを作成する場合、つまり、集計がさらに絞り込まれ、1行目と2行目に適用され、次に2行目と3行目に適用されます。

    行の使用PRECEEDINGwith Order By Inside OVER()句

    スライド式集計は、ROWS<行数>PRECEEDING with Order By(Partition Byの後)を追加することで適用できますが、ROWSPRECEEDINGはウィンドウ内のフレームのスコープを決定します。

    たとえば、顧客ごとに一度に2行のみのデータを集計する場合は、次のようにスライド集計を注文テーブルに適用する必要があります。

    -- Getting minimum amount, maximum amount, average amount per frame per customer window 
    SELECT CustomerName,
     Min_Amount=Min(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING), 
     Max_Amount=Max(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING) ,
     Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate  ROWS 1 PRECEDING)
     FROM SimpleOrder so
     ORDER BY CustomerName
    

    それがどのように機能するかを理解するために、フレームとウィンドウのコンテキストで元のテーブルを見てみましょう:

    顧客ピーターウィンドウの最初の行で、彼は30.50の金額の注文を出しました。これは、顧客ウィンドウ内のフレームの始まりであるため、最小値と最大値は、比較する前の行がない場合と同じです。

    次に、最小量は同じままですが、前の行(最初の行)の量が30.50で、この行の量が100.50であるため、最大値は100.50になるため、2つの最大値は100.50になります。

    次に、3番目の行に移動すると、2番目の行との比較が行われるため、2つの行の最小量は50.00で、2つの行の最大量は100.50です。

    MDX年初来(YTD)関数と実行中の集計

    MDXは、多次元データ(キューブなど)のクエリに使用される多次元表現言語であり、ビジネスインテリジェンス(BI)ソリューションで使用されます。

    https://docs.microsoft.com/en-us/sql/mdx/ytd-mdxによると、MDXの年初来(YTD)関数は、集計の実行またはスライドと同じように機能します。たとえば、パラメータが指定されていない状態でよく使用されるYTDは、現在までの現在の合計を表示します。

    つまり、この関数を年に適用すると、すべての年のデータが得られますが、3月にドリルダウンすると、年の初めから3月までのすべての合計が得られます。

    これはSSRSレポートで非常に役立ちます。

    やるべきこと

    それでおしまい!この記事を読み終えると、基本的なデータ分析を行う準備が整いました。次のことにより、スキルをさらに向上させることができます。

    1. Total Amountなどの他の列にウィンドウを作成して、実行中の集計スクリプトを作成してみてください。
    2. 合計金額などの他の列にフレームを作成して、スライド式骨材スクリプトを作成してみてください。
    3. テーブル(またはさらに多くのテーブル)に列とレコードを追加して、他の集計の組み合わせを試すことができます。
    4. この記事に記載されているサンプルスクリプトは、データセットの背後にあるSSRSレポートで使用するストアドプロシージャに変換できます。

    参照:

    • Ytd(MDX)
    • dbForge Studio for SQL Server

    1. ORA-03115を取得:anonymous pl/sqlからvarcharの配列をフェッチ中にサポートされていないネットワークデータ型または表現エラー

    2. MariaDBデータベースのすべてのビューを一覧表示する4つの方法

    3. SQLに英数字シーケンスジェネレータを使用することは可能ですか?

    4. JDBCでスクロール可能で更新可能なResultSetオブジェクトを作成する方法