How to use GROUPING function in SQL common table expression - CTE - sql-server

I have the below T-SQL CTE code where i'm trying to do some row grouping on four columns i.e Product, ItemClassification, Name & Number.
;WITH CTE_FieldData
AS (
SELECT
CASE(GROUPING(M.CodeName))
WHEN 0 THEN M.CodeName
WHEN 1 THEN 'Total'
END AS Product,
CASE(GROUPING(KK.ItemClassification))
WHEN 0 THEN KK.[ItemClassification]
WHEN 1 THEN 'N/A'
END AS [ItemClassification],
CASE(GROUPING(C.[Name]))
WHEN 0 THEN ''
WHEN 1 THEN 'Category - '+ '('+ItemClassification+')'
END AS [Name],
CASE(GROUPING(PYO.Number))
WHEN 0 THEN PYO.Number
WHEN 1 THEN '0'
END AS [Number],
ISNULL(C.[Name],'') AS ItemCode,
MAX(ISNULL(PYO.Unit, '')) AS Unit,
MAX(ISNULL(BT.TypeName, '')) AS [Water Type],
MAX(ISNULL(PYO.OrderTime, '')) AS OrderTime,
MAX(ISNULL(BUA.Event, '')) AS Event,
MAX(ISNULL(PYO.Remarks, '')) AS Remarks,
GROUPING(M.CodeName) AS ProductGrouping,
GROUPING(KK.ItemClassification) AS CategoryGrouping,
GROUPING(C.[Name]) AS ItemGrouping
FROM CTable C INNER JOIN CTableProducts CM ON C.Id = CM.Id
INNER JOIN MyData R ON R.PId = CM.PId
INNER JOIN MyDataDetails PYO ON PYO.CId = C.CId AND PYO.ReportId = R.ReportId
INNER JOIN ItemCategory KK ON C.KId = KK.KId
INNER JOIN Product M ON R.ProductId = M.ProductId
INNER JOIN WaterType BT ON PYO.WId = BT.WId
INNER JOIN WaterUnit BUA ON PYO.WUId = BUA.WUId
WHERE R.ReportId = 4360
GROUP BY M.CodeName, KK.ItemClassification, C.Name, PYO.Number
WITH ROLLUP
)
SELECT
Product,
[Name] AS Category,
Number,
Unit as ItemCode,
[Water Type],
OrderTime,
[Event],
[Comment]
FROM CTE_FieldData
Below are the issues/problems with the data being returned by the script above and they are the ones i'm trying to fix.
At the end of each ItemClassification grouping, i extra record is being added yet it does not exist in the table. (See line number 4 & 10 in the sample query results screenshot attached).
I want the ItemClassification grouping in column 2 to be at the beginning of the group not at the end of the group.
That way, ItemClassification "Category- (One)" would be at line 1 not the current line 5.
Also ItemClassification "Category- (Two)" would be at line 5 not the current line 11
Where the "ItemClassification" is displaying i would like to have columns (Number, ItemCode, [Water Type], [OrderTime], [Event], [Comment]) display null.
In the attached sample query results screenshot, those would be rows 11 & 5
The last row (13) is also unwanted.
I'm trying to understand SQL CTE and the GROUPING function but i'm not getting things right.

It looks like this is mostly caused by WITH ROLLUP and GROUPING. ROLLUP allows you to make essentially a sum line for your groupings. When you have WITH ROLLUP, it will give you NULL values for all of your non-aggregated fields in your select statement. You use GROUPING() in conjunction with ROLLUP to then label those NULL's as 'Total' or '0' or 'Category' as your query does.
1) Caused by GROUPING and ROLLUP. Take away both and this should be resolved.
2) Not sure what determines your groups and what would be defined as beginning or end. Order BY should suffice
3) Use ISNULL or CASE WHEN. If the Item Classification has a non null or non blank value, NULL each field out.
4) Take off WITH ROLLUP.

Related

Two IDs matching one causing duplicates

