Calculating week numbers from custom dates - sql-server

I have client ids and their dates of login. i want to calculate the week number with respect to their first login date
i am fairly new to sql
Demo output
ClientID Date of login Week Number
1 2019-12-20 1
1 2019-12-21 1
1 2019-12-21 1
1 2019-12-22 1
1 2019-12-29 2
1 2019-12-29 2
2 2020-01-27 1
2 2020-01-28 1
2 2020-02-05 2
2 2020-02-06 2
2 2020-02-16 3

This is very trivial date arithmetic that just requires the min DateOfLogin for each ClientID, which you can find with a windowed function.
Calculate the datediff in days between this date and the current DateOfLogin, integer divide by 7 (to return no fractional days) and then add 1 to correctly offset the WeekNum value:
declare #l table(ClientID int, DateOfLogin date);
insert into #l values(1,'2019-12-20'),(1,'2019-12-21'),(1,'2019-12-21'),(1,'2019-12-22'),(1,'2019-12-29'),(1,'2019-12-29'),(2,'2020-01-27'),(2,'2020-01-28'),(2,'2020-02-05'),(2,'2020-02-06'),(2,'2020-02-16');
select ClientID
,DateOfLogin
,(datediff(day,min(DateOfLogin) over (partition by ClientID),DateOfLogin) / 7) + 1 as WeekNum
from #l;
Output
+----------+-------------+---------+
| ClientID | DateOfLogin | WeekNum |
+----------+-------------+---------+
| 1 | 2019-12-20 | 1 |
| 1 | 2019-12-21 | 1 |
| 1 | 2019-12-21 | 1 |
| 1 | 2019-12-22 | 1 |
| 1 | 2019-12-29 | 2 |
| 1 | 2019-12-29 | 2 |
| 2 | 2020-01-27 | 1 |
| 2 | 2020-01-28 | 1 |
| 2 | 2020-02-05 | 2 |
| 2 | 2020-02-06 | 2 |
| 2 | 2020-02-16 | 3 |
+----------+-------------+---------+

This query returns the week number.
select DATENAME(WW, '2019-12-20')
This is for MSSQL.
Here might be a solution for you, you'll maybe just have to look at the way you are going to do the insert and maybe optimize it a bit better.
select 1 AS 'ClientID', '2019-12-20' AS 'LogInDate', 1 AS 'Week'
into #test
insert into #test
select top(1) 1, '2020-02-05', case DATEDIFF(week,'2020-02-05',LogInDate) when 0 then week else Week +1 end from #test where ClientID = 1 order by LogInDate desc

Related

Extract Data by comparing 4 different rows

