2011-07-19 9 views
8

Tengo una base de datos MySQL en la que la tabla A tiene una relación uno a muchos con la tabla B, y me gustaría seleccionar todas las filas en la tabla B que no tienen hijos en la tabla A. he intentado usarSeleccionar la fila principal solo si no tiene hijos

SELECT id FROM A WHERE NOT EXISTS (SELECT * FROM B WHERE B.id=A.id) 

y

SELECT id FROM A LEFT JOIN B ON A.id=B.id WHERE B.id IS NULL 

Ambos parecen lento. ¿Hay una consulta más rápida para lograr lo mismo?

En caso de que esto sea relevante, en mi base de datos la tabla A tiene alrededor de 500,000 filas y la tabla B tiene alrededor de 3 a 4 millones de filas.

Editar: Para las tablas reales en mi base de datos, explicar me da:

+----+--------------------+------------------+-------+---------------+---------------------------+---------+------+---------+--------------------------+ 
| id | select_type  | table   | type | possible_keys | key      | key_len | ref | rows | Extra     | 
+----+--------------------+------------------+-------+---------------+---------------------------+---------+------+---------+--------------------------+ 
| 1 | PRIMARY   | frontend_form471 | index | NULL   | frontend_form471_61a633e8 | 32  | NULL | 671927 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | SchoolData  | index | PRIMARY  | PRIMARY     | 49  | NULL | 3121110 | Using where; Using index | 
+----+--------------------+------------------+-------+---------------+---------------------------+---------+------+---------+--------------------------+ 

para

select number from frontend_form471 where not exists (select * from SchoolData where SchoolData.`f471 Application Number`=frontend_form471.number) 

y

+----+-------------+------------------+-------+---------------+---------------------------+---------+------+---------+------------------------------------------------+ 
| id | select_type | table   | type | possible_keys | key      | key_len | ref | rows | Extra           | 
+----+-------------+------------------+-------+---------------+---------------------------+---------+------+---------+------------------------------------------------+ 
| 1 | SIMPLE  | frontend_form471 | index | NULL   | frontend_form471_61a633e8 | 32  | NULL | 671927 | Using index; Using temporary     | 
| 1 | SIMPLE  | SchoolData  | index | PRIMARY  | PRIMARY     | 49  | NULL | 3121110 | Using where; Using index; Not exists; Distinct | 
+----+-------------+------------------+-------+---------------+---------------------------+---------+------+---------+------------------------------------------------+ 

para

select distinct number from frontend_form471 left join SchoolData on frontend_form471.number=SchoolData.`f471 Application Number` where SchoolData.`f471 Application Number` is NULL 

donde en mi caso frontend_form471 es el cuadro A y SchoolData es la tabla B

Edit2: En la tabla B (SchoolData) en mi base de datos, la identificación es la primera parte de una clave principal en dos partes, por lo que es indexado y todavía hay entradas múltiples en B con la misma identificación.

+0

'EXPLAIN SELECT id DE UNA UNIÓN IZQUIERDA B ON A.id = B.id WHERE B.id IS NULL' ¿podría publicar el resultado de EXPLAIN para ambas consultas? – Igor

+0

¿Los índices no ayudan? – Londeren

+0

¿Está seleccionando si 'COUNT (*) = 0' es más rápido? –

Respuesta

8
SELECT id FROM A LEFT OUTER JOIN B ON A.id=B.id WHERE B.id IS NULL 

puedes hacer esto. la unión externa debería traer un poco de rendimiento, pero no mucho.

nuevos sistemas de bases de datos probablemente optimizarán su consulta de todos modos para que no haya ninguna diferencia.

¡la manera correcta aquí es el almacenamiento en caché! pruebe el caché de consultas y el almacenamiento en caché a nivel de aplicación si es posible.

por supuesto que necesita los índices adecuados.

y adecuada que quiero decir en ambas mesas y preferiblemente un índice hash, ya que tendrá tiempo de búsqueda estática en comparación a cualquier árbol que tiene logarítmica

