Personalice el orden de clasificación de claves jsonb que involucra matrices

9

Tengo una tabla en PostgreSQL con algunos datos:

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"');

Y trato de ordenar estas filas así:

SELECT key FROM t2 order by key;

El resultado es:

[]
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]

Pero lo que necesito es:

[]
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] <==

¿Hay alguna manera de lograrlo?

Antonio
fuente
Entonces, ¿tienes tu respuesta aquí?
Erwin Brandstetter

Respuestas:

8

En primer lugar, su pregunta y el nombre de su columna "key"son engañosos. La clave de columna no contiene ninguna clave JSON , solo valores . De lo contrario, podríamos usar la función jsonb_object_keys(jsonb)para extraer claves, pero no es así.

Suponiendo que todas sus matrices JSON estén vacías o contengan números enteros como se demuestra. Y los valores escalares (no matrices) también son enteros.

Su orden de clasificación básico funcionaría con matrices Postgres integer(o numeric). Utilizo esta pequeña función auxiliar para convertir jsonbmatrices a 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)';

Explicación:

Luego agregue jsonb_typeof(jsonb)para llegar a:

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;

Produce el resultado deseado exactamente.

¿Por qué?

El manual para jsonbexplica:

El btreepedido de datos jsonbrara vez es de gran interés, pero para completar es:

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

Los objetos con igual número de pares se comparan en el orden:

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

Tenga en cuenta que las claves de objeto se comparan en su orden de almacenamiento; en particular, dado que las claves más cortas se almacenan antes que las claves más largas, esto puede conducir a resultados que pueden no ser intuitivos, como:

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

Del mismo modo, las matrices con igual número de elementos se comparan en el orden:

element-1, element-2 ...

El énfasis audaz es mío.
Por eso jsonb '[2]' < jsonb '[1, 2]'.
Pero las matrices de Postgres simplemente clasifican elemento por elemento: '{2}'::int[] > '{1, 2}'exactamente lo que estaba buscando.

Erwin Brandstetter
fuente
0

Refiriéndose al problema para ordenar sus resultados por valores enteros json. Tratar:

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

En su caso, parece ser una matriz para la clave de pedido. Así que primero intente concat los valores en su campo "clave".

dlg_
fuente