SQL JOIN with multiple date condition - sql

We have the first valuetable table and the query should check if there is
a next younger datetime in the correctiontable table and should add the corrvalue with the corrdatetime.
My problem query:
SELECT * FROM valuetable vt
LEFT JOIN correctiontable corr ON corr.value_id = vt.id WHERE vt.datetime <= corr.corrdatetime
is just delivering the last corrdatetime...
To clarify te results:
Row1 id1 should be NULL as the valuetable datetime is younger than the correction datetime
Row2 id2 should be 01/08/2017 00:00:00 as the datetime in valuetable is older but younger than the 01/12/2017 10:00:00 corrdatetime
Row3 id2 got its correction on 01/12/2017 10:00:00
Row4 id3 is NULL, there is no corrdatetime in correctiontable for it
Thank you all ++
+----------------------------------+
| valuetable |
+----------------------------------+
| id | datetime | value |
+----+---------------------+-------+
| 1 | 22/07/2017 13:00:00 | 123 |
+----+---------------------+-------+
| 2 | 10/08/2017 09:00:00 | 456 |
+----+---------------------+-------+
| 2 | 05/12/2017 20:00:00 | 789 |
+----+---------------------+-------+
| 3 | 11/11/2017 11:11:11 | 012 |
+----+---------------------+-------+
+-------------------------------------------------+
| correctiontable |
+-------------------------------------------------+
| id | value_id | corrdatetime | corrvalue |
+----+----------+---------------------+-----------+
| 1 | 2 | 01/08/2017 00:00:00 | 888 |
+----+----------+---------------------+-----------+
| 2 | 2 | 01/12/2017 10:00:00 | 999 |
+----+----------+---------------------+-----------+
| 3 | 1 | 01/08/2017 20:00:00 | 111 |
+----+----------+---------------------+-----------+
+--------------------------------------------------------------------+
| Result (as it should be) |
+--------------------------------------------------------------------+
| id | datetime | corrdatetime | value | corrvalue |
+----+---------------------+---------------------+-------+-----------+
| 1 | 22/07/2017 13:00:00 | NULL | 123 | NULL |
+----+---------------------+---------------------+-------+-----------+
| 2 | 10/08/2017 09:00:00 | 01/08/2017 00:00:00 | 456 | 888 |
+----+---------------------+---------------------+-------+-----------+
| 2 | 05/12/2017 20:00:00 | 01/12/2017 10:00:00 | 789 | 999 |
+----+---------------------+---------------------+-------+-----------+
| 3 | 11/11/2017 11:11:11 | NULL | 012 | NULL |
+----+---------------------+---------------------+-------+-----------+

Assuming "younger" means "logically less than", this should work for you.
select *
from valuetable a
outer apply (
select top 1 *
from correctiontable y
where y.value_id = a.id
and y.datetime < a.datetime
order by y.datetime desc
) b

Many Thanks to #KindaTechy for delivering me the right path!
I've created two querys, one for MySQL and one for >= Oracle 12.1
For MySQL:
SELECT *
FROM valuetable vt
LEFT JOIN correctiontable ON correctiontable.id
=
(SELECT corr.id
FROM correctiontable corr
WHERE vt.id = corr.value_id
AND vt.datetime <= corr.corrdatetime
ORDER BY datetime DESC
LIMIT 1)
For Oracle:
select *
from valuetable vt
outer apply (
select *
from correctiontable corr
where corr.value_id = vt.id
and corr.corrdatetime < vt.datetime
order by corr.corrdatetime desc
FETCH FIRST 1 ROWS ONLY
) b;

I found a working query, but your id column of valuetable should be unique, because otherwise you get a cross product.
SELECT vt.id, vt.datetime, corr.corrdatetime, vt.value, corr.corrvalue
FROM valuetable vt
LEFT JOIN correctiontable corr
ON corr.value_id = vt.id
AND vt.datetime >= corr.corrdatetime
By changing the date constraint from WHERE-CLAUSE to ON-CLAUSE it will impact only the join and not the result.
A made a sample for you http://sqlfiddle.com/#!9/301a6/4/0
When you need that non-unique id, the query must be improved. And also the test data set.

Related

How to concat two fields and use the result in WHERE clause?

