Alternative to using CASE in a GROUP BY - sql

I am trying to find a more compact code form and faster way to execute a query that requires (or at least I think requires) grouping by TWO case statements
I've tried using OVER PARTITION but can't seem to get the desired result
SELECT
CASE
WHEN AA.BUSINESS_SEGMENT LIKE 'YEPPERS%'
THEN 'YES'
ELSE 'NO'
END 'YZY',
CASE
WHEN MONTH(POI.EX_FACTORY_DATE) >= 1 AND MONTH(POI.EX_FACTORY_DATE) <=3
THEN
'Q1'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 4 AND MONTH(POI.EX_FACTORY_DATE) <=6
THEN
'Q2'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 7 AND MONTH(POI.EX_FACTORY_DATE) <=9
THEN
'Q3'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 10 AND MONTH(POI.EX_FACTORY_DATE) <=12
THEN
'Q4'
END 'QTR',
SUM(POI.PO_ORDERED_QUANTITY) PO_QTY
FROM VW_PO_ITEM POI
LEFT JOIN VW_ARTICLE_ATTRIBUTES AA ON AA.ARTICLE = POI.MATERIAL
WHERE POI.DEL_INDICATOR <> 'L' AND POI.EX_FACTORY_DATE BETWEEN '7/1/2019' AND '12/31/2019' AND ((POI.SHIPPING_INSTRUCT='A2' AND AA.BUSINESS_SEGMENT NOT LIKE 'YEPPERS%') OR AA.BUSINESS_SEGMENT LIKE 'YEPPERS%')
GROUP BY
CASE
WHEN AA.BUSINESS_SEGMENT LIKE 'YEPPERS%'
THEN 'YES'
ELSE 'NO'
END,
CASE
WHEN MONTH(POI.EX_FACTORY_DATE) >= 1 AND MONTH(POI.EX_FACTORY_DATE) <=3
THEN
'Q1'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 4 AND MONTH(POI.EX_FACTORY_DATE) <=6
THEN
'Q2'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 7 AND MONTH(POI.EX_FACTORY_DATE) <=9
THEN
'Q3'
WHEN MONTH(POI.EX_FACTORY_DATE) >= 10 AND MONTH(POI.EX_FACTORY_DATE) <=12
THEN
'Q4'
END
--desired result (the query above does produce this)
YZY QTR PO_QTY
-------- ---- ---------------------------------------
NO Q4 389309
YES Q4 649842
YES Q3 843438
NO Q3 20146

