【问题标题】:How to turn JOINS into Subqueries without breaking the SQL logic如何在不破坏 SQL 逻辑的情况下将 JOINS 转换为子查询
【发布时间】:2019-09-04 13:29:57
【问题描述】:

我在 T-SQL 中使用多个联接时遇到了性能问题,如果有人可以帮助我将这些联接转换为子查询,那就太好了。

每当我尝试将连接更改为子查询时,我都会丢失特定表的名称声明。例如,如果我尝试将专辑连接(这是下面代码的第一个连接)转换为子查询,我将失去别名“AS a”并且“a.Title AS Album”停止工作,所以我不知道这是怎么回事会完成的。如果有人给我一个例子,它应该如何适用于其中一种情况,我想我将能够重建所有这些情况。

SQL

SELECT
    t.TrackId, 
    t.[Name] AS Track, 
    a.Title AS Album, 
    aa.[Name] AS Artist, 
    p.[Name] AS Playlist,
    m.[Name] AS MediaType,
    il.UnitPrice AS InvoicePrice,
    CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
    CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
FROM dbo.Track AS t
INNER JOIN dbo.Album AS a
    ON t.AlbumId = a.AlbumId
INNER JOIN dbo.Artist AS aa
    ON a.ArtistId = aa.ArtistId
INNER JOIN dbo.PlaylistTrack AS plt
    ON t.TrackId = plt.TrackId
INNER JOIN dbo.Playlist AS p
    ON p.PlaylistId = plt.PlaylistId
INNER JOIN dbo.MediaType AS m
    ON t.MediaTypeId = m.MediaTypeId
INNER JOIN dbo.InvoiceLine AS il
    ON t.TrackId = il.TrackId
INNER JOIN dbo.Invoice AS i
    ON il.InvoiceId = i.InvoiceId
INNER JOIN dbo.Customer AS c
    ON i.CustomerId = c.CustomerId
INNER JOIN dbo.Employee AS e
    ON c.SupportRepId = e.EmployeeId
WHERE m.[Name] LIKE '%audio%'
ORDER BY t.[Name] ASC

【问题讨论】:

  • 那里有很多连接。优化器可能无法找到查询的最佳计划。可以发一张查询计划的图片吗?
  • 在子查询中加入连接以获得性能? ..似乎不是一个好主意..
  • m.[Name] LIKE '%audio%' 也将成为性能问题。你能在你的表上发布索引吗?
  • 它很难发布查询计划,因为它真的很大,但索引基本上是我用于连接表的所有主键。为什么 '%audio%' 会降低性能?
  • @S.Minchev 将like 与以通配符开头的模式一起使用,例如%,禁止使用索引查找来定位匹配的行。这就像要求您在字典中查找所有包含“arf”的单词,但您不知道单词的开头是什么。唯一的方法是执行 scan 而不是 seek。提示:请参阅paste the plan 了解向您的问题添加执行计划的方法。

标签: sql sql-server tsql join subquery


【解决方案1】:

在子查询中加入连接可能不是最好的解决方案

假设您已经为每个被替换的表建立了外键索引

table  Artist index on column (ArtistId)
table  PlaylistTrack index on column (TrackId)
table  Playlist index on column (PlaylistId)
table  MediaType index  on column ( MediaTypeId )
.....

为了性能,请确保您有索引

table  track a composite index  on column  (AlbumId, TrackId, MediaTypeId )
table  Album  a cmposite index  on column ( AlbumId, ArtistId )

