如何从每日交易表(客户、发票、付款)检索(借方、贷方和余额)完整和日期范围)

HRS*_*SO1 5 sql-server sql-server-2012 sql-server-2019

有 3 个表:客户、发票和付款。带有发票和付款表的客户链接。问题是,需要检索报表明智的借方、贷方和余额。主要问题是未从日期范围获得期初余额。我提供带有查询的示例数据。

客户表:

(插入新客户时,如果他们有未分配余额与期初余额,否则保留空白(空))

CREATE TABLE BASE_Customer
(
    CustomerId INT IDENTITY(1,1),
    CustomerName VARCHAR(45),
    SalesOpeningBalance MONEY NULL,
    PRIMARY KEY(CustomerId)
)

INSERT INTO BASE_Customer (CustomerName, SalesOpeningBalance) VAlUES ('Ricky', 2500) -- with opening balance.
INSERT INTO BASE_Customer (CustomerName) VAlUES ('Smith') -- without opening balance.
Run Code Online (Sandbox Code Playgroud)

发票表:

CREATE TABLE BASE_Invoice
(
    InvoiceId INT IDENTITY(1,1),
    InvoiceDate DATE,
    CustomerId INT,
    Total MONEY,
    PRIMARY KEY(InvoiceId)
)

INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-01', 1, 500) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-02', 2, 250) -- Smith
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-02', 1, 100) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-03', 1, 400) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-03', 2, 500) -- Smith
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-04', 1, 200) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-04', 2, 800) -- Smith
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-05', 1, 100) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-06', 1, 200) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-07', 1, 500) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-08', 1, 300) -- Ricky 
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-08', 2, 400) -- Smith
INSERT INTO BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES ('2022-01-09', 1, 600) -- Ricky
Run Code Online (Sandbox Code Playgroud)

付款表:

CREATE TABLE BASE_Payment
(
    PaymentId INT IDENTITY(1,1),
    PaymentDate DATE,
    CustomerId INT,
    Total MONEY,
    PRIMARY KEY(PaymentId)
)

INSERT INTO BASE_Payment (PaymentDate, CustomerId, Total) VALUES ('2022-01-03', 1, 400) -- Ricky
INSERT INTO BASE_Payment (PaymentDate, CustomerId, Total) VALUES ('2022-01-05', 1, 500) -- Ricky
INSERT INTO BASE_Payment (PaymentDate, CustomerId, Total) VALUES ('2022-01-05', 2, 300) -- Smith
INSERT INTO BASE_Payment (PaymentDate, CustomerId, Total) VALUES ('2022-01-07', 1, 600) -- Ricky
INSERT INTO BASE_Payment (PaymentDate, CustomerId, Total) VALUES ('2022-01-08', 2, 200) -- Smith
Run Code Online (Sandbox Code Playgroud)

如何按客户从上述 3 个表记录中检索报表视图(完整报表和日期范围)

笔记

  1. 插入新客户及其期初余额时,“期初余额”应出现在第一条记录上

  2. 如果从所选客户的日期范围中检索,“以前的记录期末余额”应作为期初余额

如何检索输出应在屏幕截图下方,

图01:客户(Ricky)完整声明

注意:创建此客户时给出期初余额

在此输入图像描述

图02:客户(Ricky)日期范围声明

注意:日期范围期初余额应为“以前的记录期末余额”,而不是创建该客户时给定输出图片上方的期初余额 01 2,500

在此输入图像描述

图03:客户(史密斯)完整声明

注意:创建此客户时未给出期初余额,因此不需要出现期初余额

在此输入图像描述

图04:客户(史密斯)日期范围声明

注:日期范围期初余额应为以前记录的期末余额 750(以上完整报表第 1 条记录总计)

在此输入图像描述

我尝试使用下面的查询,但没有得到像上面 4 个屏幕截图那样的预期结果,主要问题是检索日期范围时的期初余额。

--YYYY-MM-DD (Date Format)

DECLARE @CustomerId INT  = 2;

