Sum on case expression when working with dates - sql

I'm looking to create a view which will output the data in the following format
AgedPeriod BillValue Status
<1 35000 Outstanding
1-3 23386 Outstanding
3-6 5000 Outstanding
I can use the code below to SUM each case statement into a new column and I could name the column headings after the AgedPeriod listed above but even though the SUMS are right the format is wrong I would like to have the code below nested in another CASE statement that does not have to be GROUPED by b.BILL_DATE as grouping with the bill date defeats the purpose of my SUM. All attempts as using another CASE statement always bring the b.BILL_DATE out of the SUM and into the WHEN condition requiring it to be grouped.
SELECT
SUM(CASE WHEN (b.BILL_DATE <= GetDate()
AND b.BILL_DATE >= DateAdd(mm,-1, GetDate()))
THEN b.OUTSTANDING END),
SUM(CASE WHEN (b.BILL_DATE <= DateAdd(mm,-1, GetDate())
AND b.BILL_DATE >= DateAdd(mm,-3, GetDate()))
THEN b.OUTSTANDING END),
SUM(CASE WHEN (b.BILL_DATE <= DateAdd(mm,-3, GetDate())
AND b.BILL_DATE >= DateAdd(mm,-6, GetDate()))
THEN b.OUTSTANDING END)
FROM dbo.Tables
I understand this may not be achievable with the route that I have taken at present but is there be any other way I can SUM the outstanding amount on each time period? I can deal with the status column (no advice needed there)
I have added a table and some sample data and left a query to show how I would want the data split up but it would want it to be formatted as above (in a column)
Example on Sql Fiddle
Thanks

You want a group by rather than conditional aggregation. The query you want is something like this:
SELECT (CASE WHEN b.BILL_DATE >= DateAdd(month,-1, GetDate())
THEN '<1'
WHEN b.BILL_DATE >= DateAdd(month, -3, GetDate())
THEN '1-3'
WHEN b.BILL_DATE >= DateAdd(month, -6, GetDate())
THEN '3-6'
ELSE '6+'
END) as AgedPeriod,
SUM(Outstanding)
FROM dbo.Tables b
WHERE b.BILL_DATE <= GetDate()
GROUP BY (CASE WHEN b.BILL_DATE >= DateAdd(month,-1, GetDate())
THEN '<1'
WHEN b.BILL_DATE >= DateAdd(month, -3, GetDate())
THEN '1-3'
WHEN b.BILL_DATE >= DateAdd(month, -6, GetDate())
THEN '3-6'
ELSE '6+'
END);
Notes:
The groups are defined by a CASE statement. Because this is evaluated in order, you can simplify the logic.
The common condition b.BILL_DATE <= GetDate() is moved to the WHERE clause.
I added an extra condition for longer than six months. It seems like you wouldn't want to ignore these.
I don't know what the final column is supposed to be.

You could either use a subquery or CTE to perform the case when statement and then join back to the base table to get the sum for the outstanding column like this:
SELECT a.AgedPeriod
,sum(t1.Outstanding) BillValue
,a.[Status]
FROM dbo.Bill t1
JOIN (
SELECT (
CASE
WHEN b.BILLDATE >= DateAdd(month, - 1, GetDate())
THEN '<1'
WHEN b.BILLDATE >= DateAdd(month, - 3, GetDate())
THEN '1-3'
WHEN b.BILLDATE >= DateAdd(month, - 6, GetDate())
THEN '3-6'
ELSE '6+'
END
) AS AgedPeriod
,b.[ID]
,'Outstanding' [Status]
FROM dbo.Bill b
WHERE b.BILLDATE <= GetDate()
) a ON a.[ID] = t1.[ID]
GROUP BY a.AgedPeriod
,a.[Status]
Hope this helps! Here is a SQL Fiddle Demo for this:
SQL Fiddle Solution Demo

Related

SQL Query with date range Male Female