I think better is use DATEPART(quarter
FROM MyTable
GROUP BY DATEPART(quarter, columndate)

Consider a CTE to handle unit level calculation before final aggregation, avoiding repetition. Also, look into DatePart to extract date parts from datetime fields:
WITH cte AS (
SELECT CASE
WHEN AA.BUSINESS_SEGMENT LIKE 'YEPPERS%'
THEN 'YES' ELSE 'NO'
END [YZY],
DATEPART(qq, POI.EX_FACTORY_DATE) AS [QTR],
POI.PO_ORDERED_QUANTITY
FROM VW_PO_ITEM POI
LEFT JOIN VW_ARTICLE_ATTRIBUTES AA
ON AA.ARTICLE = POI.MATERIAL
WHERE POI.DEL_INDICATOR <> 'L'
AND POI.EX_FACTORY_DATE BETWEEN '2019-07-01' AND '2019-12-31'
AND (
(POI.SHIPPING_INSTRUCT='A2'
AND AA.BUSINESS_SEGMENT NOT LIKE 'YEPPERS%')
OR
AA.BUSINESS_SEGMENT LIKE 'YEPPERS%'
)
)
SELECT [YZY], [QTR],
SUM(POI.PO_ORDERED_QUANTITY) AS PO_QTY
FROM cte
GROUP BY [YZY], [QTR]

Related

SqlServer : Number of 'open' issues on a daily basis

I'm getting an error with this query "Msg 102, Level 15, State 1, Line 12
Incorrect syntax near ';'" and have been at it for a few hours now. I'm trying to calculate 'open' issues on given days (ideally between a timeframe but for now just on dates that have entries).
My Data is simplified as:
IssueID, CreationDate, CompletionDate
I'd like to tally open issues which is when Year(CompletionDate)=1900 and they are cumulative while they are open, ie: if yesterday there was 1 issue open and today has 1 issue open as well, then today's count of open is 2. They should drop off once they are resolved (Year(CompletionDate) <> 1900). Please help I think i'm close?
SELECT
x.created_date,
aOpen + Open_Issue - Resolved_Issue as totopen
from(
select
convert(varchar(10), cast(i.CreationDate as date), 101) as created_date,
sum( case when YEAR(i.CompletionDate)='1900' then 1 else 0 end) as aOpen,
sum( case when YEAR(i.CompletionDate)<>'1900' AND (i.CompletionDate >=
i.CreationDate) then 1 else 0 end ) as Open_Issue,
coalesce(tot,0) as Resolved_Issue
FROM Issues i
LEFT JOIN (SELECT count(IssueID) as tot, CompletionDate as resolved
from Issues where YEAR(CompletionDate)<>'1900' group by CompletionDate,
count(IssueID))x
ON i.CreationDate = x.resolved);
UPDATE
I have this returning output correctly on a daily basis only, as in it is not accounting for previous, still open issues (Legacy_Open_Issue) and adding them.
SELECT
created_date,
aOpen_Today + Legacy_Open_Issue - Resolved_Issue as totopen
FROM(
SELECT
convert(varchar(10), cast(i.CreationDate as date), 101) as created_date,
sum( case when YEAR(i.CompletionDate)=1900 then 1 else 0 end) as aOpen_Today,
sum( case when (YEAR(i.CompletionDate)<>1900 AND (i.CompletionDate >= i.CreationDate)) then 1 else 0 end ) as Legacy_Open_Issue,
coalesce(tot,0) as Resolved_Issue
FROM Issues i
LEFT JOIN (
SELECT count(IssueID) as tot, CompletionDate as resolved
FROM Issues
WHERE YEAR(CompletionDate)<>1900 group by CompletionDate
)x ON x.resolved = i.CreationDate
GROUP BY convert(varchar(10), cast(i.CreationDate as date), 101), coalesce(tot,0)
) AS y;
My Data is
IssueID CreationDate CompletionDate
1 1/15/2019 1/1/1900
2 1/16/2019 1/17/2019
3 1/16/2019 1/1/1900
4 1/20/2019 1/21/2019
5 1/28/2019 1/1/1900
6 1/30/2019 1/1/1900
My Output is
created_date totopen
1/15/2019 1
1/16/2019 2
1/20/2019 1
1/28/2019 1
1/30/2019 1
My Output SHOULD be
created_date totopen
1/15/2019 1
1/16/2019 3
1/20/2019 3
1/28/2019 3
1/30/2019 4
thank you for your help
You need to alias the derived table such as:
SELECT
x.created_date,
aOpen + Open_Issue - Resolved_Issue as totopen
from(
select
convert(varchar(10), cast(i.CreationDate as date), 101) as created_date,
sum( case when YEAR(i.CompletionDate)='1900' then 1 else 0 end) as aOpen,
sum( case when YEAR(i.CompletionDate)<>'1900' AND (i.CompletionDate >=
i.CreationDate) then 1 else 0 end ) as Open_Issue,
coalesce(tot,0) as Resolved_Issue
FROM Issues i
LEFT JOIN (SELECT count(IssueID) as tot, CompletionDate as resolved
from Issues where YEAR(CompletionDate)<>'1900' group by CompletionDate,
count(IssueID))x
ON i.CreationDate = x.resolved) as DT;
Derived tables require an alias. You need to add "AS {alias}" to the end of your query. You should also format and line break the code for better legibility.
SELECT
x.created_date,
aOpen + Open_Issue - Resolved_Issue as totopen
from(
select
convert(varchar(10), cast(i.CreationDate as date), 101) as created_date,
sum( case when YEAR(i.CompletionDate)=1900 then 1 else 0 end) as aOpen,
sum( case when YEAR(i.CompletionDate)<>1900 AND (i.CompletionDate >= i.CreationDate) then 1 else 0 end ) as Open_Issue,
coalesce(tot,0) as Resolved_Issue
FROM Issues i
LEFT JOIN (
SELECT count(IssueID) as tot, CompletionDate as resolved
from Issues
where YEAR(CompletionDate)<>1900
group by CompletionDate
)x ON i.CreationDate = x.resolved
group by convert(varchar(10), cast(i.CreationDate as date), 101)
) AS y;
Also, SO generally doesn't do multiple questions per post. I addressed the error/alias issue, but if you have results issues, you should post a new question with sample data and expected results.
To be able to use date ranges and ensure there are no gaps in your output (ie. on dates where no tickets were created), you may want to consider using a Dates fact / reference table like this:
Select d.Date
, count(i.IssueID) as TotalOpen
, sum(case when DateDiff(DD, d.Date, cast(i.CreationDate as date)) = 0 then 1 else 0 end) as NewOpened
, sum(case when DateDiff(DD, d.Date, cast(i.CompletionDate as date)) = 0 then 1 else 0 end) as NewClosed
From Dates d
Left join Issues i
on d.Date between convert(varchar(10), cast(i.CreationDate as date), 101) and
case when YEAR(i.CompletionDate)='1900' then d.Date else i.CompletionDate end
Group by d.Date
Fill the Dates table with all the dates you'd want to display results for (ie. everyday, weekdays) or use a where clause to filter the date range / pattern.

How to make one row from result

I am getting following result by using my store procedure. How would I get only one line of result after eliminate the nulls. ?
`cte_Sum
as
(
Select sum(NumberOfCode) as NumberOfCode, CodeName, Code, Quarters, Q1s, Q2s, Q3s, Q4s
from cte_totals
group by CodeName, Code, Quarters, Q1s, Q2s, Q3s, Q4s
)
Select
CodeName,Code,
Case When Quarters = 'Q1' Then NumberOfCode End as Q1_Number,
Case When Quarters = 'Q1' Then Q1s End as Q1_Date,
Case When Quarters = 'Q2' Then NumberOfCode End as Q2_Number,
Case When Quarters = 'Q2' Then Q2s End as Q2_Date,
Case When Quarters = 'Q3' Then NumberOfCode End as Q3_Number,
Case When Quarters = 'Q3' Then Q3s End as Q3_Date,
Case When Quarters = 'Q4' Then NumberOfCode End as Q4_Number,
Case When Quarters = 'Q4' Then Q4s End as Q4_Date
from cte_Sum`
Expected result.
I will self left join with itself for those lines where Q2 - Q4 columns are not NULL.
select t1.CodeName ,t1.Code ,
Q2.Q2_NUmber, Q2.Q2_DAte,
Q3.Q3_NUmber, Q3.Q3_DAte,
Q4.Q4_NUmber, Q4.Q4_DAte
from table t1
left join (select Code, Q2_Number, Q2_Date where Q2_Number is not NULL) Q2 on t1.Code = Q2.Code
left join (select Code, Q3_Number, Q3_Date where Q3_Number is not NULL) Q3 on t1.Code = Q3.Code
left join (select Code, Q4_Number, Q4_Date where Q4_Number is not NULL) Q4 on t1.Code = Q4.Code
Use aggregation:
Select CodeName, Code,
sum(Case When Quarters = 'Q1' Then NumberOfCode End) as Q1_Number,
max(Case When Quarters = 'Q1' Then Q1s End) as Q1_Date,
sum(Case When Quarters = 'Q2' Then NumberOfCode End) as Q2_Number,
max(Case When Quarters = 'Q2' Then Q2s End) as Q2_Date,
sum(Case When Quarters = 'Q3' Then NumberOfCode End) as Q3_Number,
max(Case When Quarters = 'Q3' Then Q3s End) as Q3_Date,
sum(Case When Quarters = 'Q4' Then NumberOfCode End) as Q4_Number,
max(Case When Quarters = 'Q4' Then Q4s End) as Q4_Date
from cte_Sum
group by CodeName, Code;
I don't think the CTE helps. You can also do:
Select CodeName, Code,
sum(Case When Quarters = 'Q1' Then NumberOfCode else 0 End) as Q1_Number,
max(Case When Quarters = 'Q1' Then Q1s End) as Q1_Date,
sum(Case When Quarters = 'Q2' Then NumberOfCode else 0 End) as Q2_Number,
max(Case When Quarters = 'Q2' Then Q2s End) as Q2_Date,
sum(Case When Quarters = 'Q3' Then NumberOfCode else 0 End) as Q3_Number,
max(Case When Quarters = 'Q3' Then Q3s End) as Q3_Date,
sum(Case When Quarters = 'Q4' Then NumberOfCode else 0 End) as Q4_Number,
max(Case When Quarters = 'Q4' Then Q4s End) as Q4_Date
from cte_totals
group by CodeName, Code;
You may also be able to simplify out the cte_totals as well, although your question doesn't show that code.

SQL Efficiency on Date Range or Separate Tables

I'm calculating historical amount from a table in years(ex. 2015-2016, 2014-2015, etc.) I would like to seek expertise if its more efficient to do it in one batch or repeat the query multiple times filtered by the date required.
Thanks in advance!
OPTION 1:
select
id,
sum(case when year(getdate()) - year(txndate) between 5 and 6 then amt else 0 end) as amt_6_5,
...
sum(case when year(getdate()) - year(txndate) between 0 and 1 then amt else 0 end) as amt_1_0,
from
mytable
group by
id
OPTION 2:
select
id, sum(amt) as amt_6_5
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 5 and 6
...
select
id, sum(amt) as amt_1_0
from
mytable
group by
id
where
year(getdate()) - year(txndate) between 0 and 1
1.
Unless you have resources issues I would go with the CASE version.
Although it has no impact on the results, filtering on the requested period in the WHERE clause might have a significant performance advantage.
2. Your period definition creates overlapping.
select id
,sum(case when year(getdate()) - year(txndate) = 6 then amt else 0 end) as amt_6
-- ...
,sum(case when year(getdate()) - year(txndate) = 0 then amt else 0 end) as amt_0
where txndate >= dateadd(year, datediff(year,0, getDate())-6, 0)
from mytable
group by id
This may be help you,
WITH CTE
AS
(
SELECT id,
(CASE WHEN year(getdate()) - year(txndate) BETWEEN 5 AND 6 THEN 'year_5-6'
WHEN year(getdate()) - year(txndate) BETWEEN 4 AND 5 THEN 'year_4-5'
...
END) AS my_year,
amt
FROM mytable
)
SELECT id,my_year,sum(amt)
FROM CTE
GROUP BY id,my_year
Here, inside the CTE, just assigned a proper year_tag for each records (based on your conditions), after that select a summary for the CTE grouped by that year_tag.

SQL: Group SUM values by date ranges and show in multiple columns by date

I have a SQL table showing charge amounts per person and an associated date. I'm looking to create a printout of each person's charges per month. I have the following code which will show me everyone's data for ONE month but I'd like to put this in one report without having to rerun this for every month's date range. Is there a way to pull this data all at once? Essentially I'd like the columns to show:
Last Name January February March Etc
name amt amt amt
Here is my code to pull this data for April as an example. You'll see the dates are codes as YYYYMMDD. This code works perfectly for one month at a time.
select pm.last_name, SUM(amt) as Total_Charge from charges c
inner join provider_mstr pm ON c.rendering_id = pm.provider_id
where begin_date_of_service >= '20150401' and begin_date_of_service <= '20150431'
group by pm.last_name
A generic solution uses SUM over CASEs:
select pm.last_name,
SUM(case when begin_date_of_service >= '20150101' and begin_date_of_service < '20150201' then amt else 0 end) as Total_Charge_Jan,
SUM(case when begin_date_of_service >= '20150201' and begin_date_of_service < '20150301' then amt else 0 end) as Total_Charge_Feb,
SUM(case when begin_date_of_service >= '20150301' and begin_date_of_service < '20150401' then amt else 0 end) as Total_Charge_Mar,
...
from charges c
inner join provider_mstr pm ON c.rendering_id = pm.provider_id
where begin_date_of_service >= '20150101' and begin_date_of_service < '20160101'
group by pm.last_name
Depending on your DBMS you might have a PIVOT function or similar...
Change SUM(amt) as Total_Charge to the following for each period
SUM(CASE WHEN begin_date_of_service BETWEEN '20150401' AND '20150430' THEN amt ELSE 0 END) AS April_Amt
SUM(CASE WHEN begin_date_of_service BETWEEN '20150301' AND '20150331' THEN amt ELSE 0 END) AS March_Amt
And update WHERE clause to include all date ranges for pull.
A condition will evaluate to 1 or 0 in a product, so you could use the following approach to do a selective sum:
select
pm.last_name,
SUM(amt * (begin_date_of_service >= '20150401' and begin_date_of_service <= '20150431')) AS 'april',
SUM(amt * (begin_date_of_service >= '20150501' and begin_date_of_service <= '20150530')) AS 'may'
...
from charges c
inner join provider_mstr pm ON c.rendering_id = pm.provider_id
group by pm.last_name

Using multiple sum case lines in query

This code does exactly what I need it to do for the desired months. Basically provides Numerator and Denominator.
SUM(CASE WHEN smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date BETWEEN '2013-01-01' and '2013-01-31' THEN 1 ELSE 0 END) AS Total_Jan13,
SUM(CASE WHEN smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date BETWEEN '2013-01-01' and '2013-01-31' and [ind_ra_cfvmc_00-30] = 'YES' THEN 1 ELSE 0 END) AS RA_Jan13,
SUM(CASE WHEN smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date BETWEEN '2013-02-01' and '2013-02-28' THEN 1 ELSE 0 END) AS Total_Feb13,
SUM(CASE WHEN smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date BETWEEN '2013-02-01' and '2013-02-28' and [ind_ra_cfvmc_00-30] = 'YES' THEN 1 ELSE 0 END) AS RA_Feb13,
SUM(CASE WHEN smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date BETWEEN '2013-03-01' and '2013-03-31' THEN 1 ELSE 0 END) AS Total_Mar13,
It can get pretty tedious when you have multiple months...is there a more efficient way of performing this calculation?
THanks!
You could group them by the year and month in a sub-select and then manipulate that:
SELECT DATEPART(year, smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date) AS TotalYear,
DATEPART(month, smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date) AS TotalMonth,
COUNT(*) AS Total
FROM [table]
GROUP BY DATEPART(year, smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date),
DATEPART(month, smsdss.c_cfv_pas_fct_pt_acct_vst_all.vst_end_date)
and that would give you the totals by month, without having to specify the months beforehand.