I have to get all oldest records based on the date-time information.
Data
Id | External Id | Date | Time
1 | 1000 | 2020-08-18 00:00:00 | 02:30:22
2 | 1000 | 2020-08-12 00:00:00 | 12:45:51
3 | 1556 | 2020-08-17 00:00:00 | 10:09:01
4 | 1919 | 2020-08-14 00:00:00 | 18:19:18
5 | 1919 | 2020-08-14 00:00:00 | 11:45:21
6 | 1919 | 2020-08-14 00:00:00 | 15:54:15
Expected result
Id | External Id | Date | Time
2 | 1000 | 2020-08-12 00:00:00 | 12:45:51
3 | 1556 | 2020-08-17 00:00:00 | 10:09:01
5 | 1919 | 2020-08-14 00:00:00 | 11:45:21
I'm currently doing this
SELECT *
FROM RUN AS T1
WHERE CONCAT(T1.DATE, T1.TIME) = (
SELECT MIN(CONCAT(T2.DATE, T2.TIME))
FROM RUN AS T2
WHERE T2.EXTERNAL_ID = T1.EXTERNAL_ID
)
Is it a correct way to do ?
Thank you, regards
Update 1 : Data type
DATE column is datetime
TIME column is varchar
You can use a window function such as DENSE_RANK()
SELECT ID, External_ID, Date, Time
FROM
(
SELECT DENSE_RANK() OVER (PARTITION BY External_ID ORDER BY Date, Time) AS dr,
r.*
FROM run r
) AS q
WHERE dr = 1
Demo

Difference between consecutive rows in SQL, inclusive of the first and last row?

