6

group by または同等のものを使用して実行中の合計を実行する良い方法を見つけるのに苦労しています。以下のカーソルベースの現在の合計は完全なテーブルで機能しますが、これを拡張して「クライアント」ディメンションを追加したいと思います。したがって、以下が作成するように現在の合計を取得しますが、1 つのテーブル内の各会社 (つまり、会社 A、会社 B、会社 C など) についてです。

CREATE TABLE test (tag int,  Checks float, AVG_COST float, Check_total float,  Check_amount float, Amount_total float, RunningTotal_Check float,  
 RunningTotal_Amount float)

DECLARE @tag int,
        @Checks float,
        @AVG_COST float,
        @check_total float,
        @Check_amount float,
        @amount_total float,
        @RunningTotal_Check float ,
        @RunningTotal_Check_PCT float,
        @RunningTotal_Amount float



SET @RunningTotal_Check = 0
SET @RunningTotal_Check_PCT = 0
SET @RunningTotal_Amount = 0
DECLARE aa_cursor CURSOR fast_forward
FOR
SELECT tag, Checks, AVG_COST, check_total, check_amount, amount_total
FROM test_3

OPEN aa_cursor
FETCH NEXT FROM aa_cursor INTO @tag,  @Checks, @AVG_COST, @check_total, @Check_amount, @amount_total
WHILE @@FETCH_STATUS = 0
 BEGIN
  SET @RunningTotal_CHeck = @RunningTotal_CHeck + @checks
  set @RunningTotal_Amount = @RunningTotal_Amount + @Check_amount
  INSERT test VALUES (@tag, @Checks, @AVG_COST, @check_total, @Check_amount, @amount_total,  @RunningTotal_check, @RunningTotal_Amount )
  FETCH NEXT FROM aa_cursor INTO @tag, @Checks, @AVG_COST, @check_total, @Check_amount, @amount_total
 END

CLOSE aa_cursor
DEALLOCATE aa_cursor

SELECT *, RunningTotal_Check/Check_total as CHECK_RUN_PCT, round((RunningTotal_Check/Check_total *100),0) as CHECK_PCT_BIN,  RunningTotal_Amount/Amount_total as Amount_RUN_PCT,  round((RunningTotal_Amount/Amount_total * 100),0) as Amount_PCT_BIN
into test_4
FROM test ORDER BY tag
create clustered index IX_TESTsdsdds3 on test_4(tag)

DROP TABLE test

----------------------------------

任意の 1 つの会社の現在の合計を計算できますが、以下の結果のようなものを生成するために、複数の会社に対して実行したいと考えています。

CLIENT  COUNT   Running Total
Company A   1   6.7%
Company A   2   20.0%
Company A   3   40.0%
Company A   4   66.7%
Company A   5   100.0%
Company B   1   3.6%
Company B   2   10.7%
Company B   3   21.4%
Company B   4   35.7%
Company B   5   53.6%
Company B   6   75.0%
Company B   7   100.0%
Company C   1   3.6%
Company C   2   10.7%
Company C   3   21.4%
Company C   4   35.7%
Company C   5   53.6%
Company C   6   75.0%
Company C   7   100.0%
4

3 に答える 3

5

私は当初、SQL Server 2012に相当するものを投稿し始めました(使用しているバージョンについて言及していなかったため)。Steveは、最新バージョンのSQL Serverでこの計算の単純さを示す素晴らしい仕事をしてくれたので、以前のバージョンのSQL Server(2005年に遡る)で機能するいくつかの方法に焦点を当てます。

これらすべての#test、#test_3、および#test_4一時テーブルが何を表すのか理解できないため、スキーマを自由に使用します。どうですか:

USE tempdb;
GO

CREATE TABLE dbo.Checks
(
  Client VARCHAR(32),
  CheckDate DATETIME,
  Amount DECIMAL(12,2)
);

INSERT dbo.Checks(Client, CheckDate, Amount)
          SELECT 'Company A', '20120101', 50
UNION ALL SELECT 'Company A', '20120102', 75
UNION ALL SELECT 'Company A', '20120103', 120
UNION ALL SELECT 'Company A', '20120104', 40
UNION ALL SELECT 'Company B', '20120101', 75
UNION ALL SELECT 'Company B', '20120105', 200
UNION ALL SELECT 'Company B', '20120107', 90;

この場合の期待される出力:

Client    Count Running Total
--------- ----- -------------
Company A 1     17.54
Company A 2     43.86
Company A 3     85.96
Company A 4     100.00
Company B 1     20.55
Company B 2     75.34
Company B 3     100.00

一方通行:

;WITH gt(Client, Totals) AS 
(
  SELECT Client, SUM(Amount) 
    FROM dbo.Checks AS c
    GROUP BY Client
), n (Client, Amount, rn) AS
(
  SELECT c.Client, c.Amount, 
    ROW_NUMBER() OVER  (PARTITION BY c.Client ORDER BY c.CheckDate)
    FROM dbo.Checks AS c
)
SELECT n.Client, [Count] = n.rn, 
  [Running Total] = CONVERT(DECIMAL(5,2), 100.0*(
    SELECT SUM(Amount) FROM n AS n2 
    WHERE Client = n.Client AND rn <= n.rn)/gt.Totals
 )
 FROM n INNER JOIN gt ON n.Client = gt.Client
 ORDER BY n.Client, n.rn;

わずかに高速な代替手段-読み取りは多くなりますが、期間は短くなり、計画は単純になります。

