Невозможно выполнить json операций в выражении where для отфильтрованного и затем приведенного текста, когда для фильтрации используется объединение. Где пункт выполняется слишком рано - PullRequest
2 голосов
/ 10 апреля 2020

Postgres (12.2) Настройка:

CREATE TABLE public.test_table (
    id int NOT NULL,
    value_type text NOT NULL,
    value text NOT NULL
);
INSERT INTO public.test_table
(id, value_type, value)
VALUES (1, 'string', 'a'), 
(2, 'json', '{"hello":"world"}'),
(3, 'json', '{"color":"blue"}');

Начальные запросы:

select value::jsonb as json_value from test_table where value_type = 'json'
json_value        |
------------------|
{"hello": "world"}|
{"color": "blue"} |

Но меня интересуют только те, которые имеют 'color' .

Перемещение его в подзапрос, чтобы я мог получить только 'color', также просто отлично:

select only_json.json_value 
from(
    select value::jsonb as json_value from test_table where value_type = 'json'
) only_json
where only_json.json_value ? 'color' = true
json_value        |
------------------|
{"color": "blue"} |

Теперь давайте разберем эту основную таблицу на две части, и внезапно фактически тот же запрос имеет проблемы:


CREATE TABLE public.test_table (
    id INT PRIMARY KEY,
    value TEXT NOT NULL
);
CREATE TABLE public.test_types (
    id INT PRIMARY KEY REFERENCES public.test_table (id),
    value_type TEXT NOT NULL
);

INSERT INTO public.test_table
(id, value)
VALUES (1, 'a'), 
(2, '{"hello":"world"}'),
(3, '{"color":"blue"}');

insert into public.test_types
(id, value_type)
values (1, 'string'),
(2, 'json'),
(3, 'json');

Теперь этот запрос:

select id, value from (
select id, value::jsonb from public.test_table natural join public.test_types
where value_type = 'json') only_json

возвращает, как и ожидалось:

id|value             |
--|------------------|
 2|{"hello": "world"}|
 3|{"color": "blue"} |

Но как только я прикрепляю в выражении where это терпит неудачу:

select id, value from (
select id, value::jsonb from public.test_table natural join public.test_types
where value_type = 'json') only_json
where only_json.value ? 'color' = true
SQL Error [22P02]: ERROR: invalid input syntax for type json
  Detail: Token "a" is invalid.
  Where: JSON data, line 1: a

Это каким-то образом воскресило значение 'a', которое было хорошо устранено до этого предложения where. Так что же дает? Почему объединение заставляет его применять последнее условие where (которое должно происходить логически последним) слишком рано? Неудачные обходные пути, которые я пробовал:

  • Использование левого соединения вместо естественного соединения.
  • Применение where value_type = 'json' к объединенной таблице перед объединением.
  • Перемещение его на «с».
  • Создание представления с последующим применением условия where к элементу выбора из представления.
  • Создание столбца с помощью выбора с именем is_color_holder с помощью SELECT only_json.value ? 'color' as is_color_holder , Этот столбец заполняется правильно, но если я использую предложение where, WHERE is_color_holder = true, я получаю ту же ошибку.
  • Повторение выражения value_type='json' в условии проблемати c where.
  • Перемещение приведенного ниже подзапроса.
  • Замена объединения на where id in (select id from public.test_types where value_type = 'json')
  • Объединения в стиле запятой.
  • Сначала центрируйте запрос вокруг таблицы типов, а затем объединяйте тип значения после того, как типы уже отфильтрованы.

Это ошибка, о которой я должен сообщить postgres? Я что-то упустил?

Редактировать: мне удалось один обходной путь. Смотрите мой ответ для более подробной информации. Тем не менее все еще ищу лучший ответ.

select id, value from (
    select id, case when value_type = 'json' then value::jsonb else to_jsonb(value) end as value, value_type from 
    public.test_table natural join public.test_types
    where value_type = 'json') as_json
where value ? 'color' = true
id|value            |
--|-----------------|
 3|{"color": "blue"}|

Ответы [ 2 ]

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

Я подозреваю, что вы видите преждевременную оптимизацию, вызванную нажатием предиката.

В Postgres, общей стратегией, позволяющей избежать этого, является взлом offset 0:

select id, value from (
    select id, value 
    from public.test_table 
    inner join public.test_types using(id)
    where value_type = 'json'
    offset 0       -- (try to) prevent predicate pushdown
) only_json
where value::jsonb ? 'color'

Демонстрация на DB Fiddle

0 голосов
/ 11 апреля 2020

Я нашел обходной путь. Я опубликую здесь как 'ответ' и отредактирую вышеупомянутый вопрос.

Но если у меня есть лучший ответ для меня, я сделаю Ваш как правильный.

Приведение не- json значений к json с "CASE" работает нормально:

select id, value from (
    select id, case when value_type = 'json' then value::jsonb else to_jsonb(value) end as value, value_type from 
    public.test_table natural join public.test_types
    where value_type = 'json') as_json
where value ? 'color' = true
id|value            |
--|-----------------|
 3|{"color": "blue"}|
...