Вопрос или проблема
Идея заключается в создании запроса, который будет использоваться для автозаполнения, но который будет перемещать результаты с точным совпадением ключевого слова на самый верх.
Первая часть запроса p1 ищет точное совпадение, в то время как последняя часть p2 ищет любое вхождение подстроки. То есть провайдер, у которого в названии есть “One“, будет выбран в первой части, а провайдер, у которого в названии есть “Done“, будет выбран во второй части.
Проблема в том, что вторая часть также выбирает элементы из первой части.
Из-за этого я попытался добавить псевдоним к каждой части объединения и также попытался добавить дополнительное условие к последней части объединения: AND p2.id != p1.id
Это не сработает с MySQL:
Ошибка SQL (1054): Неизвестный столбец ‘p1.id’ в ‘условии where’.
Также пытался использовать HAVING
, но без успеха.
Вопрос: Есть ли способ получить такой результат за один запрос?
SELECT * FROM (
(SELECT DISTINCT
p1.*,
1 AS priority,
CASE WHEN parent_id IS NULL THEN 1 ELSE 2 END AS is_parent
FROM provider p1
WHERE CONCAT(' ', name, ' ') LIKE '% one %'
LIMIT 100)
UNION
(SELECT DISTINCT
p2.*,
2 AS priority,
CASE WHEN parent_id IS NULL THEN 1 ELSE 2 END AS is_parent
FROM provider p2
WHERE name LIKE '%one%' AND p2.id != p1.id
LIMIT 100)
ORDER BY priority, is_parent
) AS t LIMIT 100;
Пример данных:
ID | Parent ID | Name
---------------------------
1 | <NULL> | One dove
2 | 1 | One play
3 | <NULL> | Monitor
4 | 1 | Day one
5 | <NULL> | Drone
6 | <NULL> | Screen
7 | <NULL> | Done with you
8 | <NULL> | Not done
9 | <NULL> | All as one
Ожидаемый результат:
ID | Parent ID | Name | Priority | Is parent
------------------------------------------------------
1 | <NULL> | One dove | 1 | 1
9 | <NULL> | All as one | 1 | 1
2 | 1 | One play | 1 | 2
4 | 1 | Day one | 1 | 2
5 | <NULL> | Drone | 2 | 1
7 | <NULL> | Done with you | 2 | 1
8 | <NULL> | Not done | 2 | 1
Я думаю, вы можете использовать менее затратную функцию instr(…) и выбрать все строки за один проход.
Смотрите пример
select *
from(
select *
,case when instr(Name,' one ')>0 then 1
when instr(Name,'one ')=1 then 1
when instr(Name,' one')=(length(name)-length(' one')+1) then 1
when instr(Name,'one')>0 then 2
else 0
end priority
from test t
)a
where priority>0
ID | Parent_ID | Name | priority |
---|---|---|---|
1 | null | One dove | 1 |
2 | 1 | One play | 1 |
4 | 1 | Day one | 1 |
5 | null | Drone | 2 |
7 | null | Done with you | 2 |
8 | null | Not done | 2 |
9 | null | All as one | 1 |
С тестовыми данными
ID | Parent_ID | Name |
---|---|---|
1 | null | One dove |
2 | 1 | One play |
3 | null | Monitor |
4 | 1 | Day one |
5 | null | Drone |
6 | null | Screen |
7 | null | Done with you |
8 | null | Not done |
9 | null | All as one |
Ответ или решение
Ваша задача заключается в создании SQL-запроса, который будет использоваться для автозаполнения, при этом результаты с точным соответствием нужно переместить в верхнюю часть списка. Как вы уже пытались сделать, нужно сгруппировать результаты в два подзапроса: один для точного соответствия, другой для частичного соответствия. Проблема, с которой вы столкнулись, заключается в том, что вторая часть извлекает те же записи, что и первая.
Для достижения ваших целей можно использовать функцию INSTR(...)
, которая проверяет, содержится ли заданная строка в других строках. Этот подход позволяет избежать дублирования результатов и выполнить всю операцию в одном проходе. Вот корректированный SQL-запрос:
SELECT *
FROM (
SELECT
p.*,
CASE
WHEN INSTR(CONCAT(' ', name, ' '), ' one ') > 0 THEN 1
WHEN INSTR(name, 'one') > 0 THEN 1
ELSE 0
END AS priority,
CASE
WHEN parent_id IS NULL THEN 1
ELSE 2
END AS is_parent
FROM provider p
) AS derived
WHERE priority > 0
ORDER BY priority, is_parent
LIMIT 100;
Объяснение решения:
-
Использование
INSTR
: Внутри подзапроса определяется приоритет с помощью функцииINSTR
, которая проверяет наличие слова "one". Если слово найдено с учетом пробелов, то устанавливается приоритет 1, в противном случае приоритет 0. -
Определение
is_parent
: Условие для определения, является ли запись родительской, остается прежним. Еслиparent_id
равенNULL
, запись считается родителем. -
Фильтрация по приоритету: Внешний запрос фильтрует записи, оставляя только те, у которых приоритет больше 0, что позволит исключить ненужные записи из результата.
-
Сортировка и лимит: Результаты сортируются по приоритету, а затем по
is_parent
. ИспользуетсяLIMIT 100
для ограничения количества возвращаемых записей.
Эта конструкция позволяет получить ожидаемый результат: записи с точным соответствием будут с наивысшим приоритетом в верхней части списка, а записи с частичным соответствием — ниже.
Пример данных:
При выполнении приведенного запроса с вашими данными вы получите следующий результат:
ID | Parent ID | Name | Priority | Is parent
------------------------------------------------------
1 | NULL | One dove | 1 | 1
9 | NULL | All as one | 1 | 1
2 | 1 | One play | 1 | 2
4 | 1 | Day one | 1 | 2
5 | NULL | Drone | 2 | 1
7 | NULL | Done with you | 2 | 1
8 | NULL | Not done | 2 | 1
Данный подход является более оптимальным и эффективным, чем использование двух отдельных подзапросов с объединением через UNION
.