【讨论】:

    【解决方案2】:

    你的意思是什么类型的子类?比如:

    SELECT t.TrackId, 
        t.[Name] AS Track, 
        (SELECT title FROM dbo.Album WHERE AlbumId = t.AlbumId) AS AlbumTitle
    

    如果您删除对Album 的加入,则不会发生这种情况,因为您需要专辑引用才能找到艺术家。如果你想加入一个子查询,你可以这样做,并保留别名:

    SELECT
        t.TrackId, 
        t.[Name] AS Track, 
        a.Title AS Album, 
        aa.[Name] AS Artist, 
        p.[Name] AS Playlist,
        m.[Name] AS MediaType,
        il.UnitPrice AS InvoicePrice,
        CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
        CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
    FROM dbo.Track AS t
    INNER JOIN (SELECT * FROM dbo.Album) AS a
       ON t.AlbumId = a.AlbumId
       -- rest of joins
    

    但从逻辑上讲,这和你现在的完全一样,查询优化器生成的计划不会有任何差异。即使这样:

    SELECT
        t.TrackId, 
        t.[Name] AS Track, 
        aa.Title AS Album, -- note change here
        aa.[Name] AS Artist, 
        p.[Name] AS Playlist,
        m.[Name] AS MediaType,
        il.UnitPrice AS InvoicePrice,
        CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
        CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
    FROM dbo.Track AS t
    INNER JOIN (SELECT alb.Title
                     , art.Name
                     , alb.AlbumId
                  FROM dbo.Album alb
            INNER JOIN dbo.Artist art
                    ON art.ArtistId = alb.ArtistID) AS aa
       ON t.AlbumId = aa.AlbumId
    INNER JOIN dbo.PlaylistTrack AS plt
       ON t.TrackId = plt.TrackId
    -- rest of them
    

    将产生完全相同的计划。我们可能已经将Album Artist 连接移动到连接子查询,但本质上,它仍然是相同的东西——内部连接。

    这样的事情不会对您的表现有所帮助。可能对您有所帮助的是在这些表上创建索引。如果您经常执行这种查询,您还可以创建索引视图,例如:

    CREATE VIEW BoughtTracks
    WITH SCHEMABINDING
    AS
    SELECT
        il.InvoiceLineId, -- I'm guessing here, we need a unique ID
        t.TrackId, 
        t.[Name] AS Track, 
        a.Title AS Album, 
        aa.[Name] AS Artist, 
        p.[Name] AS Playlist,
        m.[Name] AS MediaType,
        il.UnitPrice AS InvoicePrice,
        CONCAT(c.FirstName, ' ', c.LastName) AS CustomerName,
        CONCAT(e.FirstName, ' ', e.LastName) AS ResponsibleEmployeeName
    FROM dbo.Track AS t
    INNER JOIN dbo.Album AS a
        ON t.AlbumId = a.AlbumId
    INNER JOIN dbo.Artist AS aa
        ON a.ArtistId = aa.ArtistId
    INNER JOIN dbo.PlaylistTrack AS plt
        ON t.TrackId = plt.TrackId
    INNER JOIN dbo.Playlist AS p
        ON p.PlaylistId = plt.PlaylistId
    INNER JOIN dbo.MediaType AS m
        ON t.MediaTypeId = m.MediaTypeId
    INNER JOIN dbo.InvoiceLine AS il
        ON t.TrackId = il.TrackId
    INNER JOIN dbo.Invoice AS i
        ON il.InvoiceId = i.InvoiceId
    INNER JOIN dbo.Customer AS c
        ON i.CustomerId = c.CustomerId
    INNER JOIN dbo.Employee AS e
        ON c.SupportRepId = e.EmployeeId
    WHERE m.[Name] LIKE '%audio%'
    
    CREATE UNIQUE CLUSTERED INDEX ux ON BoughtTracks (InvoiceLineId);
    

    这会减慢对这些表的插入速度,但在 BoughtTracks 上选择会很快(您也可以在该视图上创建其他索引),例如:

    SELECT * 
      FROM BoughtTracks WITH (NOEXPAND) -- NOEXPAND is important
     WHERE CustomerName = 'Joe Smith'
    

    执行速度可能比您当前的查询快几个数量级,这当然取决于您的数据大小。特别是如果您在其上创建索引

    CREATE INDEX ix_CustomerName ON BoughtTracks (CustomerName) 
    INCLUDE (...) -- maybe include some columns you know you will need when querying for CustomerName
    WHERE (...) -- maybe there are alsways accompanying predicates when querying for CustomerName
    

    【讨论】:

    • 看起来很有趣,马上去试试
    • 唯一的问题是这个视图,因为我不断地将新数据插入到跟踪表中 - 可以说每 2 分钟一次,所以我还需要一个触发器来填充视图
    • 您不需要触发器,这不是表格。视图就像一个保存的选择查询,它有一个具体化的索引,有点像一个表,但 SQL Server 会负责更新它。
    猜你喜欢
    • 2018-10-25
    • 1970-01-01
    • 2015-06-11
    • 1970-01-01
    • 2021-06-30
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 2011-01-07
    相关资源
    最近更新 更多