How label each output group type - sql-server

I have a query like this:
Product
orderid TypeId datefulfilled
17749 Spec 2022-10-11 18:35:25.000
17754 Spec 2022-10-12 18:35:25.000
17755 Spec 2022-10-12 18:35:25.000
17756 Spec 2022-10-12 18:35:25.000
17757 Spec 2022-10-16 18:35:25.000
17769 Spec 2022-11-24 18:35:25.000
17788 Spec 2022-12-12 18:35:25.000
17819 Spec 2022-12-19 18:35:25.000
17829 Spec 2022-12-19 18:35:25.000
17830 Spec 2022-01-08 18:35:25.000
17830 Cert 2022-01-08 18:35:25.000
select
count(distinct p.orderid) as overall_count, format(datefulfilled, 'yyyy/MM')
from Product p where datefulfilled <= dateadd(year,-1,getdate()) and typeId in ('Spec') group by format(datefulfilled,'yyyy/MM') order by format(datefulfilled,'yyyy/MM')
gives result counts like:
overall_count
2 2022/12
1 2023/01
How do I get it to label the individual output formatted like this (each output row has overall_count text preceding it):
overall_count 2 2022/12
overall_count 1 2022/12
I'm having trouble finding any info on how to do this searching the internet. This will help people using the report do their calculations.

concat() is a nice fit here.
Just an aside, format() has some great features, but the performance is dreadful (it should be used sparingly). Notice that I use convert(varchar(7),datefulfilled,111) instead
Updated: used a CROSS APPLY to reduce the number of date conversions
Example
select NewValue = concat('overall_count'
,
' '
,
count(distinct p.orderid)
,' '
,yyyymm
)
from Product p
cross apply ( values ( convert(varchar(7),datefulfilled,111) ) ) b(yyyymm)
where datefulfilled <= dateadd(year,-1,getdate())
and typeId in ('Spec')
group by yyyymm
order by yyyymm
Results
NewValue
overall_count 1 2022/01

Related

T-SQL: Query to eliminate non-unique results

I'm trying to select the create date for the most recent request # for each unique business unit. Because there are more unique create dates then there are business units, I get non-unique business units in my business unit column. I don't need all create dates, just the most recent one.
If you look at the CTE in v2, I want the most recent CreateDate for each value returned from the CTE.
Any assistance is appreciated.
Version 1:
SELECT
SQ.[Business Unit Impacted] [BU],
COUNT(RD.RequestID) [ReqCount],
(SELECT TOP 1 RD.CreateDate) [Create]
FROM
REP_RequestData RD
LEFT JOIN
REP_StandardQuestionResponses SQ ON SQ.RequestDataId = RD.Id
WHERE
RD.ProductID = 'Firewall.Change.Request'
GROUP BY
SQ.[Business Unit Impacted], RD.CreateDate
Version 2:
WITH D AS
(
SELECT
SQ.[Business Unit Impacted] [BU],
COUNT(RD.RequestID) [ReqCount]
FROM
REP_RequestData RD
LEFT JOIN
REP_StandardQuestionResponses SQ ON SQ.RequestDataId = RD.Id
WHERE
RD.ProductID = 'Firewall.Change.Request'
GROUP BY
SQ.[Business Unit Impacted]
)
SELECT
D.BU,
D.ReqCount,
(SELECT TOP 1 RD.CreateDate) [create]
FROM
D
LEFT JOIN
REP_StandardQuestionResponses SQ ON SQ.[Business Unit Impacted] = D.BU
LEFT JOIN
REP_REQUESTDATA RD on SQ.RequestDataId = RD.Id
WHERE
RD.ProductID = 'Firewall.Change.Request'
GROUP BY
D.BU, D.ReqCount, RD.CreateDate
if I understood correctly you just need the latest date so it's a matter of grouping only by BU and obtain the max(date):
SELECT
SQ.[Business Unit Impacted] [BU],
COUNT(RD.RequestID) [ReqCount],
MAX(RD.CreateDate) [Create]
FROM
REP_RequestData RD
LEFT JOIN
REP_StandardQuestionResponses SQ ON SQ.RequestDataId = RD.Id
WHERE
RD.ProductID = 'Firewall.Change.Request'
GROUP BY
SQ.[Business Unit Impacted]
In Version 2, you need to add a where clause inside of your subselect statement (select top 1 RD.CreateDate) to limit the return to the Business Unit returned on the same line along with an order by to get the latest CreateDate. It would look something like the code below:
(select top 1 RD.CreateDate where RD.BU = D.BU order by RD.CreateDate desc)
That should return the latest CreateDate based on the Business Unit.

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

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.

