Настроить порядок сортировки ключей jsonb с массивами

9

У меня есть таблица в PostgreSQL с некоторыми данными:

create table t2 (
    key jsonb,
    value jsonb
);

INSERT INTO t2(key, value)
 VALUES
 ('1', '"test 1"')
,('2', '"test 2"')
,('3', '"test 3"')
,('[]', '"test 4"')
,('[1]', '"test 5"')
,('[2]', '"test 6"')
,('[3]', '"test 7"')
,('[1, 2]', '"test 8"')
,('[1, 2, 3]', '"test 9"')
,('[1, 3]', '"test 10"')
,('[1,2,4]', '"test 11"')
,('[1, 2,4]', '"test 12"')
,('[1,3,13]', '"test 13"')
,('[1, 2, 15]', '"test 15"');

И я пытаюсь отсортировать эти строки следующим образом:

SELECT key FROM t2 order by key;

Результат:

[]
1
2
3
[1]
[2] <==
[3] <==
[1, 2]
[1, 3] <==
[1, 2, 3]
[1, 2, 4]
[1, 2, 4]
[1, 2, 15]
[1, 3, 13]

Но то, что мне нужно, это:

[]
1
2
3
[1]
[1, 2]
[1, 2, 3]
[1, 2, 4]
[1, 2, 4]
[1, 2, 15]
[1, 3] <==
[1, 3, 13]
[2] <==
[3] <==

Есть ли способ добиться этого?

Антонио
источник
Итак, у вас есть свой ответ здесь?
Эрвин Брандштеттер

Ответы:

8

Во-первых, ваш вопрос и название столбца "key"вводят в заблуждение. Ключ столбца не содержит ключей JSON , только значения . В противном случае мы могли бы использовать функцию jsonb_object_keys(jsonb)для извлечения ключей, но это не так.

Предполагая, что все ваши массивы JSON либо пусты, либо содержат целые числа, как показано. И скалярные значения (не массивы) также являются целочисленными.

Ваш основной порядок сортировки будет работать с массивами Postgres integer(или numeric). Я использую эту маленькую вспомогательную функцию для преобразования jsonbмассивов в Postgres int[]:

CREATE OR REPLACE FUNCTION jsonb_arr2int_arr(_js jsonb)
   RETURNS int[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT j::int FROM jsonb_array_elements_text(_js) j)';

Объяснение:

Затем добавьте, jsonb_typeof(jsonb)чтобы получить:

SELECT key
FROM   t2
ORDER  BY key <> '[]'             -- special case for empty array
        , jsonb_typeof(key) DESC  -- 'number' before 'array'
        , CASE jsonb_typeof(key)  -- sort arrays as converted int[]
            WHEN 'array'  THEN jsonb_arr2int_arr(key)
            WHEN 'number' THEN ARRAY[key::text::int]
          END;

Дает желаемый результат точно.

Почему?

Руководство jsonbобъясняет:

btreeЗаказ для jsonbдатумов редко представляет большой интерес, но для полноты это:

Object > Array > Boolean > Number > String > Null
Object with n pairs > object with n - 1 pairs
Array with n elements > array with n - 1 elements

Объекты с одинаковым количеством пар сравниваются в следующем порядке:

key-1, value-1, key-2 ...

Обратите внимание, что ключи объектов сравниваются в порядке их хранения; в частности, поскольку более короткие ключи хранятся перед более длинными ключами, это может привести к неинтуитивным результатам, таким как:

{ "aa": 1, "c": 1} > {"b": 1, "d": 1}

Аналогично, массивы с равным количеством элементов сравниваются в следующем порядке:

element-1, element-2 ...

Жирный акцент мой.
Вот почему jsonb '[2]' < jsonb '[1, 2]'.
Но массивы Postgres просто сортируют поэлементно: '{2}'::int[] > '{1, 2}'- именно то, что вы искали.

Эрвин Брандштеттер
источник
0

Обращаясь к задаче упорядочить результаты по целочисленным значениям json. Пытаться:

select myjson from mytable order by (myjson->>'some_int')::int;

В вашем случае это, кажется, массив для ключа заказа. Поэтому сначала попробуйте объединить значения в поле «ключ».

dlg_
источник