tablas suma restar resta español diferentes desde consultas columnas cero campos aprender sql postgresql performance relational-division set-operations

suma - ¿Cómo configurar de manera eficiente restar una tabla de unión en PostgreSQL?



restar dos consultas sql (9)

(ver ACTUALIZACIÓN abajo)

Esta consulta encuentra una buena work_unit con un simple IZQUIERDA IZQUIERDA para encontrar una habilidad faltante en la tabla de habilidades más corta que tiene el trabajador solicitante. El truco es que siempre que falte una habilidad, habrá un valor NULO en la unión y esto se traduce a un 1 y la work_unit se elimina al dejar los valores con todos los valores de 0 , es decir, tener un max de 0 .

Siendo SQL clásico, esta sería la consulta más fuertemente dirigida a optimización por el motor:

SELECT work_unit_id FROM work_units_skills s LEFT JOIN (SELECT skill_id FROM workers_skills WHERE worker_id = 1) t ON (s.skill_id=t.skill_id) GROUP BY work_unit_id HAVING max(CASE WHEN t.skill_id IS NULL THEN 1 ELSE 0 END)=0 -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1 FOR UPDATE SKIP LOCKED;

ACTUALIZAR

Para capturar work_units sin habilidades, lanzamos la tabla work_units en JOIN:

SELECT r.id AS work_unit_id FROM work_units r LEFT JOIN work_units_skills s ON (r.id=s.work_unit_id) LEFT JOIN (SELECT skill_id FROM workers_skills WHERE worker_id = 1) t ON (s.skill_id=t.skill_id) GROUP BY r.id HAVING bool_or(s.skill_id IS NULL) OR bool_and(t.skill_id IS NOT NULL) -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1 FOR UPDATE SKIP LOCKED;

Tengo las siguientes tablas:

  • work_units - auto explicativo
  • workers - auto explicativo
  • skills : cada unidad de trabajo requiere una serie de habilidades si desea trabajar en ellas. Cada trabajador es competente en una serie de habilidades.
  • work_units_skills - unir tabla
  • workers_skills - unirse a la tabla

Un trabajador puede solicitar la siguiente unidad de trabajo de prioridad más alta gratuita (lo que sea que signifique) que se le asignará.

Actualmente tengo:

SELECT work_units.* FROM work_units -- some joins WHERE NOT EXISTS ( SELECT skill_id FROM work_units_skills WHERE work_unit_id = work_units.id EXCEPT SELECT skill_id FROM workers_skills WHERE worker_id = 1 -- the worker id that made the request ) -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1 FOR UPDATE SKIP LOCKED;

Sin embargo, esta condición hace que la consulta sea de 8 a 10 veces más lenta.

¿Hay una mejor manera de expresar que las habilidades de work_units deben ser un subconjunto de las habilidades de los workers o algo para mejorar la consulta actual?

Un poco más de contexto:

  • La tabla de skills es bastante pequeña.
  • Tanto work_units como los workers tienden a tener muy pocas habilidades asociadas.
  • work_units_skills tiene índice en work_unit_id .
  • Intenté mover la consulta sobre workers_skills a un CTE. Esto dio una leve mejora (10-15%), pero aún es demasiado lento.
  • Cualquier unidad de trabajo sin habilidad puede ser recogida por cualquier usuario. Aka un conjunto vacío es un subconjunto de cada conjunto.

Con Postgres, la división relacional a menudo se puede expresar de manera más eficiente utilizando matrices.

En tu caso creo que lo siguiente hará lo que quieras:

select * from work_units where id in (select work_unit_id from work_units_skills group by work_unit_id having array_agg(skill_id) <@ array(select skill_id from workers_skills where worker_id = 6)) and ... other conditions here ... order by ...

array_agg(skill_id) recopila todos los skill_ids para cada work_unit y los compara con las habilidades de un trabajador específico utilizando el operador <@ ("está contenido por"). Esa condición devuelve todos los work_unit_ids donde la lista de skill_ids está contenida en las habilidades para un solo trabajador.

En mi experiencia, este enfoque suele ser más rápido que el equivalente o cruzar soluciones.

Ejemplo en línea: http://rextester.com/WUPA82849


Con la información actual solo puedo responder en una corazonada. Intente eliminar la instrucción EXCEPT y ver si se vuelve significativamente más rápido. Si lo hace, puede agregar esa parte de nuevo, pero usando las condiciones de DÓNDE. En mi experiencia, los operadores de conjuntos (MINUS / EXCEPT, UNION, INTERSECT) son los asesinos de rendimiento.


La subconsulta correlacionada lo está castigando, especialmente con el uso adicional de EXCEPTO.

Parafraseando su consulta, solo le interesa un work_unit_id cuando un trabajador específico tiene TODAS las habilidades de work_unit? (Si una work_unit tiene una habilidad asociada, pero el usuario especificado no tiene esa habilidad, ¿excluir esa work_unit?)