I'm trying to get the successive differences of rows of data in SQL, including differences between first and last row and 0.
I have two tables that look like this
+------------+-------+ +------------+-------+
| Date | Name | | Date | Value |
+------------+-------+ +------------+-------+
| 2019-10-10 | AAA | | 2019-10-11 | 100 |
| 2019-10-11 | BBB | | 2019-10-12 | 150 |
| 2019-10-12 | CCC | | 2019-10-14 | 300 |
| 2019-10-13 | DDD | +------------+-------+
| 2019-10-14 | EEE |
| 2019-10-15 | FFF |
+------------+-------+
The end result I'm looking for is
+------------+-------+-------+---------------+------------+
| Date | Name | Value | PreviousValue | Difference |
+------------+-------+-------+---------------+------------+
| 2019-10-11 | BBB | 100 | 0 | 100 |
| 2019-10-12 | CCC | 150 | 100 | 50 |
| 2019-10-14 | EEE | 300 | 150 | 150 |
| 2019-10-15 | FFF | 0 | 300 | -300 |
+------------+-------+-------+---------------+------------+
I can get the first row by using LAG, but I don't quite know how to get the last row at the same time.
SELECT
d.[Date],
d.[Name],
v.[Value],
[PreviousValue] = COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
[PreviousLossAmount] = v.[Value] - COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
FROM
[Dates] d
LEFT JOIN
[Values] v
ON
d.[Date] = v.[Date]
Note that in reality, my tables are more complex, and I'd need to group and partition by multiple columns.
In a subquery, you can LEFT JOIN, and use ROW_NUMBER() to identify the last record. Then in the outer query you can filter out rows that did not match in the LEFT JOIN while allowing the last record.
Other considerations:
you just need to order LAG() by d.[Date] instead of v.[Date]. This properly handles the case when the left join comes up empty (ie when v.[Date] is null).
You also want to use COALESCE() on v.[Value], since this may come up null too.
Query:
SELECT
[Date],
[Name],
[Value],
[PreviousValue],
[PreviousLossAmount]
FROM (
SELECT
d.[Date],
d.[Name],
[Value] = COALESCE(v.[Value], 0),
[PreviousValue] = COALESCE(LAG(v.[Value]) OVER (ORDER BY d.[Date]), 0),
[PreviousLossAmount] = COALESCE(v.[Value], 0)
- COALESCE(LAG(v.[Value]) OVER (ORDER BY d.[Date]), 0),
rn = ROW_NUMBER() OVER(ORDER BY d.[Date] DESC),
vDate = v.[Date]
FROM [Dates] d
LEFT JOIN [Values] v ON d.[Date] = v.[Date]
) t
WHERE vDate IS NOT NULL OR RN = 1
ORDER BY [Date]
Demo on DB Fiddle:
Date | Name | Value | PreviousValue | PreviousLossAmount
:------------------ | :--- | ----: | ------------: | -----------------:
11/10/2019 00:00:00 | BBB | 100 | 0 | 100
12/10/2019 00:00:00 | CCC | 150 | 100 | 50
14/10/2019 00:00:00 | EEE | 300 | 0 | 300
15/10/2019 00:00:00 | FFF | 0 | 300 | -300
You seem to want:
select d.[Date], d.[Name], v.[Value],
PreviousValue = COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
PreviousLossAmount]= v.[Value] - COALESCE(LAG(v.[Value) OVER (ORDER BY v.[Date]), 0)
from dates d join
[Values] v
on d.[Date] = v.[Date]
union all
select top (1) d.date, d.name, 0 as value, v.value, - v.value
from dates d cross join
(select top (1) v.*
from values v
order by v.date desc
) v
order by d.date;

Repeating ID based on

I have a very simple requirement but I'm struggling to find a way around this.
I have a very simple query:
SELECT
ServiceCode,
StartDate,
Available,
Nights,
BookingID
FROM #tmpAvailability
LEFT JOIN vwRSBooking B
ON B.Depart = A.StartDate
AND B.ServiceCode = A.SupplierCode
AND B.StatusID IN (2640, 2621)
ORDER BY StartDate;
Made up of 2 tables
#tmpAvailability which consists of the following fields:
SupplierCode
StartDate
Available
vwRSBooking which consists of the following fields
BookingID
DepartDate
Code
Nights
StatusID
Departure and startdate can be joined to link the first day, and the servicecode and suppliercode can be joined to make sure that the availability is linked to the same supplier.
Which produces an output like this:
Code | Dates | Available | Nights | BookingID
TEST | 2018-01-04 | 1 | NULL | NULL
TEST | 2018-01-05 | 1 | NULL | NULL
TEST | 2018-01-06 | 0 | 4 | 123456
TEST | 2018-01-07 | 0 | NULL | NULL
TEST | 2018-01-08 | 0 | NULL | NULL
TEST | 2018-01-09 | 0 | NULL | NULL
TEST | 2018-01-10 | 1 | NULL | NULL
TEST | 2018-01-11 | 1 | NULL | NULL
TEST | 2018-01-12 | 1 | NULL | NULL
TEST | 2018-01-13 | 0 | NULL | 234567
TEST | 2018-01-14 | 0 | NULL | NULL
TEST | 2018-01-15 | 0 | NULL | NULL
What I need is when the BookingID in for 4 days that the bookingID and the nights are spread across those days, for example:
Code | Dates | Available | Nights | BookingID
TEST | 2018-01-04 | 1 | NULL | NULL
TEST | 2018-01-05 | 1 | NULL | NULL
TEST | 2018-01-06 | 0 | 4 | 123456
TEST | 2018-01-07 | 0 | 4 | 123456
TEST | 2018-01-08 | 0 | 4 | 123456
TEST | 2018-01-09 | 0 | 4 | 123456
TEST | 2018-01-10 | 1 | NULL | NULL
TEST | 2018-01-11 | 1 | NULL | NULL
TEST | 2018-01-12 | 1 | NULL | NULL
TEST | 2018-01-13 | 0 | 3 | 234567
TEST | 2018-01-14 | 0 | 3 | 234567
TEST | 2018-01-15 | 0 | 3 | 234567
TEST | 2018-01-16 | 1 | NULL | NULL
If anyone has any ideas on how to solve it would be most appreciated.
Andrew
You could replace your vwRSBooking with another view which uses a CTE to obtain all the dates the booking covers. Then use the view's coverdate for joining to the #tmpAvailability table:
CREATE VIEW vwRSBookingFull
AS
WITH cte ( bookingid, nights, depart, code, coverdate)
AS (SELECT bookingid,
nights,
depart,
code,
depart
FROM vwRSBooking
UNION ALL
SELECT c.bookingid,
c.nights,
c.depart,
c.code,
DATEADD(d, 1, c.coverdate)
FROM cte c
WHERE DATEDIFF(d, c.depart, c.coverdate) < (c.nights - 1))
SELECT c.bookingid,
c.nights,
c.depart,
c.code,
c.coverdate
FROM cte c
GO
You will need a calendar table with all the dates in the date range your dates may fall into. For this example, I build one for January 2018. We can then join onto this table to create the additional rows.
Here is the sample code I used. You can see it at SQL Fiddle.
CREATE TABLE code (
code varchar(max),
dates date,
available int,
nights int,
bookingid int
)
INSERT INTO code VALUES
('TEST','2018-01-04','1',NULL,NULL),
('TEST','2018-01-05','1',NULL,NULL),
('TEST','2018-01-06','0',4,123456),
('TEST','2018-01-07','0',NULL,NULL),
('TEST','2018-01-08','0',NULL,NULL),
('TEST','2018-01-09','0',NULL,NULL),
('TEST','2018-01-10','1',NULL,NULL),
('TEST','2018-01-11','1',NULL,NULL),
('TEST','2018-01-12','1',NULL,NULL),
('TEST','2018-01-13','0',3,234567),
('TEST','2018-01-14','0',NULL,NULL),
('TEST','2018-01-15','0',NULL,NULL)
CREATE TABLE dates (
dates date
)
INSERT INTO dates VALUES
('2018-01-01'),('2018-01-02'),('2018-01-03'),('2018-01-04'),('2018-01-05'),('2018-01-06'),('2018-01-07'),('2018-01-08'),('2018-01-09'),('2018-01-10'),('2018-01-11'),('2018-01-12'),('2018-01-13'),('2018-01-14'),('2018-01-15'),('2018-01-16'),('2018-01-17'),('2018-01-18'),('2018-01-19'),('2018-01-20'),('2018-01-21'),('2018-01-22'),('2018-01-23'),('2018-01-24'),('2018-01-25'),('2018-01-26'),('2018-01-27'),('2018-01-28'),('2018-01-29'),('2018-01-30'),('2018-01-31')
Here is the query based on this dataset:
SELECT
code.code,
dates.dates,
code.available,
code.nights,
code.bookingid
FROM code
LEFT JOIN dates ON
dates.dates >= code.dates
AND dates.dates < DATEADD(DAY,nights,code.dates)
Edit: Here is an example using your initial query as a subquery to join your result set onto the dates table if you want a copy & paste. Still requires creating the dates table.
SELECT
ServiceCode,
StartDate,
Available,
Nights,
BookingID
FROM (
SELECT
ServiceCode,
StartDate,
Available,
Nights,
BookingID
FROM #tmpAvailability
LEFT JOIN vwRSBooking B
ON B.Depart = A.StartDate
AND B.ServiceCode = A.SupplierCode
AND B.StatusID IN (2640, 2621)
) code
LEFT JOIN dates ON
dates.dates >= code.dates
AND dates.dates < DATEADD(DAY,nights,code.dates)
ORDER BY StartDate;

Getting first and last values from contiguous ranges

I want to get the first enter_date and the last leave_date for contiguous enter_day and leave_day values for each id. Given this example data:
+-----+------------+------------+-----------+-----------+
| id | enter_date | leave_date | enter_day | leave_day |
+-----+------------+------------+-----------+-----------+
| 111 | 2016-07-29 | 2016-12-01 | 1 | 75 |
| 111 | 2016-12-02 | 2017-01-13 | 76 | 95 |
| 111 | 2017-01-17 | 2017-06-02 | 96 | 181 |
| 222 | 2016-07-29 | 2016-12-02 | 1 | 76 |
| 222 | 2017-01-30 | 2017-06-02 | 105 | 181 |
| 333 | 2016-08-01 | 2017-06-02 | 1 | 180 |
+-----+------------+------------+-----------+-----------+
I want the following result:
+-----+------------+------------+
| id | enter_date | leave_date |
+-----+------------+------------+
| 111 | 2016-07-29 | 2017-06-02 |
| 222 | 2016-07-29 | 2016-12-02 |
| 222 | 2017-01-30 | 2017-06-02 |
| 333 | 2016-08-01 | 2017-06-02 |
+-----+------------+------------+
I want one record for ID 111 because there are no gaps between any enter_day and the previous leave_day.
I want both records for ID 222 because there is a gap (days 75 through 104).
EDIT: What I have so far, which isn't giving me the correct leave_date for ID 111:
with cte as (
select a.id, a.enter_date, a.leave_date, b.enter_date next_ed, b.leave_date next_ld
from #tbl a
join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
)
select id, min(enter_date) enter_date, max(leave_date) leave_date
from cte
group by id
union
select a.id, a.enter_date, a.leave_date
from #tbl a
left join #tbl b on b.id = a.id and b.enter_day = a.leave_day + 1
left join cte c on c.id = a.id and c.next_ed = a.enter_date and c.next_ld = a.leave_date
where b.id is null and c.id is null
order by 1,3
Below is an example of Gaps-and-Islands for ranges.
I used an ad-hoc tally table, but an actual number/tally would do the trick as well.
If you just run the inner query, you will see very quickly how your sample data of 6 rows explodes into 514 rows. Then is is a small matter of applying the grouped aggregation to get the final results.
Example
Declare #YourTable Table ([id] int,[enter_date] date,[leave_date] date,[enter_day] int,[leave_day] int)
Insert Into #YourTable Values
(111,'2016-07-29','2016-12-01',1,75)
,(111,'2016-12-02','2017-01-13',76,95)
,(111,'2017-01-17','2017-06-02',96,181)
,(222,'2016-07-29','2016-12-02',1,76)
,(222,'2017-01-30','2017-06-02',105,181)
,(333,'2016-08-01','2017-06-02',1,180)
Select ID
,[enter_date] = min([enter_date])
,[leave_date] = max([leave_date])
From (
Select *
,Grp = N - Row_Number() over (Partition By ID Order by N)
From #YourTable A
Join (
Select Top (Select max([leave_day]-[enter_day])+1 From #YourTable)
N=-1+Row_Number() Over (Order By (Select Null))
From master..spt_values n1,master..spt_values n2
) B on B.N between [enter_day] and [leave_day]
) A
Group By [ID],Grp
Order By [ID],min([enter_date])
Returns
ID enter_date leave_date
111 2016-07-29 2017-06-02
222 2016-07-29 2016-12-02
222 2017-01-30 2017-06-02
333 2016-08-01 2017-06-02

SQL Query to Join Two Tables Based On Closest Timestamp

I need to retrieve the records from dbo.transaction (transaction of all users-more than one transaction for each user) that having timestamp which is closest to the time in dbo.bal (current balance details of each user-only one record for each user)
ie, the resultant records should equal to the no of records in the dbo.bal
Here i tried the below query, am getting only the records less than the time in dbo.bal. But there are some record having timestamp greater than and closest to dbo.bal.time
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
WHERE TIMESTAMP <= dbo.bal.time
ORDER BY TIMESTAMP DESC) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC
here is my table structure,
dbo.transaction
---------------
| uid| userId | description| timestamp | credit | transactionBal
-------------------------------------------------------------------------
| 1 | 101 | buy credit1| 2012-01-25 03:23:31.624 | 100 | 500
| 2 | 102 | buy credit5| 2012-01-18 03:13:12.657 | 500 | 700
| 3 | 103 | buy credit3| 2012-01-15 02:16:34.667 | 300 | 300
| 4 | 101 | buy credit2| 2012-01-13 05:34:45.637 | 200 | 300
| 5 | 101 | buy credit1| 2012-01-12 07:45:21.457 | 100 | 100
| 6 | 102 | buy credit2| 2012-01-01 08:18:34.677 | 200 | 200
dbo.bal
-------
| uid| userId | balance | time |
-----------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 |
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 |
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 |
And the result should be like,
| Id | userId | balance | time | credit | transactionBal
-----------------------------------------------------------------------------
| 1 | 101 | 500 | 2012-01-13 05:34:45.645 | 200 | 300
| 2 | 102 | 700 | 2012-01-01 08:18:34.685 | 200 | 200
| 3 | 103 | 300 | 2012-01-15 02:16:34.672 | 300 | 300
Please help me.. Any help is must appreciated...Thankyou
It would be helpful if you posted your table structures, but ...
I think your inner query needs a join condition. (That is not actually in your question)
Your ORDER BY clause in the inner query could be ABS(TIMESTAMP - DB0.BAL.TIME). That should give you the smallest difference between the 2.
Does that help ?
Based on the follwing Sql Fiddle http://sqlfiddle.com/#!3/7a900/15 I came up with ...
SELECT
bal.uid,
bal.userId,
bal.balance,
bal.time,
trn.timestamp,
trn.description,
datediff(ms, bal.time, trn.timestamp)
FROM
money_balances bal
JOIN money_transaction trn on
trn.userid = bal.userid and
trn.uid =
(
select top 1 uid
from money_transaction trn2
where trn2.userid = trn.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
)
WHERE
bal.time IS NOT NULL
ORDER BY
bal.time DESC
I cannot vouch for its performance because I know nothing of your data, but I believe it works.
I have simplified my answer - I believe what you need is
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
and from that you can derive all the datasets you need.
for example :
with matched_credits as
(
SELECT
bal.uid as baluid,
(
select top 1 uid
from money_transaction trn2
where trn2.userid = bal.userid
order by abs(datediff(ms, bal.time, trn2.timestamp))
) as tranuid
FROM
money_balances bal
)
select
*
from
matched_credits mc
join money_balances mb on
mb.uid = mc.baluid
join money_transaction trn on
trn.uid = mc.tranuid
Try:
SELECT dbo.bal.uid,
dbo.bal.userId,
dbo.bal.balance,
dbo.bal.time,
(SELECT TOP 1 transactionBal
FROM dbo.transaction
ORDER BY abs(datediff(ms, dbo.bal.time, TIMESTAMP))) AS newBal
FROM dbo.bal
WHERE dbo.bal.time IS NOT NULL
ORDER BY dbo.bal.time DESC