The Table data is as below, need to extract records that met the below conditions
Here Value = Value2-Value1
Value of two days back data should be > 2
Value of last day data is < 0
Value of next day data is < 4 and >0
Value of after next day data > 4
All the dates are weekdays,if any date falls on friday, need to compare with next day ie.. Monday. and comparision is with alternative days only
from below output have to be.
1 4-1-2018 15 18
2 3-1-2018 3 0
-----------------------------------
code Date Value1 Value2
---------------------------------------
1 1-1-2018 13 14
1 2-1-2018 14 18
1 3-1-2018 15 11
1 4-1-2018 15 18
1 5-1-2018 15 18
1 6-1-2018 11 18
1 7-1-2018 15 18
2 1-1-2019 1 3
2 2-1-2018 2 5
2 3-1-2018 3 0
2 4-1-2018 3 7
2 5-1-2018 3 4
2 6-1-2018 3 9
2 7-1-2018 3 7
I am pretty much confused at comparing multiple rows, any help is greatly appreciated.
Starting with v2012 we have support for LAG() and LEAD(). Try this out:
SET DATEFORMAT dmy;
DECLARE #tbl TABLE(code INT,[Date] DATE,Value1 INT,Value2 INT);
INSERT INTO #tbl VALUES
(1,'1-1-2018',13,14)
,(1,'2-1-2018',14,18)
,(1,'3-1-2018',15,11)
,(1,'4-1-2018',15,18)
,(1,'5-1-2018',15,18)
,(1,'6-1-2018',11,18)
,(1,'7-1-2018',15,18)
,(2,'1-1-2019', 1, 3)
,(2,'2-1-2018', 2, 5)
,(2,'3-1-2018', 3, 0)
,(2,'4-1-2018', 3, 7)
,(2,'5-1-2018', 3, 4)
,(2,'6-1-2018', 3, 9)
,(2,'7-1-2018', 3, 7);
WITH cte AS
(
SELECT *
,LAG(Value2-Value1,2) OVER(PARTITION BY code ORDER BY [Date]) TwoDaysBack
,LAG(Value2-Value1,1) OVER(PARTITION BY code ORDER BY [Date]) Yesterday
,LEAD(Value2-Value1,1) OVER(PARTITION BY code ORDER BY [Date]) tomorrow
,LEAD(Value2-Value1,2) OVER(PARTITION BY code ORDER BY [Date]) TwoDaysAhead
FROM #tbl
)
SELECT *
FROM cte;
I do not really understand, how you want to use these values in a filter to get the expected output. If you need help with this, just come back...
The result
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| code | Date | Value1 | Value2 | TwoDaysBack | Yesterday | tomorrow | TwoDaysAhead |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-01 | 13 | 14 | NULL | NULL | 4 | -4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-02 | 14 | 18 | NULL | 1 | -4 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-03 | 15 | 11 | 1 | 4 | 3 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-04 | 15 | 18 | 4 | -4 | 3 | 7 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-05 | 15 | 18 | -4 | 3 | 7 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-06 | 11 | 18 | 3 | 3 | 3 | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-07 | 15 | 18 | 3 | 7 | NULL | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-02 | 2 | 5 | NULL | NULL | -3 | 4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-03 | 3 | 0 | NULL | 3 | 4 | 1 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-04 | 3 | 7 | 3 | -3 | 1 | 6 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-05 | 3 | 4 | -3 | 4 | 6 | 4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-06 | 3 | 9 | 4 | 1 | 4 | 2 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-07 | 3 | 7 | 1 | 6 | 2 | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2019-01-01 | 1 | 3 | 6 | 4 | NULL | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
The idea in short:
Both, LAG() and LEAD() take an argument for the needed value, a second, how many rowswe want to skip, and as a third argument a default value you might specify to avoid NULLs in the result, when there is no row in scope.
The OVER() clause will tell any windowing function if we want to think of the set as divided in groups and the sort order (otherwise the system would not know what is leading or lagging.
With lead and lag window functions:
with cte as (
select *,
lag(value2 - value1, 2) over (partition by code order by date) prev2,
lag(value2 - value1, 1) over (partition by code order by date) prev1,
lead(value2 - value1, 1) over (partition by code order by date) next1,
lead(value2 - value1, 2) over (partition by code order by date) next2
from tablename
)
select code, date, value1, value2
from cte
where prev2 > 2 and prev1 < 0 and next1 > 0 and next1 < 4 and next2 > 4
See the demo.
Results:
code | date | value1 | value2
---: | :------------------ | -----: | -----:
1 | 01/04/2018 00:00:00 | 15 | 18
2 | 01/04/2018 00:00:00 | 3 | 7
There is a difference between your expected results for code = 2 and my results, so check its validity.

SQL group by date difference with previous row

I looking for some grouping using datetime daily rows to build date range intervals
My table is something like:
id | A | B | Date
1 | 1 | 2 | 1/10/2010
2 | 1 | 2 | 2/10/2010
3 | 1 | 2 | 3/10/2010
4 | 1 | 3 | 4/10/2010
5 | 1 | 3 | 5/10/2010
6 | 1 | 2 | 6/10/2010
7 | 1 | 2 | 7/10/2010
8 | 1 | 2 | 8/10/2010
My first try was:
SELECT A, B, MIN(DATE), MAX(date)
FROM table
GROUP BY A, B
So after group by A, B and use min and max with date on my select, I get invalid results due the repetition of B = 2.
A B Date A B min(Date) max(Date)
1 | 1 | 2 | 1/10/2010 1 2 | 1/10/2010 8/10/2010
2 | 1 | 2 | 2/10/2010 Invalid
3 | 1 | 2 | 3/10/2010 ------->
6 | 1 | 2 | 6/10/2010
7 | 1 | 2 | 7/10/2010
8 | 1 | 2 | 8/10/2010
I'm looking for how to calculate the third member of the group by...
So the expected intervals results:
A B Start Date End Date
.. | 1 | 2 | 1/10/2010 | 3/10/2010
.. | 1 | 3 | 4/10/2010 | 5/10/2010
.. | 1 | 2 | 6/10/2010 | 8/10/2010
I need to support SQL Server 2008
Thank you in advance for your help
The following is an easy way to deal with "islands and gaps" where you need to find gaps in consecutive dates:
SELECT A, B, StartDate = MIN([Date]), EndDate = MAX([Date])
FROM
(
SELECT *,
RN = DATEDIFF(DAY, 0, [Date]) - ROW_NUMBER() OVER (PARTITION BY A, B ORDER BY [Date])
FROM myTable
) AS T
GROUP BY A, B, RN;
To break it down into slightly simpler-to-understand logic: you assign each date a number (DATEDIFF(DAY, 0, [Date]) here) and each date a row number (partitioned by A and B here), then any time there's a gap in the dates, the difference between those two will change.
There are a variety of resources you can use to understand different approaches to "islands and gaps" problems. Here is one that might help you with tackling other varieties of this in the future: https://www.red-gate.com/simple-talk/sql/t-sql-programming/the-sql-of-gaps-and-islands-in-sequences/

How to update SQL rows based on other rows with shared ID?

Currently, I have a table that looks like below:
ID|Date |Val
1 |1/1/2016|1
2 |1/1/2016|0
3 |1/1/2016|0
1 |2/1/2016|0
2 |2/1/2016|1
3 |2/1/2016|1
1 |3/1/2016|0
2 |3/1/2016|0
3 |3/1/2016|0
I want to update it so that the value carries over for each ID, but not on earlier dates than when the value first appeared. Also, the value can only change 0 to 1, not vice versa. So the final product would look like:
ID|Date |Val
1 |1/1/2016|1
2 |1/1/2016|0
3 |1/1/2016|0
1 |2/1/2016|1
2 |2/1/2016|1
3 |2/1/2016|1
1 |3/1/2016|1
2 |3/1/2016|1
3 |3/1/2016|1
I've tried a few code combinations, but the conditional of carrying the value after the date where the value first appears is tripping me up. I'd appreciate any help!
In SQL Server 2012+, using the aggregate max() as a window function with over() (inside a common table expression to simplify the update):
;with cte as (
select *
, MaxVal = max(convert(int,val)) over (partition by id order by date)
from t
)
update cte
set val = maxVal
where val <> maxVal
rextester demo: http://rextester.com/ZPGWB94088
result:
+----+------------+-----+
| id | Date | Val |
+----+------------+-----+
| 1 | 2016-01-01 | 1 |
| 2 | 2016-01-01 | 0 |
| 3 | 2016-01-01 | 0 |
| 1 | 2016-02-01 | 1 |
| 2 | 2016-02-01 | 1 |
| 3 | 2016-02-01 | 1 |
| 1 | 2016-03-01 | 1 |
| 2 | 2016-03-01 | 1 |
| 3 | 2016-03-01 | 1 |
+----+------------+-----+
Prior to SQL Server 2012, you could use something like this:
update t
set Val = 1
from t
inner join (
select i.Id, min(i.Date) as Date
from t as i
where i.Val = 1
group by i.Id
) as m
on t.Id = m.Id
and t.Date >= m.Date
and t.Val = 0
rextester demo: http://rextester.com/RLEAO15622

SQL Server query for next row value where previous row value

This query gives me Event values from 1 to 20 within an hour, how to add to that if a consecutive Event value is >=200 as well?
SELECT ID, count(Event) as numberoftimes
FROM table_name
WHERE Event >=1 and Event <=20
GROUP BY ID, DATEPART(HH, AtHour)
HAVING DATEPART(HH, AtHour) <= 1
ORDER BY ID desc
In this dummy 24h table:
+----+-------+--------+
| ID | Event | AtHour |
+----+-------+--------+
| 1 | 1 | 11:00 |
| 1 | 4 | 11:01 |
| 1 | 1 | 11:02 |
| 1 | 20 | 11:03 |
| 1 | 200 | 11:04 |
| 1 | 1 | 13:00 |
| 1 | 1 | 13:05 |
| 1 | 2 | 13:06 |
| 1 | 500 | 13:07 |
| 1 | 39 | 13:10 |
| 1 | 50 | 13:11 |
| 1 | 2 | 13:12 |
+----+-------+--------+
I would like to select IDs with Event with values with range between 1 and 20 followed immediately by value greater than or equal to 200 within an hour.
Expected result should be something like that:
+----+--------+
| ID | AtHour |
+----+--------+
| 1 | 11 |
| 1 | 13 |
| 2 | 11 |
| 2 | 14 |
| 3 | 09 |
| 3 | 12 |
+----+--------+
or just how many times it has happened for unique ID instead of which hour.
Please excuse me I am still rusty with post formatting!
CREATE TABLE data (Id INT, Event INT, AtHour SMALLDATETIME);
INSERT data (Id, Event, AtHour) VALUES
(1,1,'2017-03-16 11:00:00'),
(1,4,'2017-03-16 11:01:00'),
(1,1,'2017-03-16 11:02:00'),
(1,20,'2017-03-16 11:03:00'),
(1,200,'2017-03-16 11:04:00'),
(1,1,'2017-03-16 13:00:00'),
(1,1,'2017-03-16 13:05:00'),
(1,2,'2017-03-16 13:06:00'),
(1,500,'2017-03-16 13:07:00'),
(1,39,'2017-03-16 13:10:00')
;
; WITH temp as (
SELECT rownum = ROW_NUMBER() OVER (PARTITION BY id ORDER BY AtHour)
, *
FROM data
)
SELECT a.id, DATEPART(HOUR, a.AtHour) as AtHour, COUNT(*) AS NumOfPairs
FROM temp a JOIN temp b ON a.rownum = b.rownum-1
WHERE a.Event BETWEEN 1 and 20 AND b.Event >= 200
AND DATEDIFF(MINUTE, a.AtHour, b.AtHour) <= 60
GROUP BY a.id, DATEPART(HOUR, a.AtHour)
;

Need help based on conditions in SQL Server

I have a question about SQL Server.
Table patient:
pn | code | date | doctorcode
---------------------------------------
1 | 10 |2015-02-19 | 100
1 | 10 |2015-02-19 | 101
1 | 10 |2015-02-19 | 102
2 | 10 |2015-02-12 | 101
2 | 10 |2015-02-13 | 102
2 | 10 |2015-02-14 | 103
3 | 10 |2015-02-15 | 103
3 | 10 |2015-02-18 | 104
3 | 10 |2015-02-26 | 105
Table Patientref:
pn | code | sdate | edate | Status
-------------------------------------------------
1 | 10 |2015-02-13 | 2015-02-19 | 1
1 | 10 |2015-02-19 | 2015-03-24 | 2
1 | 10 |2015-04-28 | 2015-05-08 | 4
2 | 10 |2015-02-08 | 2015-02-19 | 4
2 | 10 |2015-02-09 | 2015-02-19 | 2
2 | 10 |2015-02-10 | 2015-02-19 | 2
2 | 10 |2015-02-11 | 2015-02-18 | 1
3 | 10 |2015-02-10 | 2015-02-17 | 4
3 | 10 |2015-02-10 | 2015-02-17 | 3
3 | 10 |2015-02-11 | 2015-02-18 | 3
2 | 10 |2015-04-10 | 2015-05-19 | 2
3 | 10 |2015-02-11 | 2015-02-18 | 1
3 | 10 |2015-02-26 | 2015-03-18 | 1
Here we need consider patient dates that fall between sdate and edate of the patientrefs table, and then we need to consider the highest status values in order (for example, the highest values in order - 2 is first highest, 4 is second highest, 3 is third highest, and 1 is fourth highest value)
If the date falls between multiple different sdate and edate with the same status values, then we need to consider the latest sdate value and from that entire record we need to extract that value.
Examples: patient
pn | code | date | doctorcode
2 | 10 |2015-02-12 | 101
2 | 10 |2015-02-13 | 102
2 | 10 |2015-02-14 | 103
Table : Patientref:
pn | code | sdate | edate | Status
2 | 10 |2015-02-08 | 2015-02-19 | 4
2 | 10 |2015-02-09 | 2015-02-19 | 2
2 | 10 |2015-02-10 | 2015-02-19 | 2
2 | 10 |2015-02-11 | 2015-02-18 | 1
Here, pn=2 values have dates which fall between sdate and edate of patientref table. Then we give highest values status is 2, and status 2 values have two records, then we go for max sdate(latest sdate). Then this pn=2 latest sdates is 2015-02-10 and we need to retrieve the corresponding edate and status values.
Based on this, the desired output is below:
pn | code | date | doctorcode | sdate |edate |status
1 | 10 |2015-02-19 | 100 |2015-02-19 |2015-03-24 | 2
1 | 10 |2015-02-19 | 101 |2015-02-19 |2015-03-24 | 2
1 | 10 |2015-02-19 | 102 |2015-02-19 |2015-03-24 | 2
2 | 10 |2015-02-12 | 101 |2015-02-10 |2015-02-19 | 2
2 | 10 |2015-02-13 | 102 |2015-02-10 |2015-02-19 | 2
2 | 10 |2015-02-14 | 103 |2015-02-10 |2015-02-19 | 2
3 | 10 |2015-02-15 | 103 |2015-02-10 |2015-02-17 | 4
3 | 10 |2015-02-18 | 104 |2015-02-11 |2015-02-18 | 3
3 | 10 |2015-02-26 | 105 |2015-02-26 |2015-03-18 | 1
I tried it like this:
select
a.pn, a.code, a.doctorcode, a.date,
b.sdate, b.edate, b.status
from
patient a
left join
(select
b.pn, b.code, b.sdate, b.edate,
row_number() over (partition by pn, org
order by case when status=2 then 1 when status=4 then 2 when status=3 then 3 when status=1 then 4 end desc,sdate desc) as rn
from patientref) b on a.pn = b.pn and a.code = b.code
and a.rn = 1
and a.date between b.sdate and b.edate
But it does not give the expected result. How can I write the query to achieve this task in SQL Server?
First off, to handle the status sorting you should really have a table in your system showing how they can be sorted. This would just be a table that has the status ID and a sort order column showing sorting priority. However, for your query you can just create a table variable to manage it.
declare #statuses table
([status] int,
sort_order int)
insert into #statuses ([status], sort_order) values (2,0);
insert into #statuses ([status], sort_order) values (4,1);
insert into #statuses ([status], sort_order) values (3,2);
insert into #statuses ([status], sort_order) values (1,3);
Then you can use CROSS APPLY to query your patient table and use the highest priority record from your patientref table:
select
p.pn,
p.code,
p.date,
p.doctorcode,
ca.sdate,
ca.edate,
ca.status
from patient p
cross apply
(select
top 1
pr.pn,
pr.code,
pr.sdate,
pr.edate,
pr.status
from patientref pr
inner join #statuses s on pr.status = s.status
where pr.pn = p.pn
and pr.code = p.code
and p.date between pr.sdate and pr.edate
order by s.sort_order, pr.sdate desc) as ca

Resources