web-dev-qa-db-fra.com

Comparer des chaînes dans postgres en utilisant des opérateurs de comparaison?

Dans de nombreux langages de programmation, vous pouvez comparer des chaînes en utilisant des opérateurs comme>,> =, <etc ... et le langage basera la comparaison sur la position de la lettre dans l'alphabet.

Par exemple en PHP

if ('a' < 'b') {
    echo 'Yes';
} else {
    echo 'No';
}
> Yes

Cependant en postgres ou mysql

SELECT
CASE WHEN 'a' < 'b' THEN 'yes' END
FROM table
Output: null

J'ai une table avec des chaînes que je dois comparer les unes aux autres via SQL.

Par exemple: 6,2 (5a) 6,2 (5b) - ce serait supérieur à 6,2 (5a) ou 6,2 (15) - ce serait supérieur à 6,2 (5a)

J'ai pensé attribuer un numéro à une lettre à l'aide d'une expression rationnelle, mais cela casserait les comparaisons lorsqu'il n'y a pas de lettre.

Comment procéderiez-vous uniquement en SQL?

7
bakamike

[~ # ~] note [~ # ~]: La réponse originale s'est déclenchée sur un hareng rouge.

Une comparaison simple trie caractère par caractère.

select 'a1' < 'a9'; -- true because 'a' = 'a' and '1' < '9'.

... mais passe rapidement au pot.

select 'a10' < 'a9'; -- also true for the same reason.

Ce que vous voulez est un tri naturel où les parties de chaîne sont comparées en tant que chaînes et les nombres sont comparés en tant que nombres. Faire un tri naturel en SQL n'est pas la chose la plus simple. Vous avez besoin de largeurs de champ fixes pour trier chaque sous-chaîne séparément, ou peut-être quelque chose avec des expressions régulières ...

Heureusement, il y a pg_natural_sort_order , une extension Postgres qui implémente un tri naturel efficace.

Si vous ne pouvez pas installer d'extensions, vous pouvez utiliser une procédure stockée comme btrsort by 2kan.

CREATE FUNCTION btrsort_nextunit(text) RETURNS text AS $$
    SELECT 
        CASE WHEN $1 ~ '^[^0-9]+' THEN
            COALESCE( SUBSTR( $1, LENGTH(SUBSTRING($1 FROM '[^0-9]+'))+1 ), '' )
        ELSE
            COALESCE( SUBSTR( $1, LENGTH(SUBSTRING($1 FROM '[0-9]+'))+1 ), '' )
        END

$$ LANGUAGE SQL;

CREATE FUNCTION btrsort(text) RETURNS text AS $$
    SELECT 
        CASE WHEN char_length($1)>0 THEN
            CASE WHEN $1 ~ '^[^0-9]+' THEN
                RPAD(SUBSTR(COALESCE(SUBSTRING($1 FROM '^[^0-9]+'), ''), 1, 12), 12, ' ') || btrsort(btrsort_nextunit($1))
            ELSE
                LPAD(SUBSTR(COALESCE(SUBSTRING($1 FROM '^[0-9]+'), ''), 1, 12), 12, ' ') || btrsort(btrsort_nextunit($1))
            END
        ELSE
            $1
        END
      ;
$$ LANGUAGE SQL;

Bien qu'il ne fournisse pas d'opérateur de comparaison et je ne vais pas prétendre le comprendre. Cela vous permet de l'utiliser dans un order by.

select * from things order by btrsort(whatever);

Pour éviter que vos requêtes triées naturellement ne se transforment en boue sur de grandes tables, vous pouvez créer un index btree sur le résultat de cette fonction .

create index things_whatever_btrsort_idx ON things( btrsort(whatever) );

SELECT
  CASE WHEN 'a' < 'b' THEN 'yes' END
  FROM table
  Output: null

Cela ne produira rien si la table est vide. Vous n'avez pas besoin d'un tableau pour tester les instructions select.

SELECT
CASE WHEN 'a' < 'b' THEN 'yes' END  -- yes
13
Schwern