У меня есть таблица в 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
.
Вы можете использовать оператор «@>», чтобы проверить, содержится ли '{"data":null}'
в пути data-> «block».
SELECT *
FROM tab
WHERE (data->'block') @> '{"data":null}'
Выход:
Посмотрите демо здесь.
(data->>'block')::JSONB
можно упростить до data->'block'
Ожидаете ли вы повышения производительности от этой @a_horse_with_no_name? Или это просто для краткости?
К сожалению, я думаю, что для операций json не требуется много оптимизации ... для получения дополнительной информации вы можете посмотреть этот ответ или, возможно, @a_horse_with_no_name определенно даст больше информации в этом отношении.
Ну, это должно быть быстрее, потому что вы не конвертируете значение jsonb в текст, чтобы преобразовать его обратно в jsonb. Но я не знаю, сможете ли вы измерить разницу. Но я считаю, что это намного чище.
@a_horse_with_no_name, есть ли у вас какие-либо предложения по индексам (или любому другому механизму) для ускорения этого запроса? Я только что провел некоторое исследование индексов GIN, и мне не ясно, поможет ли индекс GIN в этом случае.
Используйте два предиката, чтобы увидеть, существует ли ключ * и является ли значение 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 )
;
Ожидаете ли вы, что это решение будет быстрее, чем другое предложенное решение? В качестве альтернативы, выиграет ли это решение от индексов?
Просто используйте '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'
), но да, это, безусловно, самый чистый. Есть ли у вас какие-либо предложения о том, какой тип индекса я мог бы использовать, чтобы сделать этот запрос быстрее?
чудесно, спасибо! Я попробую этот индекс btree и посмотрю, как он поможет. Я предполагаю, что индекс btree будет намного меньше, чем индекс GIN, верно?
Да, индекс джина в этом случае кажется несколько избыточным.
Индекс btree работал отлично. Запрос теперь очень быстрый даже для ~ 17 миллионов строк.
чудесно, спасибо! Этот запрос работает, но он очень медленный. Есть ли способ создать индекс только для определенных ключей (или определенных путей) в столбце json, чтобы ускорить это?