PostgreSQL La base de donnees la plus sophistiquee au monde.

Forums PostgreSQL.fr

Le forum officiel de la communauté francophone de PostgreSQL

Vous n'êtes pas identifié(e).

#1 19/02/2021 16:19:52

Jean-Marc68
Membre

Création d'une fonction de tri

Bonjour,
Je ne suis pas un grand habitué des regex, aussi je fais appel à vos connaissances.
J'ai trouvé la fonction naturalsort qui permet le tri naturel.
Mais j'ai besoin d'un tri un peu différent.

Résultat recherché :
1-84
1-84-1
1-84-2
1-85
1A-4
1A-30-2
1-F-2-3
1-F-12
1-F-13-1

Logique : les tirets représentent des "étages". Donc le 1 contient le 1-1, le 1-2, qui lui contient le 1-2-1, le 1-2-2, ...
              Là où se trouve ma difficulté c'est qu'au premier niveau il y a parfois des lettres. Exemple 1A, 2C, ... mais aussi des lettres avec des tirets devant (et on ne peut pas les changer. C'est effectivement comme ça et je dois vivre avec).
Donc quand un tiret précède une lettre, il ne faut pas en tenir compte dans le tri. Pour le tri, 12-F devrait être considéré comme 12F, mais pour le tri uniquement. Remarque : les lettres existent seulement au 1er supérieur.
J'ai vraiment besoin d'aide pour ce tri parce que j'ai beau essayer, je ne m'en sors pas.

Merci de vos z'avis z'avisés.

Hors ligne

#2 19/02/2021 17:40:38

rjuju
Administrateur

Re : Création d'une fonction de tri

Il n'est pas compliqué de supprimer le tiret s'il est suivi d'une lettre dans le tri, il suffit d'utiliser une clause telle que :

SELECT ...
FROM ...
ORDER BY regexp_replace(lacolonne, '-([a-zA-Z])', '\1')

et dans votre cas j'imagine en ajoutant une clause COLLATE pour prendre en compte un tri différent.

Hors ligne

#3 19/02/2021 18:59:38

Jean-Marc68
Membre

Re : Création d'une fonction de tri

Merci de ton aide rjuju.
C'est vrai que c'est une bonne idée.
Par contre je ne peux pas indexer avec ça, or j'ai quand-même pas mal de lignes et le tri ne se fait que sur cette mode, donc je pensais l'indexer comme ça.

J'avais trouvé cette fonction qui me permet d'indexer ma table en tri naturel, et j'espérais pouvoir modifier le regex pour qu'il ne tienne pas compte du tiret précédent une lettre, mais mes connaissances sont trop limitées et dans mes essais je bousille le regex au lien de l'améliorer pour mon besoin.

CREATE OR REPLACE FUNCTION public.naturalsort(text)
    RETURNS bytea
    LANGUAGE 'sql'
    COST 100
    IMMUTABLE STRICT PARALLEL UNSAFE
AS $BODY$
select string_agg(
		convert_to(
			coalesce(
				r[2],
				length(length(r[1])::text) || length(r[1])::text || r[1])
			,'SQL_ASCII')
		,'\x00')
    from regexp_matches($1, '0*([0-9]+)|([^0-9]+)', 'g') r;
$BODY$;

Hors ligne

#4 19/02/2021 21:22:37

Jean-Marc68
Membre

Re : Création d'une fonction de tri

rjuju a écrit :

Il n'est pas compliqué de supprimer le tiret s'il est suivi d'une lettre dans le tri, il suffit d'utiliser une clause telle que :

SELECT ...
FROM ...
ORDER BY regexp_replace(lacolonne, '-([a-zA-Z])', '\1')

et dans votre cas j'imagine en ajoutant une clause COLLATE pour prendre en compte un tri différent.

Merci de ton aide.
N'arrivant pas à modifier directement le regex pour tenir compte de ton conseil, J'ai modifié le regexp_matches comme ceci :

regexp_matches(regexp_replace($1, '-([a-zA-Z])', '\1'), '0*([0-9]+)|([^0-9]+)', 'g')

Bon, d'accord, je pense que ce n'est pas le regex le plus propre qui soit. Et sans doute est-ce plus lent qu'un regex correctement écrit parce qu'il me semble que que comme je j'ai écrit il y a une "couche" en plus (Il fait un regex de remplacement puis le regex de sélection au lieu de tout faire en une fois dans le regex).
Je crois qu'il doit y avoir moyen de "supprimer" le tiret qui est avant la lettre directement dans le regex (pas dans la première partie du match, comme je l'ai fait), mais je ne trouve pas la solution.
En tout cas il me semble qu'il fonctionne comme je l'ai écrit (du moins il fonctionne avec les tests que j'ai faits).
Quant au COLLATE, je ne connaissais pas, mais si j'ai bien compris ce que j'ai lu, ce serait pour la gestion des caractères accentués (UTF-8 p.ex). Or dans mon cas je n'en ai pas besoin. Ce sont (normalement) seulement des lettres majuscules de A à F (mais je garde quand-même le [a-zA-Z] au cas où une ligne aurait été mal encodée (il faut vivre avec l'existant).

