SQL Server transpose a query result - sql-server

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

Related

Creating Ageing report (>0), (0-30), (31-60)

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.

How can separate morning and evening shifts patients with gender wise using SQL stored procedure

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

Aging report 30,60,90 above

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

pass date dynamically to a query to calculate day wise results

I created a new topic as i am trying to divide my initial question into multiple pieces.
The initial topic can be found in the following link.
I have created a SQL Fiddle link with table and sample data.
This is the Query that i have right now.
SELECT
Order1,
COUNT(UNit.UNIT) AS Units,
SUM(CASE
WHEN (DATEDIFF(dd, INSV_DATE, '2015-01-21')) >= 31 THEN 31
WHEN (DATEDIFF(dd, INSV_DATE, '2015-01-21')) < 0 THEN 0
ELSE (DATEDIFF(dd, INSV_DATE, '2015-01-21'))
END) AS Days31
FROM UNIT
WHERE Unit.INSV_DATE < '2015-01-21'
AND UNIT.MODEL IN ('Toyota')
AND (UNIT.Customer IN ('Jona'))
GROUP BY [Order1],
customer
how do i loop and pass date dynamically in the Datediff for a period of one month?
I want the 31 days output calculated for day wise.
The output should be like
Date | Order1 | Unit | Day31
----------------------------------
May20 | 90909 | 5 | 128
May19 | 90909 | 4 | 124
May17 | 90909 | 2 | 62
I actually want to do something like the following.
SELECT
Order1,
COUNT(UNit.UNIT) AS Units,
SUM(CASE
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-20')) >= 31 THEN 31
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-20')) < 0 THEN 0
ELSE (DATEDIFF(dd, INSV_DATE, '2015-05-20'))
END) AS Days31
FROM UNIT
WHERE Unit.INSV_DATE < '2015-05-20'
AND UNIT.MODEL IN ('Toyota')
AND (UNIT.Customer IN ('Jona'))
GROUP BY [Order1],
customer
SELECT
Order1,
COUNT(UNit.UNIT) AS Units,
SUM(CASE
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-19')) >= 31 THEN 31
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-19')) < 0 THEN 0
ELSE (DATEDIFF(dd, INSV_DATE, '2015-05-19'))
END) AS Days31
FROM UNIT
WHERE Unit.INSV_DATE < '2015-05-19'
AND UNIT.MODEL IN ('Toyota')
AND (UNIT.Customer IN ('Jona'))
GROUP BY [Order1],
customer
SELECT
Order1,
COUNT(UNit.UNIT) AS Units,
SUM(CASE
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-18')) >= 31 THEN 31
WHEN (DATEDIFF(dd, INSV_DATE, '2015-05-18')) < 0 THEN 0
ELSE (DATEDIFF(dd, INSV_DATE, '2015-05-18'))
END) AS Days31
FROM UNIT
WHERE Unit.INSV_DATE < '2015-05-18'
AND UNIT.MODEL IN ('Toyota')
AND (UNIT.Customer IN ('Jona'))
GROUP BY [Order1],
customer
Running the same query for everyday with the different date.
If you can guide me in making a day wise query that will be great.
You can build the list of dates using a recursive CTE, then CROSS JOIN it against your table to get the list of days you want:
DECLARE #StartDate date = 'Jan 1, 2015'
DECLARE #EndDate date = DATEADD(DAY, 30, #StartDate)
;WITH cte AS (
SELECT #StartDate AS ReportDate
UNION ALL
SELECT DATEADD(DAY, 1, ReportDate)
FROM cte
WHERE ReportDate < #EndDate
)
SELECT Order1,COUNT(UNit.UNIT) As Units,sum(CASE
WHEN (datediff(dd,INSV_DATE,cte.ReportDate)) >= 31 THEN 31
WHEN (datediff(dd,INSV_DATE,cte.ReportDate)) < 0 THEN 0
ELSE (datediff(dd,INSV_DATE,cte.ReportDate))END) as Days31
FROM UNIT
CROSS JOIN cte
WHERE Unit.INSV_DATE < cte.ReportDate AND
UNIT.MODEL in('Toyota') AND(UNIT.Customer in('Jona' ))
group by [Order1],customer
Based on my understanding of what you want, here is my "guess":
DECLARE #startDate DATE = CAST(MONTH(GETDATE()) AS VARCHAR) + '/' + '01/' + + CAST(YEAR(GETDATE()) AS VARCHAR) -- cast as mm/dd/yyyy
DECLARE #endDate DATE = GETDATE() -- mm/dd/yyyy
--creates a list of dates for the running month
;WITH month_dates
AS (
SELECT [Date] = DATEADD(Day, Number, #startDate)
FROM master.dbo.spt_values
WHERE Type = 'P'
AND DATEADD(day, Number, #startDate) <= #endDate
)
SELECT month_dates.[Date]
,Order1
,COUNT(UNit.UNIT) AS Units
,sum(CASE
WHEN (datediff(dd, INSV_DATE, month_dates.[Date])) >= 31
THEN 31
WHEN (datediff(dd, INSV_DATE, month_dates.[Date])) < 0
THEN 0
ELSE (datediff(dd, INSV_DATE, month_dates.[Date]))
END) AS Days31
FROM UNIT
CROSS JOIN month_dates --cross join list of dates
WHERE Unit.INSV_DATE < month_dates.[Date]
AND UNIT.MODEL IN ('Toyota')
AND (UNIT.Customer IN ('Jona'))
AND UNIT.Order1 = 'A1056729' --added this filter to test the output
GROUP BY month_dates.[Date]
,[Order1]
,customer
ORDER BY month_dates.[Date] desc

Quickest way to get aggregate date statistics / pivot

I am tryig to write what must be a fairly common audit report; number of rows added to a table over time; reported back against previous cycles to understand the trends in the data.
I have a table that audits creation of rows in the database. It has a field RowEnteredDate date time. I am looking to create an audit report Week/ Month/ Current Quarter / Year.
In my head I am looking at this as multiple passes over the data around the dates; which is quite costly in my database. My reasoning at the moment is
I started out with working out the dates for my year / month / quarter
set datefirst 1
declare #dateranges table (
rangelabel varchar(100),
startdate datetime,
enddate datetime,
myrowcount integer identity(1,1)
)
insert into #dateranges (Rangelabel, startdate, enddate)
select
'Current Year',
DATEADD(yy, DATEDIFF(yy,0,GETDATE()), 0),
DATEADD(ms,-3,DATEADD(yy, DATEDIFF(yy,0,GETDATE() )+1, 0))
insert into #dateranges (Rangelabel, startdate, enddate)
select
'Current Quarter',
DATEADD(qq, DATEDIFF(qq,0,GETDATE()), 0),
DATEADD(qq, DATEDIFF(qq, - 1, getdate()), - 1)
insert into #dateranges (Rangelabel, startdate, enddate)
select
'Current Month',
DATEADD(month, DATEDIFF(month, 0, getdate()), 0),
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0))
If my table is tblOfUsefullFacts and my date row is RowEnteredDate what is the best way to get the aggregate; broken by Day.
Date Range Mon Tues Wed Thu Fri Sat Sun
Year To date 12000 13000 12000 3200 98000 8900 4000
Quarter 1 302 407 201 97 1732 120 37
Month ...
I can get the totals by day easily enough using a query like this
select
count(*) ,
datepart(weekday, RowEnteredDate)
from
tblOfUsefullFacts aa
Where
datepart(weekday, RowEnteredDate) is not null
group by datepart(weekday, RowEnteredDate)
order by datepart(weekday, RowEnteredDate) sac
This selects the data out row by row; which i could pivot and loop round to get the data. Im slightly nervous as the real numbers are in the 10's of millions in them and would like to not impact the underlying processing if i can avoid it.
As i need to do this in multiple passes is there a lighter way to do this without running the loops to get the totals? Or a mechanism in SQL my fuzzy brain is ignoring.
This should give you an idea how to do it. Sorry for any syntax errors, it isn't tested.
;with cte as
(
select
d.rangelabel,
datepart(weekday, RowEnteredDate) as WkDay,
count(*) as RowCt
from tblOfUsefullFacts f
join #dateranges d on f.RowEnteredDate between d.startdate and d.enddate
Where datepart(weekday, RowEnteredDate) is not null
group by d.rangelabel,datepart(weekday, RowEnteredDate)
)
select
RangeLabel,
sum(case when WkDay = 1 then RowCt else 0 end) as Sunday,
sum(case when WkDay = 2 then RowCt else 0 end) as Monday,
sum(case when WkDay = 3 then RowCt else 0 end) as Tuesday,
sum(case when WkDay = 4 then RowCt else 0 end) as Wednesday,
sum(case when WkDay = 5 then RowCt else 0 end) as Thursday,
sum(case when WkDay = 6 then RowCt else 0 end) as Friday,
sum(case when WkDay = 7 then RowCt else 0 end) as Saturday
from cte
group by RangeLabel

Resources