【问题标题】:Sql server dynamic sequencesSql server 动态序列
【发布时间】:2014-12-30 13:40:28
【问题描述】:

这是一个理论场景,

假设我有一个客户表和一个发票表。 1 个客户可以有多张发票。

现在我希望每张发票都有一个对该客户唯一的发票编号

ClientId InvoiceNo
1        IN0001
2        IN0001
2        IN0002
2        IN0003
3        IN0001

目前我在我的应用程序中通过查看最大值等来控制它,但这显然不是一个很好的解决方案。我宁愿让我的数据库为我做这件事,因为它应该消除为单个客户创建重复发票号码的风险(竞争条件?)

我一直在阅读 Sql Server 2012 的 SEQUENCE,这听起来不错,但问题是我仍然需要每个客户端一个单独的序列

CREATE SEQUENCE InvoiceNum_Client1.....
CREATE SEQUENCE InvoiceNum_Client2.....
CREATE SEQUENCE InvoiceNum_Client3.....

但是每次创建新客户端时,从我的应用程序中进行数据库元更改会让人感觉很脏。然后我的触发器将不得不做这样的事情(我什至不会开始知道该怎么做)

NEXT VALUE FOR InvoiceNum_Client + @ClientId

所以我的下一个想法是有一个“序列”表,即

ClientID   INSequenceNumber
1           1
2           3
3           3

在我的触发器中,获取给定客户端的 InSequenceNumber,使用它来生成我的 invoiceNumber,然后更新序列表,将同一客户端的 InSequenceNumber 递增 1。它应该具有相同的效果,但我只是不确定事务和范围等的内部工作原理

所以我的问题是

  1. 我的自卷顺序方法有什么大的缺点吗?
  2. 还有其他我可能忽略的解决方案吗?

谢谢!

【问题讨论】:

    标签: sql sql-server sql-server-2012


    【解决方案1】:

    是否有特定原因要求发票编号仅对每个客户唯一?在大多数 ERP 系统中,发票编号通常是全球唯一的,这使得实施变得更加容易。无论如何,您应该有一个包含主键的 Invoice 表(并且您不应该使用复合主键 - 这只是非常糟糕的数据建模)。

    这给我们留下了一个场景,您可能根本不需要在数据库中存储“per-client-invoice-number”。假设您有一个名为“Invoices”的表,其中包含以下数据:

    Id   | ClientId
    ---------------
      1  |        1
      2  |        1
      3  |        2
      4  |        1
      5  |        3
      6  |        2
    

    这里,Id 是 Invoices 表的主键,ClientId 是外键。像这样的查询:

    SELECT 
        ClientId,
        'IN' + RIGHT('0000' + 
                 CONVERT(VARCHAR, ROW_NUMBER() OVER (PARTITION BY ClientId 
                                                     ORDER BY Id)) AS InvoiceNo,
        Id
    FROM Invoices
    ORDER BY ClientId, InvoiceNo
    

    会返回:

    ClientId | InvoiceNo | Id
    ---------------------------
          1  |    IN0001 |  1
          1  |    IN0002 |  2
          1  |    IN0003 |  4
          2  |    IN0001 |  3
          2  |    IN0002 |  6
          3  |    IN0001 |  5
    

    【讨论】:

    • 非常好的答案,谢谢。我没有意识到有 OVER / PARTITION BY 关键字。很强大。要回答您的第一个问题,有一个有效的用例可以让每个客户拥有唯一的发票编号。这是一个由多个客户端使用的共享系统,每个客户端彼此完全独立。如果我使用全球唯一的发票序列,客户将在他们的发票序列中体验“漏洞”,这会让审计员提出问题
    • 我明白了。在这种情况下,您绝对应该将每个客户的发票编号保留在表中。但是恐怕 SEQUENCE 运算符在这方面对您没有多大帮助。相反,您应该使用自己建议的表格方法,或者使用 MAX() 和锁定机制来确保永远不会创建重复的发票编号。
    • @Dan 。 . .在 SQL Server 中,从不使用不带长度的 varchar。您永远不知道给定上下文中的默认长度是否足够。所以,你应该有CONVERT(VARCHAR(255), . . .(或者你最喜欢的长度)。
    • @GordonLinoff:始终明确指定长度是一个好习惯。然而,在这种情况下,默认长度始终为 30,这已经足够了。根据the documentation for char and varchar,当您未在CONVERT 语句中指定长度时,可以保证长度为30。
    【解决方案2】:

    为什么客户端必须有自己的序列?有一个全局序列号。然后,如果您想按顺序获取客户端序列,请使用row_number()

    select i.*, row_number() over (partition by clientid order by invoiceno) as ClientInvoiceSequence
    from invoices;
    

    注意:您可能希望order by 是另一个字段,例如日期。

    如果您开始将这些信息存储在数据库中,您将需要做大量的记账和仔细的事务管理:

    • “同时”输入两张发票会怎样?
    • 发票被删除后会发生什么?
    • 当发票被修改而可能会改变顺序时会发生什么?

    最好使用标识列并在需要时计算序列。

    【讨论】:

    • 谢谢,也是一个很好的答案。与丹非常相似,但我给了他分数,因为他击败了你,即使只有短短几秒钟 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-10
    • 1970-01-01
    • 2011-01-15
    相关资源
    最近更新 更多