【问题标题】:I want optimized query in SQL Server 2012我想在 SQL Server 2012 中优化查询
【发布时间】:2026-01-03 04:15:02
【问题描述】:
DECLARE @cid int=1
DECLARE @datee date='2016-01-25';

WITH dl AS (
    SELECT DISTINCT
            dl.imei,
            dl.vname,
            dl.driver,
            dl.mobileno
    FROM devices_list dl 
    WHERE dl.cid=@cid
),
gd AS (
    SELECT  b.imei,
            MAX(b.odometer) odometer,
            MIN(b.odometer) minod 
    FROM gpsdata b 
    WHERE FORMAT(b.packettime,'yyyy-MM-dd') = @datee
    GROUP BY b.imei
),
gd1 AS (    
    SELECT  b.imei,
            MAX(b.odometer) odometer,
            MIN(b.odometer) minod2
    FROM gpsdata b 
    WHERE FORMAT(b.packettime,'yyyy-MM-dd') = DATEADD(day,-1,@datee)
    GROUP BY b.imei
),
gd2 AS (
    SELECT  b.imei,
            MAX(b.odometer) odometer
    FROM gpsdata b 
    WHERE FORMAT(b.packettime,'yyyy-MM-dd') = DATEADD(day,-7,@datee)
    GROUP BY b.imei
),
gd3 AS (
    SELECT  b.imei,
            MIN(b.odometer) odometer
    FROM gpsdata b 
    WHERE FORMAT(b.packettime,'yyyy-MM')=FORMAT(@datee,'yyyy-MM') 
    GROUP BY b.imei
)

SELECT  dl.imei,
        dl.vname,
        dl.driver,
        dl.mobileno,
        gd.odometer,
        (gd.odometer-gd.minod) today,
        (gd1.odometer-gd1.minod2) yesterday,
        (gd.odometer-gd2.odometer) last7days,
        (gd.odometer-gd3.odometer) thismonth 
FROM dl 
LEFT JOIN gd ON dl.imei=gd.imei
LEFT JOIN gd1 ON dl.imei=gd1.imei
LEFT JOIN gd2 ON dl.imei=gd2.imei
LEFT JOIN gd3 ON dl.imei=gd3.imei;

上述查询运行良好,但执行时间过长。所以请建议我一个优化的查询。

【问题讨论】:

  • 请显示执行计划。还要量化你的信息。什么太长了,有多少数据等等。杀死 FORMAT 东西 - 它们的意思是“不使用索引”,因为它们是不可分割的。
  • 执行速度缓慢通常是由于缺少索引。您在需要的地方有索引吗?
  • @OlivierDeMeulder 即使他有索引,查询条件也不是 sargeable - 因此不会使用索引。
  • @TomTom 正在存储过程中执行此查询。我在 gpsdata 表中有大约 500 万条记录。
  • @OlivierDeMeulder 我有索引的 id 字段。

标签: sql-server sql-server-2012


【解决方案1】:

查看您的查询,您正在查询整个 gpsdata 表并使用 LEFT JOIN gd ON dl.imei=gd.imei 过滤 JOIN 上的结果。我认为您应该尝试减少从 gpsdata 上的查询处理的行数。

要利用这一点,您应该在 imei 字段上建立一个索引。

也许这对你有用:

DECLARE @cid int=1
DECLARE @datee date='2016-01-25';

   --Create a temp table to store the devices list
   SELECT DISTINCT
            dl.imei,
            dl.vname,
            dl.driver,
            dl.mobileno
    INTO #TMP
    FROM devices_list dl 
    WHERE dl.cid=@cid

WITH dl AS (
    --Load from the temp table
    SELECT DISTINCT
            dl.imei,
            dl.vname,
            dl.driver,
            dl.mobileno
    FROM #TMP
),
gd AS (
    SELECT  b.imei,
            MAX(b.odometer) odometer,
            MIN(b.odometer) minod 
    FROM gpsdata b 
    --Join to the temp table to reduce the number of rows processed
    INNER JOIN #TMP ON b.imei = #TMP.imei
    WHERE FORMAT(b.packettime,'yyyy-MM-dd') = @datee
    GROUP BY b.imei
),
. . . 

SELECT  dl.imei,
        dl.vname,
        dl.driver,
        dl.mobileno,
        gd.odometer,
        (gd.odometer-gd.minod) today,
        (gd1.odometer-gd1.minod2) yesterday,
        (gd.odometer-gd2.odometer) last7days,
        (gd.odometer-gd3.odometer) thismonth 
