【问题标题】:Is there a way to optimize or replace a while loop in sql?有没有办法优化或替换 sql 中的 while 循环?
【发布时间】:2019-09-05 21:36:49
【问题描述】:

我们有几百万行数据需要通过在started_at 日期和ended_at 日期之间的每个日期添加一行来“分解”出来。 while 循环是我们查询中耗时最长的。

关于如何优化或替换它的任何想法?

IF (OBJECT_ID('TempDb..#exploded_services') IS NOT NULL)
  DROP TABLE #exploded_services;

CREATE TABLE #exploded_services
  (
   target_date date,
   move_id varchar(30),
   initiation_id varchar(30),
   initiated_at date,
   booked_at date,
   transferee varchar(60),
   account_id varchar(30),
   mc_id varchar(30),
   po varchar(60),
   weight int,
   service varchar(150),
   started_at date,
   ended_at date,
   location_id nvarchar(64),
   description varchar(max),
   provider varchar(max),
   mode varchar(60),
   origin_location_id nvarchar(64),
   destination_location_id nvarchar(64),
   transferee_phone varchar(40),
   transferee_email varchar(100),
   status varchar(10),
   ordinal int
  );


WHILE (@pointer <= @end_date)
 BEGIN
   INSERT INTO #exploded_services
   SELECT
     @pointer,
     svcs.*
   FROM #Services svcs
   WHERE @pointer BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,@end_date)
   SET @pointer = DATEADD(dd, 1, @pointer)
 END;

【问题讨论】:

  • 在 select 语句中添加 RowNumber,在 where 子句中添加用户 DATEADD(dd, Row_Number Column Value, @pointer)。单个 select 语句可以插入所有行。
  • 只需使用一条插入语句即可。这里循环的意义何在?
  • 请阅读声明式和命令式语言结构之间的区别。特林是你的答案。切勿在 SQL 声明性语句中使用循环。
  • 另外,请不要使用 dd 这样的简写形式。输入day 并没有更多的努力,但它确实更具可读性(更不用说可靠了)。
  • 您正在为日期范围创建天数。在编程语言中,这是通过循环完成的。在 SQL 中,您通常会为此使用递归查询。我现在没有时间发布答案。希望其他人会这样做。

标签: sql sql-server tsql while-loop


【解决方案1】:

这可以使用 Tally 表来实现。下面是一个示例,说明如何使用通过级联 ctes 动态创建的示例。

WITH 
E(n) AS(
    SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
    SELECT a.n FROM E a, E b
),
E4(n) AS(
    SELECT a.n FROM E2 a, E2 b
),
cteTally(n) AS(
    SELECT TOP(DATEDIFF(DD, @pointer, @end_date) + 1) 
            ROW_NUMBER() OVER(ORDER BY (SELECT NULL))-1 n
    FROM E4
)
INSERT INTO #exploded_services
SELECT
    DATEADD( dd, n @pointer),
    svcs.*
FROM #Services svcs
JOIN cteTally t ON DATEADD( dd, n @pointer) BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,@end_date);

【讨论】:

    【解决方案2】:
    1. 创建一个包含一个日期列的表。
    2. 填写适用于您的服务的所有可能日期。
    3. 使用以下内容填充目标表:
     INSERT INTO #exploded_services
       SELECT
         dates_table.date,
         svcs.*
       FROM #Services svcs
       INNER JOIN dates_table ON dates_table.date BETWEEN svcs.started_at AND COALESCE(svcs.ended_at,_arbitrary_end_date_)
    

    【讨论】:

      【解决方案3】:

      您可以尝试使用 CTE 生成所有所需日期的以下代码:

       -- cte to get all dates needed
       ;with cte as (
          select @pointer ptr
          union all
          select DATEADD(dd, 1, @pointer) from cte
          where @pointer < @end_date
       )
       -- adjusted insert query
       INSERT INTO #exploded_services
       select c.*, s.*
       from #Services s
       join cte c on c.ptr between s.started_at and coalesce(svcs.ended_at,@end_date)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-02
        • 2011-11-09
        • 2011-05-14
        • 1970-01-01
        • 1970-01-01
        • 2021-07-27
        相关资源
        最近更新 更多