I am trying to write an Ageing Report on SQL Server which shows the total amount of overdue invoices (later on I will have to deduct Credit Notes) that fall in the different columns depending on how many days the have been overdued. I.e (>0), (0-30), (31-60), (61-90), etc.
This is the part of the query I have written so far mostly looking at old post in this forum but it's giving me a lot of duplicates even for Accounts where there is not due balance.
Any idea what I am doing wrong?
SELECT O.cardcode AS [Account],
O.cardname AS [Name],
O.u_creditlimit AS [Credit Limit],
O.u_onhold AS [On Hold],
O.balance,
Isnull(CASE
WHEN Datediff(day, INV.docduedate, Getdate()) >= 0 AND Datediff(day, INV.docduedate, Getdate()) < 30
THEN (
SELECT Sum(doctotal)
FROM oinv
WHERE cardcode = INV.cardcode)
END, 0) AS [0 to 30 Days],
Isnull(CASE
WHEN Datediff(day, INV.docduedate, Getdate()) >= 31 AND Datediff(day, INV.docduedate, Getdate()) < 60
THEN (
SELECT Sum(doctotal)
FROM oinv
WHERE cardcode = INV.cardcode)
END, 0) AS [31 to 60 Days],
Isnull(CASE
WHEN Datediff(day, INV.docduedate, Getdate()) >= 61 AND Datediff(day, INV.docduedate, Getdate()) < 90
THEN (
SELECT Sum(doctotal)
FROM oinv
WHERE cardcode = INV.cardcode)
END, 0) AS [61 to 90 Days],
Isnull(CASE
WHEN Datediff(day, INV.docduedate, Getdate()) >= 91 AND Datediff(day, INV.docduedate, Getdate()) < 120
THEN (
SELECT Sum(doctotal)
FROM oinv
WHERE cardcode = INV.cardcode)
END, 0) AS [91 to 120 Days],
Isnull(CASE
WHEN Datediff(day, INV.docduedate, Getdate()) >= 121
THEN(
SELECT Sum(doctotal)
FROM oinv
WHERE cardcode = INV.cardcode)
END, 0) AS [121+ Days]
FROM ocrd O
INNER JOIN oinv INV
ON O.cardcode = INV.cardcode
WHERE territory = 3
AND INV.docstatus = 'O'
Thank you very much.
You can clean this up a bit
First. use a CROSS APPLY to calculate the Days-Past-Due once, and then a conditional aggregation for the final results
Example (Untested)
Select O.cardcode
,O.cardname
,[Credit Limit] = max(O.u_creditlimit)
,[On Hold] = max(O.u_onhold)
,[0 to 30 Days] = sum( case when DPD between 0 and 30 then doctotal else 0 end)
,[31 to 60 Days] = sum( case when DPD between 31 and 60 then doctotal else 0 end)
,[61 to 90 Days] = sum( case when DPD between 61 and 90 then doctotal else 0 end)
,[91 to 120 Days] = sum( case when DPD between 91 and 120 then doctotal else 0 end)
,[121+ Days ] = sum( case when DPD >=121 then doctotal else 0 end)
From ocrd O
Join oinv INV on O.cardcode = INV.cardcode
Cross Apply (values ( Datediff(day, INV.docduedate, Getdate()) ) ) P(DPD)
Where territory = 3
and INV.docstatus = 'O'
and DPD >= 0
Group By O.cardcode
,O.cardname
EDIT - CREDIT NOTES
Without sample data or structures here is my GUESS
Select O.cardcode
,O.cardname
,[Credit Limit] = max(O.u_creditlimit)
,[On Hold] = max(O.u_onhold)
,[0 to 30 Days] = sum( case when DPD between 0 and 30 then doctotal - isnull(creditnotes,0) else 0 end)
,[31 to 60 Days] = sum( case when DPD between 31 and 60 then doctotal - isnull(creditnotes,0) else 0 end)
,[61 to 90 Days] = sum( case when DPD between 61 and 90 then doctotal - isnull(creditnotes,0) else 0 end)
,[91 to 120 Days] = sum( case when DPD between 91 and 120 then doctotal - isnull(creditnotes,0) else 0 end)
,[121+ Days ] = sum( case when DPD >=121 then doctotal - isnull(creditnotes,0) else 0 end)
From ocrd O
Join oinv INV on O.cardcode = INV.cardcode
LEFT JOIN CREDITNOTESTable CN ON O.cardcode = CN.cardcode
Cross Apply (values ( Datediff(day, INV.docduedate, Getdate()) ) ) P(DPD)
Where territory = 3
and INV.docstatus = 'O'
and DPD >= 0
Group By O.cardcode
,O.cardname
when asking questions is a great idea to provide demo data, and a better one to provide it as an object we can easily re recreate:
DECLARE #table TABLE (RecordID INT IDENTITY, CardCode INT, CardName NVARCHAR(100), u_CreditLimit DECIMAL(10,2), u_onhold DECIMAL(10,2), balance DECIMAL(10,2))
INSERT INTO #table (CardCode, CardName, u_CreditLimit, u_onhold, balance) VALUES (1, 'John Smith', 10000, 0, 200),
(1, 'John Smith', 10000, 0, 400),
(1, 'John Smith', 10000, 0, 200)
This would allow someone to just run the TSQL to create and populate the object.
Now using that object we could write something like
SELECT RecordID, CardCode, CardName, U_CreditLimit, U_OnHold, Balance, COALESCE(LAG(Balance,1) OVER (PARTITION BY CardCode ORDER BY RecordID) - Balance,Balance) AS RunningTotal
FROM #table
This psudeo code, and may need some tweaking to get exactly what you're looking for.
A quick word on LAG and its pal LEAD. You specify a column and an offset in rows. LAG looks backwards, LEAD forwards. They both use over clauses just like any other windowed function.
Related
I want to separate the morning and evening patient checked-in count gender-wise and also if gender age less then show the Female child and M child on the basis of gender.
SELECT
COUNT(ch.EnteredOn) AS counter,
CONVERT(date, ch.EnteredOn) AS sessionDay,
SUM(CASE WHEN CAST(CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) < 12 THEN 1 ELSE 0 END) AS 'Morning',
SUM(CASE WHEN CAST(CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) >= 12 THEN 1 ELSE 0 END) AS 'Evening',
SUM(CASE WHEN p.Gender = 1 THEN 1 ELSE 0 END) AS Male,
SUM(CASE WHEN p.Gender = 2 THEN 1 ELSE 0 END) AS Fmale,
SUM(CASE WHEN DATEDIFF(hour, P.DOB, GETDATE()) / 8766 <= 18
AND P.Gender = 1 THEN 1 ELSE 0 END) AS MChild,
SUM(CASE WHEN DATEDIFF(hour, P.DOB, GETDATE()) / 8766 <= 18
AND P.Gender = 2 THEN 1 ELSE 0 END) AS FChild
FROM
Patient.CheckIn ch
INNER JOIN
Patient.Patients P ON ch.PatientId = p.PatientId
GROUP BY
Ch.EnteredOn
I have only three columns like Gender, Time and DOB. Gender id 1 shows the male and Gender id 2 is using for females.
enter image description here
SELECT convert(date, ch.EnteredOn) AS sessionDay, CAST( CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) <12 as morning, count(ch.EnteredOn) AS counter
,sum(case when p.Gender=1 Then 1 ELSE 0 end) as Male
,sum(case when p.Gender=2 Then 1 ELSE 0 end) as Fmale
,Sum( case when DATEDIFF(hour,P.DOB,GETDATE())/8766<=18 AND P.Gender=1 Then 1 ELSE 0 end ) as MChiled
,Sum( case when DATEDIFF(hour,P.DOB,GETDATE())/8766<=18 AND P.Gender=2 Then 1 ELSE 0 end ) as FChiled
FROM Patient.CheckIn ch
inner join Patient.Patients P on ch.PatientId=p.PatientId
Group by Ch.EnteredOn
Group BY can be
Group by convert(date, ch.EnteredOn) AS sessionDay, CAST( CONVERT(CHAR(2), ch.EnteredOn, 108) AS INT) <12
I try to count the no. of bills received between 30,60 and above 90 days.
Here is my T-SQL query:
SELECT
costcentreid,
'Current' = CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) < 30
THEN COUNT(PVNo)
END,
'30_days' = CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) BETWEEN 30 AND 60
THEN COUNT(PVNo)
END,
'60_days' = CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) BETWEEN 60 AND 90
THEN COUNT(PVNo)
END,
'90_plus' = CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) > 90
THEN COUNT(PVNo)
END
FROM
SPRGMMS..PVRegister
GROUP BY
CostCentreId
When I run this, I get an error:
Msg 8120, Level 16, State 1, Line 2
Column '[DB]..PVRegister.BillDate' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Kindly help
You need the aggregation outside
SELECT
costcentreid,
COUNT(CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) < 30
THEN PVNo
END) as 'Current',
COUNT(CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) BETWEEN 30 AND 60
THEN PVNo
END) as '30_days',
COUNT(CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) BETWEEN 60 AND 90
THEN PVNo
END) as '60_days',
COUNT(CASE
WHEN DATEDIFF(DAY, BillDate, Getdate()) > 90
THEN PVNo
END) as '90_plus'
FROM
SPRGMMS..PVRegister
GROUP BY CostCentreId
SELECT
costcentreid,
SUM(CASE WHEN d = 1 THEN 1 ELSE 0 END) AS '30_days'
SUM(CASE WHEN d = 2 THEN 1 ELSE 0 END) AS '60_days',
SUM(CASE WHEN d = 3 THEN 1 ELSE 0 END) AS '90_days',
FROM (
SELECT PVNo, costcentreid, DATEDIFF(DAY, BillDate, Getdate())) / 30 AS d FROM SPRGMMS..PVRegister
) T
WHERE d <= 3
GROUP BY costcentreid
I have a table with the following data:
ItemId PendingTime
1 2016-11-23 15:57:56.000
2 2016-07-09 17:33:09.000
3 2015-11-27 18:34:03.000
and so on..
I would like to know sum of items pending since last 30 mins, last 1 hr, last 1 week, last 1 month and over an year. I came up with the following query:
SELECT
SUM(CASE
WHEN datediff(MINUTE, PENDING_TIME,GETDATE()) <= 30
THEN 1 ELSE 0
END) AS '30 Min',
Sum(case when (datediff(MINUTE,PENDING_TIME, GETDATE())) > 30 AND (datediff(MINUTE,PENDING_TIME, GETDATE())) <= 60 then 1 Else 0 End) as 'Over 30 Min',
Sum(case when (datediff(MINUTE,PENDING_TIME, GETDATE())) > 60 AND (datediff(HOUR,PENDING_TIME, GETDATE())) <= 24 then 1 Else 0 End) as 'Over 1 Hr',
Sum(case when (datediff(DAY,PENDING_TIME, GETDATE())) > 1 AND (datediff(DAY,PENDING_TIME, GETDATE())) < 30 then 1 Else 0 End) as '1 month',
Sum(case when datediff(DAY,PENDING_TIME, GETDATE()) >= 30 then 1 Else 0 End) as 'More than month'
from ItemsTable where datepart(yyyy,PENDING_TIME) = DATEPART(yyyy,GETDATE())
This returns data as:
30 Min Over 30 Min Over 1 Hr 1 month More than month
----------- ----------- ----------- ----------- ---------------
100 350 NULL NULL NULL
I would like the results in 2 column format as:
30 Min ------ 45
Over 30 ------ 100
Over 1 hr ------ null
Over 1 month ------ null
I am aware of the pivot function, however the columns I have selected aren't really in the table I am selecting from, rather they are created as result of aggregate function.
How do I get this in 2 column and multiple rows format.
Thanks for reading through.
You can create a derived column which classifies the waits into different types, then group by that column:
With waitCodes As (
Select Case
When DateDiff(Minute, PENDING_TIME, GetDate()) <= 30 Then '30 Min'
When DateDiff(Minute, PENDING_TIME, GetDate()) <= 60 Then 'Over 30'
When DateDiff(Minute, PENDING_TIME, GetDate()) <= 1440 Then 'Over 1 hr'
When DateDiff(Minute, PENDING_TIME, GetDate()) <= 43200 Then '1 month'
Else 'More than a month' End As [WaitTime]
From ItemsTable
Where DatePart(yyyy, PENDING_TIME) = DatePart(yyyy,GetDate()))
Select [WaitTime], Count(*) As n
From waitCodes
Group By [WaitTime];
In addition, I strongly recommend you change the Where clause in your query so that it doesn't apply a function to the PENDING_TIME column. There are multiple benefits of this.
...
Where PENDING_TIME >= Convert(date, Convert(char(4), DatePart(yyyy, GetDate()))+'-01-01 00:00:00.000', 121)
And PENDING_TIME < Convert(date, Convert(char(4), DatePart(yyyy, GetDate())+1)+'-01-01 00:00:00.000', 121)
....
you can use cross apply statement per orignal sql like this
SELECT c.* FROM
(
SELECT
SUM(CASE
WHEN datediff(MINUTE, PENDING_TIME,GETDATE()) <= 30
THEN 1 ELSE 0
END) AS '30 Min',
Sum(case when (datediff(MINUTE,PENDING_TIME, GETDATE())) > 30 AND (datediff(MINUTE,PENDING_TIME, GETDATE())) <= 60 then 1 Else 0 End) as 'Over 30 Min',
Sum(case when (datediff(MINUTE,PENDING_TIME, GETDATE())) > 60 AND (datediff(HOUR,PENDING_TIME, GETDATE())) <= 24 then 1 Else 0 End) as 'Over 1 Hr',
Sum(case when (datediff(DAY,PENDING_TIME, GETDATE())) > 1 AND (datediff(DAY,PENDING_TIME, GETDATE())) < 30 then 1 Else 0 End) as '1 month',
Sum(case when datediff(DAY,PENDING_TIME, GETDATE()) >= 30 then 1 Else 0 End) as 'More than month'
from ItemsTable where datepart(yyyy,PENDING_TIME) = DATEPART(yyyy,GETDATE())
)
CROSS APPLY(VALUES('30 MIN',a),('Over 30',b),('Over 1 hr',c),('Over 1 month',d),('More than month',e)) c(title,[value])
result:
title value
--------------- -----------
30 MIN 100
Over 30 350
Over 1 hr NULL
Over 1 month NULL
More than month NULL
I have a query that returns a score on whether or not 3 of the columns = 1 and then if the ProviderID exists in the 2nd table. I need to be able to return a score for each month for 6 months using column Time_Stamp, not including current month. The below returns the score for last month. How can I include the remaining 5 months and ROW_NUMBER() them?
DECLARE #ProviderID int = '1717';
WITH cte as
(
SELECT TOP 1
a.ProviderID, Time_Stamp,
SUM(CASE WHEN [AdditionalReports] = '1' THEN 5 ELSE 0 END) as AdditionalReports,
SUM(CASE WHEN [UniqueReportRequests] = '1' THEN 15 ELSE 0 END) as UniqueReportsRequests,
SUM(CASE WHEN [SurveyCompleted] = '1' THEN 30 ELSE 0 END) as SurveyCompleted,
MAX(CASE WHEN b.ProviderID IS NULL THEN 0 ELSE 50 END) as SubscriptionExists
FROM
ProviderValueCard a
LEFT JOIN
SubscriptionsTV b ON a.ProviderID = b.ProviderID
WHERE
a.ProviderID = #ProviderID AND GroupID = 2
AND Time_Stamp BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0) AND DATEADD(DAY, -(DAY(GETDATE())), GETDATE())
GROUP BY
Time_Stamp, a.ProviderID, event
ORDER BY
event DESC, Time_Stamp DESC
)
SELECT
ProviderID, Time_Stamp,
(AdditionalReports + UniqueReportsRequests + SurveyCompleted + SubscriptionExists) AS TotalScore
FROM
cte
Here is how to grab the first/last day of previous months:
2 months ago:
DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 2, 0) as FirstD2monthsago,
DATEADD(DAY, -DAY(GETDATE()), DATEADD(MONTH, -1, GETDATE())) AS last_day_2_months_ago
3 months ago etc:
DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 3, 0) as FirstD3monthsago,
DATEADD(DAY, -DAY(GETDATE()), DATEADD(MONTH, -2, GETDATE())) AS last_day_3_months_ago
Desired output
ProviderID Time_Stamp TotalScore Row_Number
----------- ----------------------- -----------
1717 2014-08-29 12:11:17.610 70 1
1717 2014-07-29 12:11:17.610 95 2
1717 2014-06-29 12:11:17.610 100 3
1717 2014-05-29 12:11:17.610 70 4
1717 2014-04-29 12:11:17.610 70 5
1717 6
DECLARE #ProviderID INT, #Now DATETIME, #Months INT
SELECT #ProviderID = 1717, #Now = GETDATE(), #Months = 5
WITH
date_range_cte AS (
SELECT 1 AS RowNum, DATEADD(mm,-1,#Now) AS StartDate, DATEADD(mm,0,#Now) AS EndDate
UNION ALL
SELECT d.RowNum + 1 AS RowNum, DATEADD(mm,(-d.RowNum - 1),#Now) AS StartDate, DATEADD(mm,-d.RowNum,#Now) AS EndDate
FROM date_range_cte d
WHERE d.RowNum + 1 <= #Months
),
main_cte AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY a.ProviderID, d.RowNum, d.StartDate ORDER BY Time_Stamp DESC) AS ordinal_position,
a.ProviderID,
d.RowNum,
d.StartDate,
[AdditionalReports] * 5 AS AdditionalReports,
[UniqueReportRequests] * 15 AS UniqueReportsRequests,
[SurveyCompleted] * 30 as SurveyCompleted,
CASE WHEN b.ProviderID IS NULL THEN 0 ELSE 50 END as SubscriptionExists
FROM ProviderValueCard a
INNER JOIN date_range_cte d ON d.StartDate < Time_Stamp AND Time_Stamp <= d.EndDate
LEFT OUTER JOIN SubscriptionsTV b ON a.ProviderID = b.ProviderID
WHERE a.ProviderID = #ProviderID AND GroupID = 2
)
SELECT ProviderID, RowNum, StartDate, (AdditionalReports + UniqueReportsRequests + SurveyCompleted + SubscriptionExists) AS TotalScore
FROM main_cte
WHERE ordinal_position = 1
ORDER BY RowNum
Here's a couple of ways (psuedocode):
1 - Make a cte just like your existing one for each month you want to get. The only thing you need to change in each one is this line:
AND Time_Stamp BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0) AND DATEADD(DAY, -(DAY(GETDATE())), GETDATE())
For 2 months ago, you would change it to this:
AND Time_Stamp BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 2, 0) AND DATEADD(mm, DATEDIFF(mm, 0, GETDATE()) - 1, 0)
and so on for the other months back.
2 - Referring to the same line in your cte above, change it to a -6 to get data for the past 6 months. Then include a MONTH(TimeStamp) column in your select list and group by it to get one row per month for the past 6 months.
I have a table structure from there i have to make a query in some different way
Table Structure
id unique identifier
code varchar(5) Not Null
Recording_date Datetime Not Null
Max_Temp numeric(5,2) Not Null
Min_Temp numeric(5,2) Not Null
We have some data as well in this table.We have data only for 2013 year and for first 3 months.
But the main thing is that i have to return's data in such a format like
Please help me to create a query for such a logic.
Thanks in advance.
Presuming you have one recording per day then
SELECT
DATEPART(m, Month_Start) + ' ' + DATEPART(yyyy, Month_Start)
, Max_Temp_Days
, CASE
WHEN Denominator = 0 THEN 0
ELSE (Max_Temp_Days / Denominator) * 100
END AS Percent_Max_Temp_Days
, Min_Temp_Days
, CASE
WHEN Denominator = 0 THEN 0
ELSE (Min_Temp_Days / Denominator) * 100
END AS Percent_Max_Temp_Days
FROM (
SELECT
DATEADD(MONTH, DATEDIFF(MONTH, 0, Recording_Date), 0) Month_Start
, Sum(CASE WHEN Max_Temp <= 0 THEN 1 END) Max_Temp_Days
, Sum(CASE WHEN Min_Temp <= 0 THEN 1 END) Min_Temp_Days
, COUNT(*) Denominator
FROM TemperatureRecordings
WHERE Recording_Date BETWEEN '2013-01-01' AND '2013-03-31'
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Recording_Date), 0)
) t
ORDER BY Month_Start
This works for all month data
DECLARE #maxTempratureTable table(monthName varchar(20), [No. of days Max<=0] int,[Percentage Max <=0] float)
INSERT INTO maxTempratureTable
SELECT RIGHT(CONVERT(VARCHAR, Recording_date, 106), 8) ,
COUNT(*) ,
COUNT(*) / DAY(DATEADD(mm,DATEDIFF(mm,-1,Recording_date),-1)) * 100
FROM tablename
WHERE Max_Temp <=0
GROUP BY RIGHT(CONVERT(VARCHAR, Recording_date, 106), 8)
DECLARE #minTempratureTable table(monthName varchar(20), [No. of days Min<=0] int,[Percentage Min<=0] float)
INSERT INTO #minTempratureTable
SELECT RIGHT(CONVERT(VARCHAR, Recording_date, 106), 8) ,
COUNT(*) ,
COUNT(*) / DAY(DATEADD(mm,DATEDIFF(mm,-1,Recording_date),-1)) * 100
FROM tablename
WHERE Min_Temp <=0
GROUP BY RIGHT(CONVERT(VARCHAR, Recording_date, 106), 8)
SELECT * FROM #minTempratureTable min
INNER JOIN #maxTempratureTable max
ON min.monthName = max.monthName
SELECT
MONTH(Recording_date),
SUM(CASE WHEN Max_Temp <= 0 THEN 1 ELSE 0 END),
SUM(CASE WHEN Max_Temp <= 0 THEN 1 ELSE 0 END) / COUNT(*),
SUM( CASE WHEN Min_Temp <= 0 THEN 1 ELSE 0 END ),
SUM( CASE WHEN Min_Temp <= 0 THEN 1 ELSE 0 END ) / COUNT(*)
FROM temperatures
GROUP BY MONTH(Recording_date)