dynamic SQL - Allocate count of midnights to correct month - sql-server

This is a fairly common issue that I've seen a lot of people duck tape together solutions for, but it's never quite right. Hoping this forum can get it ironed out. I have a table:
create table temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
What I'd like to count is the number of midnights the patient was admitted in the correct month. So in the example above the patient would be admitted at midnight on 1/31, 2/1, and 2/2 dates. So my output in sql should look something like:
01-2020 02-2020
------- --------
1 2
I know it has to be dynamic SQL, because the columns need to be created with respect to the date range queried. Although, I'm pretty stumped as to next steps.

create table #temp
( PatientID varchar(12),
AdmitDate datetime,
DischargeDate datetime
)
insert into #temp values ('Patient1','1/30/2020 13:23:44', '2/2/2020 15:12:52')
--Virtually creates a dates table
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='P' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='P' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
)
select dateadd(m,datediff(m, 0, d.thedate),0) [Month], count(1) PatientDays
from dates d
join #temp t on d.thedate between t.[AdmitDate] and t.[DischargeDate]
group by datediff(m, 0, d.thedate)
order by [Month];

Related

Inserting values to NULL rows based on the closest date that is not NULL and is earlier than the row date

I'm getting foreign exchange rates from Riksbanken API. This API doesn't give values for holidays, saturdays or sundays. So my FX rates is empty for those days. However when I convert currencies in my tables I do a join based on date & currency. So in a case where I have a transaction on a Sunday then I can't convert it because that is NULL, and transaction amount * NULL = NULL.
Basically this is my situation:
DECLARE #fxrates TABLE(cur varchar(5), rate decimal, rowDate date);
INSERT INTO #fxrates VALUES
('EUR',10.40,'2020-04-30')
, ('EUR',10.50,'2020-05-01')
, ('EUR',NULL,'2020-05-02')
, ('EUR',NULL,'2020-05-03')
, ('EUR',10.60,'2020-05-04')
, ('EUR',10.70,'2020-05-05')
DECLARE #value TABLE(cur varchar(5), amount decimal, rowDate date);
INSERT INTO #value VALUES
('EUR',1500,'2020-04-30')
, ('EUR',9000,'2020-05-01')
, ('EUR',1000,'2020-05-02')
, ('EUR',300,'2020-05-03')
, ('EUR',160,'2020-05-04')
, ('EUR',170,'2020-05-05')
--How I convert the values
select v.amount * fx.rate as [Converted amount] from #fxrates fx
JOIN #value v
on fx.cur=v.cur
and fx.rowDate=v.rowDate
My solution for this would be to always replace NULL with the "earlier" value, based on date. However I have no idea how that logic would be implemented in SQL. So my fxrates table would look like this:
('EUR',10.40,'2020-04-30')
('EUR',10.50,'2020-05-01')
('EUR',10.50,'2020-05-02')
('EUR',10.50,'2020-05-03')
('EUR',10.60,'2020-05-04')
('EUR',10.70,'2020-05-05')
Would replacing the NULL values be the best approach moving forward? And how can I achieve that using SQL?
I think you can use CROSS APPLY to get the latest exchange rate that is valid before or at the transaction date.
SELECT v.amount * fx.rate AS [Converted amount]
FROM #value v
CROSS APPLY (SELECT TOP 1
fx.rate
FROM #fxrates fx
WHERE fx.cur = v.cur
AND fx.rowdate <= v.rowdate
AND fx.rate IS NOT NULL
ORDER BY fx.rowdate DESC) fx;
You could use a correlated subquery here to find the most recent non NULL price for every date which be missing a price:
SELECT
cur,
COALESCE(rate,
(SELECT TOP 1 f2.rate FROM #fxrates f2
WHERE f2.cur = f1.cur AND f2.rowDate < f1.rowDate AND f2.rate IS NOT NULL
ORDER BY f2.rowdate DESC)
) AS rate,
rowDate
FROM #fxrates f1
ORDER BY
cur,
rowDate;
Demo

Month Difference Without additional table

