2012-04-14 20 views
5

Tengo esta consulta para recopilar la información sobre un solo pedido y se vuelve bastante compleja.MySQL ¿Existe un límite para InnerJoin?

No tengo ningún dato para probar, así que estoy preguntando, si alguien tiene experiencia con esto en conjuntos de datos pequeños y grandes, hay un límite en la cantidad de combinaciones que puede o debe hacer en una sola consulta ? ¿Sería aconsejable dividir las consultas grandes en partes más pequeñas o esto no hace una diferencia significativa?

Además, ¿es legal tener una cláusula WHERE después de cada INNER JOIN?

Gracias por su consejo.

Ésta es la consulta:

# Order: Get Order 

function getOrder($order_id) { 
    $sql = "SELECT (order.id, order.created, o_status.status, 
        /* payment info */ 
        order.total, p_status.status, 
        /* ordered by */ 
        cust_title.title, cust.forename, cust.surname, 
        customer.phone, customer.email, 
        cust.door_name, cust.street1, 
        cust.street2, cust.town, 
        cust.city, cust.postcode, 
        /* deliver to */ 
        recip_title.title, recipient.forename, recipient.surname, 
        recipient.door_name, recipient.street1, 
        recipient.street2, recipient.town, 
        recipient.city, recipient.postcode, 
        /* deliver info */ 
         shipping.name, order.memo, 
        /* meta data */ 
        order.last_update) 
       FROM tbl_order AS order 

     INNER JOIN tbl_order_st AS o_status 
       ON order.order_status_id = o_status.id 

     INNER JOIN tbl_payment_st AS p_status 
       ON order.payment_status_id = p_status.id 

     INNER JOIN (SELECT (cust_title.title, cust.forename, cust.surname, 
          customer.phone, customer.email, 
     /* ordered by */ cust.door_name, cust.street1, 
          cust.street2, cust.town, 
          cust.city, cust.postcode) 
         FROM tbl_customer AS customer 
       INNER JOIN tbl_contact AS cust 
          ON customer.contact_id = cust.id 
       INNER JOIN tbl_contact_title AS cust_title 
         ON cust.contact_title_id = cust_title.id 
        WHERE order.customer_id = customer.id) 
       ON order.customer_id = customer.id 

     INNER JOIN (SELECT (recip_title.title, recipient.forename, recipient.surname, 
     /* deliver to */ recipient.door_name, recipient.street1, 
          recipient.street2, recipient.town, 
          recipient.city, recipient.postcode) 
         FROM tbl_contact AS recipient 
       INNER JOIN tbl_contact_title AS recip_title 
         ON recipient.contact_title_id = recip_title.id 
        WHERE order.contact_id = recipient.id) 
       ON order.contact_id = recipient.id 

     INNER JOIN tbl_shipping_opt AS shipping 
       ON order.shipping_option_id = shipping.id 

      WHERE order.id = '?';"; 
    dbQuery($sql, array((int)$order_id)); 
    $rows = dbRowsAffected(); 
    if ($rows == 1) 
     return dbFetchAll(); 
    else 
     return null; 
} 

Desde que alguien solicita el esquema para esta consulta, aquí está:

# TBL_CONTACT_TITLE 

DROP TABLE IF EXISTS tbl_contact_title; 
CREATE TABLE tbl_contact_title(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    title CHAR(3) 
) ENGINE = InnoDB; 
INSERT INTO tbl_contact_title 
    (title) 
VALUES ('MR'), 
    ('MRS'), 
    ('MS'); 


# TBL_CONTACT 

DROP TABLE IF EXISTS tbl_contact; 
CREATE TABLE tbl_contact(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    contact_title_id INT, 
    FOREIGN KEY(contact_title_id) REFERENCES tbl_contact_title(id) ON DELETE SET NULL, 
    forename VARCHAR(50), 
    surname VARCHAR(50), 
    door_name VARCHAR(25), 
    street1 VARCHAR(40), 
    street2 VARCHAR(40), 
    town VARCHAR(40), 
    city VARCHAR(40), 
    postcode VARCHAR(10), 
    currency_id INT, 
    FOREIGN KEY(currency_id) REFERENCES tbl_currency(id) ON DELETE SET NULL 
) ENGINE = InnoDB; 

# TBL_CUSTOMER 

DROP TABLE IF EXISTS tbl_customer; 
CREATE TABLE tbl_customer(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    contact_id INT, 
    FOREIGN KEY(contact_id) REFERENCES tbl_contact(id) ON DELETE SET NULL, 
    birthday DATE, 
    is_male TINYINT, 
    phone VARCHAR(20), 
    email VARCHAR(50) NOT NULL 
) ENGINE = InnoDB, AUTO_INCREMENT = 1000; 

# TBL_ORDER_ST 

DROP TABLE IF EXISTS tbl_order_st; 
CREATE TABLE tbl_order_st(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    status VARCHAR(25) 
) ENGINE = InnoDB; 
INSERT INTO tbl_order_st 
    (status) 
VALUES 
    ('NEW'), 
    ('PROCESSING'), 
    ('SHIPPED'), 
    ('COMPLETED'), 
    ('CANCELLED'); 


# TBL_SHIPPING_OPT 

DROP TABLE IF EXISTS tbl_shipping_opt; 
CREATE TABLE tbl_shipping_opt(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    name VARCHAR(50), 
    description VARCHAR(255), 
    cost DECIMAL(6,3) 
) ENGINE = InnoDB; 
INSERT INTO tbl_shipping_opt 
    (name, description, cost) 
VALUES 
    ('UK Premier', 'U.K. Mainland upto 30KG, Next Working Day', 8.00), 
    ('Europe Standard', 'Most European Destinations* upto 30KG, 2 to 5 Working Days *please check before purchase', 15.00); 


