【发布时间】:2018-02-21 15:17:19
【问题描述】:
编辑:发票顺序编号是多个国家/地区的法律。
编辑:我的变量命名不佳表明我想使用生成的 Id 作为键。不是这种情况。应该坚持使用“invoiceNumber”。
我的问题与https://stackoverflow.com/a/24196374/1980516这里提出的完全相同
但是,由于建议的解决方案引发了语法错误,因此我对其进行了调整以使用游标。
首先,对于给定的 Business+Year 组合,有一个生成新 Nr 的存储过程:
CREATE PROCEDURE PROC_NextInvoiceNumber @businessId INT, @year INT, @Nr NVARCHAR(MAX) OUTPUT
AS MERGE INTO InvoiceNextNumbers ini
USING (VALUES (@businessId, @year)) Incoming(BusinessId, Year)
ON Incoming.BusinessId = ini.BusinessId AND Incoming.Year = ini.Year
WHEN MATCHED THEN UPDATE SET ini.Nr = ini.Nr + 1
WHEN NOT MATCHED BY TARGET THEN INSERT (BusinessId, Year, Nr)
VALUES(@businessId, @year, 1)
OUTPUT INSERTED.Nr;
然后,使用该存储过程,我创建了一个 INSTEAD OF INSERT 触发器:
CREATE TRIGGER TRIG_GenerateInvoiceNumber ON Invoices INSTEAD OF INSERT
AS
BEGIN
DECLARE @BusinessId INT
DECLARE @InvoiceId INT
DECLARE @BillingDate DATETIME2(7)
-- Cursors are expensive, but I don't see any other way to call the stored procedure per row
-- Mitigating factor: Mostly, we're only inserting one Invoice at a time
DECLARE InsertCursor CURSOR FAST_FORWARD FOR
SELECT BusinessId, Id, BillingDate FROM INSERTED
OPEN InsertCursor
FETCH NEXT FROM InsertCursor
INTO @BusinessId, @InvoiceId, @BillingDate
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @year INT
SET @year = year(@BillingDate)
DECLARE @Number NVARCHAR(MAX)
EXEC PROC_NextInvoiceNumber @BusinessId, @year, @Number OUTPUT
-- SET @Number = 'this works'
INSERT INTO Invoices (BusinessId, Id, BillingDate, Number)
VALUES (@BusinessId, @InvoiceId, @BillingDate, @Number)
FETCH NEXT FROM InsertCursor
INTO @BusinessId, @InvoiceId, @BillingDate
END
CLOSE InsertCursor
DEALLOCATE InsertCursor
END
如果我取消注释 SET @Number = 'this works',那么在我的数据库中,在 Invoice.Number 中成功设置了确切的字符串('this works')。
不知何故,我的 OUTPUT 参数未设置,我不知道为什么不设置.. 有人可以解释一下吗?
EDIT 更新以响应 cmets(谢谢):
我有一个用于 Invoice 的复合键 (BusinessId, Id)。所需的最终结果是一个唯一的发票
Identifier编号,格式为“20180001”,它是一个连续的数字序列在 businessId 中。所以业务 1 的发票编号为 20180001、20180002、20180003,而业务 2 也的发票编号为 20180001、20180002、20180003。(但复合主键不同)我也不想要那个光标,但我在上面提到的问题所建议的框架内没有看到其他方法。
使用现有业务 ID 和年份手动调用 PROC_NextInvoiceNumber 返回 NULL。
如果我尝试在 PROC_NextInvoiceNumber 中设置 Id,如果我将其设置在 MERGE 内,我会得到
A MERGE statement must be terminated by a semi-colon (;).,如果我设置在 MERGE 外,我会得到The multi-part identifier "INSERTED.Nr" could not be bound.。
【问题讨论】:
-
PROC_NextInvoiceIdentifier 的代码在哪里?当我在一个而不是触发器中看到一个光标时,我感到畏缩。该触发器的主体处于循环中,因此您可以获得键值。这是一个巨大的危险信号,表明某些东西设计得不好。由于语法错误而求助于游标?您应该修复代码,而不是使用光标。
-
没有
AS MERGE。 MERGE 是一个类似于 INSERT、UPDATE 或 DELETE 的命令。 无法捕获存储过程结果,除非将结果插入到表中。为什么你在触发器中使用 cursor,而不是在里面写 MERGE?您可以将 MERGE 放在子查询或 CTE 中,并将其结果插入 Invoices -
BTW MERGE 将为插入和更新的行返回结果both。这意味着您最终会在
Invoices表中出现重复项。为什么不在触发器中放置单独的 INSERT/UPDATE 语句?结果会干净很多 -
您没有在存储过程代码中的任何位置设置/更改 OUTPUT 参数
@id的值。当您自行运行该过程(在触发器之外)时,该过程是否返回预期结果? -
PS:SQL Server 有sequences。
PROC_NextInvoiceIdentifier可以在 SELECT 或 VALUES 子句中替换为NEXT VALUE FOR MyInvoiceSequence
标签: sql sql-server