Aggregation, grouping error even using OVER PARTITION BY - sql

I'm getting this error
'PRINTING_DATE' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause
but by using OVER PARTITION BY I expected this issues won't appear, why am I still getting this error?
DECLARE #REPORT AS NVARCHAR(50) = '2019-06-19 00:00:00.000'
SELECT
SUM(CASE
WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS SHIPPED,
SUM(CASE
WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) - SUM(CASE
WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END) AS TO_SHIP
FROM
TABLE1 T1
INNER JOIN
TABLE2 T2 ON T1.DELIVERY = T2.DELIVERY
WHERE
PLANT = 'XXX'

As HoneyBadger pointed out, you only have a "OVER PARTITION BY" in the first SUM, not the second.
Try this as a simplified version:
DECLARE #T1 TABLE(PLANT INT,PARTS INT,P_DATE DATE,QTY1 INT, QTY2 INT)
INSERT INTO #T1 VALUES(1,1,'2019-07-03',40,60)
INSERT INTO #T1 VALUES(1,1,'2019-07-03',50,80)
SELECT
SUM(QTY1) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS SHIPPED
,SUM(QTY1) OVER (PARTITION BY PLANT, PARTS, P_DATE) -
SUM(QTY2) OVER (PARTITION BY PLANT, PARTS, P_DATE) AS TOSHIP
,SUM(QTY1) - SUM(QTY2) AS TOSHIP2
FROM #T1
WHERE PLANT = 1
This will raise an error:
Column '#T1.PLANT' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If you comment/delete the line
,SUM(QTY1) - SUM(QTY2) AS TOSHIP2
it will produce a result.
BTW: in this example the error also points to the first SUM, but this one is not the problem.

It is rather unclear what you are trying to do. But I can give a hint: window functions are of no use if your result set is an aggregation that returns a single row.
So, you might want this:
SELECT SUM(CASE WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) AS SHIPPED,
(SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) -
SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END)
) AS TO_SHIP
FROM TABLE1 T1 INNER JOIN
TABLE2 T2
ON T1.DELIVERY = T2.DELIVERY
WHERE PLANT = 'XXX';
Of course, you might want an aggregation query that returns more than one row:
SELECT PLANT, PARTS, P_DATE,
SUM(CASE WHEN (P_DATE < #REPORT AND P_DATE > DATEADD(DAY, -7, #REPORT))
THEN QTY_PICKED
ELSE 0
END) AS SHIPPED,
(SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_MII
ELSE 0
END) -
SUM(CASE WHEN E_DATE > #REPORT AND E_DATE < DATEADD(DAY, 7, #REPORT)
THEN QTY_PICKED
ELSE 0
END)
) AS TO_SHIP
FROM TABLE1 T1 INNER JOIN
TABLE2 T2
ON T1.DELIVERY = T2.DELIVERY
WHERE PLANT = 'XXX'
GROUP BY PLANT, PARTS, P_DATE;

Related

Return 1 row with various sums based on date?

