14

我正在尝试为每个用户选择一行。我不在乎我得到哪个图像。此查询在 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
4

6 回答 6

17

迄今为止发布的解决方案使用MIN/MAX聚合或ROW_NUMBER可能不是最有效的(取决于数据分布),因为它们通常必须在为每个组选择一个之前检查所有匹配的行。

使用AdventureWorks 示例数据库来说明,以下查询都为每个从 Transaction History 表中选择一个TransactionType和:ReferenceOrderIDProductID

使用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;

前1名

CROSS APPLY_TOP (1)

前面的查询需要连接并NULL为没有交易历史的产品返回 a。使用CROSS APPLYwithTOP解决了这两个问题:

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可能是最有效的。

于 2013-03-10T02:48:29.267 回答
4

如果用户有多个图像,而您只想要一张图像,您想要哪一张?虽然 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;

你也可以MAXMIN. 并且根据 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,并使用别名,这样您就不必在整个查询中重复完整的表名......

于 2013-03-09T19:28:46.493 回答
3

你需要一个聚合函数。正确的聚合函数取决于应用程序。这意味着你是唯一能说出来的人。一个原始的黑客攻击它:

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

于 2013-03-09T19:27:24.420 回答
2

根据需要使用 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
于 2013-03-09T19:28:15.940 回答
1

如果多个图像可供一个用户使用,这将选择第一个(按字母顺序)条目

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
于 2013-03-09T19:26:40.257 回答
1

使用时,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
于 2013-03-09T19:26:45.517 回答