trate de poner una explicación antes de la consulta para ver lo que realmente ralentiza esto abajo .

si realmente necesita que esto sea rápido, puede volver a diseñar su estructura de datos.

puede crear un disparador para marcar un indicador en la tabla A si hay una entrada correspondiente en la tabla. por supuesto, esta redundancia de datos de identificación, pero a veces vale la pena. solo piense en ello como almacenamiento en caché.

una última idea: puede intentar SELECT id FROM A WHERE id NOT IN (SELECT id FROM B) puede ser un poco más rápido porque no es necesaria una unión real, sin embargo, también puede ser más lento porque la búsqueda en el conjunto de ser será un análisis completo. No estoy muy seguro de cómo se procesará, pero puede valer la pena intentarlo.

+0

Esta es la mejor solución ... o coincide o no, pero solo devuelve el registro cuando NO existe ... Un ciclo único a través de la tabla principal ... Similar al enfoque que también he ofrecido en el pasado. – DRapp

+2

Solo MySQL tiene esto: otros motores son mejores con NOT EXISTS http://explainextended.com/2009/09/18/not-in-vs-not-exists-vs-left-join-is-null-mysql/ – gbn

+0

Creo que el punto más importante que hizo es sobre los índices de hash. Los usaría si pudiera, pero InnoDB no los admite, y no estoy preparado para cambiar de motor solo para que esta consulta funcione. – murgatroid99

1

Va a ser lento sin importar cómo lo mires. El peor de los casos será un cruce completo creando 2 billones de posibles coincidencias (4 mill * 500k).

Es probable que el segundo funcione más rápido, ya que es una consulta única.

1

Usted podría intentar

SELECT id FROM A WHERE A.id NOT IN (SELECT id FROM B) 

pero que no sé si esto será más rápido. Hubiera intentado la izquierda unir primero. Creo que tu problema tiene más que ver con los índices. ¿Tiene índices en ambos campos de identificación?

0

Asegúrese de tener un índice en A.id y otro en B.id.

Lo que parece un poco extraño es que te unas a A.id con B.id. ¿B.id es la clave foránea de A o es la clave principal de B?

+0

B.id es la clave externa de A y la mitad de una clave principal de dos columnas. – murgatroid99

+0

¿Importa? por supuesto, maby la estructura de datos se podría rafacturer .. –

+0

Solo queríamos asegurarnos de que la unión estuviera bien. – phlogratos

1

Su indexación es pobre.

Para todas las formas (existe, EN, LEFT JOIN) debería tener índices de id en ambos tablas

+0

id-s parece PK, por lo que la consulta debe ser rápida. – Igor

+0

@Igor: O bien la tabla secundaria tiene su propio sustituto (no se usa aquí, la identificación es una columna FK) o la identificación es parte de una clave compuesta. A menos que sea una relación 1: 1 ... Por lo tanto, no puede suponer índices correctos en ambos lados – gbn

+0

B.id definitivamente no es PK porque hay muchas filas con el mismo ID en B. – phlogratos

0

Si el esquema es algo como esto:

CREATE TABLE b(
    id int, 
    value varchar(255) 
) 

CREATE TABLE a(
    id int, 
    father_id int, 
    value varchar(255) 
) 

Si desea que todas las filas de la tabla a que no tienen hijos en el cuadro a por qué no intentar algo como:

SELECT * FROM B WHERE id NOT IN (SELECT father_id FROM A GROUP BY father_id) 

no he probado, pero creo que es es fester Recuerde poner un índice sobre Identificación

Esperanza esto ayuda

0

Por qué no pruebas valor vacío en lugar de NULL. En SQL, el valor NULL nunca es verdadero en comparación con cualquier otro valor, incluso NULL. Una expresión que contiene NULL siempre produce un valor NULL a menos que se indique lo contrario en la documentación para los operadores y las funciones involucradas en la expresión.

Cuestiones relacionadas