SELECT 
[Date], 
Particulars, 
ABS(Debit) AS Debit, 
ABS(Credit) AS Credit, 
SUM(CASE WHEN Credit = 0 THEN Debit ELSE Credit END) OVER (ORDER BY [Date], [Particulars]) AS Balance 
FROM 
(
SELECT 
NULL AS [Date], 
'OPENING BALANCE' AS [Particulars], 
ISNULL(SalesOpeningBalance, 0) AS [Debit], 
0 AS [Credit], 
ISNULL(SalesOpeningBalance, 0) AS [Balance] 
FROM BASE_Customer 
WHERE (CustomerId = @CustomerId) 
UNION ALL 
SELECT 
BASE_Invoice.InvoiceDate AS [Date], 
'INVOICE' AS [Particulars], 
BASE_Invoice.Total AS [Debit], 
0 AS [Credit], 
ISNULL(BASE_Customer.SalesOpeningBalance, 0) AS [Balance] 
FROM BASE_Customer INNER JOIN BASE_Invoice ON BASE_Customer.CustomerId = BASE_Invoice.CustomerId 
WHERE (BASE_Customer.CustomerId = @CustomerId) --AND (BASE_Invoice.InvoiceDate BETWEEN '2022-01-04' AND '2022-01-05')
UNION ALL 
SELECT 
BASE_Payment.PaymentDate AS [Date], 
'PAYMENT' AS [Particulars], 
0 AS [Debit], 
-1.0 * BASE_Payment.Total AS [Credit], 
ISNULL(BASE_Customer.SalesOpeningBalance, 0) AS [Balance] 
FROM BASE_Customer INNER JOIN BASE_Payment ON BASE_Customer.CustomerId = BASE_Payment.CustomerId 
WHERE (BASE_Customer.CustomerId = @CustomerId) --AND (BASE_Payment.PaymentDate BETWEEN '2022-01-04' AND '2022-01-05')
) u 
ORDER BY [Date]
Run Code Online (Sandbox Code Playgroud)

Pat*_*rst 2

我稍微压缩了您的示例 DDL/DML:

DECLARE @BASE_Customer TABLE (CustomerId INT IDENTITY(1,1) PRIMARY KEY, CustomerName VARCHAR(45), SalesOpeningBalance DECIMAL(10,2) NULL);
INSERT INTO @BASE_Customer (CustomerName, SalesOpeningBalance) VAlUES 
('Ricky', 2500), ('Smith', NULL);

DECLARE @BASE_Invoice TABLE (InvoiceId INT IDENTITY(1,1) PRIMARY KEY, InvoiceDate DATE, CustomerId INT, Total DECIMAL(10,2));
INSERT INTO @BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES 
('2022-01-01', 1, 500), ('2022-01-02', 2, 250), ('2022-01-02', 1, 100), ('2022-01-03', 1, 400),  
('2022-01-03', 2, 500), ('2022-01-04', 1, 200), ('2022-01-04', 2, 800), ('2022-01-05', 1, 100),  
('2022-01-06', 1, 200), ('2022-01-07', 1, 500), ('2022-01-08', 1, 300), ('2022-01-08', 2, 400), 
('2022-01-09', 1, 600); 

DECLARE @BASE_Payment TABLE (PaymentId INT IDENTITY(1,1) PRIMARY KEY, PaymentDate DATE, CustomerId INT, Total DECIMAL(10,2));
INSERT INTO @BASE_Payment (PaymentDate, CustomerId, Total) VALUES 
('2022-01-03', 1, 400), ('2020-01-05', 1, 500), ('2020-01-05', 2, 300), ('2020-01-07', 1, 600), 
('2020-01-08', 2, 200); 
Run Code Online (Sandbox Code Playgroud)
DECLARE @CustomerID INT, @DateFrom DATE, @DateTo DATE

SET @CustomerID = 1
SET @DateFrom = '2022-01-03'
SET @DateTo = '2022-01-08'