I have a query that works but I need to incorporate in that query the following:
For male date is older or equals 3 months;
For female date is older or equals 4 months;
SELECT *
FROM Davaoci
WHERE DatumPoslednjegDavanja >= DATEADD(month, -3, GETDATE())
AND KrvnaGrupa = 'APos'
ORDER BY DatumPoslednjegDavanja DESC
Use a CASE statement:
SELECT * FROM Davaoci
WHERE DatumPoslednjegDavanja >= DATEADD(
month,
CASE WHEN Pol = 'M' THEN -3 ELSE -4 END,
GETDATE()
)
AND KrvnaGrupa = 'APos'
ORDER BY DatumPoslednjegDavanja DESC
EDITED: Based off your comments on the question, I made some adjustments to my answer. Working through our language barrier, I think this is what you're looking for.
NOTE: The way I have this set up now, it will only accept rows where the Pol column has an 'M' or 'F'. You may need to adjust the ELSE as needed.
SELECT *
FROM Davaoci
WHERE
KrvnaGrupa = 'APos'
AND CASE
WHEN Pol = 'M'
THEN DatumPoslednjegDavanja >= DATEADD(month, -3, GETDATE())
WHEN Pol = 'F'
THEN DatumPoslednjegDavanja <= DATEADD(month, -4, GETDATE())
ELSE FALSE
END
ORDER BY
DatumPoslednjegDavanja DESC;

Roll weekend counts into monday counts

I have a query like this:
select date, count(*)
from inflow
where date >= dateadd(year, -2, getdate())
group by date
order by date
I need to exclude Saturday and Sunday dates, and instead add their counts into the following Monday. What would be the best way to do this? Do I need to exclude Saturday, Sunday, and Mondays, then add them on with a join to a different query? The query above is a simplified version, this is a relatively big query, so I need to keep efficiency in mind.
Well, this is a somewhat brute-force approach:
select date,
(case when datename(weekday, date) = 'Monday')
then cnt + cnt1 + cnt2
else cnt
end) as cnt
from (select date, count(*) as cnt,
lag(count(*), 1, 0) over (order by date) as prev_cnt,
lag(count(*), 2, 0) over (order by date) as prev_cnt2
from inflow
where date >= dateadd(year, -2, getdate())
group by date
) d
where datename(weekday, date) not in ('Saturday', 'Sunday')
order by date;
Note: This is assuming English-language settings so the datename() logic works.
An alternative method without subqueries;
select v.dte, count(*) as cnt
from inflow i cross apply
(values (case when datename(weekday, i.date) = 'Saturday'
then dateadd(day, 2, i.date)
when datename(weekday, i.date) = 'Sunday'
then dateadd(day, 1, 9.date)
else i.date
end)
) v.dte
where i.date >= dateadd(year, -2, getdate())
group by v.dte
order by date;
You state for performance, however without knowing the full picture it's quite hard to understand how to optimise the query.
While I've been working on this, I noticed Gordon Linoff's answer, however I'll continue to write my version up as well, we both following the same path, but get to the answer a little different.
WITH DateData (date, datetoapply)
AS
(
SELECT
[date],
CASE DATEPART(w, [date])
WHEN 5 THEN DATEADD(d, 2, [date])
WHEN 6 THEN DATEADD(d, 1, [date])
ELSE date
END as 'datetoapply'
FROM inflow
WHERE [date] >= dateadd(year, -2, getdate())
)
SELECT datetoapply, count(*)
FROM DateData
GROUP BY datetoapply
ORDER BY datetoapply
While I could not get Gordon's query working as expected, I can confirm that "DATEPART(w, [date])" performs much better than "DATENAME(weekday, [date])", which if replaced in the query above increases the server processing time from 87ms to 181ms based on a table populated with 10k rows in Azure.

MS SQL - 3 'OR's within one OR