Assume a table of purchase transactions with columns CustId, Amount, DatePosted where Amount is the value of the transaction, and DatePosted is a DATETIME value. Given a specific CustId, how would I write a select such that it returns a single row with the following columns: CustId, total value of transactions in the last 3 days, last 60 days, 1 year, 2 years (5 columns total).
Example table:
CustId
Amount
DatePosted
1234
698.02
2023-01-23Z12:34:56
1234
582.69
2022-12-15Z19:57:23
1234
7775.22
2022-12-02Z02:34:32
1234
18.72
2022-01-23Z12:34:56
1234
2.27
2021-01-23Z12:34:56
Expected output given the sample data above when searching using CustId=1234:
CustId
3-day Total
60-day Total
1 year Total
2 year Total
1234
698.02
9055.93
9074.65
9076.92
You could get all purchase data for the last 2 years, then using SUM with SQL CASE expression to calculate total value for each time-range.
SELECT
CustId,
SUM(CASE WHEN DatePosted >= Last3Day THEN Amount ELSE 0 END) AS [3-day Total],
SUM(CASE WHEN DatePosted >= Last60Day THEN Amount ELSE 0 END) AS [60-day Total],
SUM(CASE WHEN DatePosted >= Last1Year THEN Amount ELSE 0 END) AS [1 year Total],
SUM(CASE WHEN DatePosted >= Last2Year THEN Amount ELSE 0 END) AS [2 year Total]
FROM
<your data table>,
(SELECT
DATEADD(DAY, -3, GETDATE()) AS Last3Day,
DATEADD(DAY, -60, GETDATE()) AS Last60Day,
DATEADD(YEAR, -1, GETDATE()) AS Last1Year,
DATEADD(YEAR, -2, GETDATE()) AS Last2Year) timerange
WHERE DatePosted >= Last2Year
GROUP BY CustId;
Demo: http://sqlfiddle.com/#!18/9eecb/179880
This query assumes 2 year max. If you want to go further back then change the where clause as well. No need to use coalesce or a derived table. SQL server query planner may be smart enough to provide similar performance for all these solutions but this is easier to understand:
SELECT
CustId,
SUM(CASE WHEN DatePosted >= DATEADD(day, -3, GETDATE()) THEN Amount ELSE 0 END) AS [3-day Total],
SUM(CASE WHEN DatePosted >= DATEADD(day, -60, GETDATE()) THEN Amount ELSE 0 END) AS [60-day Total],
SUM(CASE WHEN DatePosted >= DATEADD(year, -1, GETDATE()) THEN Amount ELSE 0 END) AS [1 year Total],
SUM(Amount) AS [2 year Total]
FROM PurchaseTransactions
WHERE CustId = 1234 AND DatePosted >= DATEADD(year, -2, GETDATE())
GROUP BY CustId
This is set up so that you can set #CustID = null and the query will return results for all customers in the set.
EDIT: Updated my query below to give you more flexibility across your desired ranges should you wish to derive additional heuristics. (Counts, averages, etc.)
Also removed coalesce as it's simply not needed here.
DECLARE #CustID BIGINT;
SELECT table1.custID,
SUM([3Day].amount) AS [3DayTotal],
COUNT([3Day].amount) AS [3DayCount]
SUM([60Day].amount) AS [60DayTotal],
SUM([1Year].amount) AS [1YearTotal],
Sum([2Year].amount) AS [2YearTotal],
AVG([2Year].amount) AS [2YearAverage]
FROM table1 LEFT OUTER JOIN
(SELECT custID, Amount FROM table1 WHERE DatePosted > DATEADD(DAY, -3, GETDATE())) AS [3Day] ON table1.CustID = [3Day].CustID LEFT OUTER JOIN
(SELECT custID, Amount FROM table1 WHERE DatePosted > DATEADD(DAY, -60, GETDATE())) AS [60Day] ON table1.CustID = [60Day].CustID LEFT OUTER JOIN
(SELECT custID, Amount FROM table1 WHERE DatePosted > DATEADD(YEAR, -1, GETDATE())) AS [1Year] ON table1.CustID = [1Year].CustID LEFT OUTER JOIN
(SELECT custID, Amount FROM table1 WHERE DatePosted > DATEADD(YEAR, -2, GETDATE()) AS [2Year] ON table1.CustID = [2Year].CustID
WHERE table1.CustID = #CustID
OR #CustID IS NULL
GROUP BY table1.CustID

How can I include more columns other than just percentage column in my sql query result?

When I use the below query I only get percentage column but I want the (buyer_id, buyer_name, created_date,total_work_orders_with_gtv_first_60_days, total_gtv_first_60_days, total_net_amount_first_60_days) to show as columns too. Would really appreciate your help please.
WITH results_cte AS (
SELECT
b.buyer_id,
b.buyer_name,
CAST(b.created_date AS DATE) AS created_date,
COALESCE(wo.total_work_orders_with_gtv_first_60_days, 0) AS total_work_orders_with_gtv_first_60_days,
COALESCE(wo.total_gtv_first_60_days, 0) AS total_gtv_first_60_days,
COALESCE(wo.total_net_amount_first_60_days, 0) AS total_net_amount_first_60_days
FROM dw.buyer b
LEFT JOIN (SELECT wo.buyer_id,
COUNT(CASE WHEN wo.gtv_date < DATEADD(DAY, 60, b.created_date) THEN wo.work_order_id ELSE NULL END) AS total_work_orders_with_gtv_first_60_days,
SUM(CASE WHEN wo.gtv_date < DATEADD(DAY, 60, b.created_date) THEN wo.gtv ELSE NULL END) AS total_gtv_first_60_days,
SUM(CASE WHEN wo.gtv_date < DATEADD(DAY, 60, b.created_date) THEN wo.net_amount ELSE NULL END) AS total_net_amount_first_60_days
FROM dw.work_order wo
JOIN dw.buyer b
ON wo.buyer_id = b.buyer_id
WHERE wo.gtv > 0
GROUP BY wo.buyer_id) wo
ON b.buyer_id = wo.buyer_id
WHERE b.buyer_segmentation = 'S - Self-Service'
AND b.status = 'Active'
AND b.created_date >= DATEADD(YEAR, -1, GETDATE())
)
SELECT (SELECT CAST(count(DISTINCT buyer_id) AS float) FROM results_cte WHERE total_work_orders_with_gtv_first_60_days > 0)
/ (SELECT CAST(count(DISTINCT buyer_id) AS float) FROM results_cte ) AS percentage

Can I use sum with case statement on a max/min function?

I would like to sum the occurrence of a case.I tried to use the sum on the case statement but I get this error, how can it be solved?
Msg 130, Level 15, State 1, Line 3
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
My code example:
select
sum(case when cast(max(ptl.RowDate)as int) = cast(Min(ptl.RowDate) as int) then 1 else 0 end)
,DATEPART(WEEK, rowdate) week_
from m.dbo.tblLog ptl (nolock)
where rowdate > GETDATE()-91
group by DATEPART(WEEK, rowdate)
Update:
Based on comments: I would like to count distinct users that were visiting the webpage only once.I would like to get the counts based on weekly time frame.
You can not use aggregate functions inside another. You need to use sub-query or CTE:
SELECT SUM(something) as s,
week_
FROM (
select case when cast(max(ptl.RowDate)as int) = cast(Min(ptl.RowDate) as int) then 1 else 0 end as something,
DATEPART(WEEK, rowdate) week_
from m.dbo.tblLog ptl (nolock)
where rowdate > GETDATE()-91
group by DATEPART(WEEK, rowdate)
) as res
GROUP BY week_
Alternative way to your query
SELECT SUM(CASE WHEN CAST(MaxRowDate AS INT) = cast(MinRowDate AS INT) THEN 1 ELSE 0 END)
week_
FROM
(
SELECT MAX(ptl.RowDate) MaxRowDate,MIN(ptl.RowDate) MinRowDate,DATEPART(WEEK, rowdate) week_
FROM m.dbo.tblLog ptl (NOLOCK)
WHERE rowdate > GETDATE()-91
GROUP BY DATEPART(WEEK, rowdate)
)M
GROUP BY week_
If you just want a flag, why would you want a sum():
select (case when cast(max(ptl.RowDate)as int) = cast(Min(ptl.RowDate) as int)
then 1 else 0
end) as OneValueFlag,
DATEPART(WEEK, rowdate) as week_
from m.dbo.tblLog ptl (nolock)
where rowdate > GETDATE() - 91
group by DATEPART(WEEK, rowdate);
It is also entirely unclear why the value would be converted to a date:
select (case when max(ptl.RowDate) = Min(ptl.RowDate)
then 1 else 0
end) as OneValueFlag,
DATEPART(WEEK, rowdate) as week_
from m.dbo.tblLog ptl (nolock)
where rowdate > GETDATE() - 91
group by DATEPART(WEEK, rowdate);

How to show 0 value using COUNT and SELECTon a SQL query

I have ONLY 1 table called Meeting that stores all meeting requests.
This table can be EMPTY.
It has several columns including requestType (which can only be "MT") meetingStatus (can only be either pending, approved, denied or canceled) and meetingCreatedTime
I want to count how many requests of each status's type (in other words how many requests are pending, how many are approved, denied and canceled) for the last 30 days
Problem is that if there is no request then nothing display but I want to display 0, how do I do it? Here is my query now:
SELECT [requestType],
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Approved') As 'Approved',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Pending') As 'Pending',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Canceled') As 'Canceled',
( SELECT COUNT ([requestType]) FROM [Meeting] WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) AND [meetingStatus] = 'Denied') As 'Denied'
FROM [Meeting]
WHERE CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE) GROUP BY [requestType]
Result:
What I want is:
SELECT
RT.requestType,
SUM(CASE WHEN M.meetingStatus = 'Approved' THEN 1 ELSE 0 END) AS Approved,
SUM(CASE WHEN M.meetingStatus = 'Pending' THEN 1 ELSE 0 END) AS Pending,
SUM(CASE WHEN M.meetingStatus = 'Canceled' THEN 1 ELSE 0 END) AS Canceled,
SUM(CASE WHEN M.meetingStatus = 'Denied' THEN 1 ELSE 0 END) AS Denied,
FROM
(SELECT DISTINCT requestType FROM Meeting) RT
LEFT OUTER JOIN Meeting M ON
M.requestType = RT.requestType AND
M.meetingCreatedTime >= DATEADD(DAY, -30, GETDATE())
GROUP BY
RT.requestType
The SUMs are a much clearer (IMO) and much more efficient way of getting the counts that you need. Using the requestType table (assuming that you have one) lets you get results for every request type even if there are no meetings of that type in the date range. The LEFT OUTER JOIN to the meeting table allows the request type to still show up even if there are no meetings for that time period.
All of your CASTs between date values seem unnecessary.
Move those subqueries into simple sum/case statements:
select rt.request_type,
sum(case when [meetingStatus] = 'Approved' then 1 else 0 end),
sum(case when [meetingStatus] = 'Pending' then 1 else 0 end),
sum(case when [meetingStatus] = 'Canceled' then 1 else 0 end),
sum(case when [meetingStatus] = 'Denied' then 1 else 0 end)
from ( select 'MT' ) rt (request_type) --hopefully you have lookup table for this
left
join [Meeting] m on
rt.request_type = m.request_type and
CAST([meetingCreatedTime] AS DATE) >= CAST(DateAdd(DAY,-30,Getdate()) AS DATE)
group
by rt.request_type;
This is one possible approach to force one line to be visible in any case. Adapt this to your needs...
Copy it into an empty query window and execute... play around with the WHERE part...
DECLARE #Test TABLE (ID INT IDENTITY, GroupingKey VARCHAR(100));
INSERT INTO #Test VALUES ('a'),('a'),('b');
SELECT TOP 1 tbl.CountOfA
,tbl.CountOfB
,tbl.CountOfC
FROM
(
SELECT 1 AS Marker
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='a') AS CountOfA
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='b') AS CountOfB
,(SELECT COUNT(*) FROM #Test WHERE GroupingKey='c') AS CountOfC
WHERE (1=1) --play here with (1=0) and (1=1)
UNION ALL
SELECT 2,0,0,0
) AS tbl
ORDER BY Marker

