I'm creating a view to serve as a report that displays row data from various tables as columns, in some cases involving concatenation. My query works excellent when I use a temporary table, but it needs to be a view, and I'm wrestling with converting the temporary table approach to subqueries.
I've created a couple sample tables and data to simplify this question as much as possible. Here's the schema:
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)
Here's the initial step:
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
Here's the grouped select which converts it to my report view:
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
It works fantastic except I have trouble converting it to a view because of the intermediate aggregate function AVG(PartPercentComplete)
, and the concatenation trick with STUFF
. My report requires several of each of these.
I know I can't nest AVG()
within MAX()
as that gives Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
(FYI I'm aware of PIVOT
but it'd need to be over multiple columns, though I realize that's possible too, CASE
seems easier and I'm told faster.)
My totally incorrect and poor attempt:
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
Obviously #Temp
is invalid in both locations. I'm not a SQL expert and I'm hoping there is a clean and relatively simple way to do this, maybe even avoiding the self-join.
Edit
I added in
MAX(PartId) AS [LatestPartId]
and
MAX(CASE [PartId] WHEN [LatestPartId] THEN [PartName] ELSE NULL END) AS [Latest Part]
as it's a better example of why I need an intermediate GROUP BY
.