Nous devons faire des rapports sur les valeurs qui sont généralement des chaînes mixtes de chiffres et de lettres qui doivent être triées «naturellement». Des choses comme, par exemple, "P7B18" ou "P12B3". @Les chaînes seront principalement des séquences de lettres puis des nombres alternés. Cependant, le nombre de ces segments et la longueur de chacun peuvent varier.
Nous aimerions que les parties numériques de celles-ci soient triées dans l'ordre numérique. Évidemment, si je gère directement ces valeurs de chaîne avec ORDER BY
, alors "P12B3" va précéder "P7B18", car "P1" est antérieur à "P7", mais j'aimerais l'inverse, car "P7" précède naturellement "P12".
Je voudrais également pouvoir faire des comparaisons de gamme, par exemple, @bin < 'P13S6'
ou certaines autres. Je n'ai pas à gérer les nombres à virgule flottante ou négatifs; ce seront strictement des entiers non négatifs avec lesquels nous avons affaire. Les longueurs de chaîne et le nombre de segments peuvent potentiellement être arbitraires, sans limites supérieures fixes.
Dans notre cas, la casse des chaînes n'est pas importante, bien que s'il existe un moyen de le faire de manière sensible au classement, d'autres pourraient le trouver utile. La partie la plus laide de tout cela est que j'aimerais pouvoir faire à la fois le tri et le filtrage de plage dans la WHERE
clause.
Si je faisais cela en C #, ce serait une tâche assez simple: faire une analyse pour séparer l'alpha du numérique, implémenter IComparable, et vous avez essentiellement terminé. Bien sûr, SQL Server ne semble pas offrir de fonctionnalités similaires, du moins pour autant que je sache.
Quelqu'un connaît de bonnes astuces pour faire fonctionner cela? Y a-t-il une possibilité peu médiatisée de créer des types CLR personnalisés qui implémentent IComparable et ont ce comportement comme prévu? Je ne suis pas non plus opposé aux astuces XML stupides (voir aussi: concaténation de liste), et j'ai aussi des fonctions de wrapper de correspondance / extraction / remplacement de regex CLR disponibles sur le serveur.
EDIT: Comme un exemple un peu plus détaillé, je voudrais que les données se comportent quelque chose comme ça.
SELECT bin FROM bins ORDER BY bin
bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0
c'est-à-dire briser les chaînes en jetons de toutes les lettres ou de tous les nombres et les trier respectivement par ordre alphabétique ou numérique, les jetons les plus à gauche étant le terme de tri le plus significatif. Comme je l'ai mentionné, morceau de gâteau dans .NET si vous implémentez IComparable, mais je ne sais pas comment (ou si) vous pouvez faire ce genre de chose dans SQL Server. Ce n'est certainement pas quelque chose que j'ai rencontré en une dizaine d'années de travail avec.
P7B12
pourrait donc devenirP 07 B 12
(via ASCII)80 07 65 12
, donc80076512
Réponses:
Vous voulez un moyen sensé et efficace de trier les nombres en chaînes en tant que nombres réels? Envisagez de voter pour ma suggestion Microsoft Connect: Prise en charge du "tri naturel" / DIGITSASNUMBERS comme option de classement
Il n'y a pas de moyen facile et intégré de le faire, mais voici une possibilité:
Normalisez les chaînes en les reformatant en segments de longueur fixe:
VARCHAR(50) COLLATE Latin1_General_100_BIN2
. La longueur maximale de 50 peut devoir être ajustée en fonction du nombre maximal de segments et de leurs longueurs maximales potentielles.AFTER [or FOR] INSERT, UPDATE
déclencheur de sorte que vous êtes assuré de définir correctement la valeur de tous les enregistrements, même ceux entrant via des requêtes ad hoc, etc. Bien sûr, cette UDF scalaire peut également être gérée via SQLCLR, mais elle devra être testée pour déterminer laquelle est réellement plus efficace. **UPPER()
fonction au résultat final de tous les segments (de sorte qu'elle ne doit être effectuée qu'une seule fois et non par segment). Cela permettra un tri correct étant donné le classement binaire de la colonne de tri.AFTER INSERT, UPDATE
déclencheur sur la table qui appelle l'UDF pour définir la colonne de tri. Pour améliorer les performances, utilisez laUPDATE()
fonction pour déterminer si cette colonne de code se trouve même dans laSET
clause de l'UPDATE
instruction (simplementRETURN
si elle est fausse), puis joignez lesINSERTED
etDELETED
pseudo-tables de la colonne de code pour ne traiter que les lignes dont les valeurs de code ont été modifiées . Veillez à spécifierCOLLATE Latin1_General_100_BIN2
cette condition JOIN pour garantir la précision de la détermination de la modification.Exemple:
Dans cette approche, vous pouvez trier via:
Et vous pouvez effectuer un filtrage de plage via:
ou:
Le filtre
ORDER BY
et leWHERE
filtre doivent utiliser le classement binaire défini pour enSortColumn
raison de la priorité du classement .Les comparaisons d'égalité seraient toujours effectuées sur la colonne de valeur d'origine.
D'autres pensées:
Utilisez un UDT SQLCLR. Cela pourrait fonctionner, bien qu'il ne soit pas clair s'il présente un gain net par rapport à l'approche décrite ci-dessus.
Oui, un UDT SQLCLR peut faire remplacer ses opérateurs de comparaison par des algorithmes personnalisés. Cela gère les situations dans lesquelles la valeur est comparée à une autre valeur qui est déjà du même type personnalisé ou à une valeur qui doit être implicitement convertie. Cela devrait gérer le filtre de gamme dans un
WHERE
état.En ce qui concerne le tri de l'UDT en tant que type de colonne normal (pas une colonne calculée), cela n'est possible que si l'UDT est "ordonné en octets". Être "ordonné par octets" signifie que la représentation binaire de l'UDT (qui peut être définie dans l'UDT) trie naturellement dans l'ordre approprié. En supposant que la représentation binaire est traitée de manière similaire à l'approche décrite ci-dessus pour la colonne VARCHAR (50) qui a des segments de longueur fixe qui sont rembourrés, cela serait admissible. Ou, s'il n'était pas facile de garantir que la représentation binaire serait naturellement ordonnée de la bonne manière, vous pourriez exposer une méthode ou une propriété de l'UDT qui génère une valeur qui serait correctement ordonnée, puis créer une
PERSISTED
colonne calculée sur celle-ci. méthode ou propriété. La méthode doit être déterministe et marquée commeIsDeterministic = true
.Les avantages de cette approche sont:
Parse
méthode de l'UDT prend laP7B18
valeur et la convertit, vous devriez pouvoir simplement insérer les valeurs naturellement commeP7B18
. Et avec la méthode de conversion implicite définie dans l'UDT, la condition WHERE permettrait également d'utiliser simplement P7B18 ».Les conséquences de cette approche sont:
PERSISTED
colonne calculée sur une propriété ou une méthode de l'UDT, vous obtiendrez alors la représentation renvoyée par la propriété ou la méthode. Si vous souhaitez laP7B18
valeur d' origine , vous devez appeler une méthode ou une propriété de l'UDT codée pour renvoyer cette représentation. Étant donné que vous devez deToString
toute façon remplacer la méthode, c'est un bon candidat pour fournir cela.Il n'est pas clair (du moins pour moi en ce moment car je n'ai pas testé cette partie) à quel point il serait facile / difficile d'apporter des modifications à la représentation binaire. La modification de la représentation stockée et triable peut nécessiter la suppression et l'ajout du champ. En outre, la suppression de l'assembly contenant l'UDT échouerait si elle était utilisée de l'une ou l'autre manière, vous devez donc vous assurer qu'il n'y avait rien d'autre dans l'assembly que cet UDT. Vous pouvez
ALTER ASSEMBLY
remplacer la définition, mais il existe certaines restrictions à ce sujet.D'un autre côté, le
VARCHAR()
champ est constitué de données déconnectées de l'algorithme et ne nécessiterait que la mise à jour de la colonne. Et s'il y a des dizaines de millions de lignes (ou plus), cela peut être fait dans une approche par lots.Implémentez la bibliothèque ICU qui permet de faire ce tri alphanumérique. Bien que très fonctionnelle, la bibliothèque n'est disponible qu'en deux langues: C / C ++ et Java. Ce qui signifie que vous devrez peut-être effectuer quelques ajustements pour le faire fonctionner dans Visual C ++, ou il y a de fortes chances que le code Java puisse être converti en MSIL à l'aide d' IKVM . Il existe un ou deux projets côté .NET liés sur ce site qui fournissent une interface COM accessible en code managé, mais je pense qu'ils n'ont pas été mis à jour depuis un certain temps et je ne les ai pas essayés. Le mieux serait ici de gérer cela dans la couche d'application dans le but de générer des clés de tri. Les clés de tri seraient alors enregistrées dans une nouvelle colonne de tri.
Ce n'est peut-être pas l'approche la plus pratique. Cependant, il est toujours très cool qu'une telle capacité existe. J'ai fourni un examen plus détaillé d'un exemple de cela dans la réponse suivante:
Existe-t-il un classement pour trier les chaînes suivantes dans l'ordre suivant 1,2,3,6,10,10A, 10B, 11?
Mais le schéma traité dans cette question est un peu plus simple. Pour un exemple montrant que le type de modèle traité dans cette question fonctionne également, veuillez vous rendre sur la page suivante:
ICU Collation Demo
Sous "Paramètres", définissez l'option "numérique" sur "activé" et toutes les autres doivent être définies sur "par défaut". Ensuite, à droite du bouton "trier", décochez l'option "forces de diff" et cochez l'option "trier les clés". Remplacez ensuite la liste des éléments de la zone de texte "Entrée" par la liste suivante:
Cliquez sur le bouton "trier". La zone de texte "Sortie" doit afficher les éléments suivants:
Veuillez noter que les clés de tri sont structurées en plusieurs champs, séparés par des virgules. Chaque champ doit être trié indépendamment, ce qui présente un autre petit problème à résoudre si vous devez l'implémenter dans SQL Server.
** En cas de problème de performances concernant l'utilisation des fonctions définies par l'utilisateur, veuillez noter que les approches proposées les utilisent le moins possible. En fait, la principale raison du stockage de la valeur normalisée était d'éviter d'appeler un UDF pour chaque ligne de chaque requête. Dans l'approche principale, l'UDF est utilisé pour définir la valeur de
SortColumn
, et cela se fait uniquement surINSERT
etUPDATE
via le déclencheur. La sélection de valeurs est beaucoup plus courante que l'insertion et la mise à jour, et certaines valeurs ne sont jamais mises à jour. Pour chaqueSELECT
requête qui utilise leSortColumn
pour un filtre de plage dans laWHERE
clause, l'UDF n'est nécessaire qu'une seule fois pour chacune des valeurs range_start et range_end pour obtenir les valeurs normalisées; l'UDF n'est pas appelé par ligne.En ce qui concerne l'UDT, l'utilisation est en fait la même qu'avec l'UDF scalaire. La signification, l'insertion et la mise à jour appellent la méthode de normalisation une fois par chaque ligne pour définir la valeur. Ensuite, la méthode de normalisation serait appelée une fois par requête pour chaque range_start et range_value dans un filtre de plage, mais pas par ligne.
Un point en faveur de la gestion de la normalisation entièrement dans un FDU SQLCLR est que, étant donné qu'il ne fait aucun accès aux données et est déterministe, s'il est marqué comme
IsDeterministic = true
, il peut alors participer à des plans parallèles (ce qui pourrait aider les opérationsINSERT
etUPDATE
) alors qu'un T-SQL UDF empêchera l'utilisation d'un plan parallèle.la source