SQL Selecting the earliest date from many within a row - sql

I am working in MS SQL Server Management Studio. I have created a view keyed on patientId, each patient/row has 12 associated dates. Is there a way in SQL that I can find the min and max dates for each row?
Any help would be much appreciated.

You can use "unpivot". Check the example below:
CREATE TABLE dates
(
number INT PRIMARY KEY ,
date1 DATETIME ,
date2 DATETIME ,
date3 DATETIME
)
INSERT INTO dates
VALUES ( 1, '1/1/2018', '2/4/2018', '3/1/2018')
INSERT INTO dates
VALUES ( 2, '1/2/2018', '2/3/2018', '3/3/2018')
INSERT INTO dates
VALUES ( 3, '1/3/2018', '2/2/2018', '3/2/2018')
INSERT INTO dates
VALUES ( 4, '1/4/2018', '2/1/2018', '3/4/2018')
GO
SELECT number ,
MIN(dDate) mindate,
MAX(dDate) maxDate
FROM dates UNPIVOT ( dDate FOR nDate IN ( Date1, Date2,Date3 ) ) as u
GROUP BY number
GO

I would do this using cross apply:
select t.*, v.mind, v.maxd
from t cross apply
(select min(v.d) as mind, max(v.d) as maxd
from (values (d1), (d2), (d3), (d4), (d5), (d6), (d7), (d8), (d9), (d10), (d11), (d12)
) v(d)
) v;
Note that min() and max() ignore NULL values.

Another way:
create table MyDates
(
ID int,
D1 datetime,
D2 datetime,
D3 datetime
)
insert MyDates(ID, D1, D2, D3)
values (1, '19000101', '19720506', '20060204'),
(2, '20170624', '20180821', '20180901'),
(3, '19820202', '19840721', '19851231')
select *,
(select min(v) from (values(D1), (D2), (D3)) t(v)) [Min],
(select max(v) from (values(D1), (D2), (D3)) t(v)) [Max]
from MyDates

Related

Find missing months in SQL

So this post remains unanswered and not useful
Finding missing month from my table
This link Get Missing Month from table requires a lookup table... which is not my first choice.
I have a table with Financial Periods, and a reference number. Each reference numbers has a series of financial periods which may start anywhere, and end anywhere. The test is simply that between the start and end, there is no gap - i.e. there must be every financial period period the smallest and largest dates, when grouped by reference number.
A financial period is a month.
So... in this example below, Reference Number A is missing May 2016.
REF MONTH
A 2016-04-01
A 2016-06-01
A 2016-07-01
B 2016-03-01
B 2016-04-01
B 2016-05-01
C 2022-05-01
-- Find the boundaries of each ref
select REF
, MIN(Month) as smallest
, MAX(Month) as largest
from myTable
group by REF
-- But how to find missing items?
SQL Server 2019.
Clearly a Calendar Table would make this a small task (among many others)
Here is an alternative using the window function lead() over()
Example
Declare #YourTable Table ([REF] varchar(50),[MONTH] date) Insert Into #YourTable Values
('A','2016-04-01')
,('A','2016-06-01')
,('A','2016-07-01')
,('B','2016-03-01')
,('B','2016-04-01')
,('B','2016-05-01')
,('C','2022-05-01')
;with cte as (
Select *
,Missing = datediff(MONTH,[Month],lead([Month],1) over (partition by Ref order by [Month]))-1
From #YourTable
)
Select * from cte where Missing>0
Results
REF MONTH Missing
A 2016-04-01 1
I added one more row of input to demonstrate the solution better.
with forecast (
REF,
[MONTH]
) as (
select REF
, [MONTH]
from (
values
('A', {d '2016-04-01'})
, ('A', {d '2016-06-01'})
, ('A', {d '2016-07-01'})
, ('B', {d '2016-03-01'})
, ('B', {d '2016-04-01'})
, ('B', {d '2016-05-01'})
, ('B', {d '2016-09-01'})
, ('C', {d '2022-05-01'})
) x (REF, [MONTH])
),
-- define the date ranges
daterange as (
select REF
, min([MONTH]) as dtmin
, max([MONTH]) as dtmax
from forecast
group by REF
),
-- get all of the [end of month] dates in the range
dt (
REF,
[MONTH]
) as (
select REF
, dtmin
from daterange dr
union all
select dt.REF
, dateadd(month, 1, [MONTH])
from dt dt
inner join daterange dr on dr.REF = dt.REF
where dateadd(month, 1, [MONTH]) <= dr.dtmax
)
-- find the missing months
select REF
, [MONTH]
from dt
except
select REF
, [MONTH]
from forecast
order by 1, 2
-- or list all of the months for each REF
--select REF
--, [MONTH]
--from dt