Hors ligne

#5 20/02/2021 07:36:17

rjuju
Administrateur

Re : Création d'une fonction de tri

Je ne comprends pas ce que vous cherchez à obtenir.  Le besoin que vous avez décrit et de supprimer un tiret avant une lettre, ce qui est le comportement de la regexp initiale.  Votre regexp_matches va renvoyer un ensemble de tableaux et j'ai du mal à imaginer que cela soit raisonnable dans le cade d'un tri.


La clause COLLATE permet de spécifier la manière de trier du texte.  Avez-vous regardé https://www.postgresql.org/docs/current … N-MANAGING notamment le paragraphe 23.2.2.3.2. ICU Collations ?

Hors ligne

#6 20/02/2021 16:02:38

Jean-Marc68
Membre

Re : Création d'une fonction de tri

rjuju, sans doute me suis-je mal exprimé. Néanmoins, merci de vous attarder sur ma difficulté.
Je vais tenter de réexpliquer pour être plus clair.
Dans une colonne, j'ai des données qui sont exactes. Il est en effet possible d'avoir BL1, BL1-1, 50-1, 50A-1-2 et 50-A-3-4, 256-1-1.
Toutefois, lors d'un tri classique, il sortira dans l'ordre :
256-1-1
50-1
50-A-3-4
50A-1-2
BL1
BL1-1

Or il devrait sortir
50-1
50A-1-2
50-A-3-4
256-1-1
BL1
BL1-1

En fait ce sont des lots de terrains. Prenons le 256-1-1 en exemple. 256 est le lot de départ. Il a été divisé au min en 2 (256-1-1 et 256-1-2), puis la première partie a été divisée (admettons en 3 pour former 256-1-1, 256-1-2 et 256-1-1-3).
Jusque là la fonction naturalsort(text) que j'ai trouvée et donnée plus haut fonctionne bien. Même avec du 50-1-1 et du 50A-1-1 (50 et 50A ne sont pas le même lot. 50 et 50A ont été divisés tous les 2. C'est pour ça qu'on trouve 50-1-1 et 50A-1-2)
Malheureusement au cours des années certains ont écrit 50A et d'autres 50-A et le gouvernement les a acceptés comme tel. Il n'aurait pas fallu, mais je dois vivre avec. Je dois donc traiter 50A-1-2 et 50-A-3-4 qui sont en fait tous les 2 à considérer comme du 50A, mais pour le tri uniquement. Le lot dans la table doit impérativement rester avec son tiret.
Donc en fait, puisqu'il n'y a des lettres qu'au niveau supérieur, pour le tri on peut retirer le tiret qui précède une lettre (comme vous l'aviez suggéré). Puis trier par niveau.
Enfin, je voudrais pouvoir indexer la table sur ce champs et dans cet ordre. Parce que c'est la clé de recherche la plus souvent utilisée.

