使用交叉应用子查询优化 sql 查询的问题

Posted

技术标签:

【中文标题】使用交叉应用子查询优化 sql 查询的问题【英文标题】:Problem optimizing sql query with cross apply sub query 【发布时间】:2021-03-31 05:14:10 【问题描述】:

所以我有三张桌子:

MakerParts,保存车辆零件的主要信息:

Id MakerId PartNumber Description
1 1 ABC1234 Tire
2 1 XYZ1234 Door

MakerPrices,保存零件的价格历史变化(参考MakerPartNumberId 上的MakerParts.Id,以及MakerPriceUpdatesUpdateId 上的表格):

Id MakerPartNumberId UpdateId Price
1 1 1 9.83
2 1 2 11.23

MakerPriceUpdates,保存价格更新的日期。此更新基本上是上传到我们系统的 CSV 文件。一个文件,此表一行,表上多个价格变化MakerPrices

Id Date FileName
1 2019-01-09 00:00:00.000 temp.csv
2 2019-01-11 00:00:00.000 temp2.csv

这意味着一个零件 (MakerParts) 可能有多个价格 (MakerPrices)。价格变化的日期在 MakerPricesUpdates 表中。

我想选择所有最近价格为零的 MakerParts,按 MakerParts 表上的 MakerId 进行过滤。

我尝试过的:

select mp.* from MakerParts mp cross apply
    (select top 1 Price from MakerPrices inner join 
        MakerPricesUpdates on MakerPricesUpdates.Id = MakerPrices.UpdateId where 
        MakerPrices.MakerPartNumberId = mp.Id order by Date desc) as p
where mp.MakerId = 1 and p.Price = 0

但这太慢了(我们在 MakerPrices 表上有大约 1 亿行)。我很难优化这个查询。 (结果是MakerId 1只有两行,运行了2分钟)。我也试过了:

select * from (
    select 
        mp.*,
        (select top 1 Price from MakerPrices inner join 
        MakerPricesUpdates on MakerPricesUpdates.Id = MakerPrices.UpdateId 
        where MakerPrices.MakerPartNumberId = mp.Id order by Date desc) as Price
    from MakerParts mp) as temp
where temp.Price = 0 and MakerId = 1

同样的结果,同样的时间。我的查询计划(针对第一个查询)(Management Studio 没有建议新索引):

【问题讨论】:

为什么它不应该是像下面这样的内部连接?这不会给你想要的结果吗?请检查。 select mp.* from MakerParts mp join (select top 1 Price, MakerPartNumberId from MakerPrices inner join MakerPricesUpdates on MakerPricesUpdates.Id = MakerPrices.UpdateId where MakerPrices.MakerPartNumberId = mp.Id order by Date desc) as p where mp.id = p. MakerPartNumberId 和 mp.MakerId = 1 和 p.Price = 0 这给了我Incorrect syntax near the keyword 'where'。我正在使用 SQL Azure V12。 将第一个 join 更改为 cross apply 有效,但速度很慢。 请参阅小提琴网址中的查询以供参考。 dbfiddle.uk/…谢谢 @praveen 谢谢。我得到零行作为响应。我相信内部的select top 1 * from MakerPricesUpdates order by Date desc 总是会返回相同的更新Id。这样,只会检查最近更新的零件价格,并且在此更新中更新了一些 MakerPrice,但没有价格 = 0 【参考方案1】:

我认为你可以避免加入MakerPriceUpdatesmakerprices,因为最高 UpdateId你可以找到最新的价格更新。这将为您节省一些时间。

select mp.* from MakerParts mp cross apply
    (select top 1 Price from MakerPrices where 
        MakerPrices.MakerPartNumberId = mp.Id order by MakerPrices.UpdateId desc) as p
where mp.MakerId = 1 and p.Price = 0

您可以通过使用 cte 和 row_number() 避免排序和排序来进一步减少一些时间,如下所示:

;with LatestMakerPrices as
(
    select  *,row_number()over(partition by MakerPartNumberId order by updateid desc)rn from MakerPrices 
)
select mp.* from MakerParts mp cross apply
    (select price from LatestMakerPrices lmp where lmp.MakerPartNumberId=mp.Id) as p
where mp.MakerId = 1 and p.Price = 0

有问题的查询与我的答案之间的执行计划差异:

【讨论】:

很好,第二个查询立即运行!不幸的是,按日期排序是强制性的,因为用户可以上传“旧”CSV(他们实际上是在 UI 中选择日期)。无论如何使用您的第二个查询 ordering desc by date? 请让我先试试。 当然!我需要睡一会儿,我的头快要死了。我明天再来检查;没必要着急。非常感谢。 请根据巴基斯坦时间明天下午 3:00 与我讨论,我将根据您的需要编写算法,这将为您提供更好的性能@Guilherme【参考方案2】:

尝试:

WITH tab AS (
    SELECT *, NULL as Price FROM MakerParts
    WHERE not exists ( 
        SELECT Id
        FROM MakerPrices
        WHERE MakerPrices.MakerPartNumberId = MakerParts.Id 
    )  
)

SELECT * from tab WHERE MakerId = 2
UNION ALL
SELECT a.* , Price
FROM [dbo].[MakerParts] a
    LEFT JOIN [dbo].[MakerPrices] b
    ON b.MakerPartNumberId = a.Id
WHERE MakerId = 2 AND Price = 0

【讨论】:

谢谢,但这超出了预期的两行。在额外的价格中,最近的价格> 0。【参考方案3】:

试试你的查询:

select mp.* from MakerParts mp cross apply
    (select top 1 Price from MakerPrices inner join 
        MakerPricesUpdates on MakerPricesUpdates.Id = MakerPrices.UpdateId where 
        MakerPrices.MakerPartNumberId = mp.Id order by Date desc) as p
where mp.MakerId = 1 and p.Price = 0

创建以下索引后:

CREATE NONCLUSTERED INDEX [NCIdx_MakerPrices_MakerPartNumberId_UpdateId] ON [dbo].[MakerPrices]
(
    [MakerPartNumberId] ASC,
    [UpdateId] ASC
)
INCLUDE([Price]) 

并制作ID 列的MakerPricesUpdates 表主键。

【讨论】:

谢谢!不幸的是,现在我得到了价格 = 0 的“任何”部分。结果部分的最新价格 > 0,之所以选择它们是因为过去其中一个价格是零。还有什么想法吗? 请分享一些示例数据以便更好地理解。 制作 MakerPricesUpdates 表的 ID 列主键(如果没有)并在问题中创建索引并尝试执行您的查询。

以上是关于使用交叉应用子查询优化 sql 查询的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何使用交叉连接优化 SQL 查询

使用子查询优化 SQL 查询

查询优化,(子查询)(sql-transact)

如何使用 WHERE IN 子查询优化 SQL 查询

MySQL5.7性能优化系列——SQL语句优化——使用物化策略优化子查询

优化 SQL 子查询进行统计