【问题标题】:SQL: Optimize nested query with redundant union code?SQL:使用冗余联合代码优化嵌套查询?
【发布时间】:2019-01-23 20:33:53
【问题描述】:

虽然下面的查询非常适合我的使用,但我试图了解是否有优化的方法,因为它在 UNION 的两个语句中使用相同的嵌套子查询。我的直觉认为应该有一种方法可以查找子查询一次并将该结果用于 UNION 的两个部分,但是在尝试跨 UNION 使用 join 时我遇到了语法问题。

SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
    FROM dbo.ClientLocal
    WHERE clientLocalID = 
        ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
UNION
SELECT ClientMasterName, '0', clientMasterID, 2
    FROM dbo.ClientMaster
    WHERE clientMasterID = 
        ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
ORDER BY HierarchyType ASC;

为了帮助解释为什么需要原始查询,请想象一个“本地”客户端和“主”客户端的概念——本地客户端与主客户端绑定,但主客户端可能有也可能没有本地客户。同样,与每个相关联的电话号码只能与本地客户端或主客户端绑定(因此在第二个语句中,本地客户端 ID 硬编码为“0”——因为没有关联)。

顺便说一下,这将针对 Microsoft SQL Server 2008 R2 运行。

【问题讨论】:

  • 只是因为您重复了代码,并不一定意味着它会被重复评估。优化器很可能会发现这是重复的,并重用第一次评估的结果。除非,重新评估当然似乎更合适。如果只是重复代码,请使用提供的 CTE 解决方案。
  • 这个查询中可能有很多需要优化的地方。首先,我看不出这个查询是如何运行的,因为你不能 UNION ClientLocalName 和 ClientMasterName 除非这些列具有相同的名称。你能提供一点样本数据和你的预期结果吗? ClientPhone 的 CTE 将起作用,但这实际上是您想要获得的数据吗?
  • @Shawn 列的名称无关紧要,只要联合中的数据类型相同。查询确实工作得很好。无论如何,卢卡斯的回答提供了我正在寻找的东西。
  • @stickybit 是对的,但是真实的代码在重复代码中会有更多的条件,所以这里有一个很好的鼓励不要重复。
  • @stickybit:其实重复的代码必然会被多次求值。当然,基本数据只需要加载到缓存中一次,但实际执行的频率是按要求完成的。请参阅下面的示例。

标签: sql tsql subquery query-optimization union


【解决方案1】:

如果您不喜欢重复代码,您可以使用公用表表达式:

WITH cte AS (
  SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789'
)
SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
FROM dbo.ClientLocal
WHERE clientLocalID IN (SELECT HierarchyItem FROM cte)
UNION
SELECT ClientMasterName, '0', clientMasterID, 2
FROM dbo.ClientMaster
WHERE clientMasterID IN (SELECT HierarchyItem FROM cte)
ORDER BY HierarchyType ASC;

【讨论】:

  • 值得一提的是,如果子结果中有重复项(SELECTs 充当 UNION 或 @ 的操作数,则将 UNION 更改为 UNION ALL 可以改变整体结果987654326@)。列的命名(ClientLocalID 和 ClientMasterID)表明子结果中有 PK,因此不会发生重复。但是,如果这是一种错误印象,则需要在使用 UNION ALL 时使用 SELECT DISTINCT 自行使子结果免费复制(由于重复数据删除的集合更小,这可能仍然更快)。
  • @stickybit 不错。
  • 这提供了相同的结果(对于这个查询),并提供了我能看到的所有可能的最佳“优化”。重复代码将变得更加重要,因为 CTE 将有更多的条件、子句(因此 JDBC 中的参数也更多),因此这将在很多地方保存一些代码。从执行时间的角度来看,它看起来同样有效或更好。
【解决方案2】:

因为似乎没有人提到它:代码越少并不一定意味着它的执行速度也会更快。人们似乎常常认为首先执行 CTE(或“按顺序”,以防有多个),然后应用最终查询;他们不是。事实上,服务器几乎会处理 cte,就好像它是 FROM 或 JOIN 子句中某处的派生表一样,因此选择完全相同的查询计划来执行它们。为了避免系统必须扫描ClientPhone 表两次(LIKE '%0123...' 是一个非常“烦人”的操作),您必须预先执行一次,将结果存储在临时表中,然后使用所述临时表- 实际查询中的表。

为了提供一些背景信息,我冒昧地创建了一些示例数据(参见下面的代码)。我的测试结果是:

  • 原始查询耗时 71ms
  • CTE 版本耗时 71 毫秒,使用完全相同的查询计划
  • 临时表的创建需要 26 毫秒,使用它的查询需要 23 毫秒

所以即使服务器需要创建临时表并填充它,组合后者仍然只需要 49 毫秒而不是原来的 71 毫秒!

当然,您的里程可能会根据所涉及的记录数量和重复查询的复杂性而有所不同,就像我说的那样,LIKE '%blah' 是一件烦人的事情,因为它需要对表进行全面扫描(或使用(覆盖)索引的一点运气)。如果是WHERE pk_field = @value,效果可能会大不相同)

查询愉快...

IF DB_ID('test') IS NULL CREATE DATABASE test
GO
USE test
GO

-- setup
IF OBJECT_ID('ClientPhone') IS NOT NULL DROP TABLE ClientPhone 
IF OBJECT_ID('ClientLocal') IS NOT NULL DROP TABLE ClientLocal
IF OBJECT_ID('ClientMaster') IS NOT NULL DROP TABLE ClientMaster
GO
SELECT TOP 50000 
       HierarchyItem = IDENTITY(int, 1, 1),
       PhoneNumber   = Convert(varchar(100), NewID())
  INTO dbo.ClientPhone 
  FROM sys.objects x1, sys.columns x2, sys.objects x3, sys.columns x4