I am trying to inner join however I keep getting this duplicate pop up where there are two job IDs matching one Invoice ID (inner joined with a middle table that links both).
I want to only get 1 invoice id and summing the total despite 2 job ids matching it.
Basically there is AINVOICEID (table:Invoice ) matching ABINVOICEID (table:INLines) and inside the INLines table, it contains ARJOBID that matches the JOBID in Jobs.
Select distinct sum(totalBASE) as InvoiceTotal,
DATEADD(MONTH, DATEDIFF(MONTH, 0, InvDate), 0)
from (select distinct left(JOBID,5)as JOBID
from jobs
group by JOBID
) jobs
inner join (select distinct ABINVOICEID, left(ARLJOBID,5) as arljobid
from INLines
group by ARLJOBID, ABINVOICEID
) INLines
on left(ARLJOBID,5) = left(JOBID,5)
inner join (select distinct AINVOICEID
, sum(totalBASE) as totalBASE
, InvDate
from Invoice
group by AINVOICEID, InvDate
) Invoice
on AINVOICEID = ABINVOICEID
where left(JOBID ,5)=left(ARLJOBID,5) and AINVOICEID = ABINVOICEID
and InvDate between '05/01/2022' AND '05/31/2022'
group by left(JOBID ,5), DATEADD(MONTH, DATEDIFF(MONTH, 0, InvDate), 0)
It is quite difficult to try and make sense of what you're asking without an example of the output that you're getting and what the expected output is.
However, I think you're wanting something like this:
SELECT I.AINVOICEID, SUM(I.totalBASE) AS totalBASE, InvDate
FROM Invoice I INNER JOIN
(
SELECT L.ABINVOICEID, J.JOBID
FROM INLines L INNER JOIN
Jobs J ON L.ARLJOBID = J.JOBID
GROUP BY L.ABINVOICEID, J.JOBID
) LJ ON I.AINVOICEID = L.ABINVOICEID
WHERE I.InvDate BETWEEN '05/01/2022' AND '05/31/2022'
GROUP BY I.AINVOICEID, InvDate
This is based on what your SQL query which doesn't really look like it needs to JOIN on the INLines or Jobs table because you're getting everything you need from the Invoice table in the SELECT ?
If this isn't what you're after, if you can elaborate a bit more, then the community on here should be able to better assist with your question.

Conditional subquery that returns a running total

I am trying to run a subquery with a condition that returns a running total. However, I am receiving the following error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
Is there any way this code can be salvaged? Please be aware this code is part of a larger script that executes perfectly. The reason I need to keep it in this format is because it is the "missing piece", for lack of a better word.
SELECT A.[WeekEnding],
(
SELECT SUM(A.[Weekly Sales Units]), A.[Description], A.[WeekEnding]
FROM [FACT_SALES_HISTORY] A
INNER JOIN [DIM_DATE] B
ON A.WeekEnding = B.[WeekEnding] WHERE B.[YA Latest 1 Week] = 1
GROUP BY A.[Description], A.[WeekEnding]
) AS 'YA Units'
FROM [FACT_SALES_HISTORY] A
LEFT JOIN [DIM_DATE] B
ON A.WeekEnding = B.[WeekEnding]
The output data, from the code, would look like the following:
[Weekly Sales Units]) A.[Description] A.[WeekEnding]
24 Item One 03-10-2010
55 Item Two 03-10-2010
79 Item One 03-10-2010
98 Item Five 03-10-2010
11 Item Five 03-10-2010
You can't select three different items in your subquery and then use an AS assignment. You could split that into two separate queries and then union them.
SELECT SUM(A.[Weekly Sales Units]), A.[Description], A.[WeekEnding]
FROM [FACT_SALES_HISTORY] A
INNER JOIN [DIM_DATE] B
ON A.WeekEnding = B.[WeekEnding] WHERE B.[YA Latest 1 Week] = 1
GROUP BY A.[Description], A.[WeekEnding]
UNION ALL -- This will only union distinct columns
SELECT A.[WeekEnding]...<Your other columns>
FROM [FACT_SALES_HISTORY] A
t looks like your subquery on its own would provide the sample data and the outer query is trying to sum that up by weekending. If that is the case, then the whole thing could be replaced with this:
SELECT A.[WeekEnding], SUM(A.[Weekly Sales Units]) [YA Units]
FROM [FACT_SALES_HISTORY] A
INNER JOIN [DIM_DATE] B
ON A.WeekEnding = B.[WeekEnding] WHERE B.[YA Latest 1 Week] = 1
GROUP BY A.[WeekEnding]

Select Newest Record

