JOIN 2 tables based on StartDate and EndDate column - sql

I have 2 tables as described in the SQL below:
DECLARE #Table1 TABLE
(
[ForeignKeyID] INT
,[Name] CHAR
,[StartDate] DATE
,[FinishDate] DATE
);
DECLARE #Table2 TABLE
(
[ForeignKeyID] INT
,[StartDate] DATE
,[EndDate] DATE
);
INSERT INTO #Table1 ([ForeignKeyID], [Name], [StartDate], [FinishDate])
VALUES (1, 'A', '20210101', '20210103')
,(1, 'B', '20210103', NULL);
INSERT INTO #Table2 ([ForeignKeyID], [StartDate], [EndDate])
VALUES (1, '20210101', '20210102')
,(1, '20210102', '20210103')
,(1, '20210103', '20210104')
,(1, '20210104', '20210105')
,(1, '20210105', '20210106');
SELECT
t2.ForeignKeyID, t2.StartDate, t2.EndDate, t1.Name AS OnDuty
FROM
#Table2 t2
INNER JOIN #Table1 t1 ON t2.ForeignKeyID = t1.ForeignKeyID
-- WHERE ???
ORDER BY t2.StartDate;
I want to get a result table as shown below. I have tried, but the issue is with the NULL values in the FinishDate column in Table1.
ForeignKeyID
StartDate
EndDate
OnDuty
1
1-Jan-21
2-Jan-21
A
1
2-Jan-21
3-Jan-21
A
1
3-Jan-21
4-Jan-21
B
1
4-Jan-21
5-Jan-21
B
1
5-Jan-21
6-Jan-21
B

I'm guessing at your logic here (be best if you could add the logic to your question), but a combination or and / or logic to compare the dates depending on whether FinishDate is null or not should do the trick.
SELECT t2.ForeignKeyID, t2.StartDate, t2.EndDate, t1.[Name] AS OnDuty
FROM #Table2 t2
INNER JOIN #Table1 t1 ON t2.ForeignKeyID = t1.ForeignKeyID
WHERE t2.StartDate >= t1.StartDate
AND (t2.StartDate < t1.FinishDate OR t1.FinishDate IS NULL)
ORDER BY t2.StartDate;

Related

SQL statement to get all customers with no orders TODAY(current date)

The question is, how do I write a statement that would return all customers with NO Orders TODAY using sql join?
Tables : tbl_member ,tbl_order
tbl_member consist of id,name,
tbl_order consist of id, date, foodOrdered
If you left join, the select where the table on the right is nulkl, it limits to the rows that DO NOT meet the join condition:
select t1.*
from tbl_member t1
left join tbl_member t2
on t1.id = t2.id -- assuming that t2.id relates to t1.id
and t2.date = current_date() -- today's date in mysql
where t2.id is null
Assuming tbl_order date is a datetime (it probably should be) for sql server you could use something like:
declare #tbl_member table
(
id int,
fullname varchar(50)
)
declare #tbl_order table
(
id int,
orderdate datetime,
foodOrdered varchar(50)
)
INSERT INTO #tbl_member VALUES (1, 'George Washington')
INSERT INTO #tbl_member VALUES (2, 'Abraham Lincoln')
INSERT INTO #tbl_member VALUES (3, 'Mickey Mouse')
INSERT INTO #tbl_member VALUES (3, 'Donald Duck')
INSERT INTO #tbl_order VALUES (1, '2017-07-01 13:00:00', 'Fish and Chips')
INSERT INTO #tbl_order VALUES (2, '2017-07-03 08:00:00', 'Full English')
INSERT INTO #tbl_order VALUES (3, '2017-07-25 08:00:00', 'Veggie Burger')
INSERT INTO #tbl_order VALUES (3, '2017-07-25 12:00:00', 'Bangers and Mash')
SELECT id, fullname FROM #tbl_member WHERE id NOT IN
(SELECT id FROM #tbl_order
WHERE CAST(orderDate as date) = CAST(GETDATE() as Date))
It helps if you specify what flavour database you are using as the syntax is often subtly different.

concat a column fields of a table group by the fields of another table