# TBL_PAYMENT_ST 

DROP TABLE IF EXISTS tbl_payment_st; 
CREATE TABLE tbl_payment_st(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    status VARCHAR(25) 
) ENGINE = InnoDB; 
INSERT INTO tbl_payment_st 
    (status) 
VALUES 
    ('UNPAID'), 
    ('PAID'); 


# TBL_ORDER 

DROP TABLE IF EXISTS tbl_order; 
CREATE TABLE tbl_order(
    id INT NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY(id), 
    customer_id INT, 
     FOREIGN KEY(customer_id) REFERENCES tbl_customer(id) ON DELETE SET NULL, 
    contact_id INT, 
    FOREIGN KEY(contact_id) REFERENCES tbl_contact(id) ON DELETE SET NULL, 
    created DATETIME, 
    last_update TIMESTAMP, 
    memo VARCHAR(255), 
    order_status_id INT, 
    FOREIGN KEY(order_status_id) REFERENCES tbl_order_st(id), 
    shipping_option_id INT, 
    FOREIGN KEY(shipping_option_id) REFERENCES tbl_shipping_opt(id), 
    coupon_id INT, 
    FOREIGN KEY(coupon_id) REFERENCES tbl_coupon(id) ON DELETE SET NULL, 
    total DECIMAL(9,3), 
    payment_status_id INT, 
    FOREIGN KEY(payment_status_id) REFERENCES tbl_payment_st(id) 
) ENGINE = InnoDB, AUTO_INCREMENT = 1000; 
+2

Presiónelo al máximo. Siempre que no obtenga ningún error de sintaxis o algo así como "demasiadas uniones", continúe. – hakre

+0

@hakre Gracias ... Lo tomo, el tamaño de la consulta no importa. – Ozzy

Respuesta

3

No tiene ni cerca del límite de JOINs para MySQL. Su número de uniones no es malo. Sin embargo, unirse a una tabla derivada (su subconsulta interna) como lo hace puede causar problemas de rendimiento, ya que las tablas derivadas no tienen índices. Realizar una combinación en una tabla derivada sin índices puede ser lento.

Debería considerar hacer una tabla real temporal con índices para unir, o encontrar una forma de evitar la subconsulta.

Un JOIN en MySQL es básicamente como hacer una búsqueda (buscar) para cada fila unida. Por lo tanto, MySQL tendrá que realizar muchas búsquedas si se une a muchos registros. No se trata tanto de cuántas tablas unir como de la cantidad de filas a las que se une, lo que puede ser un problema.

De todos modos, MySQL solo realizará tantas búsquedas antes de que se rinda y simplemente lea toda la tabla. Hace un buen trabajo al decidir cuál será menos costoso.

Quizás lo mejor que puede hacer es ayudarlo a adivinar mediante la actualización de las estadísticas del índice con ANALYZE TABLE.

Puede tener una cláusula WHERE por SELECCIONAR. Entonces, su subconsulta interna tendrá una cláusula WHERE y su consulta externa tendrá una cláusula WHERE, y estas se aplicarán después de JOIN (al menos lógicamente, aunque MySQL generalmente las aplicará primero para el rendimiento).

Además, todo esto es asumiendo que sabe cómo usar correctamente los índices.

+0

Estoy feliz de aceptar esta respuesta. Podría crear otra tabla (como sugirió) si tengo problemas de rendimiento, pero lo dejaré como está por ahora. Aún no sé cómo indexar ... Tendré que leer algunos tuts. Gracias @ marcus-adams – Ozzy

1

que no tienen ningún consejo general, sólo mi propia Expirience.

Tuve una vez un problema con muy mal rendimiento donde agregué tonz de tablas usando JOIN. Fue alrededor de 20 UNIRSE a lo que hice. Cuando eliminé algunas UNIONES en una prueba, la velocidad volvió a aumentar. Debido al hecho de que solo necesitaba información única, pude reemplazar la mayoría de las INSCRIPCIONES con sub selecciones. Esto resolvió mi problema desde 25 segundos para mi consulta hasta menos de 1 segundo.

Probablemente sea un hecho del diseño de su tabla, su cantidad de columnas de las tablas combinadas y sus índices en las cláusulas where fusionadas.

+0

Entonces, ¿está diciendo que puedo tener grandes consultas, pero sub-SELECT es mucho más rápido que la sintaxis JOIN? – Ozzy

+1

Creo que depende de la consulta en sí. En mi caso, selecciona de forma más rápida con la configuración de índice correcta. – YvesR

+0

Creo que debería UNIRSE a todo lo que necesita y luego comenzar a simplificarlo (selecciona solo los datos de la columna 1 y fusiona los mismos datos) – YvesR

2

Bueno, una vez lo intenté yo mismo para ver el límite para el número de combinaciones y probé una combinación con 100 tablas en mysql 5.0.4 si la recuerdo correctamente.

se me dio el siguiente error:

Too many tables. MySQL can only use 61 tables in a join.

creo que el límite para MySql 4 fue de 31 mesas.

Un consejo si planea usar tantas tablas en una consulta, entonces es hora de empezar a pensar en simplificar el diseño de su consulta.

+0

+1 para la información sobre un límite (gracias). Creo que la información pone esto en perspectiva para mí, mi 8 se une a un posible 61 no puede ser malo en absoluto. – Ozzy

2

Estaba buscando esto también, y luego encontré esta otra solución, agregando "Y" como parte de un JOIN.

INNER JOIN kopplaMedlemIntresse 
ON intressen.id = kopplaMedlemIntresse.intresse_id AND kopplaMedlemIntresse.medlem_id = 3 
Cuestiones relacionadas