FROM dl 
LEFT JOIN gd ON dl.imei=gd.imei
LEFT JOIN gd1 ON dl.imei=gd1.imei
LEFT JOIN gd2 ON dl.imei=gd2.imei
LEFT JOIN gd3 ON dl.imei=gd3.imei;

【讨论】:

    【解决方案2】:

    这应该更适合你:

    DECLARE @cid int=1
    DECLARE @datee date='2016-01-25';
    
    WITH dl AS (
        SELECT DISTINCT
                dl.imei,
                dl.vname,
                dl.driver,
                dl.mobileno
        FROM devices_list dl 
        WHERE dl.cid=@cid
    ),
    gd AS (
        SELECT  b.imei,
                MAX(b.odometer) odometer,
                MIN(b.odometer) minod 
        FROM gpsdata b 
        WHERE FORMAT(b.packettime,'yyyy-MM-dd') = @datee
        GROUP BY b.imei
    ),
    gd1 AS (    
        SELECT  b.imei,
                MAX(b.odometer) odometer,
                MIN(b.odometer) minod2
        FROM gpsdata b 
        WHERE FORMAT(b.packettime,'yyyy-MM-dd') = DATEADD(day,-1,@datee)
        GROUP BY b.imei
    ),
    gd2 AS (
        SELECT  b.imei,
                MAX(b.odometer) odometer
        FROM gpsdata b 
        WHERE FORMAT(b.packettime,'yyyy-MM-dd') = DATEADD(day,-7,@datee)
        GROUP BY b.imei
    ),
    gd3 AS (
        SELECT  b.imei,
                MIN(b.odometer) odometer
        FROM gpsdata b 
        WHERE FORMAT(b.packettime,'yyyy-MM')=FORMAT(@datee,'yyyy-MM') 
        GROUP BY b.imei
    )
    
    SELECT  dl.imei,
            dl.vname,
            dl.driver,
            dl.mobileno,
            gd.odometer,
            (gd.odometer-gd.minod) today,
            (gd1.odometer-gd1.minod2) yesterday,
            (gd.odometer-gd2.odometer) last7days,
            (gd.odometer-gd3.odometer) thismonth 
    FROM dl 
    LEFT JOIN gd ON dl.imei=gd.imei
    LEFT JOIN gd1 ON dl.imei=gd1.imei
    LEFT JOIN gd2 ON dl.imei=gd2.imei
    LEFT JOIN gd3 ON dl.imei=gd3.imei;
    

    【讨论】:

    • 下次用干净的代码编辑问题。请删除这个,这不是答案。
    【解决方案3】:
     DECLARE @cid int=1
     DECLARE @datee date='2016-01-25';
    
     SELECT  dl.imei,
             dl.vname,
             dl.driver,
             dl.mobileno,
             gd.odometer,
             (gd.odometer-gd.minod) today,
             (gd1.odometer-gd1.minod2) yesterday,
             (gd.odometer-gd2.odometer) last7days,
             (gd.odometer-gd3.odometer) thismonth 
     FROM
     ( SELECT DISTINCT
                imei,
                vname,
                driver,
                mobileno
        FROM devices_list  
        WHERE cid=@cid ) as dl LEFT JOIN
     ( SELECT  imei,
               MAX(odometer) odometer,
               MIN(odometer) minod 
        FROM gpsdata  
        --WHERE cast(packettime as datetime) = @datee
        WHERE packettime  = @datee
        GROUP BY imei
     ) as gd 
     ON dl.imei=gd.imei  LEFT JOIN
     ( SELECT  imei,
                MAX(odometer) odometer,
                MIN(odometer) minod2
        FROM gpsdata  
        WHERE packettime = DATEADD(day,-1,@datee)
        GROUP BY imei
     ) as gd1 
     ON dl.imei=gd1.imei LEFT JOIN
     ( SELECT  imei,
               MAX(odometer) odometer
        FROM gpsdata  
        WHERE packettime = DATEADD(day,-7,@datee)
        GROUP BY imei
     ) as gd2 
     ON dl.imei=gd2.imei LEFT JOIN
     ( SELECT  imei,
               MIN(odometer) odometer
        FROM gpsdata 
        --WHERE FORMAT(packettime,'yyyy-MM')=FORMAT(@datee,'yyyy-MM') 
        WHERE YEAR(packettime)=YEAR(@datee) 
        AND MONTH(packettime)=MONTH(@datee) 
        GROUP BY imei
     ) as gd3
     ON dl.imei=gd3.imei 
    

    我假设 packettime 已经是一个 datetime 列。如果不是,则每次使用时都必须投射它。

    您不妨考虑创建一个包含里程表和imei的数据包时间索引

    【讨论】: