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;