Объединить CASE и BETWEEN в WHERE - PullRequest
1 голос
/ 21 апреля 2020

Я пытаюсь выполнить то, что я считал достаточно простым запросом, однако я сталкиваюсь с синтаксическими ошибками и могу использовать некоторые рекомендации. По сути у меня есть функция с параметром ввода текста $1. Этот параметр может принимать 7 различных значений, управляемых внешним интерфейсом приложения.

В зависимости от значения этого параметра, мне нужно вернуть подмножество таблицы большего размера в определенном диапазоне от одного целого числа столбцы вернулись. Чтобы быть немного понятнее, возвращаемый запрос в функции выглядит примерно так:

SELECT val_1, val_2, val_3
  FROM schema_name.table_name
 WHERE val_3 BETWEEN
  CASE WHEN $1 = 'a' THEN 0 AND 1
       WHEN $1 = 'b' THEN 2 AND 7
         .
         . 
         .
       WHEN $1 = 'g' THEN 50 AND 100
   END;

Однако я получаю типично неопределенную ошибку:

ERROR:  syntax error at end of input
LINE 42:    END;
               ^
********** Error **********

ERROR: syntax error at end of input
SQL state: 42601
Character: 1133

Диапазоны, указанные в Все утверждения в регистре отличаются друг от друга, и между ними нет видимой закономерности. Конечно, я мог бы написать блок IF с 7 ELSE утверждениями, по сути, записав одно и то же предложение select 7 раз, но я предполагаю, что вышеупомянутое не может быть слишком далеко. У меня никогда не было необходимости использовать оператор CASE в предложении WHERE таким образом. Будем весьма благодарны за любые подсказки в правильном направлении.

Ответы [ 2 ]

1 голос
/ 21 апреля 2020

A CASE выражение может возвращать только одиночное значение (boolean в моей фиксированной версии), а не условный код, как вы пытались:

SELECT val_1, val_2, val_3
FROM   schema_name.table_name
WHERE  CASE $1
         WHEN 'a' THEN val_3 BETWEEN 0 AND 1
         WHEN 'b' THEN val_3 BETWEEN 2 AND 7
          . 
          .
          .
         WHEN 'g' THEN val_3 BETWEEN 50 AND 100
       END;

Использование «простой» формы CASE, чтобы сделать ее немного короче и быстрее.

В качестве альтернативы, просто используйте логические логики c only:

...
WHERE (
       $1 = 'a' AND val_3 BETWEEN 0 AND 1
   OR  $1 = 'b' AND val_3 BETWEEN 2 AND 7
         . 
         .
         .
   OR  $1 = 'g' AND val_3 BETWEEN 50 AND 100
      )

Скобки не нужны строго, так как приоритет оператора в любом случае работает в нашу пользу. AND связывается до OR и BETWEEN связывается до обоих. Но вам понадобятся эти скобки, если вы добавите еще одно WHERE условие с AND.

Производительность?

Для обоих вариантов Postgres 12 (не тестировалось для более ранних версий ) достаточно умен, чтобы все еще использовать индекс на (val_3) или нет, в зависимости от фактической статистики столбца. Даже для подготовленных операторов.

Если эта функция вызывается много,
, и вы работаете с подготовленными операторами (прямо или косвенно),
и производительность критична,
и некоторые из ваших диапазонов извлекают выгоду из индекса, в то время как другие не
, тогда вы все равно могли бы форкать (как минимум) два отдельных запроса для работы с обобщенными c, сохраненными планами запросов в любом случае, чтобы получить абсолютный лучший результат производительность.

1 голос
/ 21 апреля 2020

A CASE выражение возвращает 1 скалярное значение, а не 2 или интервал значений. Что вы можете сделать, это использовать его как перекрестный запрос, присоединенный к таблице:

SELECT t.val_1, t.val_2, t.val_3
FROM schema_name.table_name t
CROSS JOIN (
  SELECT CASE $1 
       WHEN 'a' THEN 0
       WHEN 'b' THEN 1
         .
   END val
) c
WHERE t.val_3 BETWEEN 2 * c.val AND 2 * c.val + 1

Редактировать. Вы можете использовать CTE, который возвращает все возможные значения $1 и интервалы для каждого значения:

WITH cte(val, min, max) AS (
  VALUES ('a', 0, 1), ('b', 2, 7), .........  
)  
SELECT t.val_1, t.val_2, t.val_3
FROM table_name t 
CROSS JOIN (SELECT * FROM cte WHERE val = $1) c
WHERE t.val_3 BETWEEN c.min AND c.max 
...