J'espère avoir été plus clair.
Et encore merci de votre aide qui m'es précieuse.

Dernière modification par Jean-Marc68 (20/02/2021 16:49:27)

Hors ligne

#7 20/02/2021 22:52:57

rjuju
Administrateur

Re : Création d'une fonction de tri

J'avais bien compris le but final, mais je ne comprends toujours pas le but exact de votre regexp_matches.


Cela étant dit, je viens de prendre le temps de tester l'exemple donné dans la documentation que j'ai pointé juste avant, et sauf erreur de ma part la regexp plus une cause COLLATE avec une collation qui va bien, comme indiqué dans mon premier message, règle le problème ?

SELECT val FROM t_sort ORDER BY regexp_replace(val, '-([a-zA-Z])', '\1') COLLATE numeric;
   val    
----------
 1-84
 1-84-1
 1-84-2
 1-85
 1A-4
 1A-30-2
 1-F-2-3
 1-F-12
 1-F-13-1
(9 rows)

Hors ligne

#8 21/02/2021 00:02:17

Jean-Marc68
Membre

Re : Création d'une fonction de tri

Merci de votre réponse.
Mais quand j'essaye

select * from _test ORDER BY regexp_replace(val, '-([a-zA-Z])', '\1') COLLATE numeric;

dans ma bdd, j'obtiens l'erreur

ERROR: ERREUR:  le collationnement « numeric » pour l'encodage « UTF8 » n'existe pas
LINE 2: ORDER by regexp_replace(val, '-([a-zA-Z])', '\1') COLLATE nu...
                                                          ^


SQL state: 42704
Character: 71

(Ma base de donnée est en UTF-8 French_Canada.1252, et le postgresql.conf contient lc_numeric = 'French_Canada.1252')
Et sans le COLLATE numeric, je n'ai pas le bon résultat, ce qui est logique puisqu'il trie les nombres en alphanumérique (remarquez le 256-1-1) :

1-1-1
1A-2-2
1-A-2-3
256-1-1
50-1
50A-1-2
50-A-3-4
BL-1
BL1-1

Je crois comme vous que si je pouvais avoir un collate qui me permettre de trier en numérique ça règlerait mon problème.

Dernière modification par Jean-Marc68 (21/02/2021 01:09:52)

Hors ligne

#9 21/02/2021 06:19:34

Jean-Marc68
Membre

Re : Création d'une fonction de tri

Ça y est.
J'ai trouvé un collate numeric dans la doc

CREATE COLLATION numeric (provider = icu, locale = 'en-u-kn-true');

Selon ce que j'ai trouvé, je crois avoir compris que les nombres sont triés comme des nombres et pas comme de l'alphanumérique par l'option kn à true. Mais j'admets ne pas avoir encore compris le en-u. Le en serait l'anglais (pour le point comme séparateur décimal et la virgule comme séparateur de milliers je suppose), mais le u, je ne comprend pas.
En tout cas ça fonctionne.
Merci beaucoup de votre aide.

Dernière modification par Jean-Marc68 (21/02/2021 06:22:23)

Hors ligne

#10 21/02/2021 07:40:06

rjuju
Administrateur

Re : Création d'une fonction de tri

le "-u-" n'est qu'un séparateur.  La documentation postgres pointe vers des liens de la documentation d'ICU qui documentent un peu les conventions de nommage, qui sont effectivement un peu cryptique.  Vous pouvez sinon consulter https://www.cybertec-postgresql.com/en/ … orruption/

Hors ligne

#11 21/02/2021 16:06:25

Jean-Marc68
Membre

Re : Création d'une fonction de tri

Merci. Ce lien m'a échappé mais j'avais trouvé de l'info ici avec pas mal de codes d'options : http://unicode.org/reports/tr35/tr35-co … ng_Options (au cas où ça intéresserait qqn)

Hors ligne

Pied de page des forums