Tengo dos mesas.
El primero es una tabla con prefijos.
code name price
343 ek1 10
3435 nt 4
3432 ek2 2
El segundo es el registro de llamadas con números de teléfono.
number time
834353212 10
834321242 20
834312345 30
Necesito escribir un script que encuentre el prefijo más largo de los prefijos para cada registro, y escribir todos estos datos en la tercera tabla, así:
number code ....
834353212 3435
834321242 3432
834312345 343
Para el número 834353212 debemos recortar '8', y luego encontrar el código más largo de la tabla de prefijos, es 3435.
Siempre debemos colocar el primer '8' y el prefijo debe estar al principio.
Resolví esta tarea hace mucho tiempo, de muy mala manera. Fue un guión terrible de Perl que hace muchas consultas para cada registro. Este guión:
Tome un número de la tabla de llamadas, haga una subcadena de longitud (número) a 1 => $ prefijo en el bucle
Haga la consulta: seleccione count (*) de prefijos donde el código como '$ prefix'
- Si cuenta> 0, tome los primeros prefijos y escriba en la tabla
El primer problema es el recuento de consultas, es call_records * length(number)
. El segundo problema son las LIKE
expresiones. Me temo que son lentos.
Traté de resolver el segundo problema:
CREATE EXTENSION pg_trgm;
CREATE INDEX prefix_idx ON prefix USING gist (code gist_trgm_ops);
Eso acelera cada consulta, pero no resuelve el problema en general.
Tengo 20k prefijos y 170k números ahora, y mi antigua solución es mala. Parece que necesito alguna solución nueva sin bucles.
Solo una consulta para cada registro de llamada o algo así.
fuente
code
en la primera tabla es el mismo que el prefijo posterior. ¿Podría por favor aclararlo? Y también será bienvenido algún arreglo de los datos de ejemplo y la salida deseada (para que sea más fácil seguir su problema).Respuestas:
Asumo el tipo de datos
text
para las columnas relevantes."Solución simple
Elementos clave:
DISTINCT ON
es una extensión de Postgres del estándar SQLDISTINCT
. Encuentre una explicación detallada de la técnica de consulta utilizada en esta respuesta relacionada en SO .ORDER BY p.code DESC
elige la coincidencia más larga, porque se'1234'
ordena después'123'
(en orden ascendente).Simple SQL Fiddle .
Sin índice, la consulta se ejecute por un muy largo tiempo (no esperó para ver que se termine). Para hacer esto rápido, necesita soporte de índice. Los índices de trigrama que mencionó, suministrados por el módulo adicional
pg_trgm
son un buen candidato. Tienes que elegir entre el índice GIN y GiST. El primer carácter de los números es solo ruido y puede excluirse del índice, lo que lo convierte en un índice funcional además.En mis pruebas, un índice GIN trigram funcional ganó la carrera sobre un índice GiST trigram (como se esperaba):
Avanzado dbfiddle aquí .
Todos los resultados de la prueba son de una instalación de prueba local de Postgres 9.1 con una configuración reducida: 17k números y 2k códigos:
Mucho más rápido todavía
Intento fallido con
text_pattern_ops
Una vez que ignoramos el primer carácter de ruido de distracción, se reduce a la coincidencia de patrón anclada izquierda básica . Por lo tanto, probé un índice de árbol B funcional con la clase de operador
text_pattern_ops
(suponiendo el tipo de columnatext
).Esto funciona excelentemente para consultas directas con un solo término de búsqueda y hace que el índice de trigrama se vea mal en comparación:
Sin embargo , el planificador de consultas no considerará este índice para unir dos tablas. He visto esta limitación antes. Todavía no tengo una explicación significativa para esto.
Índices de árbol B parciales / funcionales
La alternativa es usar verificaciones de igualdad en cadenas parciales con índices parciales. Esto se puede usar en a
JOIN
.Como normalmente solo tenemos un número limitado de
different lengths
prefijos for, podemos construir una solución similar a la presentada aquí con índices parciales.Digamos que tenemos prefijos que van de 1 a 5 caracteres. Cree una serie de índices funcionales parciales, uno para cada longitud de prefijo distinta:
Como se trata de índices parciales , todos juntos son apenas más grandes que un solo índice completo.
Agregue índices coincidentes para los números (teniendo en cuenta el carácter de ruido inicial):
Si bien estos índices solo contienen una subcadena cada uno y son parciales, cada uno cubre la mayor parte o la totalidad de la tabla. Por lo tanto, son mucho más grandes juntos que un índice total único, excepto para números largos. E imponen más trabajo para las operaciones de escritura. Ese es el costo de una velocidad increíble.
Si ese costo es demasiado alto para usted (el rendimiento de escritura es importante / demasiadas operaciones de escritura / espacio en disco un problema), puede omitir estos índices. El resto es aún más rápido, si no tan rápido como podría ser ...
Si los números nunca son más cortos que los
n
caracteres, elimine lasWHERE
cláusulas redundantes de algunos o todos, y también elimine laWHERE
cláusula correspondiente de todas las consultas siguientes.CTE recursivo
Con toda la configuración hasta ahora, esperaba una solución muy elegante con un CTE recursivo :
Sin embargo, aunque esta consulta no es mala, funciona tan bien como la versión simple con un índice GIN de trigrama, no ofrece lo que buscaba. El término recursivo se planifica una sola vez, por lo que no puede usar los mejores índices. Solo el término no recursivo puede.
UNIÓN TODO
Como estamos lidiando con un pequeño número de recursiones, podemos explicarlas de forma iterativa. Esto permite planes optimizados para cada uno de ellos. (Sin embargo, perdemos la exclusión recursiva de números ya exitosos. Por lo tanto, todavía hay margen de mejora, especialmente para un rango más amplio de longitudes de prefijos)):
Un avance, por fin!
Función SQL
Al envolver esto en una función SQL, se elimina la sobrecarga de planificación de consultas para uso repetido:
Llamada:
Función PL / pgSQL con SQL dinámico
Esta función plpgsql es muy parecida al CTE recursivo anterior, pero el SQL dinámico
EXECUTE
obliga a que la consulta se vuelva a planificar para cada iteración. Ahora hace uso de todos los índices personalizados.Además, esto funciona para cualquier rango de longitudes de prefijo. La función toma dos parámetros para el rango, pero la preparé con
DEFAULT
valores, por lo que también funciona sin parámetros explícitos:El paso final no se puede incluir fácilmente en una función. O simplemente llámalo así:
O use otra función SQL como contenedor:
Llamada:
Un poco más lento debido a la sobrecarga de planificación requerida. Pero más versátil que SQL y más corto para prefijos más largos.
fuente
Una cadena S es un prefijo de una cadena T si T está entre S y SZ donde Z es lexicográficamente más grande que cualquier otra cadena (por ejemplo, 99999999 con 9 suficientes para exceder el número de teléfono más largo posible en el conjunto de datos, o a veces 0xFF funcionará).
El prefijo común más largo para cualquier T dado también es lexicográficamente máximo, por lo que un grupo simple por y max lo encontrará.
Si esto es lento, es probable que se deba a las expresiones calculadas, por lo que también puede intentar materializar p.code || '999999' en una columna en la tabla de códigos con su propio índice, etc.
fuente