I am new to SQL query world and got stuck into one requirement.
In my Query i have toDate and fromdate input parameter, based on business logic it will return result like below.
Result:-
Month
Dec-16
Dec-16
Dec-16
Feb-17
Feb-17
Mar-17
Mar-17
now query should need to return the data for each month , if we dont have data for perticular month(in image which is Jan) then it should insert data and return data for that month too, in image we can see for Jan we dont have any data.
You can use a calendar or dates table for this sort of thing.
Without a calendar table, you can generate an adhoc set of months using a common table expression with just this:
declare #fromdate date = '20161201';
declare #todate date = '20170301';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, Months as (
select top (datediff(month, #fromdate, #todate)+1)
[Month]=convert(date,dateadd(month,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
order by [Month]
)
/* your query here: */
select
d.[Month]
, sum_col = sum(t.col)
from Months
left join tbl t
on d.[Month] = t.[Month]
group by d.[Month]
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand
Solved Query:-
Declare #customDate DATETIME
declare #datafound integer
set #customDate = #fromDate
WHILE #customDate < #toDate
BEGIN
select #datafound = count(1) from #temp where datepart(month, MonthDate) = datepart(month, #customDate)
if #datafound = 0
select Format(#customDate,'MMM-yy') as Month
SET #customDate = DATEADD(month, 1,#customDate)
END;

SQL Grouping and Counting based on dates

Getting nowhere here and seems so simple.
Test data is:
declare #table table(SpellAdminsionDate datetime, SpellDischargeDate datetime, Pat_code varchar(10))
insert into #table (SpellAdminsionDate, SpellDischargeDate, Pat_code) values('2016-09-12 15:55:00:000','2016-09-19 20:20:00:000','HEY3052275')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-09-07 17:17:00:000','2016-09-17 18:40:00:000','HEY0810155')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-09-14 16:50:00:000','2016-09-17 18:01:00:000','HEY1059266')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-09-15 02:47:00:000','2016-09-15 17:28:00:000','HEY0742883')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-08-27 00:11:00:000','2016-09-14 12:49:00:000','HEY3050628')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-09-10 12:24:00:000','2016-09-13 20:00:00:000','HEY0912392')
insert into #table(SpellAdminsionDate, SpellDischargeDate, Pat_code) values ('2016-09-12 12:51:00:000','2016-09-13 19:55:00:000','HEY0908691')
Select * from #table`
Below is my simple code displaying the same thing:
SELECT c.SpellAdmissionDate,
c.SpellDischargeDate,
c.Pat_Code
FROM [CommDB].[dbo].[vwCivicaSLAM1617Live] c
WHERE c.Hrg_Code like 'VA%'
and c.Pat_Code like 'HEY%'
ORDER BY c.SpellDischargeDate desc
All I am after is a COUNT per day of active patients, for example take the 12/09/2016 on that date the result would be 5 (based on the test data) as the other 2 cam in after the 12th.
If it makes it easier I do have a date reference table called DATE_REFERENCE which has every date available to me.
Allowing for the possibility of having no patients on a day then you want to use your date reference table as the primary and left join to the patient information. You don't identify a column name so I just used [datecol].
SELECT
d.[datecol] the_date
, count(DISTINCT c.Pat_Code) num_patients
FROM DATE_REFERENCE d
LEFT JOIN [CommDB].[dbo].[vwCivicaSLAM1617Live] c
ON d.[datecol] BETWEEN c.SpellAdmissionDate AND c.SpellDischargeDate
AND c.Hrg_Code LIKE 'VA%'
AND c.Pat_Code LIKE 'HEY%'
GROUP BY
d.[datecol]
ORDER BY
d.[datecol] DESC
I suspect there may be more than this required, but without sample data and expected results it is difficult to know what you really need.
nb. I assume the date in that date_reference table is at midnight (00:00:00) or has a data type of date (without hours/minutes etc.)
Is this what you want?
SELECT dr.date,
(SELECT COUNT(*)
FROM [CommDB].[dbo].[vwCivicaSLAM1617Live] c
WHERE dr.date between c.SpellAdmissionDate and c.SpellDischargeDate) as cnt_per_day
FROM DATE_REFERENCE dr
Add the filters you want to the correlated query .
You can achieve this by joining to your date reference table and counting by a distinct of the patient reference
SELECT
dateRef
,COUNT(DISTINCT Pat_Code) AS PatCount
FROM #table t
RIGHT JOIN #date_reference
ON SpellAdminsionDate <= dateRef
AND SpellDischargeDate >= dateRef
GROUP BY dateRef;
Active count of patients per date can be achieved by (for temp data provided here)
SELECT DISTINCT CAST(T1.SPELLADMINSIONDATE AS DATE)
AdmissionDate,PATIENTS.TotalActive
FROM #TABLE T1
CROSS APPLY (SELECT COUNT(*)TotalActive FROM #TABLE T2 WHERE
CAST(T2.SPELLADMINSIONDATE AS DATE)<=CAST(T1.SPELLADMINSIONDATE AS DATE)) PATIENTS
I think you need to bring the DATE_REFERENCE table to the Query If it contains all the day dates that you need to count patients based on them, But here is a sample of how may you get the required result form this table only
Select DISTINCT c.SpellAdmissionDate, count(c.Pat_code) as PCounter
From [CommDB].[dbo].[vwCivicaSLAM1617Live] c
GROUP BY c.SpellAdmissionDate
ORDER BY c.SpellAdmissionDate desc

SQL Server 2008 : create data where there is none?

I am struggling to create data where there is no data, I have a query that returns similar data to this:
This table shows that for client 123 we had payments in June, July and December only.
The only notable item in the query is a DATEDIFF between the month opened and MonthPayment (this gives the mnth column).
Now where I’m falling over is I need to create the above columns but for all the months that have passed regardless like below
Ignore the month payment field for the none paying months, this shouldn't return anything!
What you need to do is connect this table to a list of all values you might want, as in the question pointed to by Zohar Peled. Your case is slightly complicated, since presumably you need to be able to return multiple clients at a time and only want to see data that pertains to that client's start and end range. I've adapted code from a similar answer I posted some time ago, which should show you how this is done.
-- set up some sample data
DECLARE #MyTable TABLE
(
ClientNo INT,
Collected NUMERIC(5,2),
MonthPayment DATETIME,
MonthOpened DATETIME,
CurrentMonth DATETIME
)
INSERT INTO #MyTable
(
ClientNo,
Collected,
MonthPayment,
MonthOpened,
CurrentMonth
) -- note: I'm in the US, so I'm using the US equivalent of the months you asked for
SELECT 123, 147.25, '7/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 40, '12/1/2014', '12/1/2013', '4/1/2015'
UNION
SELECT 123, 50, '6/1/2014', '12/1/2013', '4/1/2015'
-- create a recursive CTE that contains a list of all months that you could wish to see
--define start and end limits
Declare #todate datetime, #fromdate datetime
Select #fromdate=(SELECT MIN(MonthOpened) FROM #MyTable), #todate=DATEADD(MONTH, 1, GETDATE())
-- define CTE
;With DateSequence( DateValue ) as
(
Select #fromdate as DateValue
union all
Select dateadd(MONTH, 1, DateValue)
from DateSequence
where DateValue < #todate
)
--select result
SELECT
ClientStartEnd.ClientNo,
ISNULL(MyTable.Collected, 0.00) AS Collected,
DateSequence.DateValue AS MonthPayment,
ClientStartEnd.MonthOpened,
DATEDIFF(MONTH, ClientStartEnd.MonthOpened, DateSequence.DateValue) + 1 AS Mnth,
ClientStartEnd.CurrentMonth
FROM
DateSequence
INNER JOIN
(
SELECT DISTINCT
ClientNo,
MonthOpened,
CurrentMonth
FROM #MyTable
) ClientStartEnd ON
DateSequence.DateValue BETWEEN
ClientStartEnd.MonthOpened AND
ClientStartEnd.CurrentMonth
LEFT JOIN
#MyTable MyTable ON
ClientStartEnd.ClientNo = MyTable.ClientNo AND
DateSequence.DateValue = MyTable.MonthPayment
OPTION (MaxRecursion 0)

SQL Query for Month wise count

I have table as below for package and its monthly count, like..
Package1 - 1 dollor per search - 50 searches (PER MONTH) included in package and extra search FOR MONTH will be counted separately.
I am passing data in my table like..
User package table has user's package information with its detail.
UserpackageId Userid PackageId SearchPerMonth StartDatePackage EndDatePackage
1 1 1 50 25/02/2012 25/02/2013 (1 YEAR)
Detail table
Userpackageid SearchDate SearchCost
1 26/02/2012 1 Dollor
Now I want to summarize data like
monthly package search count - as I have search per month 50 for that package.. and if extra count happens over 50 than It will show other count for extra IN MONTHLY WISE..
I agree with #Smarty that your question is pretty tough to read so I'll have to make some assumptions on your requirements.
See if this help you.
create table UserPackage(ID int identity(1,1), UserID int, PackageID int,
SearchPerMonth int, StartDate datetime, EndDate datetime)
create table Detail(ID int identity(1,1), UserPackageID int, SearchDate datetime,Cost money)
insert into UserPackage(UserID, PackageID, SearchPerMonth, StartDate, EndDate)
values(1,1,3,'2/15/12','2/15/13')
insert into Detail(UserPackageID,SearchDate,Cost) values(1,'2/16/12',1)
insert into Detail(UserPackageID,SearchDate,Cost) values(1,'2/17/12',2)
insert into Detail(UserPackageID,SearchDate,Cost) values(1,'2/18/12',3)
insert into Detail(UserPackageID,SearchDate,Cost) values(1,'2/19/12',4)
insert into Detail(UserPackageID,SearchDate,Cost) values(1,'2/20/12',5)
select
UserPackageID,
CostInLimit=sum(CostInLimit),
CostAboveLimit=sum(CostAboveLimit)
from
(
select
z.UserPackageID,
z.DetailID,
IsInLimit=case when z.RankInMonth > up.SearchPerMonth then 1 else 0 end,
CostInLimit=case when z.RankInMonth > up.SearchPerMonth then Cost else 0 end,
CostAboveLimit=case when z.RankInMonth > up.SearchPerMonth then 0 else Cost end,
z.Cost,
z.RankInMonth,
z.SearchDate
from
UserPackage up
inner join
(
select
DetailID=ID,
UserPackageID,
Cost,
RankInMonth=RANK() OVER (PARTITION BY UserPackageID ORDER BY SearchDate),
SearchDate
from
Detail
) z on z.UserPackageID = up.ID
) y
where
y.SearchDate between '2/15/12' and '3/15/12'
group by
UserPackageID][1]
Here's the result from the outer select:
Here's the result from the inner select (table y by itself):

Resources