how to combine inner join, sub queries, sum and where condition

suppose I have a table like the below:
Employee No | Wage Type|Wages|Company Code|
For each employee no, I want to create a table such as the following (I know the number of wage types)
Unique Employee No|Sum(Wage Type 1)|Sum(Wage Type 2)| Company Code
I am using the following code, however it does not works...
select [Pers No ] ,["Co Code"],[Company Code],
from [dbo].[Wages] as a
left join(
select sum([[Total Wages])as [Total Wage Type 1 for Aug]
from [dbo].[Wages]
where [Wage Description] ='Wage Type 1'
) as b on a.[Pers No ]=b.[Pers No ]
left join(
select sum([Total Wages]) as [Total Wage Type 2 for Aug]
from [dbo].[Wages]
where [Wage Description]='Wage Type 2'
) as c on b.[Pers No ] =c.[Pers No ]
group by[Pers No ], ["Co Code"],[Company Code]
into [Total Wages Group by Pers No]
Basically, I want to get the total sum of wages for each wage type group by each employee. My idea was to initially group by employee then get the sum of each type of wages within a single select statement, however that doesn't works. Thats why I am using left join now...but it doesn't work as well.
Using a conditional aggregation could probably work:
select
[Pers No ],[Company Code],
sum(case when [Wage Description] = 'OW for CPF' then [PwC_Totals] else 0 end) as [Total OW for Aug] ,
sum(case when [Wage Description] = 'AW for CPF' then [PwC_Totals] else 0 end) as [Total AW for Aug]
from [dbo].[OW and AW - aug 14_cleaned]
group by [Pers No ], [Company Code]
The column names in your table isn't all clear, so you might have to adjust them.

Counting duplicate items in different order

Goal:
To know if we have purchased duplicate StockCodes or Stock Description more than once on difference purchase orders
So, if we purchase Part ABC on Purchase Order 1 and Purchase Order 2, it should return the result of
PurchaseOrders, Part#, Qty
Purchase Order1, Purchase Order2, ABC, 2
I just don't know how to pull the whole code together, more to the point, how do I know if it's occurred on more than 1 Purchase Order without scrolling through all the results , may also have to do with Multiple (Having Count) Statements as I only seem to be doing by StockCode
SELECT t1.PurchaseOrder,
t1.MStockCode,
Count(t1.MStockCode) AS SCCount,
t1.MStockDes,
Count(t1.MStockDes) AS DescCount
FROM PorMasterDetail t1
INNER JOIN PorMasterHdr t2
ON t1.PurchaseOrder = t2.PurchaseOrder
WHERE Year(t2.OrderEntryDate) = Year(Getdate())
AND Month(t2.OrderEntryDate) = Month(Getdate())
GROUP BY t1.PurchaseOrder,
t1.MStockCode,
t1.MStockDes
HAVING Count(t1.MStockCode) > 1
Using responses I came up with the following
select * from
(
SELECT COUNT(dbo.InvMaster.StockCode) AS Count, dbo.InvMaster.StockCode AS StockCodes,
dbo.PorMasterDetail.PurchaseOrder, dbo.PorMasterHdr.OrderEntryDate
FROM dbo.InvMaster INNER JOIN dbo.PorMasterDetail ON
dbo.InvMaster.StockCode = dbo.PorMasterDetail.MStockCode
INNER JOIN dbo.PorMasterHdr ON dbo.PorMasterDetail.PurchaseOrder = dbo.PorMasterHdr.PurchaseOrder
WHERE YEAR(dbo.PorMasterHdr.OrderEntryDate) = YEAR(GETDATE())
GROUP BY dbo.InvMaster.StockCode, dbo.InvMaster.StockCode,
dbo.PorMasterDetail.PurchaseOrder, dbo.PorMasterHdr.OrderEntryDate
) Count
Where Count.Count > 1
This returns the below , which is starting to be a bit more helpful
In result line 2,3,4 we can see the same stock code (*30044) ordered 3 times on different
purchase orders.
I guess the question is, is it possible to look at If something was ordered more than once within say a 30 day period.
Is this possible?
Count StockCodes PurchaseOrder OrderEntryDate
2 *12.0301.0021 322959 2014-09-08
2 *30044 320559 2014-01-21
8 *30044 321216 2014-03-26
4 *30044 321648 2014-05-08
5 *32317 321216 2014-03-26
4 *4F-130049/TEST 323353 2014-10-22
5 *650-1157/E 322112 2014-06-24
2 *650-1757 321226 2014-03-27
SELECT *
FROM
(
SELECT h.OrderEntryDate, d.*,
COUNT(*) OVER (PARTITION BY d.MStockCode) DupeCount
FROM
PorMasterHdr h
INNER JOIN PorMasterDetail d ON
d.PurchaseOrder = h.PurchaseOrder
WHERE
-- first day of current month
-- http://blog.sqlauthority.com/2007/05/13/sql-server-query-to-find-first-and-last-day-of-current-month/
h.OrderEntryDate >= CONVERT(VARCHAR(25), DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()),101)
) dupes
WHERE
dupes.DupeCount > 1;
This should work if you're only deduping on stock code. I was a little unclear if you wanted to dedupe on both stock code and stock desc, or either stock code or stock desc.
Also I was unclear on your return columns because it almost looks like you're wanting to pivot the columns so that both purchase order numbers appear on the same line.

