Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Tính toán tổng số chạy / số dư chạy

Đối với những người không sử dụng SQL Server 2012 trở lên, con trỏ có thể là con trỏ được hỗ trợ hiệu quả nhất và được đảm bảo phương pháp bên ngoài CLR. Có các cách tiếp cận khác như "cập nhật kỳ quặc" có thể nhanh hơn một chút nhưng không đảm bảo hoạt động trong tương lai và tất nhiên là các cách tiếp cận dựa trên tập hợp với cấu hình hiệu suất hyperbol khi bảng lớn hơn và các phương pháp CTE đệ quy thường yêu cầu trực tiếp #tempdb I / O hoặc dẫn đến sự cố tràn gây ra tác động gần như tương tự.

INNER JOIN - không làm điều này:

Phương pháp chậm, dựa trên tập hợp có dạng:

SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
  ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;

Lý do này là chậm? Khi bảng lớn hơn, mỗi hàng tăng dần yêu cầu đọc n-1 hàng trong bảng. Đây là cấp số nhân và bị ràng buộc đối với lỗi, hết thời gian chờ hoặc chỉ là người dùng tức giận.

Truy vấn con có liên quan - đừng làm điều này:

Biểu mẫu truy vấn con cũng gây khó khăn tương tự vì những lý do tương tự.

