Imagine there’re 2 tables, let’s call them “Master” and “Detail”:
Master -------------------------------- | ID | field_1 | ... | field_n | --------------------------------
Detail -------------------------------------------- | ID | master_id | f_value | ... | field_n | -------------------------------------------- | 1 | 1 | 0.03 | ... | ... | -------------------------------------------- | 2 | 1 | 0.95 | ... | ... | -------------------------------------------- | 3 | 1 | 1.22 | ... | ... | -------------------------------------------- | 4 | 2 | 0.91 | ... | ... | -------------------------------------------- | 5 | 2 | 0.93 | ... | ... | -------------------------------------------- | 6 | 2 | 2.07 | ... | ... | --------------------------------------------
There’re 2 input parameters: list of Master IDs (master_id_list) and numeric value (num_value).
For every ID in master_id_list I should get one Detail record:
- If
num_value < MIN( f_value ), it should be the record withMIN( f_value ) - If
num_value > MAX( f_value ), it should be the record withMAX( f_value ) - Otherwise it should be the record with the closest maximal
f_value
Example1. master_id_list = [ 1, 2 ], num_value = 0. Result:
-------------------------------------------- | 1 | 1 | 0.03 | ... | ... | -------------------------------------------- | 4 | 2 | 0.91 | ... | ... | --------------------------------------------
Example2. master_id_list = [ 1, 2 ], num_value = 50. Result:
-------------------------------------------- | 3 | 1 | 1.22 | ... | ... | -------------------------------------------- | 6 | 2 | 2.07 | ... | ... | --------------------------------------------
Example3. master_id_list = [ 1, 2 ], num_value = 0.94. Result:
-------------------------------------------- | 2 | 1 | 0.95 | ... | ... | -------------------------------------------- | 6 | 2 | 2.07 | ... | ... | --------------------------------------------
Is it possible with one single SQL query? I’ve tried to “play” with solutions here and here but failed.
Advertisement
Answer
Let’s call num_value your needle (as in, “needle in the haystack”) that you’re looking for.
First we’ll normalize the needle so that it is no lower than the MIN(f_value) and no higher than the MAX(f_value) for each master_id.
Then we’ll look for each Detail row with the nearest f_value that’s greater than or equal to our normalized needle, grouped by master_id. (This is then just a greatest-n-per-group sql problem).
WITH normalized AS ( -- First normalize the needle for each master_id
SELECT hilo.master_id,
MAXVALUE(hilo.lo, MINVALUE(hilo.hi, d.needle)) AS needle
FROM (SELECT ? FROM rdb$database) d (needle) -- <- change this ? to your needle
CROSS JOIN
(SELECT master_id, MAX(f_value), MIN(f_value)
FROM detail GROUP BY master_id) hilo (master_id, hi, lo)
),
ranked AS ( -- Next order f_value >= needle by master_id
SELECT detail.*,
ROW_NUMBER() OVER (PARTITION BY detail.master_id ORDER BY f_value ASC)
AS rk
FROM detail
LEFT JOIN
normalized ON detail.master_id = normalized.master_id
WHERE detail.f_value >= normalized.needle
)
-- Strip off the rank ordering and SELECT what you want
SELECT id, master_id, f_value, ...
FROM ranked
WHERE rk = 1;