Join tables on nearest date in the past, in MySQL
SELECT a.id, a.sales, a.date, (SELECT TOP 1 Goal
FROM TableB b WHERE b.date < a.date
ORDER BY b.date DESC) As Goal
FROM TableA a
Going off the nearest date in the past.
MySQL LEFT JOIN ON Closest Date
Seems like you want to join on closest date that's smaller. Have you tried:
Select Range.date, A, B
from Range inner join Metric
on Metric.date = (select max(date) from Metric where date < Range.date);
MySQL join 1-to-1 by closest datetime
It seems the only way to achieve approximate 1-to-1 join is by using a cursor
within the stored procedure.
Thank you @Strawberry for pointing me in the right direction - you will see pieces of your code reused below. Here is the solution that eventually worked for me. It outputs records sorted differently, but at least it is truly 1-to-1 match.
DROP PROCEDURE IF EXISTS amerge;
DELIMITER //
CREATE PROCEDURE amerge()
BEGIN
/* Necessary declarations */
DECLARE o_ID INT DEFAULT 0;
DECLARE o_date VARCHAR(30) DEFAULT 0;
DECLARE o_old VARCHAR(2);
DECLARE o_mdiff FLOAT;
DECLARE ct INT DEFAULT 0;
DECLARE done INT DEFAULT FALSE;
DECLARE cursor1 CURSOR FOR SELECT ID, date, old, mdiff FROM t1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
/* Temporary tables */
-- copy of 'old' with diff column = min difference
CREATE TEMPORARY TABLE t1
SELECT old.*,MIN(ABS(TIMESTAMPDIFF(hour, old.date, new.date))) AS mdiff
FROM old JOIN new ON old.ID=new.ID
GROUP BY old.ID, old.date
ORDER BY mdiff ASC;
-- cartesian join with abs(diff) column
CREATE TEMPORARY TABLE t2
SELECT old.ID AS ID_1, old.date AS date_1, new.ID as ID_2, new.date AS date_2, old, new,
ABS(TIMESTAMPDIFF(hour, old.date, new.date)) AS diff
FROM old CROSS JOIN new ON old.ID=new.ID
ORDER BY diff ASC;
-- empty table to fill in with the results
CREATE TEMPORARY TABLE t3
(id_1 INT, date_1 DATETIME, id_2 INT, date_2 DATETIME, old VARCHAR(2), new VARCHAR(2), diff FLOAT);
/* Cursor */
OPEN cursor1;
getparams: LOOP
FETCH cursor1 INTO o_ID, o_date, o_old, o_mdiff;
IF done THEN
LEAVE getparams;
END IF;
SELECT COUNT(*) FROM t2 WHERE t2.ID_1=o_ID AND t2.date_1=o_date AND t2.old=o_old AND t2.diff=o_mdiff INTO ct;
CASE ct
WHEN 0 THEN
INSERT INTO t3 VALUES (o_ID, o_date, NULL, NULL, o_old, NULL, o_mdiff);
ELSE
INSERT INTO t3 SELECT * FROM t2 WHERE t2.ID_1=o_ID AND t2.date_1=o_date AND t2.old=o_old AND t2.diff=o_mdiff LIMIT 1;
END CASE;
DELETE FROM t2 WHERE t2.ID_2=o_ID AND t2.date_2 IN (SELECT date_2 FROM t3 WHERE t3.date_1=o_date);
END LOOP getparams;
CLOSE cursor1;
/* Workaround for error of reopening temp tables in MySQL */
DROP TEMPORARY TABLE t2;
CREATE TEMPORARY TABLE t2
SELECT * FROM t3;
/* Output */
SELECT * FROM t2
UNION
SELECT NULL AS ID_1, NULL AS date_1, new.ID as ID_2, new.date AS date_2, NULL AS old, new.new AS new, NULL AS diff
FROM new LEFT JOIN t3 ON t3.ID_2=new.ID AND t3.date_2 = new.date WHERE t3.ID_2 IS NULL;
END //
DELIMITER ;
CALL amerge();
And the output is (using data from the above example, with PRIMARY key set to ID+date):
id_1 date_1 id_2 date_2 old new diff
1 2016-03-07 12:20:00 1 2016-03-07 12:20:00 a u 0
1 2016-04-02 12:20:00 1 2016-04-02 12:20:00 b v 0
1 2016-03-03 12:20:00 1 2016-03-02 12:20:00 e t 24
1 2016-03-01 10:09:00 NULL c NULL 26
1 2015-04-12 10:09:00 NULL d NULL 7802
NULL 2 2016-04-12 11:03:00 NULL x NULL
MySql select the nearest date in a jointure
Your subquery doesn't select the row with the minimum date difference, it just calculates the date difference. And nearest.id
is every ID in the table.
We need to use TIME_TO_SEC(TIMEDIFF())
rather than DATEDIFF()
because some of the bookings are on the same day.
The subquery should be grouped by customer ID, not booking ID, and needs to give an alias to the time difference so we can use it in the outer query. The join needs to match the time difference to the booking table.
SELECT c.name, b.number, b.start_date
FROM customer c LEFT JOIN booking b ON (c.id = b.customer_id)
INNER JOIN (
SELECT customer_id, MIN(ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date)))) as mindiff
FROM booking
GROUP BY customer_id
) nearest ON b.customer_id = nearest.customer_id AND ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date))) = mindiff
SQLFIDDLE
To get all the customers, put the join between booking
and the subquery that finds the nearest booking by customer into a subquery, then left join that with customer
.
SELECT c.name, x.number, x.start_date
FROM customer c
LEFT JOIN
(SELECT b.customer_id, b.number, b.start_date
FROM booking b
INNER JOIN (
SELECT customer_id, MIN(ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date)))) as mindiff
FROM booking
GROUP BY customer_id
) nearest ON b.customer_id = nearest.customer_id AND ABS(TIME_TO_SEC(TIMEDIFF(NOW(), start_date))) = mindiff
) AS x ON c.id = x.customer_id
SQLFIDDLE
SQL Join on Nearest less than date
I believe this subquery will do it (not tested).
select *,
(select top 1 Discount
from table2
where table2.Date <= t.Date
order by table2.Date desc) as Discount
from Table1 t
Perhaps not the most performant however.
Edit:
Test code:
create table #table1 ([date] datetime, val int)
create table #table2 ([date] datetime, discount int)
insert into #table1 ([date], val) values ('1/26/2010', 10)
insert into #table1 ([date], val) values ('1/25/2010', 9)
insert into #table1 ([date], val) values ('1/24/2010', 8)
insert into #table1 ([date], val) values ('1/24/2010', 9)
insert into #table1 ([date], val) values ('1/23/2010', 7)
insert into #table1 ([date], val) values ('1/22/2010', 10)
insert into #table1 ([date], val) values ('1/21/2010', 11)
insert into #table2 ([date], discount) values ('1/26/2010', 2)
insert into #table2 ([date], discount) values ('1/23/2010', 1)
insert into #table2 ([date], discount) values ('1/20/2010', 0)
select *,
(select top 1 discount
from #table2
where #table2.[date] <= t.[date]
order by #table2.[date] desc) as discount
from #table1 t
drop table #table1
drop table #table2
Results:
2010-01-26 00:00:00.000 10 2
2010-01-25 00:00:00.000 9 1
2010-01-24 00:00:00.000 8 1
2010-01-24 00:00:00.000 9 1
2010-01-23 00:00:00.000 7 1
2010-01-22 00:00:00.000 10 0
2010-01-21 00:00:00.000 11 0
How to retrieve Closest date from another table dynamically MySQL
You just need to find the absolute minimum value for LockDate
minus Dates
. This will give you the closest date; lesser or greater. Rest is easy.
SELECT info.*, numbers.*
FROM info
INNER JOIN (
SELECT ID, MIN(DATEDIFF(GREATEST(LockDate, Dates), LEAST(LockDate, Dates))) Delta
FROM info
CROSS JOIN numbers
GROUP BY ID
) g ON info.ID = g.ID
INNER JOIN numbers ON DATEDIFF(GREATEST(LockDate, Dates), LEAST(LockDate, Dates)) = g.Delta
SQL Fiddle
Related Topics
How Can SQL Create Duplicate Records
Select Single Row from Child Table for Each Row in Parent Table
Aggregate String Concatenation in Oracle 10G
Why SQL Server Ignores Vaules in String Concatenation When Order by Clause Specified
Can Vba in Ms Access Using Parameter to Prevent SQL Injection
Problem with Execute Procedure in Pl/SQL Developer
View or Function '' Is Not Updatable Because the Modification Affects Multiple Base Tables
Deleting Value Using SQLite While Doing an Inner Join
Differencebetween Cross Join and Multiple Tables in One From
Why When Matched' Cannot Appear More Than Once in a 'Update' Clause of a Merge Statement
Listagg Query "Ora-00937: Not a Single-Group Group Function"
How to Use a Function-Based Index on a Column That Contains Nulls in Oracle 10+
Unpivot on an Indeterminate Number of Columns
How to Call a Stored Proc from a Function
What Does a Caret (^) Do in a SQL Query
How to Delete an Attribute from an Xml Variable in SQL Server 2008