我正在尝试为每个用户选择一行。我不在乎我得到哪个图像。此查询在 MySQL 中有效,但在 SQL Server 中无效:
SELECT user.id, (images.path + images.name) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
我正在尝试为每个用户选择一行。我不在乎我得到哪个图像。此查询在 MySQL 中有效,但在 SQL Server 中无效:
SELECT user.id, (images.path + images.name) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
迄今为止发布的解决方案使用MIN/MAX
聚合或ROW_NUMBER
可能不是最有效的(取决于数据分布),因为它们通常必须在为每个组选择一个之前检查所有匹配的行。
使用AdventureWorks 示例数据库来说明,以下查询都为每个从 Transaction History 表中选择一个TransactionType
和:ReferenceOrderID
ProductID
MIN
/MAX
聚合SELECT
p.ProductID,
MIN(th.TransactionType + STR(th.ReferenceOrderID, 11))
FROM Production.Product AS p
INNER JOIN Production.TransactionHistory AS th ON
th.ProductID = p.ProductID
GROUP BY
p.ProductID;
ROW_NUMBER
WITH x AS
(
SELECT
th.ProductID,
th.TransactionType,
th.ReferenceOrderID,
rn = ROW_NUMBER() OVER (PARTITION BY th.ProductID ORDER BY (SELECT NULL))
FROM Production.TransactionHistory AS th
)
SELECT
p.ProductID,
x.TransactionType,
x.ReferenceOrderID
FROM Production.Product AS p
INNER JOIN x ON x.ProductID = p.ProductID
WHERE
x.rn = 1
OPTION (MAXDOP 1);
ANY
聚合SELECT
q.ProductID,
q.TransactionType,
q.ReferenceOrderID
FROM
(
SELECT
p.ProductID,
th.TransactionType,
th.ReferenceOrderID,
rn = ROW_NUMBER() OVER (
PARTITION BY p.ProductID
ORDER BY p.ProductID)
FROM Production.Product AS p
JOIN Production.TransactionHistory AS th ON p.ProductID = th.ProductID
) AS q
WHERE
q.rn = 1;
有关ANY
聚合的详细信息,请参阅此博客文章。
TOP
SELECT p.ProductID,
(
-- No ORDER BY, so could be any row
SELECT TOP (1)
th.TransactionType + STR( th.ReferenceOrderID, 11)
FROM Production.TransactionHistory AS th WITH (FORCESEEK)
WHERE
th.ProductID = p.ProductID
)
FROM Production.Product AS p;
CROSS APPLY
_TOP (1)
前面的查询需要连接并NULL
为没有交易历史的产品返回 a。使用CROSS APPLY
withTOP
解决了这两个问题:
SELECT
p.Name,
ca.TransactionType,
ca.ReferenceOrderID
FROM Production.Product AS p
CROSS APPLY
(
SELECT TOP (1)
th.TransactionType,
th.ReferenceOrderID
FROM Production.TransactionHistory AS th WITH (FORCESEEK)
WHERE
th.ProductID = p.ProductID
) AS ca;
使用最佳索引,如果每个用户通常有很多图像,这APPLY
可能是最有效的。
如果用户有多个图像,而您只想要一张图像,您想要哪一张?虽然 MySQL 的语法很松散,不会强迫你做出选择,只是给你任何旧的任意值,SQL Server 让你选择。一种方法是MIN
:
SELECT u.id, MIN(i.path + i.name) AS image_path
FROM dbo.users AS u
INNER JOIN dbo.images AS i
ON u.id = i.user_id
GROUP BY u.id;
你也可以MAX
用MIN
. 并且根据 SQL Server 的版本,以及实际上是否需要更多列,可能还有其他方法可以稍微更有效地执行此操作(避免一些排序/分组工作)。例如,如果您想要单独的路径和名称,这不会很好:
SELECT u.id, MIN(i.path), MIN(i.name)
FROM dbo.users AS u
INNER JOIN dbo.images AS i
ON u.id = i.user_id
GROUP BY u.id;
...因为理论上您可以从两个不同的行中获取路径和名称,而这个结果将不再有意义。因此,您可以这样做:
;WITH x AS
(
SELECT user_id, path, name, rn = ROW_NUMBER() OVER
(PARTITION BY user_id ORDER BY (SELECT NULL))
FROM dbo.images
)
SELECT u.id, x.path, x.name
FROM dbo.users AS u
INNER JOIN x
ON u.id = x.user_id
WHERE x.rn = 1;
在现有案例中使用此变体是否有意义在很大程度上取决于这两个表的索引方式,但您可以尝试这种方法并比较计划/性能:
;WITH x AS
(
SELECT user_id, path + name AS image_path, rn = ROW_NUMBER() OVER
(PARTITION BY user_id ORDER BY (SELECT NULL))
FROM dbo.images
)
SELECT u.id, x.image_path
FROM dbo.users AS u
INNER JOIN x
ON u.id = x.user_id
WHERE x.rn = 1;
(并尝试用SELECT NULL
窄索引中的前导列替换dbo.images
。)
PS 不要使用AS 'alias'
语法。该形式已被弃用,并使别名看起来像字符串文字。还要使用架构前缀 always,并使用别名,这样您就不必在整个查询中重复完整的表名......
你需要一个聚合函数。正确的聚合函数取决于应用程序。这意味着你是唯一能说出来的人。一个原始的黑客攻击它:
SELECT user.id, max((images.path + images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
MySQL 对 GROUP BY 子句的处理被广泛认为是BAD。
根据需要使用 Max 或 Min:
SELECT user.id, max(images.path + images.name) as image_path
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
如果多个图像可供一个用户使用,这将选择第一个(按字母顺序)条目
SELECT user.id, min(images.path + images.name) as image_path
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
使用时,GROUP BY
您只能使用您聚合的列和其他列的聚合函数。
这是实现此目的的一种方法:
SELECT user.id, (MAX(images.path) + MAX(images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
尽管您更有可能想要:
SELECT user.id, MAX(images.path + images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id