How do I select columns together with aggregate functions?

Let's say I have a table of companies:
Company
coID | coName | coCSR
The coCSR field is a numeric ID which relates to the account handler table:
AccountHandler
ahID | ahFirstName | ahLastName
I also have a table of orders:
Order
orID | orCompanyID | orDate | orValue
Now what I need to produce is output structured as follows:
Company | Account handler | No. of orders | Total of orders
Here is the query I have tried, which produces an error:
SELECT coID, coName, ahFirstName+' '+ahLastName AS CSRName, COUNT(orID) AS numOrders, SUM(orValue) AS totalRevenue
FROM Company
LEFT JOIN AccountHandler ON coCSR = ahID
LEFT JOIN Order ON coID = orCompanyID
WHERE coCSR = 8
AND orDate > getdate() - 365
ORDER BY coName ASC
The error is: Column name 'AccountHandler.ahLastName' is invalid in the ORDER BY clause because it is not contained in an aggregate function and there is no GROUP BY clause.
If I use GROUP BY coID, I get Incorrect syntax near the keyword 'WHERE'. If I change the WHERE to HAVING because of the aggregate functions, I get errors telling me to remove each of the other column names that aren't contained in either an aggregate function or the GROUP BY clause.
I have to admit, I don't yet understand the logic and syntax of anything but the most basic SQL commands, I'm just trying to apply what I've seen used before, and it's not working. Please help me to get this working. Better still, can you help me understand why it doesn't work at the moment? :)
For one thing, your query is probably missing FROM Company, but that might somehow have been lost when you were writing your post.
You seem to be aggregating data by companies. Therefore you need to group by companies. The most likely reason why your attempt at grouping failed might be because you put GROUP BY in the wrong place. I think you put it before WHERE, but in fact it should go after it (and before ORDER BY):
SELECT
c.coID,
c.coName,
a.ahFirstName + ' ' + a.ahLastName AS CSRName,
COUNT(o.orID) AS numOrders,
SUM(o.orValue) AS totalRevenue
FROM Company c
LEFT JOIN AccountHandler a ON c.coCSR = a.ahID
LEFT JOIN [Order] o ON c.coID = o.orCompanyID
WHERE c.coCSR = 8
AND o.orDate > getdate() - 365
GROUP BY ...
ORDER BY c.coName ASC
Another question is, what to group by. SQL Server requires that all non-aggregated columns be specified in GROUP BY. Therefore your GROUP BY clause should look like this:
GROUP BY
c.coID,
c.coName,
a.ahFirstName,
a.ahLastName
Note that you can't reference columns by aliases assigned to them in the SELECT clause (e.g. CSRName). But you could use the ahFirstName+' '+ahLastName expression instead of the corresponding columns, it wouldn't make any difference in this particular situation.
If you ever need to add more non-aggregated columns to this query, you'll have to add them both to SELECT and to GROUP BY. At some point this may become a bit tedious. I would suggest you try the following instead:
SELECT
c.coID,
c.coName,
a.ahFirstName + ' ' + a.ahLastName AS CSRName,
ISNULL(o.numOrders, 0) AS numOrders,
ISNULL(o.totalRevenue, 0) AS totalRevenue
FROM Company c
LEFT JOIN AccountHandler a ON c.coCSR = a.ahID
LEFT JOIN (
SELECT
orCompanyID,
COUNT(orID) AS numOrders,
SUM(orValue) AS totalRevenue
FROM [Order]
GROUP BY
orCompanyID
WHERE orDate > getdate() - 365
) o ON c.coID = o.orCompanyID
WHERE c.coCSR = 8
ORDER BY c.coName ASC
That is, aggregating is done on the Order table only. The aggregated row set is then joined to the other tables. You can now pull more attributes to the output from either Company or AccountHandler without worrying about adding them to GROUP BY because grouping is not needed at that level any more.
Can you change the query like below? You should add Max and Group By Clause
SELECT
MAX(C.coID),
C.coName,
MAX(AH.ahFirstName+' '+ AH.ahLastName ) AS CSRName,
COUNT(O.orID) AS numOrders,
SUM(O.orValue) AS totalRevenue
From Company C
LEFT JOIN AccountHandler AH ON C.coCSR = AH.ahID
LEFT JOIN Order O ON C.coID = O.orCompanyID
WHERE C.coCSR = 8 AND
O.orDate > getdate() - 365
Group by C.coName
ORDER BY C.coName ASC
My Suggestion
You should use Alias for the selected Column Names
SELECT
--<<<non aggregate section of SELECT clause
coID
, coName
, [CSRName] = CONVERT(VARCHAR(100),ahFirstName + ' ' + ahLastName)
--<<<aggregate section of SELECT clause
, [numOrders] = COUNT(orID)
, [totalRevenue] = SUM(orValue)
FROM --<<<<<<sql is not too happy without FROM
Company c
LEFT JOIN AccountHandler a
ON coCSR = ahID
LEFT JOIN Order o
ON coID = orCompanyID
WHERE coCSR = 8
AND orDate > getdate() - 365
GROUP BY
coID
, coName
, CONVERT(VARCHAR(100),ahFirstName + ' ' + ahLastName) --<<<<looks like aggregate but is just text manipulation
ORDER BY coName ASC
You have two aggregate functions ; a COUNT and a SUM ; this means that you are required to do some grouping and the general rule of thumb is GROUP BY the non aggregate section of the select clause
The really big problem in your OP is that when you JOIN two tables, whatever flavour (LEFT, RIGHT, OUTER, INNER, CROSS) it has to be in the FROM clause and needs a table specified on either side of the join
Then if joining several tables you might like to use aliases for each of the tables; I've just used single lower case letter; c/o/a. Although looking at your column names these might not be needed as all columns are uniquely named.

Resources