Esto se puede lograr con JOIN y GROUP BY, y no es necesaria la correlación.

SELECT work_units.* FROM work_units -- -- some joins -- INNER JOIN ( SELECT wus.work_unit_id FROM work_unit_skills wus LEFT JOIN workers_skills ws ON ws.skill_id = wus.skill_id AND ws.worker_id = 1 GROUP BY wus.work_unit_id HAVING COUNT(wus.skill_id) = COUNT(ws.skill_id) ) applicable_work_units ON applicable_work_units.work_unit_id = work_units.id -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1

La subconsulta compara el conjunto de habilidades de un trabajador con el conjunto de habilidades de cada unidad de trabajo. Si hay alguna habilidad que tiene la unidad de trabajo que el trabajador no tiene, entonces ws.skill_id será NULL para esa fila, y como NULL es ignorado por COUNT() esto significa que COUNT(ws.skill_id) será menor que COUNT(wus.skill_id) , y para que work_unit se excluya de los resultados de la work_unit .

Esto supone que la tabla workers_skills es única sobre (work_id, skill_id) y que la tabla work_unit_skills es única sobre (work_unit_id, skill_id) . Si ese no es el caso, es posible que desee jugar con la cláusula HAVING (como COUNT(DISTINT wus.skill_id) , etc.) .


EDITAR:

La consulta anterior asume que solo un número relativamente bajo de unidades de trabajo coincidiría con los criterios de coincidencia con un trabajador específico.

Si asume que una cantidad relativamente grande de unidades de trabajo coincidiría, la lógica opuesta sería más rápida.

(Esencialmente, intente hacer que el número de filas devueltas por la subconsulta sea lo más bajo posible).

SELECT work_units.* FROM work_units -- -- some joins -- LEFT JOIN ( SELECT wus.work_unit_id FROM work_unit_skills wus LEFT JOIN workers_skills ws ON ws.skill_id = wus.skill_id AND ws.worker_id = 1 WHERE ws.skill_id IS NULL GROUP BY wus.work_unit_id ) excluded_work_units ON excluded_work_units.work_unit_id = work_units.id WHERE excluded_work_units.work_unit_id IS NULL -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1

Este compara todas las habilidades de la unidad de trabajo con las del trabajador, y solo mantiene filas donde la unidad de trabajo tiene habilidades que el trabajador no tiene.

Luego, GROUP BY la unidad de trabajo para obtener una lista de las unidades de trabajo que deben ignorarse.

Si los une LEFT a los resultados existentes, puede estipular que solo desea incluir una unidad de trabajo si no aparece en la subconsulta especificando excluded_work_units.work_unit_id IS NULL .

Las guías en línea útiles se referirán a anti-join y anti-semi-join .


EDITAR:

En general, recomendaría contra el uso de una máscara de bits.

No porque sea lento, sino porque desafía la normalización. La existencia de un solo campo que representa múltiples elementos de datos es un anti-patrón-código-sql-olor-sql general, ya que los datos ya no son atómicos. (Esto conduce al dolor en el futuro, especialmente si llega a un mundo donde tiene tantas habilidades que ya no todas se ajustan al tipo de datos elegido para la máscara de bits, o cuando se trata de gestionar cambios frecuentes o complejos para los conjuntos de habilidades.)

Dicho esto, si el rendimiento sigue siendo un problema, la des-normalización suele ser una opción muy útil. Recomiendo mantener las máscaras de bits en tablas separadas para que quede claro que son resultados de cálculo de valores normalizados / en caché. En general, sin embargo, tales opciones deberían ser un último recurso en lugar de una primera reacción.


EDITAR: Ejemplo de revisiones para incluir siempre work_units que no tienen habilidades ...

SELECT work_units.* FROM work_units -- -- some joins -- INNER JOIN ( SELECT w.id AS work_unit_id FROM work_units w LEFT JOIN work_units_skills wus ON wus.work_unit_id = w.id LEFT JOIN workers_skills ws ON ws.skill_id = wus.skill_id AND ws.worker_id = 1 GROUP BY w.id HAVING COUNT(wus.skill_id) = COUNT(ws.skill_id) ) applicable_work_units ON applicable_work_units.work_unit_id = work_units.id

La versión excluded_work_units del código (la segunda consulta de ejemplo anterior) debería funcionar sin necesidad de modificación para este caso de esquina (y es la que probé inicialmente para las métricas de rendimiento en vivo) .


Puede obtener las unidades de trabajo cubiertas por las habilidades de un trabajador en una agregación, como ya se ha demostrado. Normalmente utilizarías IN en este conjunto de unidades de trabajo.