SQL fill next date (month) with loop

I have input table, and need to add missing dates, but not to max, but up to next available month.
so I need to use loop.
SET #mindate = '2021.01'
SET #maxdate = CAST( GETDATE() AS Date ) --date today as max date
while
begin
if #mindate => #maxdate
begin
break
end
set #mindate = #mindate + 1
end
then i can get 1+.. but it does not stop to 7 month
so i totally got stuck with writing loop.
Data table :
could anybody help on code? as most examples are with joins, to data tables, or to one max value.
Paul, I'm assuming that you forgot to specify the month in your mock data.
I hope the code below may help you understand how non-trivial is what you are trying to accomplish :-) Kudos for your will to get rid of loops.
To make it better, I propose a denormalization (CAUTION!):
create another column price_valid_until
the latest prices records will have price_valid_until = '21000101' (aka, far away in the future)
when registering a new price, update the previous with new price_valid_from - 1 day
Here's the solution, with a pretty complex, but efficient query (http://sqlfiddle.com/#!18/4ab23/4)
create table price_history(
SKU varchar(255),
price_valid_from date,
price decimal(16, 2)
)
insert into price_history
values
('a', '20210101', 10),
('a', '20210107', 12),
('b', '20210102', 4),
('b', '20210110', 2),
('b', '20210214', 5);
-- This fiddler won't let me initialize and reference:
--
-- declare
-- #from_date date,
-- #to_date date;
--
-- select
-- #from_date = min(date_from),
-- #to_date = max(date_from)
-- from price_history
with
date_range as(
select
min(price_valid_from) as from_date,
--
eomonth(
max(price_valid_from)
) as to_date
from price_history
),
--
all_dates as(
select from_date as date_in_range
from date_range
-- ----------
union all
-- ----------
select dateadd(day, 1, date_in_range)
from all_dates
where
date_in_range < (
select to_date
from date_range
)
),
--
price_history_boundaries as(
select
ph.SKU,
ph.price,
--
ph.price_valid_from,
-- The latest price, so far, is valid until 01/01/2100
coalesce(
dateadd(
day,
-1,
min(ph_next.price_valid_from)
),
'21000101'
) as price_valid_until
from
price_history ph
left outer join price_history ph_next
on(
ph_next.SKU = ph.SKU
and ph_next.price_valid_from > ph.price_valid_from
)
group by ph.SKU, ph.price_valid_from, ph.price
)
select
phb.SKU,
ad.date_in_range,
phb.price
from
all_dates ad
inner join price_history_boundaries phb
on(
phb.price_valid_from <= ad.date_in_range
and phb.price_valid_until >= ad.date_in_range
)
order by phb.SKU, ad.date_in_range
You can easily achieve your desired result by creating list of dates from which to join to. Here I've used a recursive CTE to create a range of dates, adding 1 month per iteration up to the current date.
It's then a simple matter of joining to your source data, here lead() is handy for limiting the joined rows. Also, assuming SQL Server from the usage of Getdate:
declare #start date=(select Min([date]) from sourcetable);
with m as (
select 1 num, #start [Date]
union all
select num+1 , DateAdd(month,1,m.[date])
from m
where DateAdd(month,1,m.[date]) <= GetDate()
), t as (
select *, Lead([date],1,GetDate()) over (order by [date]) NextDate
from sourcetable
)
select m.[Date], t.sku, t.price
from m
join t on m.[date] >= t.[date] and m.[date] < t.nextdate
See Working Fiddle

