想象一下,我有一个非规范化的表:
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
上的索引)?是否有更优雅的表达方式?
答案 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一样)