Как запросить postgres для поля jsonb, ключ которого имеет нулевое значение

У меня есть таблица в postgres, в которой есть следующие строки в столбце jsonb с именем data:

{ "block": { "data": null, "timestamp": "1680617159" } }
{"block": {"hash": "0xf0cab6f80ff8db4233bd721df2d2a7f7b8be82a4a1d1df3fa9bbddfe2b609e28", "size": "0x21b", "miner": "0x0d70592f27ec3d8996b4317150b3ed8c0cd57e38", "nonce": "0x1a8261f25fc22fc3", "number": "0x1847", "uncles": [], "gasUsed": "0x0", "mixHash": "0x864231753d23fb737d685a94f0d1a7ccae00a005df88c0f1801f03ca84b317eb", "gasLimit": "0x1388", "extraData": "0x476574682f76312e302e302f6c696e75782f676f312e342e32", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "stateRoot": "0x2754a138df13677ca025d024c6b6ac901237e2bf419dda68d9f9519a69bfe00e", "timestamp": "0x55baa522", "difficulty": "0x3f5a9c5edf", "parentHash": "0xf50f292263f296897f15fa87cb85ae8191876e90e71ab49a087e9427f9203a5f", "sealFields": [], "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "transactions": [], "totalDifficulty": "0x239b3c909daa6", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"}, "transaction_receipts": []}

Я хотел бы написать SQL-запрос, который выбирает все строки, которые имеют значение null для block.data, но не выбирает строки, в которых нет поля data.

Я пробовал следующее, и все они потерпели неудачу:

SELECT * FROM table WHERE data->>'block'->>'data' IS NULL;
SELECT * FROM table WHERE data->'block'->'data' IS NULL;
SELECT * FROM table WHERE jsonb_extract_path_text(data, 'block', 'data') IS NULL;

Кажется, что во всех этих случаях, если поле data отсутствует, оно проходит пункт where.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
67
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете использовать оператор «@>», чтобы проверить, содержится ли '{"data":null}' в пути data-> «block».

SELECT *
FROM tab
WHERE (data->'block') @> '{"data":null}'

Выход:

данные {"блок":{"данные":null,"отметка времени":"1680617159"}}

Посмотрите демо здесь.

чудесно, спасибо! Этот запрос работает, но он очень медленный. Есть ли способ создать индекс только для определенных ключей (или определенных путей) в столбце json, чтобы ускорить это?

Paymahn Moghadasian 04.04.2023 16:49
(data->>'block')::JSONB можно упростить до data->'block'
a_horse_with_no_name 04.04.2023 16:51

Ожидаете ли вы повышения производительности от этой @a_horse_with_no_name? Или это просто для краткости?

Paymahn Moghadasian 04.04.2023 16:51

К сожалению, я думаю, что для операций json не требуется много оптимизации ... для получения дополнительной информации вы можете посмотреть этот ответ или, возможно, @a_horse_with_no_name определенно даст больше информации в этом отношении.

lemon 04.04.2023 16:53

Ну, это должно быть быстрее, потому что вы не конвертируете значение jsonb в текст, чтобы преобразовать его обратно в jsonb. Но я не знаю, сможете ли вы измерить разницу. Но я считаю, что это намного чище.

a_horse_with_no_name 04.04.2023 16:53

@a_horse_with_no_name, есть ли у вас какие-либо предложения по индексам (или любому другому механизму) для ускорения этого запроса? Я только что провел некоторое исследование индексов GIN, и мне не ясно, поможет ли индекс GIN в этом случае.

Paymahn Moghadasian 04.04.2023 17:05

Используйте два предиката, чтобы увидеть, существует ли ключ * и является ли значение null

where jsonb_path_exists(data,'$.block.data') and 
jsonb_path_query_first(data,'$.block') ->> 'data' is null;

Пример

with test as ( 
select 1 id, '{ "block": { "data": null, "timestamp": "1680617159" } }'::jsonb  as data union all
select 2 id, '{ "block": { "data": 1, "timestamp": "1680617159" } }'::jsonb as data union all
select 3 id, '{ "block": {  "timestamp": "1680617159" } }'::jsonb as data)
select t.id
from test t 
where jsonb_path_exists(data,'$.block.data') and 
jsonb_path_query_first(data,'$.block') ->> 'data' is null;

id|
--+
 1|

Этот запрос приводит к Seq Scan таблицы, как показано ниже.