I have two tables (Journal and Incident). Each incident may have more than one journal entry. I want to select the record and the most recent journal data.
The where section at the bottom is what filters the incidents I want to see. Of those, I want the journal values associated with the most recent journal entry.
This is an amalgam of code I've found on here, but when I run it I get a "Query execution failed for dataset 'DataSet1'. Unfortunately, I don't have access to the log files to see if there are clues there.
Any help is appreciated. I think I may have it nested wrong.
SELECT
b.IncidentNumber
,a.Subject
,a.CreatedDateTime
,b.SubCategory
,b.EffectiveDueDate
,b.NextActionDate
,b.ProfileFullName
FROM
(
SELECT
b.IncidentNumber
,a.Subject
,a.CreatedDateTime
,rn = row_number() OVER (PARTITION by b.IncidentNumber ORDER BY
a.CreatedDateTime DESC)
,b.SubCategory
,b.EffectiveDate
,b.NextActionDate
,b.ProfileFullName
FROM
Journal a LEFT JOIN Incident b ON
a.ParentRecordNumber = b.IncidentNumber
WHERE a.Category LIKE '%KANBAN%'
AND (b.Status LIKE' %Waiting%' OR b.status LIKE '%Active%')
AND b.SubCategory <> 'User Termination'
AND b.SubCategory <> 'Res Temp Termination'
AND a.Subject LIKE 'UP |%'
) X
WHERE rn = 1
Few things:
Outer most selected values should be from inline view aliased "X" No a. or b. as those alias only are in scope to the in inner query. (except when using coorlation but that's 1 level only I believe)
You either need to right join instead of left or change the order of the tables. I believe you want all incidents and the MOST recent Journal; not all journals and the related incident if one exists. thus I changed the order.
Lastly when using outer joins, you can only put limits on the all records table of the outer join. Where clause criteria the OUTER joined tables will cause the null records generated by the outer join to be excluded. To resolve this you must move the limiting criteria to the join or use an 'or' statement to check for null (it's cleaner to move it to the join). Think of it is applying the limit before the join occurs so Null records from incident are kept. otherwise the outer join simulates a INNER JOIN by excluding those records not in both tables (or in this case in incident but not in journal)
.
SELECT x.IncidentNumber --alias x not a/b as the from is aliased as 'X'
, x.Subject
, x.CreatedDateTime
, x.SubCategory
, x.EffectiveDueDate
, x.NextActionDate
, x.ProfileFullName
FROM (SELECT b.IncidentNumber
, a.Subject
, a.CreatedDateTime
, rn = row_number() OVER (PARTITION by b.IncidentNumber
ORDER BY a.CreatedDateTime DESC)
, b.SubCategory
, b.EffectiveDate
, b.NextActionDate
, b.ProfileFullName
FROM Incident b --switched the order I think you want all incidents and if a journal exists it's value.
LEFT JOIN Journal a
ON a.ParentRecordNumber = b.IncidentNumber
-- Since A is on the if match found to B, we need to move this to the join or we lose the records created from the outer join.
AND a.Category LIKE '%KANBAN%'
AND a.Subject LIKE 'UP |%'
--moved some where clause criteria to the join Since B is on the "all records side" of the outer join we can leave B in the where clause.
WHERE (b.Status LIKE' %Waiting%' OR b.status LIKE '%Active%')
AND b.SubCategory <> 'User Termination'
AND b.SubCategory <> 'Res Temp Termination') X
WHERE rn = 1
If you are not getting records from here, then I'd start removing some of the limiting criteria to ensure the query is functioning as desired and then add back in limits to see what's causing no records to be found.
I've finally got this report working as expected. It took a few iterations to get the query working, but it's doing what it should, now. Many thanks for your assist. I would have never gotten there without it!
SELECT x.IncidentNumber
, x.Subject
, x.CreatedDateTime
, x.SubCategory
, x.ProfileFullName
, x.PropertyNumber
, x.Status
, x.EffectiveDueDate
FROM (SELECT b.IncidentNumber
, a.Subject
, a.CreatedDateTime
, rn = row_number() OVER (PARTITION by b.IncidentNumber
ORDER BY a.CreatedDateTime DESC)
, b.SubCategory
, b.ProfileFullName
, b.PropertyNumber
, b.Status
, b.EffectiveDueDate
FROM Incident b
RIGHT JOIN Journal a
ON a.ParentRecordNumber = b.IncidentNumber
AND a.Category LIKE '%KANBAN%'
AND a.Subject LIKE 'UP |%'
WHERE (b.Status LIKE' %Waiting%' OR b.status LIKE '%Active%')
) x
WHERE rn = 1

SQL Server : Group By not working

I am building a query which works perfectly fine until I try to GROUP BY tblWJC.WJCNo
Query:
SELECT
tblWJCItem.AddedDescription
,tblWJC.WJCPrefix + Convert(Varchar(10),tblWJC.WJCNo) AS OurRef
,tblWJCItem.MaterialName
,tblStockFamily.StockFamily
,tblWJCItem.WeightToSend
,tblWJC.DateCreated
,tblWJC.WJCStatusID
,CASE
WHEN tblWJC.WJCStatusID < 2 THEN 'Pre Production'
WHEN tblWJC.WJCStatusID < 4 THEN 'In Production'
WHEN tblWJC.WJCStatusID > 4 THEN 'Ready To Ship'
ELSE 'Awaiting Lab Results'
END AS [Status]
FROM
tblWJC
INNER JOIN
tblWJCItem ON tblWJC.WJCID = tblWJCItem.WJCID
INNER JOIN
tblStockFamily ON tblWJCItem.ProductFamilyID = tblStockFamily.StockFamilyID
INNER JOIN
tblCustomer ON tblWJC.CustomerID = tblCustomer.CustomerID
INNER JOIN
tblWJCStockStatus ON tblWJC.WJCStatusID = tblWJCStockStatus.WJCStockStatusID
WHERE
tblCustomer.CustomerName = 'ROLLS ROYCE'
AND tblWJCStockStatus.WJCStockStatus <> 'Stock Usage Confirmed'
GROUP BY
tblWJC.WJCNo
ORDER BY
tblStockFamily.StockFamily;
Can any one point out my mistake here? I am new to SQL Server, still learning by doing.
Thanks
This is common that you have a field aggregation error when you group a query by a field than you don't add an aggregate function on the fields that are part of your select statement. In your case, you're doing a group by the tblWJC.WJCNo column and then you are not aggregating your fields in the select statement.
SELECT tblWJCItem.AddedDescription
,tblWJC.WJCPrefix + Convert(Varchar(10),tblWJC.WJCNo) AS OurRef
,tblWJCItem.MaterialName
,tblStockFamily.StockFamily
,tblWJCItem.WeightToSend
,tblWJC.DateCreated
,tblWJC.WJCStatusID
,CASE WHEN tblWJC.WJCStatusID < 2 THEN 'Pre Production'
WHEN tblWJC.WJCStatusID < 4 THEN 'In Production'
WHEN tblWJC.WJCStatusID > 4 THEN 'Ready To Ship'
ELSE 'Awaiting Lab Results'
END AS [Status]
If you group by a field and you want to put other fields in the select statement, these fields should be either aggregated or included in the group by statement and that seems logical since if you group data by a field, the other fields should aggregated by the group by field in order to display them.
Here is a link that contains all the aggregation function in SQL Server:
http://msdn.microsoft.com/en-us/library/ms173454.aspx

Calculated summary field based on child table

I have two tables, Order and OrderItem. There is a one-to-many relationship on Order.Order_ID=OrderItem.Order_ID
I want a query to return a list showing the status of each Order, COMPLETE or INCOMPLETE.
A COMPLETE Order is defined as one where all the related OrderItem records have a non-NULL, non-empty value in the OrderItem.Delivery_ID field.
This is what I have so far:
SELECT Order.Order_ID, 'INCOMPLETE' AS Order_status
FROM Order
WHERE EXISTS
(SELECT *
FROM OrderItem
WHERE OrderItem.Order_ID=Order.Order_ID
AND (OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID=''))
UNION
SELECT Order.Order_ID, 'COMPLETE' AS Order_status
FROM Order
WHERE NOT EXISTS
(SELECT *
FROM OrderItem
WHERE OrderItem.Order_ID=Order.Order_ID
AND (OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID=''))
ORDER BY Order_ID DESC
It works, but runs a bit slow. Is there a better way?
(N.B. I've restated the problem for clarity, actual table and field names are different)
I would suggest you have a column status on your Order table and update the status to complete when all order items get delivered.
It will make simple your query to get status as well improve performance.
Put it into a subquery to try to make the case statement less confusing:
SELECT Order_ID,
CASE WHEN incomplete_count > 0 THEN 'INCOMPLETE' ELSE 'COMPLETE' END
AS Order_status
FROM ( SELECT o.Order_ID
,SUM( CASE WHEN OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID='' THEN 1 ELSE 0 END )
AS incomplete_count
FROM Order o
INNER JOIN OrderItem i ON (i.Order_ID = o.Order_ID)
GROUP by o.Order_ID
) x
ORDER BY ORder_ID DESC
The idea is to keep a counter every time you encounter a null item. If the sum is 0, there were no empty order items.
Try this one -
SELECT
o.Order_ID
, Order_status =
CASE WHEN ot.Order_ID IS NULL
THEN 'COMPLETE'
ELSE 'INCOMPLETE'
END
FROM dbo.[Order] o
LEFT JOIN (
SELECT DISTINCT ot.Order_ID
FROM dbo.OrderItem ot
WHERE ISNULL(ot.Delivery_ID, '') = ''
) ot ON ot.Order_ID = o.Order_ID

Resources