SELECT TID, amt, RunningTotal = amt + COALESCE(
(
  SELECT SUM(amt)
    FROM dbo.Transactions AS i
    WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;

Cập nhật kỳ quặc - bạn tự chịu rủi ro:

Phương pháp "cập nhật kỳ quặc" hiệu quả hơn phương pháp trên, nhưng hành vi không được ghi lại, không có đảm bảo về trật tự và hành vi có thể hoạt động hôm nay nhưng có thể phá vỡ trong tương lai. Tôi bao gồm điều này vì nó là một phương pháp phổ biến và nó hiệu quả, nhưng điều đó không có nghĩa là tôi tán thành nó. Lý do chính mà tôi thậm chí đã trả lời câu hỏi này thay vì đóng nó là một bản sao là vì câu hỏi kia có một cập nhật kỳ quặc là câu trả lời được chấp nhận.

DECLARE @t TABLE
(
  TID INT PRIMARY KEY,
  amt INT,
  RunningTotal INT
);
 
DECLARE @RunningTotal INT = 0;
 
INSERT @t(TID, amt, RunningTotal)
  SELECT TID, amt, RunningTotal = 0
  FROM dbo.Transactions
  ORDER BY TID;
 
UPDATE @t
  SET @RunningTotal = RunningTotal = @RunningTotal + amt
  FROM @t;
 
SELECT TID, amt, RunningTotal
  FROM @t
  ORDER BY TID;

CTE đệ quy

Điều đầu tiên này dựa vào TID để liền kề, không có khoảng cách:

;WITH x AS
(
  SELECT TID, amt, RunningTotal = amt
    FROM dbo.Transactions
    WHERE TID = 1
  UNION ALL
  SELECT y.TID, y.amt, x.RunningTotal + y.amt
   FROM x 
   INNER JOIN dbo.Transactions AS y
   ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
  FROM x
  ORDER BY TID
  OPTION (MAXRECURSION 10000);

Nếu bạn không thể dựa vào điều này, thì bạn có thể sử dụng biến thể này, biến thể này chỉ đơn giản là tạo một chuỗi liền kề bằng cách sử dụng ROW_NUMBER() :

;WITH y AS 
(
  SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
    FROM dbo.Transactions
), x AS
(
    SELECT TID, rn, amt, rt = amt
      FROM y
      WHERE rn = 1
    UNION ALL
    SELECT y.TID, y.rn, y.amt, x.rt + y.amt
      FROM x INNER JOIN y
      ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
  FROM x
  ORDER BY x.rn
  OPTION (MAXRECURSION 10000);

Tùy thuộc vào kích thước của dữ liệu (ví dụ:các cột mà chúng tôi không biết về), bạn có thể thấy hiệu suất tổng thể tốt hơn bằng cách chỉ nhồi các cột có liên quan vào bảng #temp trước và xử lý dựa trên đó thay vì bảng cơ sở:

CREATE TABLE #x
(
  rn  INT PRIMARY KEY,
  TID INT,
  amt INT
);

INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
  TID, amt
FROM dbo.Transactions;

;WITH x AS
(
  SELECT TID, rn, amt, rt = amt
    FROM #x
    WHERE rn = 1
  UNION ALL
  SELECT y.TID, y.rn, y.amt, x.rt + y.amt
    FROM x INNER JOIN #x AS y
    ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
  FROM x
  ORDER BY TID
  OPTION (MAXRECURSION 10000);

DROP TABLE #x;

Chỉ phương pháp CTE đầu tiên sẽ cung cấp hiệu suất so với bản cập nhật kỳ quặc, nhưng nó tạo ra giả định lớn về bản chất của dữ liệu (không có khoảng trống). Hai phương pháp còn lại sẽ lùi lại và trong những trường hợp đó, bạn cũng có thể sử dụng con trỏ (nếu bạn không thể sử dụng CLR và bạn chưa sử dụng SQL Server 2012 trở lên).

Con trỏ

Mọi người đều nói rằng con trỏ là xấu và cần phải tránh chúng bằng mọi giá, nhưng điều này thực sự đánh bại hiệu suất của hầu hết các phương pháp được hỗ trợ khác và an toàn hơn so với bản cập nhật kỳ quặc. Phương pháp duy nhất tôi thích hơn giải pháp con trỏ là phương pháp 2012 và CLR (bên dưới):

CREATE TABLE #x
(
  TID INT PRIMARY KEY, 
  amt INT, 
  rt INT
);

INSERT #x(TID, amt) 
  SELECT TID, amt
  FROM dbo.Transactions
  ORDER BY TID;

DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
  FOR SELECT TID, amt FROM #x ORDER BY TID;

OPEN c;

FETCH c INTO @tid, @amt;

WHILE @@FETCH_STATUS = 0
BEGIN
  SET @rt = @rt + @amt;
  UPDATE #x SET rt = @rt WHERE TID = @tid;
  FETCH c INTO @tid, @amt;
END

CLOSE c; DEALLOCATE c;

SELECT TID, amt, RunningTotal = rt 
  FROM #x 
  ORDER BY TID;

DROP TABLE #x;

SQL Server 2012 trở lên

Các hàm cửa sổ mới được giới thiệu trong SQL Server 2012 làm cho tác vụ này dễ dàng hơn rất nhiều (và nó cũng hoạt động tốt hơn tất cả các phương pháp ở trên):

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;

Lưu ý rằng trên các tập dữ liệu lớn hơn, bạn sẽ thấy rằng tùy chọn trên hoạt động tốt hơn nhiều so với một trong hai tùy chọn sau, vì RANGE sử dụng bộ đệm trên đĩa (và mặc định sử dụng RANGE). Tuy nhiên, điều quan trọng cần lưu ý là hành vi và kết quả có thể khác nhau, vì vậy hãy đảm bảo cả hai đều trả về kết quả chính xác trước khi quyết định giữa chúng dựa trên sự khác biệt này.

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;

CLR

Để hoàn thiện, tôi cung cấp một liên kết đến phương pháp CLR của Pavel Pawlowski, đây là phương pháp được ưa chuộng hơn trên các phiên bản trước SQL Server 2012 (nhưng không phải năm 2000 rõ ràng).

http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/

Kết luận

Nếu bạn đang sử dụng SQL Server 2012 trở lên, lựa chọn là hiển nhiên - hãy sử dụng SUM() OVER() mới cấu trúc (với ROWS so với RANGE ). Đối với các phiên bản trước đó, bạn sẽ muốn so sánh hiệu suất của các phương pháp thay thế trên lược đồ, dữ liệu của mình và - lưu ý đến các yếu tố không liên quan đến hiệu suất - xác định phương pháp tiếp cận nào phù hợp với bạn. Nó rất có thể là cách tiếp cận CLR. Dưới đây là các đề xuất của tôi, theo thứ tự ưu tiên:

  1. SUM() OVER() ... ROWS , nếu từ năm 2012 trở lên
  2. Phương pháp CLR, nếu có thể
  3. Phương pháp CTE đệ quy đầu tiên, nếu có thể
  4. Con trỏ
  5. Các phương thức CTE đệ quy khác
  6. Cập nhật kỳ lạ
  7. Tham gia và / hoặc truy vấn con có liên quan

Để biết thêm thông tin về so sánh hiệu suất của các phương pháp này, hãy xem câu hỏi này trên http://dba.stackexchange.com:

https://dba.stackexchange.com/questions/19507/running-total-with-count

Tôi cũng đã viết blog chi tiết hơn về những so sánh này ở đây:

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

Ngoài ra đối với các tổng số đang chạy được nhóm / phân vùng, hãy xem các bài đăng sau:

http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals

Việc phân vùng dẫn đến một truy vấn tổng đang chạy

Nhiều Tổng số Đang chạy với Nhóm Theo



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tính toán số tháng đầy đủ giữa hai ngày trong SQL

  2. Thay thế lần xuất hiện đầu tiên của chuỗi con trong một chuỗi trong SQL

  3. Tạo cột được tính toán bằng cách sử dụng dữ liệu từ một bảng khác

  4. Cách lấy mô hình khôi phục của cơ sở dữ liệu trong SQL Server bằng T-SQL

  5. Làm cách nào để in VARCHAR (MAX) bằng Print Statement?