如何在视图中透视和连接数据?

时间:2012-09-24 02:09:40

标签: sql tsql pivot

我正在创建一个视图,用作报告,将各个表中的行数据显示为列,在某些情况下涉及连接。 当我使用临时表时,我的查询效果很好,但它需要是一个视图,我正在努力将临时表方法转换为子查询

我已经创建了几个示例表和数据,以尽可能简化这个问题。 这是架构:

CREATE TABLE [Car] (
    [CarId] [int] NOT NULL,
    [Name] [varchar](50) NOT NULL,
 CONSTRAINT [PK_Car] PRIMARY KEY CLUSTERED ([CarId] ASC))
 INSERT INTO [Car] VALUES
    (1, 'Honda')

CREATE TABLE [Part] (
    [PartId] [int] IDENTITY(1,1) NOT NULL,
    [CarId] [int] NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [PercentComplete] [decimal](3, 2) NOT NULL,
 CONSTRAINT [PK_Part] PRIMARY KEY CLUSTERED  ([PartId] ASC))
INSERT INTO [Part] VALUES
    (1, 'Engine', 0.5)
    ,(1, 'Transmission', 0.75)
    ,(1, 'Suspension', 0.3)

这是最初的步骤:

SELECT
    c.CarId
    ,c.Name AS [CarName]
    ,p.Name AS [PartName]
    ,p.PercentComplete AS [PartPercentComplete]
INTO #Temp
FROM Car c
JOIN Part p
ON p.CarId = c.CarId

Initial Setup

以下是将其转换为报告视图的分组选择:

SELECT
    MAX(CarName) AS [Car Name]

    ,STUFF((
        SELECT ', ' + [PartName] 
        FROM #Temp
        WHERE (CarId = t1.CarId) 
        FOR XML PATH (''))
    ,1,2,'') AS [Parts]

    ,MAX(CASE [PartCompleteAvg] WHEN 1.00 THEN 'Complete' ELSE 'Incomplete' END) AS [Part Status]
    ,MAX(CASE [PartId] WHEN [LatestPartId] THEN [PartName] ELSE NULL END) AS [Latest Part]
FROM (
    SELECT
        CarId
        ,AVG(PartPercentComplete) AS [PartCompleteAvg]
        ,MAX(PartId) AS [LatestPartId]
    FROM #Temp
    GROUP BY CarId
) t1
LEFT JOIN #Temp t2
ON t2.CarId = t1.CarId
GROUP BY t1.CarId

Report

除非我因为中间聚合函数AVG(PartPercentComplete)而无法将其转换为视图,并且使用STUFF连接技巧,否则它的效果非常好。我的报告要求其中的几个。

我知道我无法在AVG()中嵌套MAX()因为Cannot perform an aggregate function on an expression containing an aggregate or a subquery.

