J'ai deux tables.
Le premier est un tableau avec des préfixes
code name price
343 ek1 10
3435 nt 4
3432 ek2 2
Deuxièmement, les enregistrements d'appels avec des numéros de téléphone
number time
834353212 10
834321242 20
834312345 30
J'ai besoin d'écrire un script qui trouve le préfixe le plus long parmi les préfixes pour chaque enregistrement, et d'écrire toutes ces données dans la troisième table, comme ceci:
number code ....
834353212 3435
834321242 3432
834312345 343
Pour le numéro 834353212, nous devons couper «8», puis trouver le code le plus long de la table des préfixes, son 3435.
Nous devons toujours supprimer le premier «8» et le préfixe doit être au début.
J'ai résolu cette tâche il y a longtemps, de très mauvaise façon. C'était un script perl terrible qui faisait beaucoup de requêtes pour chaque enregistrement. Ce script:
Prendre un nombre dans la table des appels, faire une sous-chaîne de longueur (nombre) à 1 => $ préfixe dans la boucle
Faites la requête: sélectionnez count (*) parmi les préfixes où le code comme '$ prefix'
- Si count> 0, prenez les premiers préfixes et écrivez dans le tableau
Le premier problème est le nombre de requêtes - ça l'est call_records * length(number)
. Le deuxième problème concerne les LIKE
expressions. J'ai bien peur que ce soit lent.
J'ai essayé de résoudre le deuxième problème en:
CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);
Cela accélère chaque requête, mais n'a pas résolu le problème en général.
J'ai maintenant 20 000 préfixes et 170 000 numéros, et mon ancienne solution est mauvaise. On dirait que j'ai besoin d'une nouvelle solution sans boucles.
Une seule requête pour chaque enregistrement d'appel ou quelque chose comme ça.
la source
code
dans le premier tableau est le même que le préfixe plus tard. Pourriez-vous clarifier cela? Et une correction des données d'exemple et de la sortie souhaitée (afin qu'il soit plus facile de suivre votre problème) sera également la bienvenue.Réponses:
Je suppose que le type de données
text
pour les colonnes pertinentes.Solution "simple"
Éléments clé:
DISTINCT ON
est une extension Postgres du standard SQLDISTINCT
. Trouvez une explication détaillée de la technique de requête utilisée dans cette réponse connexe sur SO .ORDER BY p.code DESC
sélectionne la correspondance la plus longue, car'1234'
trie après'123'
(dans l'ordre croissant).Simple SQL Fiddle .
Sans index, la requête s'exécuterait très longtemps (n'attendait pas pour la voir se terminer). Pour rendre cela rapide, vous avez besoin d'un support d'index. Les index de trigrammes que vous avez mentionnés, fournis par le module supplémentaire,
pg_trgm
sont un bon candidat. Vous devez choisir entre l'index GIN et GiST. Le premier caractère des nombres n'est que du bruit et peut être exclu de l'index, ce qui en fait un index fonctionnel en plus.Lors de mes tests, un index trigramme GIN fonctionnel a remporté la course sur un index trigramme GiST (comme prévu):
Dbfiddle avancé ici .
Tous les résultats des tests proviennent d'une installation de test Postgres 9.1 locale avec une configuration réduite: 17k numéros et 2k codes:
Beaucoup plus rapide encore
Échec de la tentative avec
text_pattern_ops
Une fois que nous ignorons le premier caractère de bruit distrayant, cela revient à une correspondance de modèle ancrée à gauche de base . J'ai donc essayé un index fonctionnel B-tree avec la classe d'opérateur
text_pattern_ops
(en supposant le type de colonnetext
).Cela fonctionne parfaitement pour les requêtes directes avec un seul terme de recherche et rend l'index du trigramme mauvais en comparaison:
Cependant , le planificateur de requêtes ne tiendra pas compte de cet index pour joindre deux tables. J'ai déjà vu cette limitation. Je n'ai pas encore d'explication valable à cela.
Index B-tree partiels / fonctionnels
L'alternative consiste à utiliser des contrôles d'égalité sur des chaînes partielles avec des index partiels. Cela peut être utilisé dans un fichier
JOIN
.Comme nous n'avons généralement qu'un nombre limité de
different lengths
préfixes for, nous pouvons créer une solution similaire à celle présentée ici avec des index partiels.Disons, nous avons des préfixes allant de 1 à 5 caractères. Créez un certain nombre d'index fonctionnels partiels, un pour chaque longueur de préfixe distinct:
Puisqu'il s'agit d' index partiels , tous ensemble sont à peine plus grands qu'un seul index complet.
Ajoutez des index correspondants pour les nombres (en tenant compte du premier caractère de bruit):
Bien que ces index ne contiennent chacun qu'une sous-chaîne et soient partiels, chacun couvre la plupart ou la totalité de la table. Ils sont donc beaucoup plus grands ensemble qu'un seul indice total - à l'exception des nombres longs. Et ils imposent plus de travail pour les opérations d'écriture. Voilà le coût d'une vitesse incroyable.
Si ce coût est trop élevé pour vous (les performances d'écriture sont importantes / trop d'opérations d'écriture / d'espace disque sont un problème), vous pouvez ignorer ces index. Le reste est encore plus rapide, sinon aussi rapide qu'il pourrait l'être ...
Si les nombres ne sont jamais plus courts que les
n
caractères, supprimez lesWHERE
clauses redondantes de tout ou partie, et supprimez également laWHERE
clause correspondante de toutes les requêtes suivantes.CTE récursif
Avec toute la configuration jusqu'à présent, j'espérais une solution très élégante avec un CTE récursif :
Cependant, bien que cette requête ne soit pas mauvaise - elle fonctionne à peu près aussi bien que la version simple avec un index GIN trigram - elle ne fournit pas ce que je visais. Le terme récursif n'est prévu qu'une seule fois, il ne peut donc pas utiliser les meilleurs index. Seul le terme non récursif peut.
UNION ALL
Comme nous avons affaire à un petit nombre de récursions, nous pouvons simplement les énoncer de manière itérative. Cela permet des plans optimisés pour chacun d'eux. (Nous perdons cependant l'exclusion récursive des numéros déjà réussis. Il y a donc encore une marge d'amélioration, en particulier pour une plus large gamme de longueurs de préfixe)):
Une percée enfin!
Fonction SQL
L'encapsulation dans une fonction SQL supprime la surcharge de planification des requêtes pour une utilisation répétée:
Appel:
Fonction PL / pgSQL avec SQL dynamique
Cette fonction plpgsql ressemble beaucoup au CTE récursif ci-dessus, mais le SQL dynamique avec
EXECUTE
oblige à replanifier la requête pour chaque itération. Maintenant, il utilise tous les index personnalisés.De plus, cela fonctionne pour n'importe quelle plage de longueurs de préfixe. La fonction prend deux paramètres pour la plage, mais je l'ai préparée avec des
DEFAULT
valeurs, elle fonctionne donc aussi sans paramètres explicites:La dernière étape ne peut pas être facilement intégrée à une seule fonction. Soit il suffit de l'appeler ainsi:
Ou utilisez une autre fonction SQL comme wrapper:
Appel:
Un peu plus lent en raison des frais généraux de planification requis. Mais plus polyvalent que SQL et plus court pour les préfixes plus longs.
la source
Une chaîne S est un préfixe d'une chaîne T ssi T est entre S et SZ où Z est lexicographiquement plus grand que toute autre chaîne (par exemple 99999999 avec suffisamment de 9 pour dépasser le numéro de téléphone le plus long possible dans l'ensemble de données, ou parfois 0xFF fonctionnera).
Le préfixe commun le plus long pour un T donné est également lexicographiquement maximal, donc un simple groupe by et max le trouvera.
Si cela est lent, cela est probablement dû aux expressions calculées, vous pouvez donc également essayer de matérialiser p.code || '999999' dans une colonne de la table des codes avec son propre index, etc.
la source