【问题标题】:Computed column expression计算列表达式
【发布时间】:2013-02-19 21:03:46
【问题描述】:

我特别需要一个名为 ProductCode

的计算列
ProductId | SellerId | ProductCode
1           1           000001
2           1           000002
3           2           000001       
4           1           000003

ProductId 是标识,以 1 递增。 SellerId 是外键。

所以我的计算列 ProductCode 必须查看卖家有多少产品并且格式为 000000。这里的问题是如何知道要查找哪些卖家产品?

我写了一个 TSQL,它不看卖家有多少产品

ALTER TABLE dbo.Product
ADD ProductCode AS  RIGHT('000000' + CAST(ProductId AS VARCHAR(6)) , 6) PERSISTED

【问题讨论】:

  • 我建议使用视图来进行这种类型的计算。如果选择性能是最重要的因素(我看到您使用的是persisted),甚至可以将视图编入索引。
  • @TimLehner 你能提供一个样本吗?我仍然想知道这是否可以使用计算列?

标签: sql-server tsql calculated-columns


【解决方案1】:

您不能拥有基于正在更新的当前行之外的数据的计算列。您可以做的最好的事情是创建一个后触发器来查询整个表以查找产品代码的下一个值。但是为了完成这项工作,您必须使用排他表锁,这将彻底破坏并发性,所以这不是一个好主意。

我也不建议使用视图,因为每次阅读表格时它都必须计算 ProductCode。这也将是一个巨大的性能杀手。如果不将值保存在数据库中以使其不再被触及,您的产品代码将受到虚假更改(例如可能删除错误输入且从未使用过的产品)。

这是我推荐的。创建一个新表:

dbo.SellerProductCode

SellerID  LastProductCode
--------  ---------------
  1        3
  2        1

此表可靠地记录了每个卖家最后使用的产品代码。在INSERT 到您的Product 表上,触发器将为所有受影响的SellerIDs 适当地更新此表中的LastProductCode,然后使用适当的值更新Product 表中所有新插入的行。它可能如下所示。

See this trigger working in a Sql Fiddle

CREATE TRIGGER TR_Product_I ON dbo.Product FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @LastProductCode TABLE (
   SellerID int NOT NULL PRIMARY KEY CLUSTERED,
   LastProductCode int NOT NULL
);

WITH ItemCounts AS (
   SELECT
      I.SellerID,
      ItemCount = Count(*)
   FROM
      Inserted I
   GROUP BY
      I.SellerID
)
MERGE dbo.SellerProductCode C
USING ItemCounts I
   ON C.SellerID = I.SellerID
WHEN NOT MATCHED BY TARGET THEN
   INSERT (SellerID, LastProductCode)
   VALUES (I.SellerID, I.ItemCount)
WHEN MATCHED THEN
   UPDATE SET C.LastProductCode = C.LastProductCode + I.ItemCount
OUTPUT
   Inserted.SellerID,
   Inserted.LastProductCode
INTO @LastProductCode;

WITH P AS (
   SELECT
      NewProductCode =
         L.LastProductCode + 1
         - Row_Number() OVER (PARTITION BY I.SellerID ORDER BY P.ProductID DESC),
      P.*
   FROM
      Inserted I
      INNER JOIN dbo.Product P
            ON I.ProductID = P.ProductID
      INNER JOIN @LastProductCode L
         ON P.SellerID = L.SellerID
)
UPDATE P
SET P.ProductCode = Right('00000' + Convert(varchar(6), P.NewProductCode), 6);

请注意,即使插入了多行,此触发器也会起作用。无需预加载SellerProductCode 表,新卖家将自动添加。这将处理并发性问题。如果遇到并发问题,可以添加适当的锁定提示而不会产生有害影响,因为表将保持非常小并且可以使用 ROWLOCK(除了需要范围锁定的 INSERT)。

请使用see the Sql Fiddle 获取演示该技术的工作、测试代码。现在您拥有真实产品代码,无需更改且可靠。

【讨论】:

  • 感谢您提供如此详细的解释。
【解决方案2】:

我通常建议使用视图来进行这种类型的计算。如果选择性能是最重要的因素,甚至可以为视图编制索引(我看到您使用的是persisted)。

计算列中不能有子查询,这实际上意味着您只能访问当前行中的数据。获得此计数的唯一方法是使用user-defined function in your computed column,或使用触发器来更新非计算列。

视图可能如下所示:

create view ProductCodes as
select p.ProductId, p.SellerId,
    (
            select right('000000' + cast(count(*) as varchar(6)), 6)
            from Product
            where SellerID = p.SellerID
                and ProductID <= p.ProductID
    ) as ProductCode
from Product p

对您的产品编号方案的一个重大警告以及视图和 UDF 选项的失败是,我们依赖于具有较低 ProductId 的行数。这意味着如果在序列中间插入一个产品,它实际上会更改现有产品的 ProductCodes 具有更高的 ProductId。此时,您必须:

  • 保证 ProductId 的排序(仅身份不这样做)
  • 依赖具有保证序列的不同列(仍然值得怀疑,但可能是 CreateDate?)
  • 使用触发器在插入时获取计数,然后永远不会更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 2011-09-05
    • 2017-06-19
    • 2012-01-02
    • 1970-01-01
    相关资源
    最近更新 更多