;WITH x(Client, CheckDate, rn, rt, gt) AS 
(
   SELECT Client, CheckDate, rn = ROW_NUMBER() OVER
   (PARTITION BY Client ORDER BY CheckDate),
    (SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client 
      AND CheckDate <= c.CheckDate),
    (SELECT SUM(Amount) FROM dbo.Checks WHERE Client = c.Client)
FROM dbo.Checks AS c
)
SELECT Client, [Count] = rn, 
  [Running Total] = CONVERT(DECIMAL(5,2), rt * 100.0/gt)
  FROM x
  ORDER BY Client, [Count];

ここではセットベースの代替案を提供しましたが、私の経験では、カーソルが現在の合計を実行するためにサポートされている最速の方法であることがよくあります。風変わりな更新など、わずかに高速に実行される他の方法もありますが、結果は保証されません。自己結合を実行するセットベースのアプローチは、ソース行の数が増えるにつれてますます高価になります。したがって、小さなテーブルでのテストでは、テーブルが大きくなるにつれてパフォーマンスが低下するため、問題なく機能するように見えます。

さまざまな現在の合計アプローチのわずかに単純なパフォーマンス比較を通過する、ほぼ完全に準備されたブログ投稿があります。グループ化されておらず、現在の合計パーセンテージではなく合計のみが表示されるため、より単純です。私はこの投稿をすぐに公開したいと思っており、このスペースを更新することを忘れないように努めます。

前の行を複数回読み取る必要がないことを考慮する別の代替手段もあります。これは、HugoKornelisが「セットベースの反復」と表現する概念です。このテクニックを最初にどこで学んだかは思い出せませんが、シナリオによっては非常に理にかなっています。

DECLARE @c TABLE
(
 Client VARCHAR(32), 
 CheckDate DATETIME,
 Amount DECIMAL(12,2),
 rn INT,
 rt DECIMAL(15,2)
);

INSERT @c SELECT Client, CheckDate, Amount,
  ROW_NUMBER() OVER (PARTITION BY Client
 ORDER BY CheckDate), 0
 FROM dbo.Checks;

DECLARE @i INT, @m INT;
SELECT @i = 2, @m = MAX(rn) FROM @c;

UPDATE @c SET rt = Amount WHERE rn = 1;

WHILE @i <= @m
BEGIN
    UPDATE c SET c.rt = c2.rt + c.Amount
      FROM @c AS c
      INNER JOIN @c AS c2
      ON c.rn = c2.rn + 1
      AND c.Client = c2.Client
      WHERE c.rn = @i;

    SET @i = @i + 1;
END

SELECT Client, [Count] = rn, [Running Total] = CONVERT(
  DECIMAL(5,2), rt*100.0 / (SELECT TOP 1 rt FROM @c
 WHERE Client = c.Client ORDER BY rn DESC)) FROM @c AS c;

これはループを実行し、ループとカーソルが悪いと誰もが言っていますが、この方法の利点の1つは、前の行の現在の合計が計算されると、前の行をすべて合計するのではなく、前の行を確認するだけで済むことです。 。もう1つの利点は、ほとんどのカーソルベースのソリューションでは、各クライアントを通過してから各チェックを実行する必要があることです。この場合、すべてのクライアントの1回目のチェックを1回実行してから、すべてのクライアントの2回目のチェックを1回実行します。したがって、(クライアント数*平均チェック数)の反復の代わりに、(最大チェック数)の反復のみを実行します。このソリューションは、単純な積算合計の例ではあまり意味がありませんが、グループ化された積算合計の例では、上記のセットベースのソリューションに対してテストする必要があります。ただし、SQL Server 2012を使用している場合は、Steveのアプローチに勝る可能性はありません。

アップデート

私はここでさまざまな現在の合計アプローチについてブログを書きました:

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

于 2012-04-29T01:22:51.047 に答える
5

これは、SUM と COUNT が ORDER BY を含む OVER 句をサポートする SQL Server 2012 でようやく簡単に実行できるようになりました。Cris の #Checks テーブル定義を使用します。

SELECT
  CompanyID,
  count(*) over (
    partition by CompanyID
    order by Cleared, ID
  ) as cnt,
  str(100.0*sum(Amount) over (
    partition by CompanyID
    order by Cleared, ID
  )/
  sum(Amount) over (
    partition by CompanyID
  ),5,1)+'%' as RunningTotalForThisCompany
FROM #Checks;

ここでSQLフィドル。

于 2012-04-29T00:11:33.973 に答える
0

プル元のスキーマを正確に理解していませんでしたが、セットベースの操作で現在の合計を実行する方法を示す一時テーブルを使用した簡単なクエリを次に示します。

CREATE TABLE #Checks
(
     ID int IDENTITY(1,1) PRIMARY KEY
    ,CompanyID int NOT NULL
    ,Amount float NOT NULL
    ,Cleared datetime NOT NULL
)

INSERT INTO #Checks
VALUES
     (1,5,'4/1/12')
    ,(1,5,'4/2/12')
    ,(1,7,'4/5/12')
    ,(2,10,'4/3/12')

SELECT Info.ID, Info.CompanyID, Info.Amount, RunningTotal.Total, Info.Cleared
FROM
(
SELECT main.ID, SUM(other.Amount) as Total
FROM
    #Checks main
JOIN
    #Checks other
ON
    main.CompanyID = other.CompanyID
AND
    main.Cleared >= other.Cleared
GROUP BY
    main.ID) RunningTotal
JOIN
    #Checks Info
ON
    RunningTotal.ID = Info.ID

DROP TABLE #Checks
于 2012-04-28T23:45:31.250 に答える