SQL Calculate Days between two dates in one table - sql

I have a table dbo.Trans which contains an id called bd_id(varchar) and transfer_date(Datetime), also an identifier member_id pk is trns_id and is sequential
Duplicates of bd_id and member_id exist in the table.
transfer_date |bd_id| member_id | trns_id
2008-01-01 00:00:00 | 432 | 111 | 1
2008-01-03 00:00:00 | 123 | 111 | 2
2008-01-08 00:00:00 | 128 | 111 | 3
2008-02-04 00:00:00 | 123 | 432 | 4
.......
For each member_id, I want to get the amount of days between dates and for each bd_id
E.G., member 111 used 432 from 2008-01-01 until 2008-02-01 so return should be 2
Then next would be 5
I know the DATEDIFF() function exists but I am not sure how to get the difference when dates are in the same table.
Any help appreciated.

You could try something like this.
select T1.member_id,
datediff(day, T1.transfer_date, T3.transfer_date) as DD
from YourTable as T1
cross apply (select top 1 T2.transfer_date
from YourTable as T2
where T2.transfer_date > T1.transfer_date and
T2.member_id = T1.member_id
order by T2.transfer_date) as T3
SE-Data

You must select 1st and 2nd records that you want, then get their dates and get DATEDIFF of those two dates.
DATEDIFF(date1, date2);

Your problem is getting the next member date.
Here is an example using a correlated subquery to get the next date:
select t.*, datediff(day, t.transfer_date, nextdate) as Days_Between
from (select t.*,
(select min(transfer_date)
from trans t2
where t.bd_id = t2.bd_id and
t.member_id = t2.member_id and
t.transfer_date < t2.transfer_date
) as NextDate
from trans t
) t
SQL Server 2012 has a function called lag() that makes this a bit easier to express.

Related

Redshift: Add Row for each hour in a day

I have a table contains item_wise quantity at different hour of date. trying to add data for each hour(24 enteries in a day) with previous hour available quantity. For example for hour(2-10), it will be 5.
I created a table with hours enteries (1-24) & full join with shared table.
How can i add previous available entry. Need suggestion
item_id| date | hour| quantity
101 | 2022-04-25 | 2 | 5
101 | 2022-04-25 | 10 | 13
101 | 2022-04-25 | 18 | 67
101 | 2022-04-25 | 23 | 27
You can try to use generate_series to generate hours number, let it be the OUTER JOIN base table,
Then use a correlated-subquery to get your expect quantity column
SELECT t1.*,
(SELECT quantity
FROM T tt
WHERE t1.item_id = tt.item_id
AND t1.date = tt.date
AND t1.hour >= tt.hour
ORDER BY tt.hour desc
LIMIT 1) quantity
FROM (
SELECT DISTINCT item_id,date,v.hour
FROM generate_series(1,24) v(hour)
CROSS JOIN T
) t1
ORDER BY t1.hour
Provided the table of int 1 .. 24 is all24(hour) you can use lead and join
select t.item_id, t.date, all24.hour, t.quantity
from all24
join (
select *,
lead(hour, 1, 25) over(partition by item_id, date order by hour) - 1 nxt_h
from tbl
) t on all24.hour between t.hour and t.nxt_h

Need to count rows during a date period then count rows based on what that date period is using other conditions

I am trying to calculate subscription churn. I have a table that looks like the following:
subid | startdate | enddate
000001 | 9/26/2016 | 10/26/2016
000002 | 11/4/2015 | 12/4/2016
000003 | 11/18/2016| 12/18/2016
000004 | 8/3/2016 | 10/16/2016
000005 | 7/16/2016 | 11/29/2016
To calculate churn by month I need to create logic that looks like the following:
select
date_trunc('month',enddate) as month,
count(id), --of all accounts with an enddate of that month
count(id), --of all accounts that have a start date prior to that month and an end date equal to or after that month
from table1
Essentially the goal here is to find the number of subscriptions ending on a given month and also count the number of subscriptions that are still active during that month. I have no idea how to do this within the same group since the second count(id) is conditional to the first.
The result of the example table rows would be this:
date | count1 | count2
10/1/2016 | 2 | 4
11/1/2016 | 1 | 3
12/1/2016 | 2 | 3
You can use correlated sub-queries to get the different counts.
select distinct
date_trunc('month',enddate) as mnth,
(select count(*) from table1
where date_trunc('month',enddate) = date_trunc('month',t1.enddate)) count1,
(select count(*) from table1
where date_trunc('month',enddate) >= date_trunc('month',t1.enddate)
and date_trunc('month',startdate) <= date_trunc('month',t1.enddate)) count2
from table1 t1
order by 1
Another way is to self-join.
select
date_trunc('month',t1.enddate) as mnth,
count(distinct t1.subid) c1,
count(distinct t2.subid) c2
from table1 t1
left join table1 t2 on date_trunc('month',t2.enddate)>= date_trunc('month',t1.enddate) and date_trunc('month',t2.startdate)<= date_trunc('month',t1.enddate)
group by date_trunc('month',t1.enddate)
order by 1
Sample Demo

