2012-04-14 10 views
5

Ho questa query per raccogliere le informazioni su un singolo ordine e diventa piuttosto complessa.MySQL C'è un limite a InnerJoin?

Non ho alcun dato da testare, quindi sto chiedendo, se qualcuno ha esperienza con questo in piccoli e grandi set di dati, c'è un limite al numero di join che puoi o dovresti fare in una singola query ? Sarebbe consigliabile suddividere le query di grandi dimensioni in parti più piccole o questo non fa una differenza significativa?

Inoltre, è legale avere una clausola WHERE dopo ogni INNER JOIN?

Grazie per il vostro consiglio.

Ecco la domanda:

# 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; 
} 

Dal momento che qualcuno ha richiesto lo schema per questa query, qui è:

# 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

Spingere al massimo. Finché non si verificano errori di sintassi o qualcosa di simile a "troppi join", procedere. – hakre

+0

@hakre Grazie ... Lo prendo allora, la dimensione della query non ha importanza. – Ozzy

risposta

3

Non hai nessun limite di JOIN per MySQL. Il tuo numero di join non è male. Tuttavia, l'adesione a una tabella derivata (la subquery interna), come si sta facendo, può causare problemi di prestazioni, poiché le tabelle derivate non hanno indici. L'esecuzione di un join su una tabella derivata senza indici può essere lenta.

È consigliabile prendere in considerazione la creazione di una tabella temporanea reale con indici per l'unione, oppure cercare un modo per evitare la sottoquery.

Un JOIN in MySQL è fondamentalmente come fare una ricerca (ricerca) per ogni riga unita. Quindi, MySQL dovrà eseguire molte ricerche se si stanno unendo molti record. Non è tanto il numero di tabelle a cui ti unisci che il numero di righe a cui ti colleghi che può essere un problema.

In ogni caso, MySQL eseguirà solo così tanti tentativi prima che si arrenda e basta leggere l'intera tabella. Fa un buon lavoro nel decidere quale sarà meno costoso.

Forse la cosa migliore che puoi fare è aiutarlo a indovinare aggiornando le statistiche dell'indice con ANALYZE TABLE.

È possibile avere una clausola WHERE per SELECT. Quindi la sottoquery interna avrà una clausola WHERE e la tua query esterna avrà una clausola WHERE, che verrà applicata dopo il JOIN (almeno logicamente, sebbene MySQL generalmente li applicherà prima per le prestazioni).

Inoltre, tutto questo presuppone che tu sappia come utilizzare correttamente gli indici.

+0

Sono felice di accettare questa risposta. Potrei semplicemente creare un altro tavolo (come hai suggerito) se avessi problemi di prestazioni, ma lo lascerò come è per ora. Non ho ancora conoscenza dell'indicizzazione ... Dovrò leggere alcuni tutorial. Grazie @ marcus-adams – Ozzy

1

Non ho alcun consiglio generale, solo il mio expirience.

Avevo una volta un problema con prestazioni pessime in cui ho aggiunto tonnellate di tabelle utilizzando JOIN. Era circa il 20 JOIN che ho fatto. Quando ho rimosso alcuni JOIN in un test, la velocità è aumentata di nuovo. A causa del fatto che avevo solo bisogno di una singola informazione, ero in grado di sostituire la maggior parte dei JOIN con sotto-selezioni. Questo ha risolto il mio problema da 25 secondi per la mia query fino a meno di 1 secondo.

È probabilmente un dato di fatto della progettazione della tabella, la quantità di colonne delle tabelle unite e gli indici nelle clausole di join in join.

+0

Quindi stai dicendo che posso avere query di grandi dimensioni ma sub-SELECT è molto più veloce della sintassi JOIN? – Ozzy

+1

Penso che dipenda dalla query stessa. Nel mio caso, sub seleziona dove andare più veloce con l'impostazione giusta dell'indice. – YvesR

+0

Penso che dovresti UNIRE tutto ciò di cui hai bisogno e quindi iniziare a semplificarlo (sottoselezioni solo per i dati della colonna 1 e fusioni degli stessi dati) – YvesR

2

Beh una volta ho provato anch'io a vedere il limite per il numero di join e ho testato un join con 100 tabelle su mysql 5.0.4 se lo ricordo correttamente.

mi è stato dato il seguente errore:

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

penso che il limite per MySql 4 era di 31 tavoli.

Un consiglio se si prevede di utilizzare molte tabelle in una query, quindi è il momento di iniziare a pensare a semplificare la progettazione della query.

+0

+1 per le informazioni su un limite (grazie). Penso che le informazioni mi mettano in prospettiva per me, i miei 8 contatti da un possibile 61 non possono essere affatto male. – Ozzy

2

Stavo cercando anche questo, e poi ho trovato questa altra soluzione, aggiungendo "AND" come parte di un JOIN.

INNER JOIN kopplaMedlemIntresse 
ON intressen.id = kopplaMedlemIntresse.intresse_id AND kopplaMedlemIntresse.medlem_id = 3 
Problemi correlati