I have two tables say t1 and t2 which join with testid.I need to concat val column of t2 if d1,d2 and id of table t1 is same.Please refer to the following tables.
CREATE TABLE #t1 (d1 varchar(100),d2 varchar(100),id int,testid int)
INSERT INTO #t1 (d1,d2,id,testid) VALUES ('p','q',1,101)
INSERT INTO #t1 (d1,d2,id,testid) VALUES ('r','s',2,102)
INSERT INTO #t1 (d1,d2,id,testid) VALUES ('p','q',1,103)
INSERT INTO #t1 (d1,d2,id,testid) VALUES ('r','s',1,104)
CREATE TABLE #t2 (testid int,val varchar(100))
INSERT INTO #t2 (testid,val) values (101,'x')
INSERT INTO #t2 (testid,val) values (102,'y')
INSERT INTO #t2 (testid,val) values (103,'z')
INSERT INTO #t2 (testid,val) values (104,'xx')
The result should be:
d1 d2 pid val
p q 1 x,z
r s 2 y
r s 1 xx
IF OBJECT_ID('tempdb.dbo.#t1') IS NOT NULL
DROP TABLE #t1
IF OBJECT_ID('tempdb.dbo.#t2') IS NOT NULL
DROP TABLE #t2
CREATE TABLE #t1 (d1 VARCHAR(100), d2 VARCHAR(100), id INT, testid INT)
INSERT INTO #t1 (d1, d2, id, testid)
VALUES ('p', 'q', 1, 101)
, ('r', 's', 2, 102)
, ('p', 'q', 1, 103)
, ('r', 's', 1, 104)
CREATE TABLE #t2 (testid INT, val VARCHAR(100))
INSERT INTO #t2 (testid, val)
VALUES (101, 'x')
, (102, 'y')
, (101, 'z')
, (104, 'xx')
SELECT *
FROM (
SELECT d1, d2, id, value = STUFF((
SELECT [text()] = ',' + val
FROM #t2
WHERE #t2.testid = #t1.testid
FOR XML PATH('')), 1, 1, '')
FROM #t1
) t
WHERE t.value IS NOT NULL
output -
d1 d2 id value
--- --- ---- ----------------
p q 1 x,z
r s 2 y
r s 1 xx

Summing up the records as per given conditions

