【问题标题】:SQL repeating exists subquerySQL重复存在子查询
【发布时间】:2020-06-21 06:11:55
【问题描述】:

我正在尝试编写一个查询,从两个表中提取多个列,然后查看在过去 28 天内是否有另一个实例包含相同类型的数据。

我的查询工作正常,它看起来像这样:

SELECT
     a.col_a
    ,a.col_b
    ,a.col_c
    ,b.col_a
    ,b.col_b
    ,CASE WHEN EXSISTS (SELECT a_sub.col_a
                        FROM a_sub
                        INNER JOIN b_sub
                          ON a_sub.x = b_sub.x
                        WHERE
                          b.col_a = b_sub.col_a
                          AND b.col_b <> b_sub.col_b
                          AND a.date > a_sub.date
                          AND a.date <= DATEADD(d, 28, a_sub.date)
                          AND a.col_c = a_sub.col_c
                          AND (a.col_d IS NULL OR a.col_d <> 7)
                          AND (a_sub.col_d IS NULL OR a_sub.col_d <> 7)
                        ) THEN 'Yes'
                          ELSE 'No'
                          END AS IsRepeat28
FROM a
INNER JOIN b ON a.x = b.x

这不是特别快,但也不会太慢而不会成为问题。问题是我需要重复上述 WHEN EXISTS 子查询,但要在 21、14、7 和 1 天期间重复 5 次 - 然后查询时间从大约 15 秒到大约 10 分钟。

我还需要调整日期以及标记“IsRepeat”的行,还有一个基于“HasRepeat”的版本

AND a_sub.date > a.date AND a_sub.date <= DATEADD(d, 28, a.date)

代替原来的

AND a.date > a_sub.date AND a.date <= DATEADD(d, 28, a_sub.date)

这意味着有 10 个这些 EXISTS 子查询,这需要将近 45 分钟。

我的问题确实是双重的;有没有更有效的方法来编写这个子查询,有没有更好的方法来重复它而不是将几乎相同的代码写出 10 次?

【问题讨论】:

  • 如果不是检查窗口中是否存在日期,而是检索前一个匹配日期,则可以使用一个日期为每个持续时间构建标志。这应该让你减少一次往返。如果您使用的是现代版本的 SQL Server,那么您可以使用 LAG 窗口函数将其归结为对表的单次读取。
  • 更改参数,查询需要更长的时间来执行。这让我想起了我在创建和重用低效执行计划时遇到的问题。我建议您可以尝试将 OPTION(RECOMPILE) 命令添加到您的代码中,也许它会有所帮助:stackoverflow.com/questions/20864934/… - 但请注意,我是 SE 上的一个随机陌生人,我建议您一些我自己甚至不完全理解的东西,所以请好好研究一下 :)
  • 性能问题的起点通常是实际的执行计划。请参阅paste the plan,了解在您的问题中包含执行计划的方法。存在哪些索引(如果有)?

标签: sql-server tsql subquery exists


【解决方案1】:

我将任何产生数据的查询称为问题。 SQL 应该只用于查询数据。如果您了解您的流程及其所做的工作,您就不需要执行计划。

首先,根据您拥有的记录数,将您的主要投影作为第 1 步分离到临时表或表变量中。在生产查询中缓存您需要的每个集合,您可以将负载大小作为选择计数 (*) 来衡量。

    insert into @mytablevariable values
    SELECT
       a.col_a,
       a.col_b,
       a.col_c,
       b.col_a,
       b.col_b
       -- build this column later
    FROM a
    INNER JOIN b ON a.x = b.x

接下来,缓存该嵌套查询的结果,这样就不会在每次迭代时都执行内连接。

    insert into @mysub values
    SELECT a_sub.col_a
    FROM a_sub
    INNER JOIN b_sub
       ON a_sub.x = b_sub.x
       AND b.col_b <> b_sub.col_b

我们故意制造了浪费,因此我们可以通过根据您的处理要求改造 @mysub 来管理它。我们的目标是通过按正确顺序获取正确数据来消除工作量。

在这个过程中最突出的是日期字段在日期范围内的 WHERE 子句。

    AND a.date > a_sub.date AND a.date <= DATEADD(d, 28, a_sub.date)

自然地,表不按日期列值索引,因此这种 WHERE 比较必须进行全表扫描,或者我们称之为提取器。提取器从集合中提取数据的一部分(子集)。因为这个提取器在一个子查询中,它像任何其他嵌套进程一样为每个外部记录结果执行,所以会一遍又一遍地检查整个表。我们总是希望将提取最小化到一个遍,这仅在我们完成对提取器的组织标准时完成。切勿嵌套提取器。

首先,通过外部@mytablevariable 查询中所需的日期值范围来最小化@mysub 提取器结果的大小。

    insert into @mysub values
    SELECT a_sub.col_a
    FROM a_sub
    INNER JOIN b_sub
       ON a_sub.x = b_sub.x
       AND b.col_b <> b_sub.col_b
       and a_sub.date <= DATEADD(d, 28, select min(a.date) from @mytablevariable)
       and a_sub.date > (select max(a.date) from @mytablevariable)

然后,您可以将@mysub 投影到您的 21 天子集中,然后再将较小的集投影给其他人。对于 WHERE 子句的其余部分,使用您要比较的列展开 @mysub。要构建您的最终产品,请从第一个表中选择所有内容,并按如下方式对每条记录进行一次查找。

    select 
        *,
        case 
            when b.col_a in (select col_a from @mysub28) then 'Yes'
            else 'No'
        end as IsRepeat28,
        case 
            when b.col_a in (select col_a from @mysub21) then 'Yes'
            else 'No'
        end as IsRepeat21
        -- rinse, repeat
    from 
    @mytablevariable

任何时候,只要您有一个生产流程,就可以分离出您的组件并按照您的提取器的选择标准进行处理,这样它们就只能通过一次。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-04
    • 1970-01-01
    • 1970-01-01
    • 2018-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-22
    相关资源
    最近更新 更多