Alternate for LAG function in SQL Server 2005 to calculate growth rate

How could I create a column with the growth rate of my database?
I have a database and created another table (only with the date and count field) where I have the date and total number of records in it. I would also like to insert another column with the percentage of my growth:
SELECT CONVERT(DATE, GETDATE()) AS Date, COUNT(*) AS Count
FROM [Database]
My table looks like this:
Date Count Growth Rate
01/01/18 20.000,00
01/02/18 25.000,00 25,00%
01/03/18 40.000,00 60,00%
I use SQL Server 2005, cause this can not use the lag function. How could I do it?
You can use a correlated sub query to emulate LAG function like so:
WITH cte (Date, Count) AS (
SELECT '2018-01-01', 20000.00 UNION
SELECT '2018-01-02', 25000.00 UNION
SELECT '2018-01-03', 40000.00
)
SELECT *, (
SELECT TOP 1 Count
FROM cte AS x
WHERE Date < t.Date
ORDER BY Date DESC
) AS PoorMansLag -- 100 * (Count / PoorMansLag - 1) gives you the result in OP
FROM cte AS t
Another possible approach:
CREATE TABLE #LagTable (
[Date] datetime,
[Count] numeric(10, 2)
)
INSERT INTO #LagTable VALUES ('2018-01-01', 20000.00)
INSERT INTO #LagTable VALUES ('2018-01-02', 25000.00)
INSERT INTO #LagTable VALUES ('2018-01-03', 40000.00);
WITH cte AS (
SELECT [Date], [Count], ROW_NUMBER() OVER (ORDER BY [Date]) AS RN
FROM #LagTable
)
SELECT
t1.[Date],
t1.[Count],
(t1.[Count] - COALESCE(t2.[Count], 0)) / t2.[Count] * 100 AS GrowthRate
FROM cte t1
LEFT JOIN cte t2 ON (t1.RN = t2.RN + 1)
Output:
Date Count GrowthRate
2018-01-01 00:00:00.000 20000.00 NULL
2018-02-01 00:00:00.000 25000.00 25.0000000000000
2018-03-01 00:00:00.000 40000.00 60.0000000000000

datediff between 2 asymmetric column from same table

how can i use date difference between two asymmetric row from same table?
select
[endtime]
,[realtime]
from [prin]
where ([realtime] is not null) and ([endtime] is not null and)
order by [realtime]
between end date time and next start date time
Use the DateDiff function to get the difference between two dates.
Add the day parameter to get the difference in days.
SELECT
[endtime]
,[realtime]
,datediff (day,[endtime],lead ([realtime]) over (order by [realtime])) as days_endtime_2_next_realtime
FROM [prin]
WHERE ([realtime] is not null)
AND ([endtime] is not null)
ORDER BY [realtime]
create table #a
(
date_1 date,
date_2 date
)
insert into #a values
('1-10-16','2-10-16'),
('2-10-16','3-10-16'),
('3-10-16','4-10-16')
;with cte as
(
select
date_1,date_2
,lead(date_2,1) over (order by date_2 ) as lead_value
from #a
where (date_1 is not null) and (date_2 is not null )
)
select date_1,date_2,
datediff(Day,date_1,lead_value) as diffrence from cte

Get average of last 7 days