I have a table like below, What I need that for any particular fund and up to any particular date logic will sum the amount value. Let say I need the sum for 3 dates as 01/28/2015,03/30/2015 and 04/01/2015. Then logic will check for up to first date how many records are there in table . If it found more than one record then it'll sum the amount value. Then for next date it'll sum up to the next date but from the previous date it had summed up.
Id Fund Date Amount
1 A 01/20/2015 250
2 A 02/28/2015 300
3 A 03/20/2015 400
4 A 03/30/2015 200
5 B 04/01/2015 500
6 B 04/01/2015 600
I want result to be like below
Id Fund Date SumOfAmount
1 A 02/28/2015 550
2 A 03/30/2015 600
3 B 04/01/2015 1100
Based on your question, it seems that you want to select a set of dates, and then for each fund and selected date, get the sum of the fund amounts from the selected date to the previous selected date. Here is the result set I think you should be expecting:
Fund Date SumOfAmount
A 2015-02-28 550.00
A 2015-03-30 600.00
B 2015-04-01 1100.00
Here is the code to produce this output:
DECLARE #Dates TABLE
(
SelectedDate DATE PRIMARY KEY
)
INSERT INTO #Dates
VALUES
('02/28/2015')
,('03/30/2015')
,('04/01/2015')
DECLARE #FundAmounts TABLE
(
Id INT PRIMARY KEY
,Fund VARCHAR(5)
,Date DATE
,Amount MONEY
);
INSERT INTO #FundAmounts
VALUES
(1, 'A', '01/20/2015', 250)
,(2, 'A', '02/28/2015', 300)
,(3, 'A', '03/20/2015', 400)
,(4, 'A', '03/30/2015', 200)
,(5, 'B', '04/01/2015', 500)
,(6, 'B', '04/01/2015', 600);
SELECT
F.Fund
,D.SelectedDate AS Date
,SUM(F.Amount) AS SumOfAmount
FROM
(
SELECT
SelectedDate
,LAG(SelectedDate,1,'1/1/1900') OVER (ORDER BY SelectedDate ASC) AS PreviousDate
FROM #Dates
) D
JOIN
#FundAmounts F
ON
F.Date BETWEEN DATEADD(DAY,1,D.PreviousDate) AND D.SelectedDate
GROUP BY
D.SelectedDate
,F.Fund
EDIT: Here is alternative to the LAG function for this example:
FROM
(
SELECT
SelectedDate
,ISNULL((SELECT TOP 1 SelectedDate FROM #Dates WHERE SelectedDate < Dates.SelectedDate ORDER BY SelectedDate DESC),'1/1/1900') AS PreviousDate
FROM #Dates Dates
) D
If i change your incorrect sample data to ...
CREATE TABLE TableName
([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
;
INSERT INTO TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '2015-01-28 00:00:00', 250),
(2, 'A', '2015-01-28 00:00:00', 300),
(3, 'A', '2015-03-30 00:00:00', 400),
(4, 'A', '2015-03-30 00:00:00', 200),
(5, 'B', '2015-04-01 00:00:00', 500),
(6, 'B', '2015-04-01 00:00:00', 600)
;
this query using GROUP BY works:
SELECT MIN(Id) AS Id,
MIN(Fund) AS Fund,
[Date],
SUM(Amount) AS SumOfAmount
FROM dbo.TableName t
WHERE [Date] IN ('01/28/2015','03/30/2015','04/01/2015')
GROUP BY [Date]
Demo
Initially i have used Row_number and month function to pick max date of every month and in 2nd cte i did sum of amounts and joined them..may be this result set matches your out put
declare #t table (Id int,Fund Varchar(1),Dated date,amount int)
insert into #t (id,Fund,dated,amount) values (1,'A','01/20/2015',250),
(2,'A','01/28/2015',300),
(3,'A','03/20/2015',400),
(4,'A','03/30/2015',200),
(5,'B','04/01/2015',600),
(6,'B','04/01/2015',500)
;with cte as (
select ID,Fund,Amount,Dated,ROW_NUMBER() OVER
(PARTITION BY DATEDIFF(MONTH, '20000101', dated)ORDER BY dated desc)AS RN from #t
group by ID,Fund,DATED,Amount
),
CTE2 AS
(select SUM(amount)Amt from #t
GROUP BY MONTH(dated))
,CTE3 AS
(Select Amt,ROW_NUMBER()OVER (ORDER BY amt)R from cte2)
,CTE4 AS
(
Select DISTINCT C.ID As ID,
C.Fund As Fund,
C.Dated As Dated
,ROW_NUMBER()OVER (PARTITION BY RN ORDER BY (SELECT NULL))R
from cte C INNER JOIN CTE3 CC ON c.RN = CC.R
Where C.RN = 1
GROUP BY C.ID,C.Fund,C.RN,C.Dated )
select C.R,C.Fund,C.Dated,cc.Amt from CTE4 C INNER JOIN CTE3 CC
ON c.R = cc.R
declare #TableName table([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
declare #Sample table([SampleDate] datetime)
INSERT INTO #TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '20150120 00:00:00', 250),
(2, 'A', '20150128 00:00:00', 300),
(3, 'A', '20150320 00:00:00', 400),
(4, 'A', '20150330 00:00:00', 200),
(5, 'B', '20150401 00:00:00', 500),
(6, 'B', '20150401 00:00:00', 600)
INSERT INTO #Sample ([SampleDate])
values ('20150128 00:00:00'), ('20150330 00:00:00'), ('20150401 00:00:00')
-- select * from #TableName
-- select * from #Sample
;WITH groups AS (
SELECT [Fund], [Date], [AMOUNT], MIN([SampleDate]) [SampleDate] FROM #TableName
JOIN #Sample ON [Date] <= [SampleDate]
GROUP BY [Fund], [Date], [AMOUNT])
SELECT [Fund], [SampleDate], SUM([AMOUNT]) FROM groups
GROUP BY [Fund], [SampleDate]
Explanation:
The CTE groups finds the earliest SampleDate which is later than (or equals to) your
data's date and enriches your data accordingly, thus giving them the group to be summed up in.
After that, you can group on the derived date.

Updating Using Aggregate Function

I am trying to update a column of the table using the below query.. But I get an error
An aggregate may not appear in the set list of an UPDATE statement
Code:
UPDATE Test.dbo.Table1
SET InDate = MIN(b.Date)
FROM
Test.dbo.Table1 a
LEFT OUTER JOIN
Test.dbo.Table2 b
ON
a.ID1 = b.ID2
WHERE b.Code = 'IN';
I want to update the InDate column in my table with the oldest date from Table2 (b.Date) column where (b.code) is 'IN'
What is wrong in here?
You need to put the aggregate in a temp table or subquery and you need an explicit GROUP BY statement.
UPDATE Test.dbo.Table1
SET InDate = min_date
FROM Test.dbo.Table1 c inner join
(SELECT a.id1, MIN(b.Date) min_date
FROM Test.dbo.Table1 a
LEFT OUTER JOIN Test.dbo.Table2 b
ON a.ID1 = b.ID2
Group by a.id1) d
ON c.ID1 = d.ID1
WHERE c.Code = 'IN';
I think this will do what you want. I've removed the aliases to make it as clear as possible:
UPDATE Table1
SET InDate = (
SELECT MIN(Table2.Date)
FROM Table2
WHERE Table1.ID1 = Table2.ID2
AND Table2.Code = 'IN'
)
You could use apply to get the min date and then use that in the update statement:
UPDATE a
SET a.InDate = b.MinBDate
FROM Table1 a
OUTER APPLY
(
SELECT MIN(b.InDate) MinBDate
FROM Table2 b
WHERE b.Id = a.Id
AND b.Code = 'IN'
) b
Maybe this?
UPDATE Test.dbo.Table1
SET InDate = b.Date
FROM
Test.dbo.Table1 a
INNER JOIN (
select
b.ID2,
MIN(b.Date) Date
from Test.dbo.Table2 b
where
WHERE b.Code = 'IN'
group by
b.ID2
) b
ON
a.ID1 = b.ID2
Assuming your data model is something like the following, joining to a derived table should do the trick:
--Data Setup:
DECLARE #Table1 TABLE (ID1 INT, InDate DATETIME)
DECLARE #Table2 TABLE (ID2 INT, ID1 INT, Date DATETIME, Code VARCHAR(12))
INSERT INTO #Table1 (ID1)
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
INSERT INTO #Table2 (ID2, ID1, Date, Code)
VALUES
(1, 1, '1/1/2014', 'OUT'),
(2, 1, '5/1/2014', 'IN'),
(3, 1, '3/1/2013', 'IN'),
(4, 2, '1/1/2014', 'OUT'),
(5, 2, '1/1/2014', 'IN'),
(6, 3, '1/1/2014', 'IN'),
(7, 4, '1/1/2014', 'IN'),
(8, 5, '1/1/2014', 'IN'),
(9, 6, '2/1/2014', 'OUT'),
(10, 7, '3/1/2014', 'IN'),
(11, 8, '4/1/2014', 'IN'),
(12, 9, '2/1/2014', 'IN'),
(12, 9, '2/1/2014', 'IN'),
(12, 10, '1/2/2014', 'IN'),
(12, 10, '1/3/2014', 'IN'),
(12, 10, '1/4/2014', 'IN'),
(12, 10, '1/1/2014', 'OUT')
--Actual Update:
UPDATE T1
SET InDate = T2.MinDate
FROM #Table1 T1
JOIN (SELECT T2.ID1, MIN(Date) AS MinDate
FROM #Table2 T2
WHERE T2.Code = 'IN'
GROUP BY T2.ID1) T2 ON T2.ID1 = T1.ID1
--Results
SELECT *
FROM #Table1

Merging records based on a time difference?

I have the following table:
CREATE TABLE #TEMP (id int, name varchar(255), startdate datetime, enddate datetime)
INSERT INTO #TEMP VALUES(1, 'John', '2011-01-11 00:00:00.000','2011-01-11 00:01:10.000')
INSERT INTO #TEMP VALUES(2, 'John', '2011-01-11 00:00:20.000','2011-01-11 00:01:50.000')
INSERT INTO #TEMP VALUES(3, 'John', '2011-01-11 00:01:40.000','2011-01-11 00:01:50.000')
INSERT INTO #TEMP VALUES(4, 'Adam', '2011-01-11 00:00:40.000','2011-01-11 00:01:20.000')
INSERT INTO #TEMP VALUES(5, 'Adam', '2011-01-11 00:00:10.000','2011-01-11 00:01:30.000')
SELECT * FROM #TEMP
DROP TABLE #TEMP
I am trying to merge all records with the same name within a range of 60 seconds to each other to get the following:
John 2011-01-11 00:00:00.000 2011-01-11 00:01:10.000
John 2011-01-11 00:01:40.000 2011-01-11 00:01:50.000
Adam 2011-01-11 00:00:10.000 2011-01-11 00:01:20.000
Any suggestions on how to do this on a table with about 50K records? Currently, I managed to get to this:
SELECT * FROM #TEMP
CREATE TABLE #Merge(id1 int, id2 int)
INSERT INTO #Merge
SELECT id, uuid
FROM
(
SELECT t.id, u.uuid, t.name, t.startdate, t.enddate, u.ustartdate, u.uenddate,
(CASE WHEN (DATEDIFF(second, t.startdate, u.ustartdate) <= 60 AND DATEDIFF(second, t.startdate, u.ustartdate) >= 0) then 1 else 0 END) Flag
FROM #Temp t
INNER JOIN
(SELECT id AS uuid, name, startdate AS ustartdate, enddate AS uenddate
FROM #Temp) u
ON t.name = u.name AND t.startdate != u.ustartdate AND t.id != u.uuid
) w
WHERE Flag = 1
SELECT * FROM #Merge
-- Insert non-mergable records
CREATE TABLE #TEMP2 (id int, name varchar(255), membergroup varchar(255), startdate datetime, enddate datetime)
INSERT INTO #TEMP2
SELECT * FROM #TEMP
WHERE id NOT IN (SELECT id1 FROM #Merge UNION SELECT id2 FROM #Merge)
SELECT * FROM #TEMP2
Of course, I am not sure how to proceed from here. The #Merge table gives me rows that are to be merged. What I did was to insert non-mergable rows first into #Temp2 first.
EDIT:
Updated set of rows, just in case:
INSERT INTO #TEMP VALUES(1, 'John', 'A', '2011-01-11 00:00:00.000','2011-01-11 00:01:10.000')
INSERT INTO #TEMP VALUES(2, 'John', 'A', '2011-01-11 00:00:01.000','2011-01-11 00:01:10.000')
INSERT INTO #TEMP VALUES(3, 'John', 'B', '2011-01-11 00:00:20.000','2011-01-11 00:01:50.000')
INSERT INTO #TEMP VALUES(4, 'John', 'C', '2011-01-11 00:01:40.000','2011-01-11 00:01:50.000')
INSERT INTO #TEMP VALUES(5, 'John', 'C', '2011-01-11 00:01:50.000','2011-01-11 00:02:20.000')
INSERT INTO #TEMP VALUES(6, 'Adam', 'A', '2011-01-11 00:00:40.000','2011-01-11 00:01:20.000')
INSERT INTO #TEMP VALUES(7, 'Adam', 'B', '2011-01-11 00:00:10.000','2011-01-11 00:01:30.000')
INSERT INTO #TEMP VALUES(8, 'Adam', 'B', '2011-01-11 00:03:10.000','2011-01-11 00:04:30.000')
The code below manage's to show both merged rows (rows 1-2,4-5) and unique rows (row 3)
SELECT DISTINCT a.id,a.name,a.startdate,a.enddate
FROM temp a
LEFT JOIN temp b ON a.name = b.name AND a.id < b.id AND DATEDIFF(s,a.startdate,b.startdate)<=60
LEFT JOIN temp c ON c.name = a.name AND c.id < a.id AND DATEDIFF(s,c.startdate,a.startdate)<=60
WHERE (b.id IS NOT NULL OR c.id IS NULL) AND a.id <= COALESCE(c.id,a.id)
Given you haven't said how to use the 60 second interval and your sample code showed only a startdate comparison, here you go
SELECT
*
FROM
#Temp t1
CROSS APPLY
(SELECT TOP 1*
FROM #Temp t2
WHERE t1.name = t2.name AND DATEDIFF(second, t1.startdate, t2.startdate) < 60 AND t1.id < t2.id
ORDER BY id DESC
) t2x
Based on startdate only, row pairs 1/2 and 4/5 make it into the output. Row 3 doesn't so you'll have to explain why you added it.
That is, row id = 3 is not within 60 seconds of row 1 or 2 based on startdate. So it shouldn't be in the output.
This assumes that id and startdate are both increasing.
Edit, after chat:
SELECT
*
FROM
#Temp t1
CROSS APPLY
(SELECT TOP 1 *
FROM #Temp t2
WHERE t1.name = t2.name AND DATEDIFF(second, t1.startdate, t2.startdate) < 60 AND t1.id < t2.id
ORDER BY t2.id DESC
) t2x
UNION ALL
SELECT
t1.*, t1.*
FROM
#Temp t1
WHERE NOT EXISTS
(
SELECT
t1ZZ.id, t2xZZ.id
FROM
#Temp t1ZZ
CROSS APPLY
(SELECT TOP 1 *
FROM #Temp t2ZZ
WHERE t1ZZ.name = t2ZZ.name AND DATEDIFF(second, t1ZZ.startdate, t2ZZ.startdate) < 60 AND t1ZZ.id < t2ZZ.id
ORDER BY t2ZZ.id DESC
) t2xZZ
WHERE
t1.id IN (t1ZZ.id, t2xZZ.id)
)