如何在 WHERE 子句中使用 MAX 重写查询

All*_*len 5 sql-server sql-server-2008-r2

maxwhere子句中有一个带有 a 的查询,这很慢。

select count(*)
from   TableName tbl1
where  tbl1.id = (
                select max(tbl2.id) 
                from TableName tbl2
                where tbl2.companyId = tbl1.companyId)
and    tbl1.active = 2
Run Code Online (Sandbox Code Playgroud)

请注意,两个表是相同的。

我把它改写为:

select count(*) 
from   TableName tbl1 
inner join (
                select max(id) as id, companyId 
                from TableName tbl2 
                where tbl2.active= 2
                group by companyId
        ) tbl2 
        on  tbl2.companyId = tbl1.companyId 
        and tbl2.id=tbl1.id
Run Code Online (Sandbox Code Playgroud)

但记录数不同。

谁能说我如何重写它以获得更好的性能?

Pau*_*ite 7

概括

添加索引:

CREATE INDEX index_name 
ON TableName 
    (companyid DESC, id DESC) 
INCLUDE 
    (active);
Run Code Online (Sandbox Code Playgroud)

试试这个重写:

SELECT COUNT_BIG(*) 
FROM
(
    SELECT TOP (9223372036854775807) 
        TN1.active
    FROM TableName AS TN1
    WHERE
        TN1.id =
        (
            SELECT MAX(TN2.id) 
            FROM TableName AS TN2
            WHERE
                TN2.companyid = TN1.companyid
        )
) AS SQ1
WHERE
    SQ1.active = 2;
Run Code Online (Sandbox Code Playgroud)

细节

原始查询规范似乎是:

表中有多少行active = 2;所述ID列等于最高ID值在所有的行相同的内companyid(无活性= 2限制,为了清楚)。

有一种方法可以通过对合适索引的单次扫描来获得这些结果。

例如,给定表:

CREATE TABLE #TableName
(
    companyid integer NOT NULL,
    id integer NOT NULL,
    active integer NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

几行样本数据:

INSERT #TableName
    (companyid, id, active)
VALUES
    (1, 1, 2),
    (1, 2, 2),
    (1, 3, 0),
    (2, 2, 2),
    (2, 3, 2),
    (2, 3, 0);
Run Code Online (Sandbox Code Playgroud)

随着索引:

CREATE INDEX index_name 
ON #TableName 
    (companyid DESC, id DESC) 
INCLUDE 
    (active);
Run Code Online (Sandbox Code Playgroud)

查找没有谓词(active = 2)的行的查询(基于原始是:

SELECT
    TN1.active
FROM #TableName AS TN1
WHERE
    TN1.id =
    (
        SELECT MAX(TN2.id) 
        FROM #TableName AS TN2
        WHERE
            TN2.companyid = TN1.companyid
    );
Run Code Online (Sandbox Code Playgroud)

尽管查询两次引用同一个表,但该查询的执行计划具有对索引的单次扫描

细分市场计划

基本思想是按键顺序扫描索引,返回每组companyid值中具有最高id的行。

不幸的是,我们不能简单地将谓词active = 2添加到该查询而不破坏为我们提供单次扫描的优化器转换。问题(如果您感兴趣的话)是新谓词被向下推到计划树,导致形状不再与“按顶部分组”变换匹配。

一种解决方法TOP是在我们想要转换的查询部分周围添加一个无意义的规范,防止新谓词被推送过去:

SELECT COUNT_BIG(*) 
FROM
(
    SELECT TOP (9223372036854775807) 
        TN1.active
    FROM #TableName AS TN1
    WHERE
        TN1.id =
        (
            SELECT MAX(TN2.id) 
            FROM #TableName AS TN2
            WHERE
                TN2.companyid = TN1.companyid
        )
) AS SQ1
WHERE
    SQ1.active = 2;
Run Code Online (Sandbox Code Playgroud)

执行计划与之前类似,有一个额外的 Top(我们添加的无意义的 bigint.max ),一个用于active = 2谓词的过滤器,以及一个用于计算行数的最终 Stream Aggregate:

最终计划

我不能保证你会用真实数据得到这个计划,或者它会更快。这完全取决于行的数量和分布。

新的 Top 还具有不赞成并行计划的效果(group by top 是并行兼容的,但全局 top 不是;并行必须停止并围绕它重新启动)。

然而,在出现更好的选择或提供更多细节之前,您可以尝试一下。


Vla*_*nov 1

如果我正确理解您的查询,以下查询应该产生相同的结果,但如果您在 上创建索引,它可能会更有效(companyId, id)。列应按此顺序包含在索引中。

select count(*)
from TableName tbl1
where  
    tbl1.id = 
    (
        select TOP(1) tbl2.id
        from TableName tbl2
        where tbl2.companyId = tbl1.companyId
        order by tbl2.id DESC
    )
    and tbl1.active = 2
;
Run Code Online (Sandbox Code Playgroud)