3

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

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)?有没有更优雅的表达方式?

4

3 回答 3

5

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

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

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

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

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

SELECT FirstName, CountryName, CheckSum(CountryName) AS CountryId
FROM Persons;
于 2014-06-19T20:55:22.893 回答
1

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

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

我是否正确假设第二个查询通常会更好地执行(不仅仅是在我设计的数据集上)?

不必要。如果您在 上创建了一个聚集索引CountryName,那么排序将不是问题,所有事情都将一次性完成。

有没有更优雅的表达方式?

一个“正确”的计划是一次性进行散列和散列查找。

读取的每条记录都必须与哈希表匹配。在匹配时,将返回存储的 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
于 2014-06-19T21:02:27.770 回答
-1

group by use 也在后台排序运算符(组基于“排序和比较”,如 C# 中的 Icomparable)

于 2014-06-19T21:03:30.453 回答