SELECT ClientLocalName = 'client dummy', 
       ClientLocalID   = Convert(int, Rand(HierarchyItem * 37) * 50000), 
       ClientMasterID   = Convert(int, Rand(HierarchyItem * 47) * 50000), 
       HierarchyType   = 1
  INTO dbo.ClientLocal
  FROM dbo.ClientPhone 

SELECT ClientMasterName = 'master dummy', 
       ClientLocalID    = Convert(int, Rand(HierarchyItem * 51) * 50000), 
       ClientMasterID   = Convert(int, Rand(HierarchyItem * 53) * 50000), 
       HierarchyType    = 2
  INTO dbo.ClientMaster
  FROM dbo.ClientPhone 

GO
-- original 
SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
  FROM dbo.ClientLocal
 WHERE ClientLocalID = ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%01')
UNION
SELECT ClientMasterName, '0', ClientMasterID, 2
  FROM dbo.ClientMaster
 WHERE ClientMasterID = ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%01')
ORDER BY HierarchyType ASC;

-- CTE
;WITH cte
   AS (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%01')

SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
  FROM dbo.ClientLocal
 WHERE ClientLocalID = ANY (SELECT HierarchyItem FROM cte)
UNION
SELECT ClientMasterName, '0', ClientMasterID, 2
  FROM dbo.ClientMaster
 WHERE ClientMasterID = ANY (SELECT HierarchyItem FROM cte)
ORDER BY HierarchyType ASC;

-- temp table
IF OBJECT_ID('tempdb..#ClientPhone') IS NOT NULL DROP TABLE #ClientPhone

SELECT HierarchyItem INTO #ClientPhone FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%01'

SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
  FROM dbo.ClientLocal
 WHERE ClientLocalID = ANY (SELECT HierarchyItem FROM #ClientPhone)
UNION
SELECT ClientMasterName, '0', ClientMasterID, 2
  FROM dbo.ClientMaster
 WHERE ClientMasterID = ANY (SELECT HierarchyItem FROM #ClientPhone)
ORDER BY HierarchyType ASC;

【讨论】:

    【解决方案3】:
    ;with subQry as (
        SELECT ClientLocalName, ClientLocalID, ClientMasterID
            FROM dbo.ClientLocal
            WHERE clientLocalID = 
                ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
    )
    SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
        FROM subQry
    UNION
    SELECT ClientMasterName, '0', clientMasterID, 2
        FROM subQry
    ORDER BY HierarchyType ASC;
    

    【讨论】:

    • 因为subQry使用dbo.ClientLocal,所以整体查询两次找到本地ID,没有找到任何MasterID
    • 你是对的。我没有意识到您正在查询 2 个不同的表。所以,我认为最好的方法是 Lukasz Szozda 的回答。
    • 你可以试试这个。不确定这是否有助于提高性能。我认为这将是相同的。 WITH cte AS (SELECT distinct HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789') SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType FROM dbo.ClientLocal 内部连接 ​​CTE on ClientLocal.clientLocalID = cte.HierarchyItem UNION SELECT ClientMasterName, '0' , clientMasterID, 2 FROM dbo.ClientMaster 内连接 cte on ClientMaster.clientMasterID = cte.HierarchyItem ORDER BY HierarchyType ASC;
    【解决方案4】:

    我能看到的唯一明显优化是使用union all 而不是union:

    SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
    FROM dbo.ClientLocal
    WHERE clientLocalID = ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
    UNION ALL
    SELECT ClientMasterName, '0', clientMasterID, 2
    FROM dbo.ClientMaster
    WHERE clientMasterID = ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
    ORDER BY HierarchyType ASC;
    

    您显然在子选择之间没有重复项(因为最后一列)。这假设您在每个子选择中也没有重复项。

    我不知道 SQL Server 对= ANY 的处理方式,但我认为它与IN 基本相同。 = ANY 子查询将运行一次(对于每个子查询),但第二次将缓存结果。您可以将其简化为:

    SELECT ClientLocalName, ClientLocalID, ClientMasterID, 1 HierarchyType
    FROM ((SELECT ClientLocalName, ClientLocalID, ClientMasterID
           FROM dbo.ClientLocal
          ) UNION ALL
          (SELECT ClientLocalName, '0', ClientMasterID
           FROM dbo.ClientMasterName
          ) 
         ) c        
    WHERE c.clientLocalID = ANY (SELECT HierarchyItem FROM dbo.ClientPhone WHERE PhoneNumber LIKE '%0123456789')
    ORDER BY HierarchyType ASC;
    

    我不知道这是否会有很大的改进。

    LIKE 模式在数字开头有一个通配符,因此很难优化。

    【讨论】:

    • 值得一提的是,如果子结果中有重复项(SELECTs 充当 UNION 或 @ 的操作数,则将 UNION 更改为 UNION ALL 可以改变整体结果987654333@)。列的命名(ClientLocalID 和 ClientMasterID)表明子结果中有 PK,因此不会发生重复。但是,如果这是一种错误印象,则需要在使用 UNION ALL 时使用 SELECT DISTINCT 自行使子结果免费复制(由于重复数据删除的集合更小,这可能仍然更快)。
    • 不幸的是,这里的基本问题是 WHERE 在谓词中使用了不同的列——第一个是 clientLocalID,第二个是 clientMasterID——即使两者是相同的类型并且格式化相同,因为列名不同,这将从结果中排除 clientMasterID。
    猜你喜欢
    • 2018-07-24
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-17
    • 1970-01-01
    • 2014-09-04
    相关资源
    最近更新 更多