计算列表达式

时间:2013-02-19 21:03:47

标签: sql-server tsql calculated-columns

我特别需要一个名为 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

2 个答案:

答案 0 :(得分:2)

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

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

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

<强> dbo.SellerProductCode

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

此表格可靠地记录每个卖家的最后使用的产品代码。在INSERTProduct表上,触发器将针对所有受影响的LastProductCode更新此表中的SellerID,然后更新所有新插入的行具有适当值的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来查看演示该技术的经过测试的代码。现在您拥有真正的产品代码,这些代码没有任何理由可以改变并且可靠。

答案 1 :(得分:0)

我通常建议使用视图来执行此类计算。如果选择性能是最重要的因素,我甚至可以对视图编制索引(我看到你正在使用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的行数。这意味着如果在序列的中间插入产品,它实际上更改具有更高ProductId的现有产品的ProductCodes。那时,您必须:

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