SELECT a.Date, a.Particulars, a.Debit, a.Credit, a.Balance
  FROM (
SELECT CustomerID, Date, Particulars, Debit, Credit, SUM(Balance) OVER (PARTITION BY CustomerID ORDER BY Date, a.Particulars) AS Balance
  FROM (
        SELECT CAST(NULL AS DATE) AS Date, 'OPENING BALANCE' AS Particulars, CAST(NULL AS DECIMAL(10,2)) AS Debit, CAST(NULL AS DECIMAL(10,2)) AS Credit, SalesOpeningBalance AS Balance, CustomerID
          FROM @BASE_Customer
         WHERE SalesOpeningBalance IS NOT NULL
        UNION ALL
        SELECT TOP 100 PERCENT a.Date, a.Particulars, a.Debit, a.Credit, Balance, CustomerID
          FROM (
                SELECT InvoiceDate AS Date, 'INVOICE' AS Particulars, Total AS Debit, CAST(NULL AS DECIMAL(10,2)) AS Credit, -1*Total AS Balance, CustomerID
                  FROM @BASE_Invoice 
                UNION ALL
                SELECT PaymentDate AS Date, 'PAYMENT' AS Particulars,  CAST(NULL AS DECIMAL(10,2)) AS Debit, Total AS Credit, Total AS Balance, CustomerID
                  FROM @BASE_Payment
               ) a
        ORDER BY CustomerID, Date
       ) a
       ) a
 WHERE (
            @CustomerID = a.CustomerId
         OR @CustomerID IS NULL
       )
   AND (
            Date BETWEEN @DateFrom AND @DateTo
         OR @DateTo IS NULL
         OR @DateFrom IS NULL
       )
Run Code Online (Sandbox Code Playgroud)

这是一种可以获得所需输出的方法。首先,我们将三组数据以统一的格式合并在一起。请注意,我将借方和贷方放入余额列中,并将借方乘以负 1,一旦我们有了一组统一的数据,我们就可以使用窗口求和函数来计算运行余额。

我在获取所有数据后应用了参数,因为我们仍然需要预先确定请求的开始日期的任何内容,以了解从哪里开始运行余额。我还必须决定如何打破联系,因为我们只使用日期,所以我首先订购了发票。

使用 @CustomerID = 1、@DateFrom = 2022-01-03 和 @DateTo = 2022-01-08:

日期 细节 借方 信用 平衡
2022-01-03 发票 400.00 无效的 2600.00
2022-01-03 支付 无效的 400.00 3000.00
2022-01-04 发票 200.00 无效的 2800.00
2022-01-05 发票 100.00 无效的 2700.00
2022-01-06 发票 200.00 无效的 2500.00
2022-01-07 发票 500.00 无效的 2000.00
2022-01-08 发票 300.00 无效的 1700.00

使用 @CustomerID = 1 和 NULL 日期:

日期 细节 借方 信用 平衡
无效的 期初余额 无效的 无效的 2500.00
2020-01-05 支付 无效的 500.00 3000.00
2020-01-07 支付 无效的 600.00 3600.00
2022-01-01 发票 500.00 无效的 3100.00
2022-01-02 发票 100.00 无效的 3000.00
2022-01-03 发票 400.00 无效的 2600.00
2022-01-03 支付 无效的 400.00 3000.00
2022-01-04 发票 200.00 无效的 2800.00
2022-01-05 发票 100.00 无效的 2700.00
2022-01-06 发票 200.00 无效的 2500.00
2022-01-07 发票 500.00 无效的 2000.00
2022-01-08 发票 300.00 无效的 1700.00
2022-01-09 发票 600.00 无效的 1100.00

需要注意的事项:您可能应该使用 DATETIME 列而不是日期,这样两个事务捆绑的机会就会大大减少。如果 DATETIME2 可用,您可以进一步减少此值。最好以 UTC 格式存储 DATETIME 以防止时区混淆,并按此方式标记列(例如 TransactionDateTimeUTC)。对于货币栏来说,MONEY 是一个糟糕的选择,因为在四舍五入方面存在潜在的问题。您可以在单个 INSERT .. VALUES 语句中插入最多 1000 行。我使用表变量,因为它们不需要任何清理,但这只是个人喜好。

编辑:

事实证明,提供的数据使用的是 YYYY-DD-MM 日期格式,而不是 ISO 标准 YYYY-MM-DD。此编辑提供了使用不同月份而不是日期的示例数据。此编辑还考虑了有关“期初余额”添加的评论。

修改后的演示数据:

DECLARE @BASE_Customer TABLE (CustomerId INT IDENTITY(1,1) PRIMARY KEY, CustomerName VARCHAR(45), SalesOpeningBalance DECIMAL(10,2) NULL);
INSERT INTO @BASE_Customer (CustomerName, SalesOpeningBalance) VAlUES 
('Ricky', 2500), ('Smith', NULL);

