This is puzzling me no end, and I know it might be tough without the data but thought it might be a longshot to post here.
Here goes, the first code I received was this
USE [Radiotherapy]
GO
if exists (
select * from tempdb.dbo.sysobjects o
where o.xtype in ('U')
and o.id = object_id(N'tempdb..#MySampleTemp')
)
DROP TABLE #MySampleTemp;
if exists (
select * from tempdb.dbo.sysobjects o
where o.xtype in ('U')
and o.id = object_id(N'tempdb..#MyPivotTemp')
)
DROP TABLE #MyPivotTemp;
SELECT [AttendanceNumber]
,CASE WHEN AgeAtExamDate BETWEEN 0 AND 5 THEN '0-5'
WHEN AgeAtExamDate BETWEEN 6 AND 18 THEN '6-18'
WHEN AgeAtExamDate BETWEEN 19 AND 150 THEN '19+'
ELSE 'Error' END AS AgeRange
,[LocalPatientIdentifier]
,[ExaminationDate]
,[ExamExaminationCode] INTO #MySampleTemp
FROM [dbo].[tblRadiologyData]
WHERE AttendanceSiteCode IN('CNM','RNM')
--AND AttendanceStatus NOT IN ( 'Appt', 'Booked In', 'Cancelled', 'Pending' )
--AND AttendancePatientGroup = 'Out Patient'
--AND AttendancePatientCategory IN ( 'EU', 'Military', 'N.H.S.' )
--AND AttendanceSourceName <> 'PACs Support'
AND [ExaminationDate] >= '1 OCTOBER 2015' --
ORDER BY [AttendanceNumber], CASE WHEN AgeAtExamDate BETWEEN 0 AND 5 THEN '0-5'
WHEN AgeAtExamDate BETWEEN 6 AND 18 THEN '6-18'
WHEN AgeAtExamDate BETWEEN 19 AND 150 THEN '19+'
ELSE 'Error' END, [LocalPatientIdentifier], [ExaminationDate], ExamExaminationCode
SELECT [AttendanceNumber],AgeRange,[LocalPatientIdentifier],[ExaminationDate], 1 AS ExamCount,
[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14] INTO #MyPivotTemp
FROM
(
SELECT *,
row_number() OVER(PARTITION BY [AttendanceNumber]
ORDER BY [AttendanceNumber], [LocalPatientIdentifier]) rn
FROM #MySampleTemp
) AS st
pivot
(
MAX(ExamExaminationCode)
FOR rn in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14])
) AS pivottable
SELECT
[1] AS Exam01,
[2] AS Exam02,
[3] AS Exam03,
[4] AS Exam04,
[5] AS Exam05,
[6] AS Exam06,
[7] AS Exam07,
[8] AS Exam08,
[9] AS Exam09,
[10] AS Exam10,
[11] AS Exam11,
[12] AS Exam12,
[13] AS Exam13,
[14] AS Exam14,
COUNT(ExamCount) AS [No. Attendances]
FROM #MyPivotTemp
GROUP BY [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14]
ORDER BY [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14]
What I tried to do was replicate this in query as follows:
USE [Radiotherapy]
;With CTE AS (SELECT s.attendanceNumber,
MAX(CASE WHEN s.rnk = 1 THEN s.ExamExaminationCode END) as examCode1,
MAX(CASE WHEN s.rnk = 2 THEN s.ExamExaminationCode END) as examCode2,
MAX(CASE WHEN s.rnk = 3 THEN s.ExamExaminationCode END) as examCode3,
MAX(CASE WHEN s.rnk = 4 THEN s.ExamExaminationCode END) as examCode4,
MAX(CASE WHEN s.rnk = 5 THEN s.ExamExaminationCode END) as examCode5,
MAX(CASE WHEN s.rnk = 6 THEN s.ExamExaminationCode END) as examCode6,
MAX(CASE WHEN s.rnk = 7 THEN s.ExamExaminationCode END) as examCode7,
MAX(CASE WHEN s.rnk = 8 THEN s.ExamExaminationCode END) as examCode8,
MAX(CASE WHEN s.rnk = 9 THEN s.ExamExaminationCode END) as examCode9,
MAX(CASE WHEN s.rnk = 10 THEN s.ExamExaminationCode END) as examCode10,
MAX(CASE WHEN s.rnk = 11 THEN s.ExamExaminationCode END) as examCode11,
MAX(CASE WHEN s.rnk = 12 THEN s.ExamExaminationCode END) as examCode12,
MAX(CASE WHEN s.rnk = 13 THEN s.ExamExaminationCode END) as examCode13,
MAX(CASE WHEN s.rnk = 14 THEN s.ExamExaminationCode END) as examCode14
FROM (
SELECT [AttendanceNumber]
,[ExaminationDate]
,[ExamExaminationCode]
,ROW_NUMBER() OVER(PARTITION BY [AttendanceNumber]
ORDER BY [RadiologyID]) as rnk --Ordered by date ASC
FROM [Radiotherapy].[dbo].[tblRadiologyData] rd
where rd.ExaminationDate >= '01 october 2015'
and rd.AttendanceSiteCode IN('CNM','RNM') ) s
GROUP BY s.attendanceNumber)
Select CTE.examCode1,
CTE.examCode2,
CTE.examCode3,
CTE.examCode4,
CTE.examCode5,
CTE.examCode6,
CTE.examCode7,
CTE.examCode8,
CTE.examCode9,
CTE.examCode10,
CTE.examCode11,
CTE.examCode12,
CTE.examCode13,
CTE.examCode14,
COUNT(CTE.AttendanceNumber) as [No of occurances]
from CTE
GROUP by CTE.examCode1,
CTE.examCode2,
CTE.examCode3,
CTE.examCode4,
CTE.examCode5,
CTE.examCode6,
CTE.examCode7,
CTE.examCode8,
CTE.examCode9,
CTE.examCode10,
CTE.examCode11,
CTE.examCode12,
CTE.examCode13,
CTE.examCode14
ORDER BY CTE.examCode1
Great I thought until my code returned more results which puzzled me. Having broken it down I found the offending code from the original query:
ORDER BY [AttendanceNumber], CASE WHEN AgeAtExamDate BETWEEN 0 AND 5 THEN '0-5'
WHEN AgeAtExamDate BETWEEN 6 AND 18 THEN '6-18'
WHEN AgeAtExamDate BETWEEN 19 AND 150 THEN '19+'
ELSE 'Error' END, [LocalPatientIdentifier], [ExaminationDate], ExamExaminationCode
When I removed this from the first query the results matched, but my question is how and why is an ORDER BY affecting the output. I assumed this was just showing how the results are ordered? Understanding why code is doing what it's doing is something I really need to get my head around. Any advise more than welcome.
Probably it have something to do with this piece of code:
SELECT *,
row_number() OVER(PARTITION BY [AttendanceNumber]
ORDER BY [AttendanceNumber], [LocalPatientIdentifier]) rn
FROM #MySampleTemp
Obviously your ordering is not breaking a tie(it isn't deterministic) and it produces rank number differently when you have ordered data in temp table and when not ordered. Try to add some PK to your temp table so the above code will look like:
SELECT *,
row_number() OVER(PARTITION BY [AttendanceNumber]
ORDER BY [AttendanceNumber], [LocalPatientIdentifier], [SomePK]) rn
FROM #MySampleTemp
Then removing ordering from first select statement will not have an effect on the result set.
Related
Really struggling to understand the best way of doing this...
I have a table of data
**StudentID AssessmentCode ResultGroup Result**
46933 12ENG IBLevel HL
46933 12ENG Mark 6
46933 12ECO IBLevel HL
46933 12ECO Mark 5
46933 12GEO IBLevel SL
46933 12GEO Mark 6
46933 12LAN IBLevel HL
46933 12LAN Mark 4
46933 12PED IBLevel SL
46933 12PED Mark 5
46933 12SCI IBLevel SL
46933 12SCI Mark 3
67767 12FRE IBLevel HL
67767 12FRE Mark 4
67767 12MAT IBLevel SL
67767 12MAT Mark 5
and so on...
Unfortunately the result column holds 2 different bits of info. The level a student does (HL = High Level; SL = Standard Level) and then the result for that subject at that level. Note that for each student, 2 rows are generated per subject a student does, one row has the IBLevel and the next row has the result for that level.
How can I rearrange the data to get something like
StudentID HLResult SLResult TotalResult CountofHL CountofSL
46933 15 14 29 3 3
67767 13 10 23 4 2
So each student has one row of data with totals for HL, SL, both together and then a count of the number of HL and SL subjects. As I said, really not sure of the best way of going about this. In the end, I would also like to extend this to get some columns which, based on the results give some warning messages eg the TotalResult has to be great than 24 so I would love a column that simply returns whether this has been achieved or not...
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 13 10 23 4 2 False
Any help would be greatly appreciated...
Your table seems to have some serious normalization issues. You have to perform an INNER JOIN on AssessmentCode first:
SELECT t1.StudentID, t1.AssessmentCode,
t1.Result AS Level, CAST(t2.Result AS INT) AS Mark
FROM (
SELECT StudentID, AssessmentCode, Result
FROM mytable
WHERE ResultGroup = 'IBLevel' ) AS t1
INNER JOIN (
SELECT StudentID, AssessmentCode, Result
FROM mytable
WHERE ResultGroup = 'Mark' ) AS t2
ON t1.StudentID = t2.StudentID AND t1.AssessmentCode = t2.AssessmentCode
to get one row per AssessmentCode.
Output:
StudentID AssessmentCode Level Mark
-----------------------------------------
46933 12ENG HL 6
46933 12ECO HL 5
46933 12GEO SL 6
46933 12LAN HL 4
46933 12PED SL 5
46933 12SCI SL 3
67767 12FRE HL 4
67767 12MAT SL 5
You can now wrap the above query in a CTE and perform conditional aggregation to get required result:
;WITH CTE AS (
... above query here
)
SELECT StudentID,
SUM(CASE WHEN Level = 'HL' THEN Mark ELSE 0 END) AS HLResult,
SUM(CASE WHEN Level = 'SL' THEN Mark ELSE 0 END) AS SLResult,
SUM(Mark) AS TotalResult,
COUNT(CASE WHEN Level = 'HL' THEN 1 END) AS CountofHL,
COUNT(CASE WHEN Level = 'SL' THEN 1 END) AS CountofSL,
CASE WHEN SUM(Mark) > 24 THEN 'True'
ELSE 'False'
END AS MoreThan24
FROM CTE
GROUP BY StudentID
Output:
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 4 5 9 1 1 False
Demo here
My Version of Your Table
DECLARE #yourTable TABLE
(
StudentID INT,
AssessmentCode CHAR(5),
ResultGroup VARCHAR(10),
Result VARCHAR(5)
)
INSERT INTO #yourTable
VALUES (46933,'12ENG','IBLevel','HL'),
(46933,'12ENG','Mark','6'),
(46933,'12ECO','IBLevel','HL'),
(46933,'12ECO','Mark','5'),
(46933,'12GEO','IBLevel','SL'),
(46933,'12GEO','Mark','6'),
(46933,'12LAN','IBLevel','HL'),
(46933,'12LAN','Mark','4'),
(46933,'12PED','IBLevel','SL'),
(46933,'12PED','Mark','5'),
(46933,'12SCI','IBLevel','SL'),
(46933,'12SCI','Mark','3'),
(67767,'12FRE','IBLevel','HL'),
(67767,'12FRE','Mark','4'),
(67767,'12MAT','IBLevel','SL'),
(67767,'12MAT','Mark','5');
Actual Query
WITH CTE_ResultValue
AS
(
SELECT StudentID,
AssessmentCode,
ResultGroup,
CAST(Result AS INT) AS ResultValue
FROM #yourTable
WHERE ISNUMERIC(Result) = 1
),
CTE_IBLevel
AS
(
SELECT StudentID,
AssessmentCode,
Result AS IBLevel
FROM #yourTable
WHERE ISNUMERIC(Result) = 0
),
CTE_Normalized
AS
(
SELECT A.StudentID,
A.AssessmentCode,
A.ResultGroup,
A.ResultValue,
B.IBLevel
FROM CTE_ResultValue AS A
INNER JOIN CTE_IBLevel AS B
ON A.StudentID = B.StudentID
AND A.AssessmentCode = B.AssessmentCode
)
SELECT [StudentID],
[HLResult] = SUM(CASE WHEN IBLevel = 'HL' THEN ResultValue END),
[SLResult] = SUM(CASE WHEN IBLevel = 'SL' THEN ResultValue END),
[TotalResult] = SUM(ResultValue),
[CountOfHL] = SUM(CASE WHEN IBLevel = 'HL' THEN 1 END),
[CountOfSL] = SUM(CASE WHEN IBLevel = 'SL' THEN 1 END)
FROM CTE_Normalized
GROUP BY StudentID
Normalization
Your table is in serious need of normalization. If you can change it, bare minimal change would look like this:
CREATE TABLE dbo.NormalizedTable
(
StudentID INT,
AssessmentCode CHAR(5),
ResultGroup VARCHAR(25),
ResultValue SMALLINT, --smallint range should be plenty. It can store values from -32,768 to 32,767
IBLevel CHAR(2)
)
INSERT INTO dbo.NormalizedTable
SELECT *
FROM CTE_Normalized
If you can't change the way your data is structured, I'd recommend creating a view from my CTE_normalized
CREATE VIEW vw_normalizedTable
AS
WITH CTE_ResultValue
AS
(
SELECT StudentID,
AssessmentCode,
ResultGroup,
CAST(Result AS INT) AS ResultValue
FROM #yourTable
WHERE ISNUMERIC(Result) = 1
),
CTE_IBLevel
AS
(
SELECT StudentID,
AssessmentCode,
Result AS IBLevel
FROM #yourTable
WHERE ISNUMERIC(Result) = 0
),
SELECT A.StudentID,
A.AssessmentCode,
A.ResultGroup,
A.ResultValue,
B.IBLevel
FROM CTE_ResultValue AS A
INNER JOIN CTE_IBLevel AS B
ON A.StudentID = B.StudentID
AND A.AssessmentCode = B.AssessmentCode
GO
Version1:
SELECT t1.StudentID,
SUM(CASE WHEN t1.Result = 'HL' THEN t2.Result ELSE 0 END) HLResult,
SUM(CASE WHEN t1.Result = 'SL' THEN t2.Result ELSE 0 END) SLResult,
SUM(CAST(t2.Result AS INT)) TotalResult,
SUM(CASE WHEN t1.Result = 'HL' THEN 1 ELSE 0 END) CountofHL,
SUM(CASE WHEN t1.Result = 'SL' THEN 1 ELSE 0 END) CountofSL,
CASE WHEN SUM(CAST(t2.Result AS INT)) > 24 THEN 'True' ELSE 'False' END MoreThan24
FROM #t t1
JOIN #t t2 ON t1.StudentID = t2.StudentID AND
t1.AssessmentCode = t2.AssessmentCode AND
t1.ResultGroup = 'IBLevel' AND
t2.ResultGroup = 'Mark'
GROUP BY t1.StudentID
Version2:
WITH cte1 AS(
SELECT *, ROW_NUMBER() OVER(PARTITION BY StudentID, AssessmentCode
ORDER BY CASE WHEN ResultGroup = 'IBLevel' THEN 1 ELSE 2 END) AS rn FROM #t),
cte2 AS(SELECT StudentID,
AssessmentCode,
SUM(CASE WHEN Result = 'HL' THEN 1 ELSE 0 END) HL,
SUM(CASE WHEN Result = 'SL' THEN 1 ELSE 0 END) SL,
MAX(CASE WHEN rn = 2 THEN Result END) R
FROM cte1
GROUP BY StudentID, AssessmentCode)
SELECT StudentID,
SUM(hl*R) HLResult,
SUM(sl*R) SLResult,
SUM((hl + sl)*r) TotalResult,
SUM(hl) CountofHL,
SUM(sl) CountofSL,
CASE WHEN SUM((hl + sl)*r) > 24 THEN 'True' ELSE 'False' END MoreThan24
FROM cte2
GROUP BY StudentID
Output:
StudentID HLResult SLResult TotalResult CountofHL CountofSL MoreThan24
46933 15 14 29 3 3 True
67767 4 5 9 1 1 False
Just replace #t with your table.
I have a table that lists all transactions as follows:
ID Account Date Amount
---------------------------
1 2 02/01/2015 30
2 5 05/01/2015 25
3 2 05/01/2015 12
4 2 07/01/2015 42
5 5 10/012015 19
6 2 11/01/2015 58
7 3 15/01/2015 36
Would like to write a select statement that will list only the last 3 transactions of each account, as follows please.
Account Date1 Amount Date2 Amount Date3 Amount
---------------------------------------------------------------
2 11/01/2015 58 07/01/2015 42 05/01/2015 12
3 15/01/2015 36
5 10/01/2015 19 05/01/2015 25
Thank you for any advice
You can use the row_number() function in a derived table to partition the data by account, and give each date within the partition a number, and then do a conditional aggregation over the rows with the top 3 numbers, grouped by account:
select
account,
date1 = max(case when rn = 1 then date end),
amount = max(case when rn = 1 then amount end),
date2 = max(case when rn = 2 then date end),
amount = max(case when rn = 2 then amount end),
date3 = max(case when rn = 3 then date end),
amount = max(case when rn = 3 then amount end)
from (
select *, rn = row_number() over (partition by account order by date desc)
from your_table
) a
where rn <= 3
group by account
Sample SQL Fiddle
I have table called RUGS with the data below. How do I write a TSQl query to get the data as shown in Output. I am not familiar with unPIVOT
`cono ARtype days Revenue PPD
140 MCD 5 1000 500
140 MRA 6 2000 600
140 MRA 7 3000 700
141 MCD 1 5000 100
141 MRA 2 6000 200
141 MRA 3 7000 300`
Result
140 MCD 5 1000 500 MRA 6 2000 600 MRA 7 3000 700
141 MCD 1 5000 100 MRA 2 6000 200 MRA 3 7000 300
Given that every cono will have exactly 3 records (as stated in the comments), a cte with row_number can be used with case statements.
If any have less than three records, you will see blanks and zeroes in the results. Any with more than three will not have all records represented.
Here is an example with #RUGS as a table variable:
declare #RUGS table (cono int, ARType char(3), [days] int, Revenue int, PPD int)
insert into #RUGS VALUES
(140,'MCD',5,1000,500)
,(140,'MRA',6,2000,600)
,(140,'MRA',7,3000,700)
,(141,'MCD',1,5000,100)
,(141,'MRA',2,6000,200)
,(141,'MRA',3,7000,300);
with cte as
(
select row_number() over(partition by cono order by (select 1)) as rn, * from #RUGS
)
select cono,
max(case when rn = 1 then ARType else '' end) as ARType1,
max(case when rn = 1 then days else '' end) as days1,
max(case when rn = 1 then Revenue else '' end) as Revenue1,
max(case when rn = 1 then PPD else '' end) as PPD1,
max(case when rn = 2 then ARType else '' end) as ARType2,
max(case when rn = 2 then days else '' end) as days2,
max(case when rn = 2 then Revenue else '' end) as Revenue2,
max(case when rn = 2 then PPD else '' end) as PPD2,
max(case when rn = 3 then ARType else '' end) as ARType3,
max(case when rn = 3 then days else '' end) as days3,
max(case when rn = 3 then Revenue else '' end) as Revenue3,
max(case when rn = 3 then PPD else '' end) as PPD3
from cte group by cono
When unfiltered, dense_rank on FeedDeliveryNutrients.NutrientID over 150,000 rows is 3.5x faster than joining to Nutrients with row_number on Nutrients.ID and using the joined row number. When filtered to a specific flock, joining with row_number is 9x faster.
Is there any optimization technique that could get the best of both worlds in a single query?
Fastest when unfiltered (150,000 rows returned):
select
FeedDeliveries.FlockID,
FeedDeliveryID,
DeliveryLb,
Bin,
DeliveryDate,
FormulaID,
FeedEnergy,
Nutrient1, Nutrient2, Nutrient3, Nutrient4, Nutrient5, Nutrient6, Nutrient7, Nutrient8, Nutrient9, Nutrient10, Nutrient11, Nutrient12, Nutrient13, Nutrient14, Nutrient15
from (
select
FeedDeliveryID,
sum(case when dense_rank = 1 then Amount end) as Nutrient1,
sum(case when dense_rank = 2 then Amount end) as Nutrient2,
sum(case when dense_rank = 3 then Amount end) as Nutrient3,
sum(case when dense_rank = 4 then Amount end) as Nutrient4,
sum(case when dense_rank = 5 then Amount end) as Nutrient5,
sum(case when dense_rank = 6 then Amount end) as Nutrient6,
sum(case when dense_rank = 7 then Amount end) as Nutrient7,
sum(case when dense_rank = 8 then Amount end) as Nutrient8,
sum(case when dense_rank = 9 then Amount end) as Nutrient9,
sum(case when dense_rank = 10 then Amount end) as Nutrient10,
sum(case when dense_rank = 11 then Amount end) as Nutrient11,
sum(case when dense_rank = 12 then Amount end) as Nutrient12,
sum(case when dense_rank = 13 then Amount end) as Nutrient13,
sum(case when dense_rank = 14 then Amount end) as Nutrient14,
sum(case when dense_rank = 15 then Amount end) as Nutrient15
from (select *, dense_rank() over (partition by FeedDeliveryID order by NutrientID) as dense_rank from dbo.FeedDeliveryNutrients) n
group by FeedDeliveryID
) pvt
join dbo.FeedDeliveries on FeedDeliveries.ID = FeedDeliveryID
Fastest when filtered by dbo.FeedDeliveries.FlockID (~100 rows returned):
select
FeedDeliveries.FlockID,
FeedDeliveryID,
DeliveryLb,
Bin,
DeliveryDate,
FormulaID,
FeedEnergy,
Nutrient1, Nutrient2, Nutrient3, Nutrient4, Nutrient5, Nutrient6, Nutrient7, Nutrient8, Nutrient9, Nutrient10, Nutrient11, Nutrient12, Nutrient13, Nutrient14, Nutrient15
from (
select
FeedDeliveryID,
sum(case when n.row_number = 1 then Amount end) as Nutrient1,
sum(case when n.row_number = 2 then Amount end) as Nutrient2,
sum(case when n.row_number = 3 then Amount end) as Nutrient3,
sum(case when n.row_number = 4 then Amount end) as Nutrient4,
sum(case when n.row_number = 5 then Amount end) as Nutrient5,
sum(case when n.row_number = 6 then Amount end) as Nutrient6,
sum(case when n.row_number = 7 then Amount end) as Nutrient7,
sum(case when n.row_number = 8 then Amount end) as Nutrient8,
sum(case when n.row_number = 9 then Amount end) as Nutrient9,
sum(case when n.row_number = 10 then Amount end) as Nutrient10,
sum(case when n.row_number = 11 then Amount end) as Nutrient11,
sum(case when n.row_number = 12 then Amount end) as Nutrient12,
sum(case when n.row_number = 13 then Amount end) as Nutrient13,
sum(case when n.row_number = 14 then Amount end) as Nutrient14,
sum(case when n.row_number = 15 then Amount end) as Nutrient15
from dbo.FeedDeliveryNutrients
join (select *, row_number() over (order by ID) as row_number from dbo.Nutrients) n on n.ID = NutrientID
group by FeedDeliveryID
) pvt
join dbo.FeedDeliveries on FeedDeliveries.ID = FeedDeliveryID
You already got the answer. Optimize for the most critical scenario.
From start I can use optimize for the 150.000 rows scenario but you must sniff a bit on your actual production server. If that 150.000 only happens a few times /week and the 100 rows ill hit your server many times/minute that is the more critical for you.
I have a T-SQL query that I need to total out the counts for the CASE statements.
I tried to add a UNION but I am getting an error:
All queries in an SQL statement containing a UNION operator must have
an equal number of expressions in their target lists.
Any ideas? Thanks.
Query:
SELECT
CustomerID, Name, DueDate,
CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) >= 1
THEN PaymentAmount ELSE 0
END AS [Early],
CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) >=0
THEN PaymentAmount ELSE 0
END AS [On Time],
CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) = -1
THEN PaymentAmount ELSE 0
END AS [Late]
FROM
Customers
WHERE
DATEDIFF(MONTH, PaymentDate,DueDate), GetDate()) = 1
AND PaymentAmount= DuesAmount
UNION
SELECT
'-Total', '', CustomerID, Name, DueDate,
SUM(CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) >= 1
THEN PaymentAmount ELSE 0 END) AS [Early],
SUM(CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) >= 0
THEN PaymentAmount ELSE 0 END) AS [On Time],
SUM(CASE WHEN DATEDIFF(Month, PaymentDate, DueDate) = -1
THEN PaymentAmount ELSE 0 END) AS [Late]
FROM
Customers
WHERE
DATEDIFF(MONTH, PaymentDate,DueDate), GetDate()) = 1
AND PaymentAmount = DuesAmount
The error says, "All queries in a SQL statement containing a UNION operator must have an equal number of expressions in their target lists." What this means is that each SELECT must return the same number of expressions.
In the code sample you gave us, the first SELECT statement returns 6 expressions, while the second one returns 8:
SELECT CustomerID, -- 1
Name, -- 2
DueDate, -- 3
CASE ... END AS [Early], -- 4
CASE ... END AS [On Time], -- 5
CASE ... END AS [Late] -- 6
...
UNION
SELECT '-Total', -- 1
'', -- 2
CustomerID, -- 3
Name, -- 4
DueDate, -- 5
SUM(CASE ... END) AS [Early], -- 6
SUM(CASE ... END) AS [On Time], -- 7
SUM(CASE ... END) AS [Late] -- 8
...
See how the first SELECT returns 6 expressions, but the second returns 8? In a UNION, all the SELECT statements must return the same number of expressions.
You can return NULL in the first query for columns that don't have a match in the second, if necessary. For example:
SELECT NULL as [RowType], -- 1
NULL as [Padding], -- 2
CustomerID, -- 3
Name, -- 4
DueDate, -- 5
CASE ... END AS [Early], -- 6
CASE ... END AS [On Time], -- 7
CASE ... END AS [Late] -- 8
...
UNION
SELECT '-Total', -- 1
'', -- 2
CustomerID, -- 3
Name, -- 4
DueDate, -- 5
SUM(CASE ... END) AS [Early], -- 6
SUM(CASE ... END) AS [On Time], -- 7
SUM(CASE ... END) AS [Late] -- 8
...
Also, note that you don't have a comma after the DueDate column.