Улучшенный пошаговый SQL-запрос без большого количества блоков IF / ELSE - PullRequest
0 голосов
/ 24 января 2019

Рассмотрим пример таблицы с этими строками:

+----+----------+-------+
| id | postcode | value |
+----+----------+-------+
|  1 | A1A3A3   | one   |
|  2 | A1A3A4   | two   |
|  3 | A1A3B    | three |
|  4 | A1A3C    | four  |
|  5 | A1A3D    | five  |
|  6 | A1A3     | six   |
|  7 | A1A      | seven |
|  8 | A1       | eight |
+----+----------+-------+

Моя цель - выполнить запрос, в результате чего он шагает вниз по столбцу почтового индекса, пока не будет найдено точное совпадение.

Допустим, мой начальный параметр запроса - A1A3E9.Ожидаемое возвращаемое значение, основанное на образце таблицы, будет six.Важно отметить, что на каждом шаге вниз я удаляю один символ из конца начального параметра запроса.

Поэтому сначала я попытаюсь найти совпадение для A1A3E9, а затем A1A3E,а затем A1A3 и т. д.

В настоящее время я достигаю этого просто с помощью серии блоков IF / ELSE, например:

IF
    EXISTS (
        SELECT value FROM table
        WHERE postcode=:userPost6_1
    )
    BEGIN
        SELECT value FROM table
        WHERE postcode=:userPost6_2
    END
ELSE IF
    EXISTS (
        SELECT value FROM table
        WHERE postcode=:userPost5_1
    )
    BEGIN
        SELECT value FROM table
        WHERE postcode=:userPost5_2
    END
ELSE IF
    EXISTS (
        SELECT value FROM table
        WHERE postcode=:userPost4_1
    )
    BEGIN
        SELECT value FROM table
        WHERE postcode=:userPost4_2
    END
ELSE IF
    EXISTS (
        SELECT value FROM table
        WHERE postcode=:userPost3_1
    )
    BEGIN
        SELECT value FROM table
        WHERE postcode=:userPost3_2
    END
ELSE IF
    EXISTS (
        SELECT value FROM table
        WHERE postcode=:userPost2_1
    )
    BEGIN
        SELECT value FROM table
        WHERE postcode=:userPost2_2
    END

Обратите внимание, что я использую привязку параметров вPHP, так что просто для контекста, мои привязки параметров в конечном итоге выглядят так:

$stmt->bindValue(':userPost6_1', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost6_2', "A1A3E9", PDO::PARAM_STR);
$stmt->bindValue(':userPost5_1', "A1A3E",  PDO::PARAM_STR);
$stmt->bindValue(':userPost5_2', "A1A3E",  PDO::PARAM_STR);
$stmt->bindValue(':userPost4_1', "A1A3",   PDO::PARAM_STR);
$stmt->bindValue(':userPost4_2', "A1A3",   PDO::PARAM_STR);
$stmt->bindValue(':userPost3_1', "A1A",    PDO::PARAM_STR);
$stmt->bindValue(':userPost3_2', "A1A",    PDO::PARAM_STR);
$stmt->bindValue(':userPost2_1', "A1",     PDO::PARAM_STR);
$stmt->bindValue(':userPost2_2', "A1",     PDO::PARAM_STR);

У меня нет никаких проблем в отношении производительности, так как у меня есть индекс в столбце почтового индекса (который содержитБолее 40 000 строк)Меня беспокоит только то, что это визуально, неприятный запрос, чтобы посмотреть на него.

Мой вопрос: Есть ли более чистый способ написания этого запроса?

Ответы [ 2 ]

0 голосов
/ 25 января 2019

Сначала я бы спулировал все потенциально совпадающие строки, например:

select *
into #matches
from t
where postcode like 'AI%'

Это может использовать индекс по почтовому индексу и поэтому должно быть дешевым. Тогда любой запрос , который вы запускаете для совпадений, будет работать только над этим подмножеством. Даже написание UDF, который сравнивает почтовый индекс с литералом, например:

select top 1 *
from #matches
order by dbo.NumberOfMatchingCharacters(postcode,'A1A3E9') desc
0 голосов
/ 24 января 2019

Вот один метод:

select top (1) t.*
from t
where 'A1A3E9' like t.postcode + '%'
order by t.postcode desc;

Единственная проблема заключается в том, что ваши множественные операторы if, вероятно, быстрее.Получение производительности является реальной проблемой с этим типом проблемы.Один метод использует несколько join с:

select v.pc, coalesce(t0.value, t1.value, t2.value, . . . )
from (values ('A1A3E9')) v(pc) left join
     t t0
     on t0.postcode = v.pc left join
     t t1
     on t1.postcode = t0.postcode is null and
        (case when len(v.pc) > 1 then left(v.pc, len(v.pc) - 1) end) left join
     t t2
     on t1.postcode is null and
        t2.postcode = (case when len(v.pc) > 2 then left(v.pc, len(v.pc) - 2) end) left join
     . . .
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...