DECLARE @BASE_Invoice TABLE (InvoiceId INT IDENTITY(1,1) PRIMARY KEY, InvoiceDate DATE, CustomerId INT, Total DECIMAL(10,2));
INSERT INTO @BASE_Invoice (InvoiceDate, CustomerId, Total) VALUES 
('2022-01-01', 1, 500), ('2022-02-01', 2, 250), ('2022-02-01', 1, 100), ('2022-03-01', 1, 400),  
('2022-03-01', 2, 500), ('2022-04-01', 1, 200), ('2022-04-01', 2, 800), ('2022-05-01', 1, 100),  
('2022-06-01', 1, 200), ('2022-07-01', 1, 500), ('2022-08-01', 1, 300), ('2022-08-01', 2, 400), 
('2022-09-01', 1, 600); 

DECLARE @BASE_Payment TABLE (PaymentId INT IDENTITY(1,1) PRIMARY KEY, PaymentDate DATE, CustomerId INT, Total DECIMAL(10,2));
INSERT INTO @BASE_Payment (PaymentDate, CustomerId, Total) VALUES 
('2022-03-01', 1, 400), ('2022-05-01', 1, 500), ('2022-05-01', 2, 300), ('2022-07-01', 1, 600), 
('2022-08-01', 2, 200); 
Run Code Online (Sandbox Code Playgroud)

修改后的查询:

DECLARE @CustomerID INT, @DateFrom DATE, @DateTo DATE

SET @CustomerID = 1
SET @DateFrom = '2022-04-01'
SET @DateTo = '2022-05-01'

;WITH allItems AS (
        SELECT CustomerID, Date, Particulars, Debit, Credit, SUM(Balance) OVER (PARTITION BY CustomerID ORDER BY Date, a.Particulars) AS Balance
          FROM (
                SELECT CAST(NULL AS DATE) AS Date, 'OPENING BALANCE' AS Particulars, CAST(NULL AS DECIMAL(10,2)) AS Debit, CAST(NULL AS DECIMAL(10,2)) AS Credit, SalesOpeningBalance AS Balance, CustomerID
                  FROM @BASE_Customer
                 WHERE SalesOpeningBalance IS NOT NULL
                UNION ALL
                SELECT a.Date, a.Particulars, a.Debit, a.Credit, Balance, CustomerID
                  FROM (
                        SELECT InvoiceDate AS Date, 'INVOICE' AS Particulars, Total AS Debit, CAST(NULL AS DECIMAL(10,2)) AS Credit, -1*Total AS Balance, CustomerID
                          FROM @BASE_Invoice 
                        UNION ALL
                        SELECT PaymentDate AS Date, 'PAYMENT' AS Particulars,  CAST(NULL AS DECIMAL(10,2)) AS Debit, Total AS Credit, Total AS Balance, CustomerID
                          FROM @BASE_Payment
                       ) a
               ) a
)

SELECT a.Date, a.Particulars, a.Debit, a.Credit, a.Balance
  FROM (
        SELECT TOP 1 CustomerID, Date, 'OPENING BALANCE' AS Particulars, NULL AS Debit, NULL AS Credit, Balance 
          FROM allItems a
         WHERE Date < @DateFrom
         ORDER BY Date DESC
        UNION ALL
        SELECT CustomerID, Date, Particulars, Debit, Credit, Balance
          FROM allItems a
         WHERE (
                   @CustomerID = a.CustomerId
                OR @CustomerID IS NULL
               )
           AND (
                    Date BETWEEN @DateFrom AND @DateTo
                 OR @DateTo IS NULL
                 OR @DateFrom IS NULL
               )
       ) a
Run Code Online (Sandbox Code Playgroud)

修订结果(2022年4月1日至2022年5月1日)

日期 细节 借方 信用 平衡
2022-03-01 期初余额 无效的 无效的 1500.00
2022-04-01 发票 200.00 无效的 1700.00
2022-05-01 发票 100.00 无效的 1600.00
2022-05-01 支付 无效的 500.00 2100.00

修正结果(2022年5月1日至2022年7月1日)(图02)

日期 细节 借方 信用 平衡
2022-04-01 期初余额 无效的 无效的 1700.00
2022-05-01 发票 100.00 无效的 1600.00
2022-05-01 支付 无效的 500.00 2100.00
2022-06-01 发票 200.00 无效的 1900.00
2022-07-01 发票 500.00 无效的 1400.00
2022-07-01 支付 无效的 600.00 2000.00

  • “SELECT TOP 100 PERCENT”的目的是什么,为什么不只是“SELECT”? (3认同)