I have a View I created using Microsoft Dynamics 2011 and I'm now looking to turn this into SQL code to use within Excel.
I have gotten everything else to work except the OR clauses (the query doesn't return all the results that I am expecting). I'm very new to SQl and any help will be really appreciated.
AND (Child.btb_childhealthlastlacmedical is NULL
OR Child.btb_childhealthlastlacmedical <= DATEADD(month, -12, GETDATE()))
AND (Child.btb_childhealthlastdentaldate is NULL
OR Child.btb_childhealthlastdentaldate <= DATEADD(month, -12, GETDATE()))
AND (Child.btb_childhealthlastopticiandate is NULL
OR Child.btb_childhealthlastopticiandate <= DATEADD(month, -24, GETDATE()))
Final Edit Solved By xQbert's solution below. Thank you so much!!
I think you're looking for something more like this. But you might possibly want and instead or or within the brackets. Not sure what you're looking for. But basically you need to separate statements with or instead of and
AND (Child.btb_childhealthlastlacmedical is NULL OR
Child.btb_childhealthlastlacmedical <= DATEADD(month, -12, GETDATE()))
OR
(Child.btb_childhealthlastdentaldate is NULL OR
Child.btb_childhealthlastdentaldate <= DATEADD(month, -12, GETDATE()))
OR
(Child.btb_childhealthlastopticiandate is NULL OR
Child.btb_childhealthlastopticiandate <= DATEADD(month, -24, GETDATE()))
This will return true if any field meets the criteria
AND ( COALESCE(Child.btb_childhealthlastlacmedical,
DATEADD(month, -12, GETDATE())) <= DATEADD(month, -12, GETDATE())
OR COALESCE(Child.btb_childhealthlastdentaldate ,
DATEADD(month, -12, GETDATE())) <= DATEADD(month, -12, GETDATE())
OR COALESCE(Child.btb_childhealthlastopticiandate ,
DATEADD(month, -12, GETDATE())) <= DATEADD(month, -24, GETDATE())
)
You also want to change your RIGHT outer join to just a JOIN (sometimes called an inner join) This is probably why you are getting more results than you expect.
Don't mix join notations use the ANSI 92 standard (INNER JOIN, OUTER JOIN) not the 89 (comma notation) your present join would exclude the records from the family table not in filtered contact.
--RIGHT OUTER JOINS mean that dbo.FilteredContactcontact is the table with all the records. And then records from dbo.filteredAccount that match those in child and then those in filteredBusinessUnit that match. As such the fil.name filter belongs on the join.
The filters on the child table belong on the right table. I'm really surprised no one else caught me on this...
SELECT Child.fullname AS Child
FROM dbo.FilteredBusinessUnit Fil
RIGHT JOIN dbo.FilteredAccount Family
ON Fil.businessunitid=Family.owningbusinessunit
RIGHT JOIN dbo.FilteredContact Child
ON Child.accountid=Family.accountid
WHERE (Child.btb_childhealthlastlacmedical is NULL
OR Child.btb_childhealthlastlacmedical <= DATEADD(month, -12, GETDATE())
OR Child.btb_childhealthlastdentaldate is NULL
OR Child.btb_childhealthlastdentaldate <= DATEADD(month, -12, GETDATE())
OR Child.btb_childhealthlastopticiandate is NULL
OR Child.btb_childhealthlastopticiandate <= DATEADD(month, -24, GETDATE()) )
AND Child.customertypecode = 1
AND Child.owningbusinessunit = 'North West'
Note when using outer joins, any limiting criteria on tables aside from the "all records table (FilteredContactin this case)" should be placed on the join, or the outer join is negated and behaves like an inner join.
I'm not sure why you have the null checks if its because some records are null or if you had the null check to keep the records from the outer join. If the latter then this way we don't need the null checks. If the first then we need to keep the null checks.

SQL Server query to get data for last two months

SELECT
DAY(table_A.PaymentDate) as date1 ,
(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,
(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
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, -2, GETDATE())
GROUP BY
DAY(table_A.PaymentDate),
MONTH(table_A.PaymentDate)
ORDER BY
DAY(table_A.PaymentDate) ;
Not sure I fully understand.
WHERE YEAR(table_A.PaymentDate) = YEAR(CURRENT_TIMESTAMP) AND
table_A.PaymentDate >= DATEADD(MONTH, -2, GETDATE())
Here you are (1) comparing the Year elements of your payment date with CURRENT_TIMESTAMP, and (2) making sure the payment date is greater than the last 2 months based on GETDATE()?
Not sure why you are using both CURRENT_TIMESTAMP and GETDATE(). Either way, I think the second part of that WHERE statement does what you want.
If the current date is January 31, 2015, your currently logic will not return any records from December 2014. The first part of your where statement is filtering them out. If you really want the last 2 months, remove the following from the WHERE statement
YEAR(table_A.PaymentDate) = YEAR(CURRENT_TIMESTAMP) AND

SQL - Display running count of appointments grouped by time frame

I have a table
tblAppointment {
App_ID,
App_Date,
User_ID }
I currently have a statement that returns number of appointments grouped by year and month
SELECT
YEAR(App_Date) AS Year,
MONTH(App_Date) AS Month,
count(*) AS "No of Appointments"
FROM
tblapplication
GROUP BY
YEAR(App_Date),
MONTH(App_Date)
Im not sure how to write a select statement to return it with headings {time frame, No of applications}, and then have data in
row 1: time frame = week thus far, no of app = x.
row 2: time frame = month thus far, no of app = y.
ro3 3: time frame = year so far, no of app - z.
I would like to know how many appointments there are for 1. the current week, 2. the current month. 3. the current year, And have each result in its own row.
Any help in the right direction would be greatly appreciated. The actual problem is much greater than this but believe I have simplified it to the crux of the matter for now.
If you're okay with the results on one row, it's relatively easy:
select count(case when datepart(wk, App_Date) = datepart(wk, getdate())
then 1 end) as WeekSofFar
, count(case when datepart(m, App_Date) = datepart(m, getdate())
then 1 end) as MonthSofFar
, count(*) as YearSoFar
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
If the separate rows are a must-have, try something like:
select 'WeekSoFar' as Period
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(wk, App_Date) = datepart(wk, getdate())
) as NumberOfApps
union all
select 'MonthSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
and datepart(m, App_Date) = datepart(m, getdate())
)
union all
select 'YearSoFar'
, (
select count(*)
from tblApplications
where datepart(y, App_Date) = datepart(y, getdate())
)
I assumed SQL 2008 as you didn't specify.
SELECT
X.TimePeriod,
Count(
CASE X.Which
WHEN 3 THEN
CASE
WHEN App_Date > DateAdd(Day, -DatePart(Weekday, GetDate()), GetDate())
THEN 1
END
WHEN 2 THEN CASE WHEN Month(App_Date) = Month(GetDate()) THEN 1 END
ELSE 1 END
) [No of Appointments]
FROM
tblApplication
CROSS JOIN (
VALUES (1, 'Year to date'), (2, 'Month to date'), (3, 'Week to date')
) X (Which, TimePeriod)
WHERE
App_Date < Convert(date, GetDate() + 1)
AND App_Date >= DateAdd(Year, DateDiff(Year, '19000101', App_Date), '19000101')
GROUP BY
X.Which,
X.TimePeriod
ORDER BY
X.Which
If you have a lot of data in your table and an index on App_Date, this query will perform hugely better than one using date functions on the entire table without filtering.
I also have an embedded assumption that your App_Date values have no time portion (they are all set to 12am). If this is not true please let me know so I can modify case 3 to be correct.
If anyone wants to try the code here's some setup:
CREATE TABLE tblApplication (
App_Date datetime
)
INSERT tblApplication
VALUES
('2/1/2012'), ('2/5/2012'), ('2/10/2012'), ('1/2/2012'), ('1/9/2012'),
('1/15/2012'), ('1/28/2012'), ('12/1/2012'), ('12/5/2012'), ('12/10/2012'),
('11/2/2012'), ('11/9/2012'), ('11/15/2012'), ('11/28/2012')
Sorry about not using 'YYYYMMDD', I wasn't thinking when I typed it out.