explain analyze
select t.id
from test t 
where jsonb_path_exists(data,'$.block.data') and 
jsonb_path_query_first(data,'$.block') ->> 'data' is null;

Seq Scan on test t  (cost=10000000000.00..10000003289.05 rows=167 width=4) (actual time=45.745..68.798 rows=1 loops=1)
  Filter: (jsonb_path_exists(data, '$."block"."data"'::jsonpath, '{}'::jsonb, false) AND ((jsonb_path_query_first(data, '$."block"'::jsonpath, '{}'::jsonb, false) ->> 'data'::text) IS NULL))
  Rows Removed by Filter: 100002
Planning Time: 0.079 ms
JIT:
  Functions: 4
  Options: Inlining true, Optimization true, Expressions true, Deforming true
  Timing: Generation 0.714 ms, Inlining 8.161 ms, Optimization 26.128 ms, Emission 11.304 ms, Total 46.308 ms
 Execution Time: 69.586 ms  <<<--------------

Если у вас очень мало строк с определенным ключом data, вы можете извлечь выгоду из индекса

CREATE INDEX idx_test2 ON test USING btree (((jsonb_path_exists(data,'$.block.data'))));

План использует индекс и намного более эффективен

Bitmap Heap Scan on test t  (cost=551.35..2965.38 rows=167 width=4) (actual time=0.034..0.037 rows=1 loops=1)
  Filter: (jsonb_path_exists(data, '$."block"."data"'::jsonpath, '{}'::jsonb, false) AND ((jsonb_path_query_first(data, '$."block"'::jsonpath, '{}'::jsonb, false) ->> 'data'::text) IS NULL))
  Rows Removed by Filter: 1
  Heap Blocks: exact=1
  ->  Bitmap Index Scan on idx_test2  (cost=0.00..551.31 rows=50002 width=0) (actual time=0.025..0.025 rows=2 loops=1)
        Index Cond: (jsonb_path_exists(data, '$."block"."data"'::jsonpath, '{}'::jsonb, false) = true)
Planning Time: 0.164 ms
Execution Time: 0.052 ms  <<<--------------

Используемые тестовые данные

create table test as
select 1 id, '{ "block": { "data": null, "timestamp": "1680617159" } }'::jsonb  as data, repeat('x',2000) pad union all
select 2 id, '{ "block": { "data": 1, "timestamp": "1680617159" } }'::jsonb as data, repeat('x',2000) pad union all
select 3 id, '{ "block": {  "timestamp": "1680617159" } }'::jsonb as data, repeat('x',2000) pad union all
select id+3, '{ "block": {  "timestamp": "1680617159" } }'::jsonb as data, repeat('x',2000) pad from generate_series(1,100000) as t(id )
;

Ожидаете ли вы, что это решение будет быстрее, чем другое предложенное решение? В качестве альтернативы, выиграет ли это решение от индексов?

Paymahn Moghadasian 04.04.2023 16:57
Ответ принят как подходящий

Просто используйте 'null'::jsonb в условии:

select *
from my_table
where data->'block'->'data' = 'null'

Другие ответы, какими бы эффективными они ни были, излишне сложны и скрывают суть вещей. Проблема в том, что 'null'::jsonb не совпадает с Postgres null:

select 'null'::jsonb is null;

 ?column?
----------
 f
(1 row)

Вы можете использовать простой индекс btree, который может поддерживать первый запрос:

create index on my_table ((data->'block'->'data'));

Кроме того, индекс gin может поддерживать оператор @>:

create index on my_table using gin ((data->'block'->'data'));

select *
from my_table
where data->'block'->'data' @> 'null';

Я думаю, что у вас может быть опечатка в вашем ответе ('null'::jsonb против 'null'), но да, это, безусловно, самый чистый. Есть ли у вас какие-либо предложения о том, какой тип индекса я мог бы использовать, чтобы сделать этот запрос быстрее?

Paymahn Moghadasian 04.04.2023 17:08

чудесно, спасибо! Я попробую этот индекс btree и посмотрю, как он поможет. Я предполагаю, что индекс btree будет намного меньше, чем индекс GIN, верно?

Paymahn Moghadasian 04.04.2023 17:32

Да, индекс джина в этом случае кажется несколько избыточным.

klin 04.04.2023 17:34

Индекс btree работал отлично. Запрос теперь очень быстрый даже для ~ 17 миллионов строк.

Paymahn Moghadasian 05.04.2023 11:47

Другие вопросы по теме