【问题标题】:SQL, to loop or not to loop?SQL,循环还是不循环?
【发布时间】:2014-04-15 06:16:36
【问题描述】:

问题是这样的:

考虑一个程序来管理每个客户的余额限制的银行账户
{table Customers, table Limits} 其中每个 Customer.id 都有一个限制记录
然后客户说要存储限制更改的历史记录,这不是问题,因为我已经有了Limit 的日期列,但是active/latest limits 的视图查询需要更改

之前:客户限制是 1 比 1,所以一个简单的选择就可以了
现在:它将显示所有限制的记录,这意味着每个客户有多个记录,我只需要最新的限制,所以我想到了类似这样的伪代码

foreach( id in Customers)
{
  select top 1 *
  from Limits
  where Limits.customer_id = id
  order by Limits.date
}

但在通过 SO 寻找类似问题时,我遇到了类似
"95% of the time when you need a looping structure in tSQL you are probably doing it wrong"-JohnFx

"SQL is primarily a set-orientated language - it's generally a bad idea to use a loop in it."-Mark Bannister

谁能确认/解释为什么循环是错误的?在上面解释的问题中,我需要循环什么错了?

提前致谢

更新:我的解决方案 根据 TomTom 的回答和建议的链接 here,在 Dean 友好地回答代码之前,我想出了这个

SELECT *
FROM Customers c
LEFT JOIN Limits a ON a.customer_id = c.id
  AND a.date = 
    (
       SELECT MAX(date) 
       FROM Limits z 
       WHERE z.customer_id = a.customer_id
    )

我想我会分享:>

感谢您的回复,
快乐编码

【问题讨论】:

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


    【解决方案1】:

    这样可以吗?

    ;with l as (
      select *, row_number() over(partition by customer_id order by date desc) as rn
      from limits
    )
    select *
    from customers c
    left join l on c.customer_id = l.customer_id and l.rn = 1
    

    【讨论】:

      【解决方案2】:

      我假设您之前(即在实现历史功能之前)必须更新Limits 表。现在,为了实现历史功能,您已经开始插入新记录。这不会触发您的数据库和代码的大量更改吗?

      与其插入新记录,不如保持原有功能不变并创建一个新表Limits_History,它将在更新之前存储Limits 表中的所有旧值?然后,如果您想显示历史记录,您需要做的就是从该表中获取记录。这不会对您现有的 SP 和代码造成任何更改,因此更不容易出错。

      要在Limits_History 表中插入记录,您可以简单地创建一个AFTER TRIGGER 并使用deleted 魔术表。因此,您不必担心调用 SP 或其他东西来维护历史记录。触发器将为您执行此操作。触发的好例子是here

      希望对你有帮助

      【讨论】:

      • 是的,我猜你是对的,它会引起很多变化!我想在开始担心添加的功能之前,我专注于更改视图查询以恢复到以前的工作状态:/ 感谢您的提示
      【解决方案3】:

      这是错误的。您可以通过使用限制为 limit 上的最新记录的子查询来查询客户和限制来执行相同的操作。

      这在概念上类似于 Most recent record in a left join 中提出的查询

      您可能必须在 2 次连接中这样做 - 获取最近的日期,然后获取日期的限制。虽然这可能看起来很复杂 - 这是一个初学者问题,当您的 sql 语句达到 2 个打印页面或更多时,请说复杂;)

      现在,对于一个操作系统,表设计被打破了——限制应该包含最近的限制,LimitHistory 表包含历史(或:所有)条目,允许快速检索当前限制(这将是适用于所有交易)没有历史的开销。您的表设计假设所有限制都是相同的 - 对于报告数据仓库来说这可能是事实(是事实),但对于事务系统来说是错误的,因为历史没有被处理。

      【讨论】:

      • 你能解释一下为什么错了吗?我想了解问题而不仅仅是解决它:)
      • 它使查询更复杂,即更慢。而不是获得限制(适用的限制),您必须先找到它,然后得到它。事务系统通常是高吞吐量的,任何强制复杂查找的东西都可能比它必须的更慢 - 需要更多的硬件或更多的时间或两者兼而有之。鉴于当您进行事务处理时,您并不关心历史限制(仅与您现在相关的限制),因此不需要此查找。
      • 不循环而不是“错误”不是“最佳实践”吗?感谢您的帮助
      • 哦,那部分。不。看,如果您需要 100 个客户,您将 1 个 sql 语句(无循环)转换为 101 个语句(获取客户,循环)。这不是最佳做法。这是可笑的糟糕表现。
      【解决方案4】:

      为什么循环错误的确认完全在您问题的引用部分中 - SQL 是一种面向集合的语言。 这意味着当您处理集合时,没有理由遍历单行,因为您已经有了想要处理的数据的“结果”(集合)。 那么你正在做的工作应该在这组行上完成,否则你的选择是错误的。

      话虽如此,在 SQL 中执行循环的情况当然也存在,如果在数据上,它通​​常会通过游标完成,如果在计算内容时,它通常会通过 while 循环完成。 (通常,例外总是会改变)。 但是,正如引号中所提到的,通常当您想要使用循环时,您要么不应该使用循环(它的性能很差),要么您在应用程序的错误部分执行逻辑。

      基本上 - 它类似于面向对象的语言如何处理对象和对所述对象的引用。基于集合的语言适用于 - 好吧,数据集。

      SQL 基本上是以这种方式运行的——将关系数据查询到结果集中——所以在使用这种语言时,你应该让它做它可以做的事情并继续努力。就像它是 Java 或任何其他语言一样。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-07
        • 1970-01-01
        • 2011-04-02
        • 2013-01-12
        • 2018-06-26
        • 1970-01-01
        相关资源
        最近更新 更多