SELECT wu.* FROM work_units wu -- some joins WHERE wu.id IN ( SELECT wus.work_unit_id FROM work_units_skills wus LEFT JOIN workers_skills ws ON ws.skill_id = wus.skill_id AND ws.worker_id = 1 GROUP BY wus.work_unit_id HAVING COUNT(*) = COUNT(ws.skill_id) ) -- AND a bunch of other conditions -- ORDER BY something complex LIMIT 1 FOR UPDATE SKIP LOCKED;

Sin embargo, cuando se trata de acelerar las consultas, la parte principal es proporcionar los índices apropiados. (Con un optimizador perfecto, volver a escribir una consulta para obtener el mismo resultado no tendría ningún efecto, ya que el optimizador obtendría el mismo plan de ejecución).

Desea los siguientes índices (importa el orden de las columnas):

create index idx_ws on workers_skills (worker_id, skill_id); create index idx_wus on work_units_skills (skill_id, work_unit_id);

(Léalo así: Venimos con un worker_id , obtenemos los skill_ids para el trabajador, unimos las unidades de trabajo en estos skill_ids y obtenemos así el work_unit_ids ).


Puede que no se aplique a usted, pero tuve un problema similar que resolví simplemente fusionando main y sub en la misma columna usando números para main y letras para sub.

Por cierto, ¿están todas las columnas involucradas en las uniones indexadas? Mi servidor pasa de una consulta de 2 a 3 segundos en tablas de 500 k + para bloquearse en tablas de 10 k si olvido


Puede utilizar la siguiente consulta

SELECT wu.* FROM work_units wu LEFT JOIN work_units_skills wus ON wus.work_unit_id = wu.id and wus.skill_id IN ( SELECT id FROM skills EXCEPT SELECT skill_id FROM workers_skills WHERE worker_id = 1 -- the worker id that made the request ) WHERE wus.work_unit_id IS NULL;

demo (gracias, Steve Chambers por la mayoría de los datos)

Definitivamente debería tener un índice en work_units_skills(skill_id) , workers_skills(worker_id) y work_units(id) . Si desea acelerarlo, aún más, cree índices work_units_skills(skill_id, work_unit_id) y workers_skills(worker_id, skill_id) que evitan acceder a esas tablas.

La subconsulta es independiente y la unión externa debe ser relativamente rápida si el resultado no es grande.


Una simple aceleración sería usar EXCEPT ALL lugar de EXCEPT . Este último elimina duplicados, lo cual es innecesario aquí y puede ser lento.

Una alternativa que probablemente sería más rápida es usar un NOT EXISTS adicional en lugar del EXCEPT :

... WHERE NOT EXISTS ( SELECT skill_id FROM work_units_skills wus WHERE work_unit_id = work_units.id AND NOT EXISTS ( SELECT skill_id FROM workers_skills ws WHERE worker_id = 1 -- the worker id that made the request AND ws.skill_id = wus.skill_id ) )

Manifestación

http://rextester.com/AGEIS52439 - con el LIMIT eliminado para la prueba


Solución de máscara de bits
Sin ningún cambio en el diseño de su base de datos anterior, solo agregue 2 campos.
Primero: un largo o bigint (relacionado con su DBMS) en Trabajadores
Segundo: otro largo o bigint en Work_Units

Estos campos muestran habilidades de trabajo y unidades de trabajo. Por ejemplo, suponga que tiene 8 registros en la tabla de habilidades. (aviso que registra de habilidad en pequeño)
1- alguna habilidad 1
2- alguna habilidad 2
...
8- alguna habilidad 8

Entonces, si queremos establecer habilidades 1,3,6,7 en una work_unit, solo use este número 01100101.
(Ofrezco usar una versión invertida de la colocación binaria 0,1 para apoyar habilidades adicionales en el futuro).

En la práctica, puede usar 10 números base para agregar en la base de datos (101 en lugar de 01100101)

Número similar puede ser generado a los trabajadores. Cualquier trabajador elige algunas habilidades. Por lo tanto, podemos convertir los elementos seleccionados en un número y guardarlos en un campo adicional en la tabla Trabajador.

Finalmente , para encontrar el subconjunto de work_units apropiado para cualquier trabajador, SOLO seleccione de work_units y use bitwise Y como abajo.
R: new_field_of_specific_worker (muestra las habilidades de cada trabajador) que estamos buscando en las unidades de trabajo relacionadas con él en este momento.
B: new_field_of_work_units que muestra las habilidades de cada work_unit

select * from work_units where A & B = B

Darse cuenta:
1: absolutamente, esta es la manera más rápida pero tiene algunas dificultades.
2: tenemos algunas dificultades adicionales cuando se agrega una nueva habilidad o para eliminarla. Pero esto es una compensación. Agregar o eliminar nuevas habilidades ocurre menos.
3: deberíamos usar skills y work_unit_skills y workers_skills también. Pero en la búsqueda, solo usamos nuevos campos.


Además, este enfoque se puede utilizar para sistemas de gestión de TAG como los TAG de desbordamiento de pila.