Joining tables on dates, but aligning one table by an extra day - sql

I have two tables below:
table A
+------------+-------+
| Date | Value |
+------------+-------+
| 11-08-2018 | 2.3 |
| 11-09-2018 | 4.3 |
| 11-12-2018 | 2.9 |
| 11-13-2018 | 3.9 |
+------------+-------+
table B
+------------+---------+
| Date | Value |
+------------+---------+
| 11-07-2018 | -6.99 |
| 11-08-2018 | 12.3333 |
| 11-09-2018 | 14.22 |
| 11-12-2018 | 3.66 |
+------------+---------+
I need to join them on the Date field however I want to join them so I get a result like below (i.e. I want to add a day to the date field in table B also ignoring weekend)
+------------+---------+---------+
| Date | Value A | Value B |
+------------+---------+---------+
| 11-08-2018 | 2.3 | -6.99 |
| 11-09-2018 | 4.3 | 12.333 |
| 11-12-2018 | 2.9 | 14.22 |
| 11-13-2018 | 3.9 | 3.66 |
+------------+---------+---------+
How best to achieve this?

Assuming you want to join Monday with previous Friday:
SELECT *
FROM tablea
INNER JOIN tableb ON DATEADD(DAY, IIF(DATENAME(WEEKDAY, tablea.Date) = 'Monday', -3, -1), tablea.Date) = tableb.Date

