如何将子查询转换为联接以获得快速结果?

时间:2019-01-02 09:07:28

标签: c# sql-server join inner-join database-performance

我想将子查询转换为联接以提高性能。

以下子查询需要很长时间才能加载。

SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
     (SELECT TOP 1 b.Level 
      from Microframe.dbo.TrackMessages b 
      where b.IMEI = a.IMEI 
            AND b.Timestamp >= @Start 
      order by b.Timestamp ) AS Level,
    (select top 1 b.Timestamp 
     from Microframe.dbo.TrackMessages b 
     where b.IMEI = a.IMEI 
           AND b.Timestamp >= @Start 
     order by b.Timestamp ) AS TimeStamp,
    (SELECT top 1 b.Temp 
     from Microframe.dbo.TrackMessages b 
     where b.IMEI = a.IMEI 
           AND b.Timestamp >= @Start 
     order by b.Timestamp ) AS Temp 
FROM GatexServerDB.dbo.device as a
JOIN GatexReportsDB.dbo.tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})

4 个答案:

答案 0 :(得分:1)

您可以将子查询移至FROM子句并使用CROSS APPLY。由于您似乎正在处理IoT数据,因此您应该研究T-SQL的排名,开窗和分析功能。性能将在很大程度上取决于表的索引。

给出这些表:

create table #TrackMessages (
    Message_ID bigint primary key,
    imei nvarchar(50) ,
    [timestamp] datetime2,
    Level int,
    temp numeric(5,2)
);

create table #device (
    imei nvarchar(50) primary key,
    owner_id int        
);


create table #tbl_static_tank_info (
    tank_id int not null primary key,
    tank_name nvarchar(20),
    fuel_type nvarchar(20),
    capacity numeric(9,2),
    owner_id int,
    client_id int
 )

和索引:

create nonclustered index IX_MSG_IMEI_Time on #TrackMessages (imei,timestamp) include(level,temp)       ;
create INDEX IX_Device_OwnerID on #device (Owner_ID)
create INDEX IX_Tank_Client on #tbl_static_tank_info (Client_ID);
create INDEX IX_Tank_Owner  on #tbl_static_tank_info (Owner_ID);

TOP 1查询看起来像这样:

SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
     Level,
    TimeStamp,
    Temp 
FROM #device as a
inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
cross apply (SELECT top 1 imei,Temp,Level,timestamp 
            from #TrackMessages b 
            where b.IMEI = a.imei
           AND b.Timestamp >= @start 
     order by b.Timestamp ) msg 
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)

如果储罐,设备和消息之间存在1-M关系,则可以使用FIRST_VALUE分析函数来返回第一个记录ber设备,而无需使用子查询:

SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
        first_value(Temp) over (partition by b.imei order by timestamp) as temp,
        first_value(Level) over (partition by b.imei order by timestamp) as level,
        min(timestamp)  over (partition by b.imei) as timestamp
from #TrackMessages b 
    inner join #device as a on b.IMEI = a.imei
    inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)

性能将在很大程度上取决于索引,表统计信息以及索引和OVER顺序是否匹配。

可以使用LAST_VALUE修改此查询以返回每个设备的第一个和最后一个值:

SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
        first_value(Temp) over (partition by b.imei order by timestamp) as StartTemp,
        first_value(Level) over (partition by b.imei order by timestamp) as StartLevel,
        min(timestamp)  over (partition by b.imei) as StartTime,
        last_value(Temp) over (partition by b.imei order by timestamp) as EndTemp,
        lastt_value(Level) over (partition by b.imei order by timestamp) as EndLevel,
        max(timestamp)  over (partition by b.imei) as EndTime   
from #TrackMessages b 
    inner join #device as a on b.IMEI = a.imei
    inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)

服务器必须按照升序(按IX_MSG_IMEI_Time索引已执行的顺序)和降序对测量进行排序。

答案 1 :(得分:0)

这是CROSS APPLY的解决方案,就像一个函数,您可以随时声明 并将其用作连接子句。如果返回集可能不存在,则可以将CROSS APPLY更改为OUTER APPLY,在这种情况下,TrackMessages上可能没有特定IMEI的记录(返回{ {1}}个值。

NULL

但是我相信这里的关键是表上的索引。如果您已经确定问题是子查询,请确保SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id, T.Level, T.Timestamp, T.Temp FROM GatexServerDB.dbo.device as a JOIN GatexReportsDB.dbo.tbl_static_tank_info as c ON c.tank_id = a.owner_id CROSS APPLY ( SELECT TOP 1 -- Retrieve only the first record -- And return as many columns as you need b.Level, b.Timestamp, b.Temp FROM Microframe.dbo.TrackMessages AS b WHERE a.IMEI = b.IMEI AND -- With matching IMEI b.Timestamp >= @Start ORDER BY b.Timestamp) T -- Ordered by Timestamp WHERE c.client_id = 65 AND a.IMEI IS NOT NULL AND c.tank_id IN ({Tanks}) 具有以下索引:

TrackMessages

索引具有优缺点,请确保在创建或删除索引之前先进行检查。

答案 2 :(得分:0)

没有结构,我对解决方案的猜测是:

CREATE NONCLUSTERED INDEX NCI_TrackMessages_IMEI_TimeStamp ON Microframe.dbo.TrackMessages (IMEI, Timestamp)

我无法测试它,但它应该非常接近解决方案。请确认是否可行。

答案 3 :(得分:0)

您可以比较以下两种解决方案之一

通过行号窗口功能完成排序的JOIN方式

SELECT * FROM 
(
    SELECT 
        c.tank_name, 
        c.fuel_type, 
        c.capacity, 
        c.tank_id,
        Level=b.Level,
        TimeStamp=b.Timestamp,
        Temp=b.Temp,
        r=Row_number() over ( order by b.timestamp)
    FROM GatexServerDB.dbo.device as a
        JOIN GatexReportsDB.dbo.tbl_static_tank_info as c 
            ON c.tank_id = a.owner_id
        JOIN Microframe.dbo.TrackMessages as b 
            ON b.IMEI = a.IMEI AND b.Timestamp >= @Start 
    WHERE c.client_id = 65
    AND a.IMEI IS NOT NULL
    AND c.tank_id IN ({Tanks})
)T
where r=1

或如下所示的“交叉申请”方式

SELECT * FROM 
(
    SELECT 
        c.tank_name, c.fuel_type, c.capacity, c.tank_id
    FROM GatexServerDB.dbo.device as a
        JOIN GatexReportsDB.dbo.tbl_static_tank_info as c 
            ON c.tank_id = a.owner_id
            AND c.client_id = 65
            AND a.IMEI IS NOT NULL
            AND c.tank_id IN ({Tanks})
) A
CROSS APPLY 
(
    SELECT 
        TOP 1 
        b.Level, b.Timestamp,b.Temp 
    FROM Microframe.dbo.TrackMessages b
    WHERE b.IMEI = a.IMEI 
        AND b.Timestamp >= @Start 
    ORDER BY b.Timestamp 
)D