【问题标题】:Update count column from data in another table从另一个表中的数据更新计数列
【发布时间】:2023-03-10 02:23:01
【问题描述】:

在我的数据库中,我有两个表 Items(Id, ..., ToatlViews int) 和 ItemViews (id, ItemId, Timestamp)

在 ItemViews 表中,我存储了一个项目的所有视图,因为它们来到了站点。有时我想调用一个存储过程来更新 Items.ToatlViews 字段。我尝试使用游标来执行此 SP ...但更新语句是错误的。你能帮我改正吗?我可以不使用光标吗?

CREATE PROCEDURE UpdateItemsViews
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @currentItemId int
    DECLARE @currentItemCursor CURSOR
    SET @currentItemCursor = CURSOR FOR SELECT Id FROM dbo.Items

    OPEN @currentItemCursor
    FETCH NEXT FROM @currentItemCursor INTO @currentItemId
    WHILE @@FETCH_STATUS = 0
    BEGIN
        Update dbo.Items set TotalViews = count(*) 
              from dbo.ItemViews where ItemId=@currentItemId
        FETCH NEXT FROM @currentItemCursor INTO @currentItemId
    END   
END
GO

【问题讨论】:

  • 我建议您在编写 SQL 时尽一切努力不使用游标,因为总会有一种基于 set 的方式来针对数据库编写您想要的内容。当然,这条规则总会有例外。

标签: sql-server stored-procedures


【解决方案1】:

您可以使用直接的 UPDATE 语句

update Items set TotalViews = 
     (select COUNT(id) from ItemViews where ItemViews.ItemId = Items.Id)

如果这很重要,您可能想要测试各种方法的性能。

【讨论】:

  • +1 表示优雅。谈业绩;我记得在某处读到 count(1) 而不是 count(id) 更好(不是你可能会注意到它)。由于确实需要在查询中选择 id 字段...
  • @mouters 这是一个误解。 COUNT(1) 仅在我们谈论准确性时才比 COUNT(id) 好,并且仅在 id 可以为空时才更准确(或以任何方式不同)。如果您在某处(不是您的记忆)看到这一点,请指出它,因为它应该被纠正或澄清。
  • 公平评论 - 我想我很难再找到这篇文章了。
  • 这个问题是它会将 TotalViews 设置为 0 用于与选择不匹配的所有内容
  • @Dan 你的意思是在 Items 和 ItemViews 之间没有匹配的地方?在这种情况下,TotalViews 应该为 0 还是 null 是一个业务规则。在这种情况下,OP 想要 0:对于 null 它将使用不同的语句。
【解决方案2】:

您可以使用update ... from 代替光标:

update  i
set     TotalViews = iv.cnt
from    dbo.Item i
join    (
        select  ItemId
        ,       count(*) as cnt
        from    dbo.ItemViews
        group by
                ItemId
        ) iv
on      i.Id = iv.ItemId

【讨论】:

    【解决方案3】:
    ;WITH x AS 
    (
      SELECT ItemID, c = COUNT(*) 
      FROM dbo.ItemViews
      GROUP BY ItemID
    )
    UPDATE i
    SET TotalViews = x.c
    FROM dbo.Items AS i
    INNER JOIN x
    ON x.ItemID = i.ItemID;
    

    但是当您总是可以在运行时获得计数时,为什么要存储这个值呢?每次以任何方式触摸 ItemViews 表时,您都必须运行此更新语句,否则与 Items 一起存储的计数将不正确。

    您可以考虑改为设置索引视图:

    CREATE VIEW dbo.ItemViewCount
    WITH SCHEMABINDING
    AS
        SELECT ItemID, ItemCount = COUNT_BIG(*)
          FROM dbo.ItemViews
          GROUP BY ItemID;
    GO
    CREATE UNIQUE CLUSTERED INDEX x ON dbo.ItemViewCount(ItemID);
    

    现在您可以在查询中加入视图,并且知道计数始终是最新的(无需支付扫描每个项目计数的惩罚)。索引视图的缺点是,当对 ItemViews 表进行插入/更新/删除时,您会逐步支付该成本。

    【讨论】:

      【解决方案4】:

      对于谁也需要包括零计数

      UPDATE Items as i,
          (SELECT 
              i.Id as Id, COUNT(iv.ItemId) AS c
          FROM
              Items AS i
          LEFT JOIN ItemViews AS iv ON i.Id = iv.ItemId
          GROUP BY i.Id) AS ic 
      SET 
          i.TotalViews = ic.c
      WHERE
          i.Id = ic.Id
      

      【讨论】:

        【解决方案5】:

        我在写完并回答了一年后发现了这个问题/答案。答案还可以,但我追求的是更自动的东西。我最终编写了一个触发器,以在插入、删除或更新另一个表中的相关行时自动重新计算列。

        我认为这是一个比手动运行重新计算更好的解决方案,因为不会有人忘记运行代码:

        CREATE TRIGGER [dbo].[TriggerItemTotalViews] 
           ON  [dbo].[ItemViews]
           AFTER INSERT, DELETE, UPDATE
        AS 
        BEGIN
        SET NOCOUNT ON;
        
        UPDATE [Items] 
        SET [TotalViews] = 
            (
            SELECT COUNT(id) 
            FROM [ItemViews] 
            WHERE [ItemViews].[ItemId] = [Items].[ItemId]
            )
        WHERE [Items].[ItemId] IN
            (
            SELECT [ItemId] FROM [INSERTED] 
            UNION 
            SELECT [ItemId] FROM [DELETED]
            )
        END
        

        【讨论】:

          【解决方案6】:

          相同但不同:

          declare @productId int = 24;
          declare @classificationTypeId int = 86;
          
          update s
          set CounterByProductAndClassificationType = row_num
          from Samples s
          join
          (
              select row_number() over (order by (select Id)) row_num, Id
              from Samples
              where 
                  ProductId = @productId and
                  ClassificationTypeId = @classificationTypeId
          ) s_row on s.Id = s_row.Id
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-08-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多