Выберите ближайшее максимальное значение цифры c в Firebird - PullRequest
1 голос
/ 07 февраля 2020

Представьте, что есть 2 таблицы, назовем их «Master» и «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  | ... |   ...   |
--------------------------------------------

Есть 2 входных параметра: список основных идентификаторов (master_id_list) и цифра c значение (num_value).

Для каждого ID в master_id_list я должен получить одну запись сведений:

  1. Если num_value < MIN( f_value ), это должно быть запись с MIN( f_value )
  2. Если num_value > MAX( f_value ), это должна быть запись с MAX( f_value )
  3. В противном случае это должна быть запись с ближайшим максимальным f_value

Пример 1. master_id_list = [ 1, 2 ], num_value = 0. Результат:

--------------------------------------------
| 1  |      1    |   0.03  | ... |   ...   |
--------------------------------------------
| 4  |      2    |   0.91  | ... |   ...   |
--------------------------------------------

Пример2. master_id_list = [ 1, 2 ], num_value = 50. Результат:

--------------------------------------------
| 3  |      1    |   1.22  | ... |   ...   |
--------------------------------------------
| 6  |      2    |   2.07  | ... |   ...   |
--------------------------------------------

Пример 3. master_id_list = [ 1, 2 ], num_value = 0.94. Результат:

--------------------------------------------
| 2  |      1    |   0.95  | ... |   ...   |
--------------------------------------------
| 6  |      2    |   2.07  | ... |   ...   |
--------------------------------------------

Возможно ли это одним запросом SQL? Я пытался «поиграть» с решениями здесь и здесь , но не получилось.

Ответы [ 2 ]

2 голосов
/ 08 февраля 2020

Давайте назовем num_value вашу иглу (например, «иголка в стоге сена»), которую вы ищете.

Сначала мы нормализуем иглу так, чтобы она была не ниже MIN(f_value) и не выше MAX(f_value) для каждого master_id.

Затем мы будем искать каждую строку Detail с ближайшим f_value, большим или равным нашей нормализованной игле, сгруппированным на master_id. (Тогда это просто проблема наибольший-n-на-группу sql).

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;
2 голосов
/ 07 февраля 2020

Вы должны быть в состоянии использовать коррелированный подзапрос. Предполагая, что num_value находится в основной таблице, а значение f находится в подробной таблице:

select m.*,
       (select first 1 d.f_value
        from detail d
        where d.master_id = m.master_id
        order by abs(m.num_value - d.f_value)
       )
from master m;

РЕДАКТИРОВАТЬ:

Если вы хотите предпочтение большего значения - если он существует - просто измените order by на:

order by (case when d.f_value >= m.num_value then 1 else 2 end),
         abs(d.f_value - m.num_value)
...