Define a function that adds days to a date skipping the weekend days:
create function dbo.udf_AddWorkingDays(#dateToIncrement as date, #daysToAdd as int)
returns datetime
as
begin
SET #dateToIncrement = dateadd(d, #daysToAdd, #dateToIncrement)
--skip sundays
IF datename(DW, #dateToIncrement) = 'sunday'
SET #dateToIncrement = dateadd(d, 1, #dateToIncrement)
--skip saturdays
IF datename(DW, #dateToIncrement) = 'saturday'
SET #dateToIncrement = dateadd(d, 2, #dateToIncrement)
return cast(#dateToIncrement AS datetime)
end
Use the functions in your join condition:
declare #tableA table ([Date] date , [Value] decimal(10,2))
declare #tableB table ([Date] date , [Value] decimal(10,2))
insert into #tableA values
('2018-11-08', 2.3)
,('2018-11-09', 4.3)
,('2018-11-12', 2.9)
,('2018-11-13', 3.9)
insert into #tableB values
('2018-11-07', -6.99 )
,('2018-11-08', 12.3333)
,('2018-11-09', 14.22 )
,('2018-11-12', 3.66 )
select A.[Date], A.[Value], B.[Value]
from #tableA A
inner join #tableB B on A.[Date] = dbo.udf_AddWorkingDays(B.[Date], 1)
Result:

You can modify your join condition accordingly
SELECT TableA.Date, TableA.Value as [Value A], TableB.Value as [Value B]
FROM TableB
JOIN TableA
ON TableA.Date = DATEADD(day, 1, TableA.Date)

Related

Split a date range in SQL Server

I'm struggling with a solution for a problem but I couldn't find anything similar here.
I have a table "A" like:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-01-01 | 2020-12-31 | abc |
+---------+------------+------------+-----------+
and I get a table "B" like:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-03-01 | 2020-04-15 | def |
+---------+------------+------------+-----------+
And what I need is:
+---------+------------+------------+-----------+
| user_id | from | to | attribute |
+---------+------------+------------+-----------+
| 1 | 2020-01-01 | 2020-02-29 | abc |
| 1 | 2020-03-01 | 2020-04-15 | def |
| 1 | 2020-04-16 | 2020-12-31 | abc |
+---------+------------+------------+-----------+
I tried just using insert and update but I couldn't figure out how to simultaneously do both. Is there a much simpler way? I read about CTE, could this be an approach?
I'd be very thankful for your help!
Edit: more examples
TABLE A
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-01-01 | 2020-12-31 | atr1 |
| 1 | 2021-01-01 | 2021-12-31 | atr2 |
| 2 | 2020-01-01 | 2021-06-15 | atr1 |
| 3 | 2020-01-01 | 2021-06-15 | atr3 |
TABLE B
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-09-01 | 2021-02-15 | atr3 |
| 2 | 2020-04-15 | 2020-05-31 | atr2 |
| 3 | 2021-04-01 | 2022-01-01 | atr1 |
OUTPUT:
| user_id | from | to | attribute |
+=========+============+============+===========+
| 1 | 2020-01-01 | 2020-08-31 | atr1 |
| 1 | 2020-09-01 | 2021-02-15 | atr3 |
| 1 | 2021-02-16 | 2021-12-31 | atr2 |
| 2 | 2020-01-01 | 2020-04-14 | atr1 |
| 2 | 2020-04-15 | 2020-05-31 | atr2 |
| 2 | 2020-06-01 | 2021-06-15 | atr1 |
| 3 | 2020-01-01 | 2021-03-31 | atr3 |
| 3 | 2021-04-01 | 2022-01-01 | atr1 |
Initially I just asked to split the date range and make a new row because the new attribute of table B is between the one in table A. But it's only a part of the problem. Maybe it's more clear with the new dataset(?)
Sample data,
create table #TableA( userid int, fromdt date
,todt date, attribute varchar(10))
insert into #TableA (userid , fromdt , todt , attribute)
values
( 1 ,'2020-01-01','2020-12-31' , 'atr1' ),
( 1 ,'2021-01-01','2021-12-31' , 'atr2' ),
( 2 ,'2020-01-01','2021-06-15' , 'atr1' ),
( 3 ,'2020-01-01','2021-06-15' , 'atr3' )
create table #TableB( userid int,fromdt date
,todt date, attribute varchar(10))
insert into #TableB (userid,fromdt, todt, attribute)
values
( 1 ,'2020-09-01','2021-02-15' , 'atr3' ),
( 2 ,'2020-04-15','2020-05-31' , 'atr2' ),
( 3 ,'2021-04-01','2022-01-01' , 'atr1' )
;
The Script,
;WITH CTE
AS (
SELECT *
FROM #TableA
UNION ALL
SELECT *
FROM #TableB
)
,CTE2
AS (
SELECT userid
,min(fromdt) minfromdt
,max(todt) maxtodt
FROM CTE
GROUP BY userid
)
,CTE3
AS (
SELECT c.userid
,c.fromdt
,c.todt
,c.attribute
,LEAD(c.fromdt, 1) OVER (
PARTITION BY c.userid ORDER BY c.fromdt
) LeadFromdt
FROM CTE c
)
,CTE4
AS (
SELECT c3.userid
,c3.fromdt
,CASE
WHEN c3.todt > c3.LeadFromdt
THEN dateadd(day, - 1, c3.leadfromdt)
--when c3.todt<c3.LeadFromdt then dateadd(day,-1,c3.leadfromdt)
ELSE c3.todt
END AS Todt
,
--c3.todt as todt1,
c3.attribute
FROM CTE3 c3
)
,CTE5
AS (
SELECT userid
,fromdt
,todt
,attribute
FROM CTE4
UNION ALL
SELECT c2.userid
,dateadd(day, 1, c4.Todt) AS Fromdt
,maxtodt AS Todt
,c4.attribute
FROM CTE2 c2
CROSS APPLY (
SELECT TOP 1 c4.todt
,c4.attribute
FROM cte4 c4
WHERE c2.userid = c4.userid
ORDER BY c4.Todt DESC
) c4
WHERE c2.maxtodt > c4.Todt
)
SELECT *
FROM CTE5
ORDER BY userid
,fromdt
drop table #TableA, #TableB
Your output is wrong.
Also append other sample data in same example
where my script is not working.
The easiest way is to work with a calendar table. You can create one and reuse it later.
When you have one (here I called it "AllDates"), you can do something like this:
WITH cte
as
(
select ad.theDate,u.userid,isnull(b.attrib,a.attrib) as attrib,
ROW_NUMBER() over (PARTITION BY u.userid, isnull(b.attrib,a.attrib)ORDER BY ad.theDate)
- ROW_NUMBER() over (PARTITION BY u.userid ORDER BY ad.theDate) as grp
from AllDates ad
cross join (select userid from tableA union select userid from tableB) u
left join tableB b on ad.theDate between b.frm and b.toD and u.userid = b.userid
left join tableA a on ad.theDate between a.frm and a.toD and u.userid = a.userid
where b.frm is not null
or a.frm is not null
)
SELECT userid,attrib,min(theDate) as frmD, max(theDate) as toD
FROM cte
GROUP BY userid,attrib,grp
ORDER BY 1,3;
If I understand the request correctly the data from table A should be merged into table B to fill the gaps based on four scenarios, here is how I achieved it:
/*
Scenario 1 - Use dates from B as base to be filled in from A
- Start and end dates from B
*/
SELECT
B.UserId,
B.StartDate,
B.EndDate,
B.Attr
FROM #tmpB AS B
UNION
/*
Scenario 2 - Start date between start and end date of another record
- End date from B plus one day as start date
- End date from A as end date
*/
SELECT
B.UserId,
DATEADD(DD, 1, B.EndDate) AS StartDate,
A.EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND B.StartDate < A.StartDate
AND B.EndDate > A.StartDate
UNION
/*
Scenario 3 - End date between start and end date of another record or both dates between start and end date of another record
- Start date from A as start date
- Start date from B minus one as end date
*/
SELECT
B.UserId,
A.StartDate,
DATEADD(DD, -1, B.StartDate) AS EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND (B.StartDate < A.EndDate AND B.EndDate > A.EndDate
OR B.StartDate BETWEEN A.StartDate AND A.EndDate AND B.EndDate BETWEEN A.StartDate AND A.EndDate)
UNION
/*
Scenario 4 - Both dates between start and end date of another record
- End date from B minus one as start date
- End date from A as end date
*/
SELECT
B.UserId,
DATEADD(DD, -1, B.EndDate) AS StartDate,
A.EndDate,
A.Attr
FROM #tmpB AS B
JOIN #tmpA AS A ON
B.UserId = A.UserId
AND B.StartDate BETWEEN A.StartDate AND A.EndDate
AND B.EndDate BETWEEN A.StartDate AND A.EndDate

How to repeat dates for multiples values and fill missing dates in SQL

I have a table with date, ID and Values, which looks like this
I want to repeat the date range(start date, end date) for all IDs, and if the value is missing for any date enter 0.
How can I achieve this in SQL?
Thanks in Advance
Just use cross join to generate the rows and left join to bring in the existing values:
select i.id, t.date, coalesce(t.value, 0) as value
from (select distinct id from t) i cross join
(select distinct date from t) left join
t
on t.id = i.id and t.date = i.date;
You can use insert into or select into to put this data into another table.
I have tried below example in SQL Server.
First you need to have a date table with the list of dates.
Then, you need to have the combination of date, id values
Now, you need to fill the val if it exists for the date, otherwise, it will be 0.
DECLARE #FromDate DATETIME, #ToDate DATETIME;
SET #FromDate = '2020-02-01';
SET #ToDate = '2020-02-28';
DECLARE #DateTable TABLE(theDate DATE)
DECLARE #datasource TABLE(id int, dateVal DATE, Val INT)
-- all days in that period
INSERT INTO #DateTable
SELECT TOP (DATEDIFF(DAY, #FromDate, #ToDate)+1)
TheDate = CAST(DATEADD(DAY, number, #FromDate) AS DATE)
FROM [master].dbo.spt_values
WHERE [type] = N'P' ORDER BY number;
--
INSERT INTO #datasource
VALUES
(59895,'2020-02-01',5),(59895,'2020-02-03',7),
(59888,'2020-02-01',2),(59888,'2020-02-02',10)
;WITH CTE_idDates AS
(
SELECT theDate,id FROM #DateTable
CROSS JOIN
#datasource
)
SELECT theDate, id,
COALESCE((SELECT Val from #dataSource WHERE dateval=c.thedate and id=c.id),0)
FROM CTE_idDates AS c
Result set
+------------+-------+------------------+
| theDate | id | (No column name) |
+------------+-------+------------------+
| 2020-02-01 | 59895 | 5 |
| 2020-02-02 | 59895 | 0 |
| 2020-02-03 | 59895 | 7 |
| 2020-02-04 | 59895 | 0 |
| 2020-02-05 | 59895 | 0 |
| 2020-02-06 | 59895 | 0 |
.
.
.
| 2020-02-28 | 59895 | 0 |
| 2020-02-01 | 59888 | 2 |
| 2020-02-02 | 59888 | 10 |
.
.
.
| 2020-02-26 | 59888 | 0 |
| 2020-02-27 | 59888 | 0 |
| 2020-02-28 | 59888 | 0 |
+------------+-------+------------------+

SQL Server get ids in one table on Date criteria in table one and table two

I am having problems getting the ids in TABLE A that satisfies the following criteria (I have tried a lot of different things and looked at various SO answers but cannot make it work. I looked into using OVER(PARTITION BY TABLE_B.calendar)):
Open (TABLE_B) should be equal to 1 on the first calendarDay (TABLE_B) on or after 10 days after startDate(TABLE_A).
endDate (TABLE_A) should be equal to the day found in 1) (i.e. the calendarDay for the respective id that satisfies the criteria).
Sample data:
TABLE_A:
+----+------------+------------+
| id | startDate | endDate |
+----+------------+------------+
| 1 | 2011-02-14 | 2011-03-14 |
| 2 | 2012-12-19 | 2013-01-20 |
| 3 | 2014-12-19 | 2015-01-21 |
+----+------------+------------+
TABLE_B:
+-------------+------+
| calendarDay | open |
+-------------+------+
| 2011-03-14 | 1 |
| 2011-03-16 | 0 |
| 2013-01-20 | 1 |
| 2013-01-21 | 1 |
| 2015-01-21 | 0 |
| 2015-01-22 | 1 |
+-------------+------+
Desired result:
+----+------------+------------+
| id | startDate | endDate |
+----+------------+------------+
| 1 | 2011-02-14 | 2011-03-14 |
| 2 | 2012-12-19 | 2013-01-20 |
+----+------------+------------+
I think you want:
select a.*
from a cross apply
(select top (1) b.*
from b
where b.open = 1 and b.calendarDate >= dateadd(day, 10, a.startdate)
order by b.calendarDate asc
) b
where b.calendarDate = a.endDate
You could use a CTE to first get the first calendar day:
with cteId(n, id, [open])
as (
select ROW_NUMBER() over (partition by a.id order by b.calendarDay) n, a.id, b.[open]
from #TABLE_A a
inner join #TABLE_B b on b.calendarDay >= DATEADD(day, 10, a.startDate)
)
... then just join it with TABLE_A
select a.*
from #TABLE_A a
inner join cteId c on a.id = c.id
where c.n = 1 and c.[open] = 1
You can try this query.
Use Exists
select a.*
from TABLE_A as a
where exists(
SELECT 1
FROM TABLE_B b
where
a.startDate <= DateAdd(day, 10, b.calendarDay) and b.[open] = 1
)
and exists(
SELECT 1
FROM TABLE_B b
where
a.endDate = b.calendarDay and b.[open] = 1
)
sqlfiddle:http://sqlfiddle.com/#!18/320111/15
another way can try to use join
select a.*
from TABLE_A as a
INNER JOIN
(
SELECT b.*,DateAdd(day, 10, b.calendarDay) addDay
FROM TABLE_B b
where b.[open] = 1
) b on a.startDate <= addDay and a.endDate = b.calendarDay
sqlfiddle:http://sqlfiddle.com/#!18/320111/19
you could probably use Exists here to look for the matching value
Select *
From Table_A a
Where Exists (
Select 1
From Table_B b
Where b.[open] = 1
And b.calendarDay >= DateAdd(dd, 10, a.endDate)
And b.calendarDay = a.endDate)
)

Connecting two tables so that results are shown in multiple columns

Let's assume we have table with data like this
+----------------+------------+-------+
| Company_number | Date | Value |
+----------------+------------+-------+
| 123 | 2017-01-01 | 5 |
| 123 | 2017-02-01 | 10 |
| 123 | 2018-01-01 | 15 |
| 456 | 2018-01-05 | 33 |
+----------------+------------+-------+
What should I do to receive data in format
+----------------+------+------------+------------+
| Company_number | Mont | Value 2017 | Value 2018 |
+----------------+------+------------+------------+
| 123 | 01 | 5 | 15 |
| 123 | 02 | 10 | |
| 456 | 01 | 33 | |
+----------------+------+------------+------------+
I have no idea how to select all data from 2017 and connect it using the company number and month with data from 2018.
I have taken all of the records from 2017, put it into another table, and tried to use this select, but it doesn't show records when there are no common months (there is no record for February).
select
s.company_number
,datepart(month,s.date) as Month
,s.Value as Value_2017
,r.Value as Value_2018
from table1 as s
left join table2 as r on concat(r.company_number,datepart(month,r.date))=concat(s.company_number,datepart(month,s.date))
where datepart(year,s.date)='2018'
select results (with no February)
+----------------+-------+------------+------------+
| company_number | Month | Value_2017 | Value_2018 |
+----------------+-------+------------+------------+
| 123 | 1 | 15 | 5 |
| 456 | 1 | 33 | NULL |
+----------------+-------+------------+------------+
Try this out:
SELECT
COALESCE([t1].[company_number], [t2].[company_number]) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
[table1] AS [t1]
FULL OUTER JOIN
[table2] as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
Just tried it with (can be copy/pasted in editor and executed) and it works fine:
DECLARE #t1 TABLE (Company_number INT, [Date] Date, Value INT)
DECLARE #t2 TABLE (Company_number INT, [Date] Date, Value INT)
INSERT INTO #t1 VALUES (123, '2017-01-01', 5), (123, '2017-02-01', 10)
INSERT INTO #t2 VALUES (123, '2018-01-01', 15), (456, '2018-01-05', 33)
SELECT
COALESCE([t1].Company_number, [t2].Company_number) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
#t1 AS [t1]
FULL OUTER JOIN
#t2 as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
This is using CTE on the same table1 NOT TWO TABLES for 2017 and 2018.
With tmp as (Select company_id,
Datepart(year, dte) as year,
Datepart(month, dte) as month,
Sum(value) as value
from tbl
Group by company_id,
Datepart(year, dte),
Datepart(month, dte))
Select coalesce(m.company_id,n.company_id) as company_id,
coalesce(m.month,n.month) as month,
m.value as value_2017,
n.value as value_2018
From (select * from tmp
Where year=2017) m
Full outer join (select * from tmp
Where year=2018) n
On m.company_id=n.company_id
and m.month=n.month
Result:
company_number month Value_2017 Value_2018
123 1 5 5
123 2 10 (null)
456 1 (null) 23

Join 2 Tables And Filter Sql Query

I have Two tables I need to total occupancy from the two tables
Table 1 TableMaster
_______________________________
|TableNo | TableType | Shared |
-------------------------------
|1 | WT | FULL |
|2 | WT | FULL |
|3 | WT | SHARED |
|4 | WT | SHARED |
-------------------------------
Table 2 TableSharedDetails
______________________________________________
|TableNo | ReservedDate | TableType | Shared |
----------------------------------------------
|4 | 29-12-2016 | WT | FULL |
|4 | 30-12-2016 | WT | FULL |
----------------------------------------------
My tablemaster has both full and shared table. Full table occupancy is 1 and shared table occupancy is 2 for some date we convert the shared table to full table for that I am using table 2 TableSharedDetails.
When we convert the table as full on that date i need to increase full table + 1 and decrease shared table - 1
Now I want the table type wise available status for particular period for example (28-12-2016 to 31-12-2016)
OUTPUT
______________________________________________
| ReservedDate | TableType | Shared |Totalava|
----------------------------------------------
| 28-12-2016 | WT | FULL | 2 |
| 28-12-2016 | WT | Shared | 2 |
| 29-12-2016 | WT | FULL | 3 |
| 29-12-2016 | WT | Shared | 1 |
| 30-12-2016 | WT | FULL | 3 |
| 30-12-2016 | WT | Shared | 1 |
| 31-12-2016 | WT | FULL | 2 |
| 31-12-2016 | WT | Shared | 2 |
----------------------------------------------
Here Totalava is count of table from tablemaster
I have tried one procedure but it is not correct
Create Procedure TableAvaStatus (#StartDate as DateTime, #EndDate as DateTime)
as
declare #AvaStatus table (ReserveDate DateTime, TableType varchar(10), Shared varchar(10), TotalAva int)
While #StartDate < #EndDate
Begin
INSERT INTO #AvaStatus (ReserveDate, TableType, Shared, TotalAva)
SELECT
#StartDate,
TableType,
Shared,
(CASE
WHEN Shared = 'FULL' THEN COUNT(*)
ELSE COUNT(*) END)
+ (SELECT COUNT(*) FROM TableSharedDetails
WHERE Shared = 'FULL'
AND TableType = A.TableType
GROUP BY TableType) AS TotalAva
FROM TableMaster A
End
Select * From #AvaStatus
Assuming Data Type of ReservedDate is Date.
Try:
declare #startDate date
declare #endDate date
select #startDate = '2016-12-28'
select #endDate = '2016-12-31'
-- Recursive CTE to generate dates between start and end dates
;with dateRange as
(
select #startDate as dt
union all
select dateadd(dd, 1, dt)
from dateRange
where dateadd(dd, 1, dt) < +dateadd(dd, 1, #endDate)
)
select
tbl_master.dt as ReservedDate,
tbl_master.TableType,
tbl_master.Shared,
case tbl_master.shared
when 'FULL' then
tbl_master.avail+coalesce(tbl_shared.shared_count,0)
when 'SHARED' then
tbl_master.avail-coalesce(tbl_shared.shared_count,0)
else
tbl_master.avail
end as Totalavail
from (
-- Join date range data with `TableMaster` to get table availablity
select dateRange.dt,TableMaster.TableType,TableMaster.Shared,count(*) as avail
from dateRange left join TableMaster on 1=1
group by dateRange.dt,TableMaster.TableType,TableMaster.Shared
) as tbl_master
-- Join `TableSharedDetails` for adding and subtracting counts from table availability
left join (
select ReservedDate,TableType,count(*) as shared_count
from TableSharedDetails
group by ReservedDate,TableType
) tbl_shared on
tbl_master.dt=tbl_shared.ReservedDate
and tbl_master.TableType=tbl_shared.TableType;
SQL Fiddle : http://sqlfiddle.com/#!6/84458/33
Explanation:
1- Use of with dateRange : This is the recursive CTE used to generate dates between start and end dates provided to the stored procedure.
2- Once date range is generated ,Join range data with TableMaster to get the availability of tables. As there is no specific condition to join,So I put 1=1(means True in all conditions) to combine all date range data with TableMaster.
3- After getting table availability , Join TableSharedDetails to add and subtract from FULL and shared tables data.