(仅供参考我知道PIVOT,但它需要超过multiple columns,虽然我也意识到这一点,CASE似乎更容易I'm told faster。 )

我完全错误和不良的尝试:

SELECT
    MAX(CarName) AS [Car Name]
    ,STUFF((
        SELECT ', ' + [PartName] 
        FROM #Temp
        WHERE (CarId = t1.CarId) 
        FOR XML PATH (''))
    ,1,2,'') AS [Parts]

    ,MAX(CASE [PartCompleteAvg] WHEN 1.00 THEN 'Complete' ELSE 'Incomplete' END) AS [Part Status]
FROM (
    SELECT
        CarId
        ,AVG(PartPercentComplete) AS [PartCompleteAvg]
    FROM (
        SELECT
            c.CarId
            ,c.Name AS [CarName]
            ,p.Name AS [PartName]
            ,p.PercentComplete AS [PartPercentComplete]
        FROM Car c
        JOIN Part p
        ON p.CarId = c.CarId
    ) t1
    GROUP BY CarId
) t1
LEFT JOIN #Temp t2
ON t2.CarId = t1.CarId
GROUP BY t1.CarId

显然#Temp在两个地方都无效。我不是SQL专家,我希望有一个干净,相对简单的方法,甚至可以避免自我加入。

修改

我加入了

MAX(PartId) AS [LatestPartId]

MAX(CASE [PartId] WHEN [LatestPartId] THEN [PartName] ELSE NULL END) AS [Latest Part]

因为它是我需要中间GROUP BY的一个更好的例子。

3 个答案:

答案 0 :(得分:2)

也许我在您的要求中遗漏了一些内容,但这对您不起作用(在SQL Server 2008中测试并使用它创建了一个视图):

select max(c.name) CarName,
  STUFF((
        SELECT ', ' + p.Name 
        from part p
        WHERE (p.CarId = c.CarId) 
        FOR XML PATH (''))
    ,1,2,'') AS [Parts],
   CASE AVG(p.PercentComplete) WHEN 1.00 THEN 'Complete' ELSE 'Incomplete' END AS [Part Status]
from car c
inner join part p
  on c.carid = p.carid
group by c.carid

请参阅SQL Fiddle with Demo

答案 1 :(得分:1)

以下是更新后问题的更新答案。

;WITH A(CarID,CarName,PartName,IsComplete,RN)AS(
SELECT C.CarID, C.Name, P.Name, case when P.PercentComplete=1 then 1 else 0 end,
    rn=row_number() over (partition by C.CarID order by P.PartId)
From Car C
LEFT JOIN Part P on P.CarId = C.CarId
)
,B(CarID,CarName,PartNames,LastPartName,IsComplete,RN)AS(
select CarID,CarName,CAST(PartName as VARCHAR(max)),PartName,IsComplete,RN
from A
Where rn=1
union all
select A.CarID,A.CarName,B.PartNames+', '+A.PartName,A.PartName,Case when A.IsComplete=1 and B.IsComplete=1 then 1 else 0 end,A.RN
from B
join A on A.CarID=B.CarID and A.RN-1=B.RN
),C AS(
select *, RNc=Row_number() over (partition by CarID order by RN desc)
from B
)
select CarID,CarName,PartNames,LastPartName,IsComplete
from C
where RNc=1

在此之下,原始答案。


CREATE TABLE [Car] (
    [CarId] [int] NOT NULL,
    [Name] [varchar](50) NOT NULL,
    CONSTRAINT [PK_Car] PRIMARY KEY CLUSTERED ([CarId] ASC));
INSERT INTO [Car] VALUES (1, 'Honda'),
                         (2, 'Ford');

CREATE TABLE [Part] (
    [PartId] [int] IDENTITY(1,1) NOT NULL,
    [CarId] [int] NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [PercentComplete] [decimal](3, 2) NOT NULL,
    CONSTRAINT [PK_Part] PRIMARY KEY CLUSTERED  ([PartId] ASC));
INSERT INTO [Part] VALUES (1, 'Engine', 0.5),
                          (1, 'Transmission', 0.75),
                          (1, 'Suspension', 0.3),
                          (2, 'Engine', 1),
                          (2, 'Brake', 1.0);

   SELECT C.CarID,
          C.Name CarName,
          STUFF((
              SELECT ', ' + Name
               FROM Part
              WHERE CarId = C.CarId 
                FOR XML PATH ('')),1,2,'') Parts,
          CASE WHEN Progress = 1 then 'Complete' else 'Incomplete' END [Part Status]
     From Car C
LEFT JOIN (
   select CarId, SUM(PercentComplete)/Count(1) Progress
     From Part
 Group by CarId) P on P.CarID = C.CarId
CarID       CarName  Parts                               Part Status
----------- -------- ----------------------------------- -----------
1           Honda    Engine, Transmission, Suspension    Incomplete
2           Ford     Engine, Brake                       Complete

答案 2 :(得分:1)

理查德让我走上正轨。这就是我要找的东西:

SELECT c.CarId
    ,c.Name AS [CarName]
    ,STUFF((
        SELECT ', ' + Name
        FROM Part p
        WHERE p.CarId = c.CarId
        FOR XML PATH ('')),1,2,'') AS [Parts]
    ,CASE WHEN Progress = 1 THEN 'Complete' ELSE 'Incomplete' END AS [Part Status]
    ,[Latest Part]
FROM Car c
LEFT JOIN
(
    SELECT p.CarId
    ,SUM(PercentComplete)/COUNT(1) AS [Progress]
    ,MAX(CASE WHEN PartId = LatestPartId THEN p.Name ELSE NULL END) AS [Latest Part]
    FROM Part p
    LEFT JOIN (
        SELECT CarId
        ,MAX(PartId) AS [LatestPartId]
        FROM Part
        GROUP BY CarId
    ) latestPart
    ON latestPart.CarId = p.CarId
    GROUP BY p.CarId
) partInfo
ON partInfo.CarId = c.CarId

关键是将GROUP BY从主选择中删除(更喜欢)并使用GROUP BY将我的聚合代码放入一系列JOIN中,在必要时嵌套连接以计算一些中间信息。

我觉得愚蠢,因为我没想过在SELECT中使用JOIN,我的大脑只想在FROM内使用它。现在我可以有效地使用旋转和连接!

这是SQL Fiddle