SQL Find Last Entry Closest to a Date

I am trying to filter the last entry in a table closet to a defined date and I am having difficulties. Any input is greatly appreciated. Thanks! I am running Microsoft SQL Server 2008.
Table:
code | account | date | amount
1 | 1234 | 2016-02-28 | 500
2 | 1234 | 2016-03-01 | 650
3 | 1234 | 2016-03-05 | 842
4 | 7890 | 2016-02-28 | 500
5 | 7890 | 2016-03-30 | 550
I want to select only entries with a date closest to March 31 ('2016-03-31'). In this example, the entry closest to 2016-03-31 for account 1234 is entry #3 and the entry closest to 2016-03-31 for account 7890 is entry #5. In other words, I want the last entry for all accounts equal to or before a date.
3 | 1234 | 2016-03-05 | 842
5 | 7890 | 2016-03-30 | 550
Most DBMSes (including MS SQL Server) support Analytical Functions:
select *
from
(
select *,
row_number() -- create a ranking
over (partition by account -- for each account
order by date desc) as rn -- based on descending dates
from tab
where date <= date '2016-03-31'
) dt
where rn = 1 -- return the row with the "closest" date
Since no DBMS is specified, here's a kind of hacky way to do this in SQL Server. It grabs the record just before and just after the specified date:
select * from (
select top(1) * FROM mytable
where date >= '2016-03-31' order by date asc
) t1
union
select * from (
select top(1) * FROM mytable
where date <= '2016-03-31' order by date desc
) t2
This should do what you want, and it should be easy enough to understand to don't need further explanation:
select t.*
from your_table t
join (
select account, max(date) as date
from your_table
where date <= '2016-03-31'
group by account
) as subquery on t.account = subquery.account and t.date = subquery.date
Edit: for SQL Server it might be better to use an analytical function (like row_number)

Check multiple rows for value, return only row with MAX/MIN

I'm trying to write a query that will compare the value of N amount of rows and return only the row with the max value. For example, if I wanted to only return a table with non-duplicate rows, but only the row with the newest date -
key | name | value | date
1 | frank | 100 | 1/1/2013
2 | peter | 200 | 2/1/2013
3 | jonny | 300 | 3/1/2013
4 | jonny | 300 | 4/1/2013
And the query:
SELECT key, name, value, MAX(date)
FROM myTable
WHERE key IN (1,2,3,4)
I'd be expecting this to return
key | name | value | date
1 | frank | 100 | 1/1/2013
2 | peter | 200 | 2/1/2013
4 | jonny | 300 | 4/1/2013
I am unsure how to use GROUP BY, I think I'm missing something fundamental with my attempts at it.
Well if you only want the newest row you could use the following:
SELECT TOP 1 key, name, value, date
FROM myTable
ORDER BY date desc
This should return the one row with the newest date in that table.
If you wanted the newest date for each name you could use group by:
SELECT name, max(date)
FROM myTable
WHERE key in(1,2,3,4)
GROUP BY name
Max is an aggregate function. Anytime you use an aggregate function any columns that are not being aggregated have to be specified in the group by clause.
So based on your expected results you probably want this:
;with namesWithMaxDate as(
select
name
,max(date) as date
from
myTable
group by
name
)
select
myTable.[key]
,myTable.name
,myTable.value
,myTable.date
from myTable
inner join
namesWithMaxDate
on
myTable.name = namesWithMaxDate.name and
myTable.date = namesWithMaxDate.date
This is slightly more complex because you have columns that you want returned that are not included in the grouping. Hence two statements to arrive at the final result set.
Final option: good old fashioned sub-query.
select
myTable.[key]
,myTable.name
,myTable.value
,myTable.date
from myTable
inner join
( select
name
,max(date) as date
from
myTable
group by
name ) as namesWithMaxDate
on
myTable.name = namesWithMaxDate.name and
myTable.date = namesWithMaxDate.date
More here about aggregate functions.
More here about group by.
Try This one:
SELECT a.key, a.name, a.value, a.date
FROM myTable a
WHERE a.key IN (1,2,3,4)
and
a.DATE = (select MAX(date) from myTable b where a.key = b.key)