I'm attacking a problem, where I have a value for a a range of dates. I would like to consolidate the rows in my table by averaging them and reassigning the date column to be relative to the last 7 days. My SQL experience is lacking and could use some help. Thanks for giving this a look!!
E.g.
7 rows with dates and values.
UniqueId Date Value
........ .... .....
a 2014-03-20 2
a 2014-03-21 2
a 2014-03-22 3
a 2014-03-23 5
a 2014-03-24 1
a 2014-03-25 0
a 2014-03-26 1
Resulting row
UniqueId Date AvgValue
........ .... ........
a 2014-03-26 2
First off I am not even sure this is possible. I'm am trying to attack a problem with this data at hand. I thought maybe using a framing window with a partition to roll the dates into one date with the averaged result, but am not exactly sure how to say that in SQL.
Am taking following as sample
CREATE TABLE some_data1 (unique_id text, date date, value integer);
INSERT INTO some_data1 (unique_id, date, value) VALUES
( 'a', '2014-03-20', 2),
( 'a', '2014-03-21', 2),
( 'a', '2014-03-22', 3),
( 'a', '2014-03-23', 5),
( 'a', '2014-03-24', 1),
( 'a', '2014-03-25', 0),
( 'a', '2014-03-26', 1),
( 'b', '2014-03-01', 1),
( 'b', '2014-03-02', 1),
( 'b', '2014-03-03', 1),
( 'b', '2014-03-04', 1),
( 'b', '2014-03-05', 1),
( 'b', '2014-03-06', 1),
( 'b', '2014-03-07', 1)
OPTION A : - Using PostgreSQL Specific Function WITH
with cte as (
select unique_id
,max(date) date
from some_data1
group by unique_id
)
select max(sd.unique_id),max(sd.date),avg(sd.value)
from some_data1 sd inner join cte using(unique_id)
where sd.date <=cte.date
group by cte.unique_id
limit 7
> SQLFIDDLE DEMO
OPTION B : - To work in PostgreSQL and MySQL
select max(sd.unique_id)
,max(sd.date)
,avg(sd.value)
from (
select unique_id
,max(date) date
from some_data1
group by unique_id
) cte inner join some_data1 sd using(unique_id)
where sd.date <=cte.date
group by cte.unique_id
limit 7
> SQLFDDLE DEMO
Maybe something along the lines of SELECT AVG(Value) AS 'AvgValue' FROM tableName WHERE Date BETWEEN dateStart AND dateEnd That will get you the average between those dates and you have dateEnd already so you could use that result to create the row you're looking for.
For PostgreSQL a window function might be what you want:
DROP TABLE IF EXISTS some_data;
CREATE TABLE some_data (unique_id text, date date, value integer);
INSERT INTO some_data (unique_id, date, value) VALUES
( 'a', '2014-03-20', 2),
( 'a', '2014-03-21', 2),
( 'a', '2014-03-22', 3),
( 'a', '2014-03-23', 5),
( 'a', '2014-03-24', 1),
( 'a', '2014-03-25', 0),
( 'a', '2014-03-26', 1),
( 'a', '2014-03-27', 3);
WITH avgs AS (
SELECT unique_id, date,
avg(value) OVER w AS week_avg,
count(value) OVER w AS num_days
FROM some_data
WINDOW w AS (
PARTITION BY unique_id
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW))
SELECT unique_id, date, week_avg
FROM avgs
WHERE num_days=7
Result:
unique_id | date | week_avg
-----------+------------+--------------------
a | 2014-03-26 | 2.0000000000000000
a | 2014-03-27 | 2.1428571428571429
Questions include:
What happens if a day from the preceding six days is missing? Do we want to add it and count it as zero?
What happens if you add a day? Is the result of the code above what you want (a rolling 7-day average)?
For SQL Server, you can follow the below approach. Try this
1. For weekly value's average
SET DATEFIRST 4
;WITH CTE AS
(
SELECT *,
DATEPART(WEEK,[DATE])WK,
--Find last day in that week
ROW_NUMBER() OVER(PARTITION BY UNIQUEID,DATEPART(WEEK,[DATE]) ORDER BY [DATE] DESC) RNO,
-- Find average value of that week
AVG(VALUE) OVER(PARTITION BY UNIQUEID,DATEPART(WEEK,[DATE])) AVGVALUE
FROM DATETAB
)
SELECT UNIQUEID,[DATE],AVGVALUE
FROM CTE
WHERE RNO=1
Click here to view result
2. For last 7 days value's average
DECLARE #DATE DATE = '2014-03-26'
;WITH CTE AS
(
SELECT UNIQUEID,[DATE],VALUE,#DATE CURRENTDATE
FROM DATETAB
WHERE [DATE] BETWEEN DATEADD(DAY,-7,#DATE) AND #DATE
)
SELECT UNIQUEID,CURRENTDATE [DATE],AVG(VALUE) AVGVALUE
FROM CTE
GROUP BY UNIQUEID,CURRENTDATE
Click here to view result