Revenue for two months date wise

I am trying to get data for last 2 month ...but the query does not give perfect result....
SELECT DAY(table_A.PaymentDate) as date1 ,
(case when MONTH(table_A.PaymentDate) = MONTH(CURRENT_TIMESTAMP) - 1
then CAST(SUM(table_A.Total_Amount) AS INT)
else 0
end) AS last_month_CNT,
(case when MONTH(table_A.PaymentDate) = MONTH(CURRENT_TIMESTAMP)
then CAST(SUM(table_A.Total_Amount) As INT)
else 0
end) as This_month_CNT
FROM Tbl_Pan_Paymentdetails table_A
FULL OUTER JOIN Tbl_Pan_Paymentdetails table_B
ON table_A.PaymentDate=table_B.PaymentDate
WHERE YEAR(table_A.PaymentDate) = YEAR(CURRENT_TIMESTAMP)
AND
table_A.PaymentDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY
DAY(table_A.PaymentDate) ,MONTH(table_A.PaymentDate)
order by
DAY(table_A.PaymentDate);
Move the entire case expression inside the sum function and don't include the month in the group by. Also, the full outer join seems unnecessary so I removed it.
This should be what you are looking for:
SELECT
DAY(PaymentDate) as date1 ,
SUM(CASE WHEN MONTH(PaymentDate) = MONTH(CURRENT_TIMESTAMP)-1 THEN CAST(Total_Amount AS INT) ELSE 0 END) AS last_month_CNT,
SUM(CASE WHEN MONTH(PaymentDate) = MONTH(CURRENT_TIMESTAMP) THEN CAST(Total_Amount AS INT) ELSE 0 END) AS This_month_CNT
FROM Tbl_Pan_Paymentdetails
WHERE YEAR(PaymentDate) = YEAR(CURRENT_TIMESTAMP)
AND PaymentDate >= DATEADD(MONTH, -1, GETDATE())
GROUP BY DAY(PaymentDate)
ORDER BY DAY(PaymentDate);