获取组ID而无需排序的有效方法

时间:2014-06-19 20:48:20

标签: sql sql-server tsql row-number dense-rank

想象一下,我有一个非规范化的表:

CREATE TABLE Persons
(
    Id           int identity primary key,
    FirstName    nvarchar(100),
    CountryName  nvarchar(100)
)

INSERT INTO Persons
VALUES ('Mark',    'Germany'),
       ('Chris',   'France'),
       ('Grace',   'Italy'),
       ('Antonio', 'Italy'),
       ('Francis', 'France'),
       ('Amanda',  'Italy');

我需要构建一个返回每个人姓名的查询,以及他们所在国家/地区的唯一ID。 ID不一定必须是连续的;更重要的是,他们必须以任何顺序。实现这一目标的最有效方法是什么?

最简单的解决方案似乎是DENSE_RANK

SELECT FirstName, 
       CountryName, 
       DENSE_RANK() OVER (ORDER BY CountryName) AS CountryId
FROM Persons

-- FirstName  CountryName  CountryId
-- Chris      France       1
-- Francis    France       1
-- Mark       Germany      2
-- Amanda     Italy        3
-- Grace      Italy        3
-- Antonio    Italy        3

但是,这会在我的CountryName列上产生一种排序,这是一种浪费的性能损失。我提出了这个替代方案,它使用ROW_NUMBER和众所周知的技巧来抑制其排序:

SELECT P.FirstName, 
       P.CountryName,
       C.CountryId
FROM Persons P
    JOIN (
        SELECT CountryName, 
               ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS CountryId
        FROM Persons
        GROUP BY CountryName
    ) C
    ON C.CountryName = P.CountryName

-- FirstName  CountryName  CountryId
-- Mark       Germany      2
-- Chris      France       1
-- Grace      Italy        3
-- Antonio    Italy        3
-- Francis    France       1
-- Amanda     Italy        3

我是否正确假设第二个查询在一般情况下表现更好(不仅仅是在我设计的数据集上)?有哪些因素可能会有所不同(例如CountryName上的索引)?是否有更优雅的表达方式?

3 个答案:

答案 0 :(得分:5)

为什么你认为聚合会比窗口函数便宜?我问,因为我对这两方面都有一些经验,并且对此事没有强烈的意见。如果按下,我猜测窗口函数更快,因为它不必聚合所有数据,然后将结果重新加入。

这两个查询将具有非常不同的执行路径。看哪个表现更好的正确方法是尝试一下。对环境中足够大的数据样本运行两个查询。

顺便说一句,我认为没有正确的答案,因为表现取决于几个因素:

  • 哪些列被编入索引?
  • 数据有多大?它适合记忆吗?
  • 有多少个不同的国家?

如果您担心性能问题,并且只想要一个唯一的号码,则可以考虑使用checksum()。这确实存在碰撞风险。对于200个左右的国家来说,这种风险非常非常小。另外,如果确实发生了,您可以对其进行测试并对其进行一些处理。查询将是:

SELECT FirstName, CountryName, CheckSum(CountryName) AS CountryId
FROM Persons;

答案 1 :(得分:1)

你的第二个查询很可能会避免排序,因为它会使用哈希匹配聚合来构建内部查询,然后使用哈希匹配连接将ID映射到实际记录。

这确实没有排序,但必须扫描原始表两次。

  

我是否认为第二个查询在一般情况下表现更好(不仅仅是在我设计的数据集上)?

不一定。如果您在CountryName上创建了聚簇索引,则排序将不是问题,一切都将在一次通过中完成。

  

有更优雅的表达方式吗?

A"正确"计划将一次进行散列和散列查找。

每条记录在读取时都必须与哈希表匹配。在匹配时,将返回存储的ID;如果未命中,新国家/地区将被添加到哈希表中,并使用新ID分配,并且将返回新分配的ID。

但我无法想出让SQL Server在单个查询中使用此类计划的方法。

<强>更新

如果您有很多记录,很少有国家/地区,最重要的是CountryName上的非聚集索引,您可以模拟松散扫描以构建国家/地区列表:

DECLARE  @country TABLE
         (
         id INT NOT NULL IDENTITY PRIMARY KEY,
         countryName VARCHAR(MAX)
         )
;

WITH    country AS
        (
        SELECT  TOP 1
                countryName
        FROM    persons
        ORDER BY
                countryName
        UNION ALL
        SELECT  (
                SELECT  countryName
                FROM    (
                        SELECT  countryName,
                                ROW_NUMBER() OVER (ORDER BY countryName) rn
                        FROM    persons
                        WHERE   countryName > country.countryName
                        ) q
                WHERE   rn = 1
                )
        FROM    country
        WHERE   countryName IS NOT NULL
        )
INSERT
INTO    @country (countryName)
SELECT  countryName
FROM    country
WHERE   countryName IS NOT NULL
OPTION  (MAXRECURSION 0)

SELECT  p.firstName, c.id
FROM    persons p
JOIN    @country c
ON      c.countryName = p.countryName

答案 2 :(得分:-1)

group by use也在后台排序运算符(group基于'sort and compare',就像C#中的Icomparable一样)