PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Tri numérique humanisé ou naturel de chaînes mixtes de mots et de nombres

S'appuyant sur vos données de test, mais cela fonctionne avec des données arbitraires. Cela fonctionne avec n'importe quel nombre d'éléments dans la chaîne.

Enregistrez un type composé composé d'un text et un integer valeur une fois par base de données. Je l'appelle ai :

CREATE TYPE ai AS (a text, i int);

L'astuce consiste à former un tableau de ai de chaque valeur de la colonne.

regexp_matches() avec le motif (\D*)(\d*) et le g L'option renvoie une ligne pour chaque combinaison de lettres et de chiffres. Plus une ligne pendante non pertinente avec deux chaînes vides '{"",""}' Le filtrer ou le supprimer ne ferait qu'augmenter les coûts. Agrégez ceci dans un tableau, après avoir remplacé les chaînes vides ('' ) avec 0 dans le integer composant (comme '' ne peut pas être converti en integer ).

NULL les valeurs sont triées en premier - ou vous devez les caser spécialement - ou utilisez tout le shebang dans un STRICT fonction comme le propose @Craig.

Postgres 9.4 ou version ultérieure

SELECT data
FROM   alnum
ORDER  BY ARRAY(SELECT ROW(x[1], CASE x[2] WHEN '' THEN '0' ELSE x[2] END)::ai
                FROM regexp_matches(data, '(\D*)(\d*)', 'g') x)
        , data;

db<>jouez ici

Postgres 9.1 (réponse originale)

Testé avec PostgreSQL 9.1.5, où regexp_replace() avait un comportement légèrement différent.

SELECT data
FROM  (
    SELECT ctid, data, regexp_matches(data, '(\D*)(\d*)', 'g') AS x
    FROM   alnum
    ) x
GROUP  BY ctid, data   -- ctid as stand-in for a missing pk
ORDER  BY regexp_replace (left(data, 1), '[0-9]', '0')
        , array_agg(ROW(x[1], CASE x[2] WHEN '' THEN '0' ELSE x[2] END)::ai)
        , data         -- for special case of trailing 0

Ajouter regexp_replace (left(data, 1), '[1-9]', '0') comme premier ORDER BY élément pour prendre soin des premiers chiffres et des chaînes vides.

Si des caractères spéciaux comme {}()"', peuvent se produire, vous devrez les échapper en conséquence.
Suggestion de @Craig d'utiliser un ROW l'expression s'en charge.

BTW, cela ne s'exécutera pas dans sqlfiddle, mais c'est le cas dans mon cluster db. JDBC n'est pas à la hauteur. sqlfiddle se plaint :

La méthode org.postgresql.jdbc3.Jdbc3Array.getArrayImpl(long,int,Map) n'est pas encore implémentée.

Cela a depuis été corrigé :http://sqlfiddle.com/#!17/fad6e/1