SQL Server将前两行连接为两列

时间:2015-01-17 08:37:55

标签: sql-server subquery

我有两张桌子:

User

Id, Name, Email,...

Images

UserId, Priority, Path,...

我想创建一个用户视图,其中包含每个具有最高优先级的用户的两个图像,结果如下:

User.Id, User.Name, User.Email, Image1, Image2
-------------------------------------------------

我想,如果用户没有图片,则Image1Image2列都为NULL,如果只有一张图片,那么Image1将会{}设置为Image2 NULL

如何最有效地实现这一目标?我的意思是数据库性能。

1 个答案:

答案 0 :(得分:2)

将这些示例模式/数据用作模型:

CREATE TABLE #User (Id INT, Name VARCHAR(MAX), Email VARCHAR(MAX))
CREATE TABLE #Images (UserId INT, [Priority] INT, [Path] VARCHAR(MAX))
INSERT INTO #User VALUES
(1, 'Bob', 'bob@foo.com'),
(2, 'Jim', 'jim@foo.com')

INSERT INTO #Images VALUES
(1, 10, 'path1'),
(1, 5, 'path2'),
(1, 7, 'path3')

您可以使用PIVOT

SELECT UserId, [1] AS PathOfImage1, [2] AS PathOfImage2
FROM 
(SELECT UserId, [Path], 
        ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY [Priority] DESC) AS rn
 FROM #Images ) AS source
 PIVOT 
 (
   MAX([Path])
   FOR rn IN ([1], [2])
 ) AS pvt

在单个记录中为每个用户获取 top 两个图像:

UserId  PathOfImage1    PathOfImage2
------------------------------------
1       path1           path3

请注意,Id=2的用户未出现在上述结果集中,因为他没有任何相关图片。

现在,您可以对上述查询生成的表格表达式执行OUTER APPLY

SELECT u.Id, u.Name, Images.PathOfImage1, Images.PathOfImage2
FROM #User u
OUTER APPLY (
   SELECT UserId, [1] AS PathOfImage1, [2] AS PathOfImage2
   FROM (
      SELECT UserId, [Path], 
             ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY [Priority] DESC) AS rn
      FROM #Images ) AS source
   PIVOT 
   (
      MAX([Path])
      FOR rn IN ([1], [2])
   ) AS pvt
   WHERE UserId = u.Id
) Images

以获得所需的输出:

Id  Name    PathOfImage1    PathOfImage2
----------------------------------------
1   Bob     path1           path3
2   Jim     NULL            NULL

修改

如果应从Image表中提取多列,则必须用旧式条件聚合替换PIVOT,因为SQL Server不支持PIVOT上的多个聚合:

SELECT t.UserId, 
       [PathOfImage1] = MAX(CASE WHEN rn = 1 THEN [Path] ELSE NULL END),
       [PathOfImage2] = MAX(CASE WHEN rn = 2 THEN [Path] ELSE NULL END),
       [PriorityOfImage1] = MAX(CASE WHEN rn = 1 THEN [Priority] ELSE NULL END),
       [PriorityOfImage2] = MAX(CASE WHEN rn = 2 THEN [Priority] ELSE NULL END)
FROM (
  SELECT UserId, [Path], [Priority],
         ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY [Priority] DESC) AS rn
  FROM #Images ) t
GROUP BY t.UserId