Get Monthly Totals from Running Totals

I have a table in a SQL Server 2008 database with two columns that hold running totals called Hours and Starts. Another column, Date, holds the date of a record. The dates are sporadic throughout any given month, but there's always a record for the last hour of the month.
For example:
ContainerID | Date | Hours | Starts
1 | 2010-12-31 23:59 | 20 | 6
1 | 2011-01-15 00:59 | 23 | 6
1 | 2011-01-31 23:59 | 30 | 8
2 | 2010-12-31 23:59 | 14 | 2
2 | 2011-01-18 12:59 | 14 | 2
2 | 2011-01-31 23:59 | 19 | 3
How can I query the table to get the total number of hours and starts for each month between two specified years? (In this case 2011 and 2013.) I know that I need to take the values from the last record of one month and subtract it by the values from the last record of the previous month. I'm having a hard time coming up with a good way to do this in SQL, however.
As requested, here are the expected results:
ContainerID | Date | MonthlyHours | MonthlyStarts
1 | 2011-01-31 23:59 | 10 | 2
2 | 2011-01-31 23:59 | 5 | 1
Try this:
SELECT c1.ContainerID,
c1.Date,
c1.Hours-c3.Hours AS "MonthlyHours",
c1.Starts - c3.Starts AS "MonthlyStarts"
FROM Containers c1
LEFT OUTER JOIN Containers c2 ON
c1.ContainerID = c2.ContainerID
AND datediff(MONTH, c1.Date, c2.Date)=0
AND c2.Date > c1.Date
LEFT OUTER JOIN Containers c3 ON
c1.ContainerID = c3.ContainerID
AND datediff(MONTH, c1.Date, c3.Date)=-1
LEFT OUTER JOIN Containers c4 ON
c3.ContainerID = c4.ContainerID
AND datediff(MONTH, c3.Date, c4.Date)=0
AND c4.Date > c3.Date
WHERE
c2.ContainerID is null
AND c4.ContainerID is null
AND c3.ContainerID is not null
ORDER BY c1.ContainerID, c1.Date
Using recursive CTE and some 'creative' JOIN condition, you can fetch next month's value for each ContainterID:
WITH CTE_PREP AS
(
--RN will be 1 for last row in each month for each container
--MonthRank will be sequential number for each subsequent month (to increment easier)
SELECT
*
,ROW_NUMBER() OVER (PARTITION BY ContainerID, YEAR(Date), MONTH(DATE) ORDER BY Date DESC) RN
,DENSE_RANK() OVER (ORDER BY YEAR(Date),MONTH(Date)) MonthRank
FROM Table1
)
, RCTE AS
(
--"Zero row", last row in decembar 2010 for each container
SELECT *, Hours AS MonthlyHours, Starts AS MonthlyStarts
FROM CTE_Prep
WHERE YEAR(date) = 2010 AND MONTH(date) = 12 AND RN = 1
UNION ALL
--for each next row just join on MonthRank + 1
SELECT t.*, t.Hours - r.Hours, t.Starts - r.Starts
FROM RCTE r
INNER JOIN CTE_Prep t ON r.ContainerID = t.ContainerID AND r.MonthRank + 1 = t.MonthRank AND t.Rn = 1
)
SELECT ContainerID, Date, MonthlyHours, MonthlyStarts
FROM RCTE
WHERE Date >= '2011-01-01' --to eliminate "zero row"
ORDER BY ContainerID
SQLFiddle DEMO (I have added some data for February and March in order to test on different lengths of months)
Old version fiddle