2011-07-19 14 views
8

Mam bazę danych MySQL, w którym tabela A ma stosunek jeden do wielu dla tabeli B, i chciałbym zaznaczyć wszystkie wiersze w tabeli B, które nie mają dzieci w tabeli A. próbowałem, używającWybierz wiersz nadrzędny tylko wtedy, gdy nie ma dzieci

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

i

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

Oba te wydają się powoli. Czy istnieje szybsze zapytanie, aby osiągnąć to samo?

Jeśli jest to istotne, w mojej tabeli bazy danych A zawiera około 500 000 wierszy, a w tabeli B - od 3 do 4 milionów wierszy.

Edit: Dla rzeczywistych tabel w mojej bazy danych, daje mi wyjaśnić:

+----+--------------------+------------------+-------+---------------+---------------------------+---------+------+---------+--------------------------+ 
| 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 | 
+----+--------------------+------------------+-------+---------------+---------------------------+---------+------+---------+--------------------------+ 

dla

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

i

+----+-------------+------------------+-------+---------------+---------------------------+---------+------+---------+------------------------------------------------+ 
| 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 | 
+----+-------------+------------------+-------+---------------+---------------------------+---------+------+---------+------------------------------------------------+ 

dla

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

gdzie w moim przypadku frontend_form471 jest tabela A i SchoolData jest tabela B

Edit2: W tabeli B (SchoolData) w mojej bazy danych, identyfikator jest pierwszą częścią drugiej części klucza podstawowego, a więc jest zindeksowane i wciąż jest wiele wpisów w B o tym samym identyfikatorze.

+0

'EXPLAIN SELECT id FROM A LEFT JOIN B na A.id = B.id GDZIE JEST NULL' B.id można zakładać wynik wyjaśniania zarówno dla zapytań? – Igor

+0

Czy indeksy nie pomagają? – Londeren

+0

Czy zaznacza się, jeśli 'COUNT (*) = 0' jest szybszy? –

Odpowiedz

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

możesz to zrobić. połączenie zewnętrzne powinno przynieść niewielką wydajność, ale niewiele.

Nowe systemy baz danych prawdopodobnie zoptymalizują zapytanie tak, aby nie było żadnej różnicy.

Poprawnym sposobem tutaj jest buforowanie! jeśli to możliwe, spróbuj cachowania zapytań i buforowania na poziomie aplikacji.

Oczywiście potrzebujesz odpowiednich indeksów.

i przez właściwy to znaczy na obu tablicach, a korzystnie indeks hash jak będzie miał statyczny czas odnośnika w porównania do każdego drzewa, które ma logarytmiczną

Spróbuj położyć wytłumaczyć przed zapytania, aby zobaczyć, co naprawdę spowalnia to w dół .

jeśli naprawdę potrzebujesz tego, aby być szybkim, możesz poprawić strukturę danych.

można ewentualnie utworzyć wyzwalacz, aby oznaczyć flagę w tabeli A, czy istnieje odpowiedni wpis w tabeli. oczywiście to nadmiarowość danych id, ale czasami jest tego warte. po prostu pomyśl o tym jak o buforowaniu.

ostatnia myśl: możesz spróbować SELECT id FROM A WHERE id NOT IN (SELECT id FROM B) może to być trochę szybciej, ponieważ nie jest konieczne rzeczywiste dołączanie, jednak może być wolniejsze, ponieważ wyszukiwanie w zestawie będzie pełnym skanem. Nie jestem do końca pewien, jak to będzie przetworzone, ale warto spróbować.

+0

To jest najlepsze rozwiązanie ... Pasuje albo nie, ale zwraca tylko rekord, kiedy go NIE istnieje ... Pojedynczy cykl przez tabelę nadrzędną ... Podobnie jak podejście, które również zaoferowałem w przeszłości. – DRapp

+2

Tylko MySQL ma to: inne silniki są lepsze z NOT EXISTS http://explainextended.com/2009/09/18/not-in-vs-not-exists-vs-left-join-is-null-mysql/ – gbn

+0

Myślę, że najważniejszym punktem, jaki podjąłeś, jest indeksowanie hash. Używałbym ich, gdybym mógł, ale InnoDB ich nie wspiera i nie jestem przygotowany na zmianę silników tylko po to, aby ta kwerenda działała. – murgatroid99

1

To będzie powolny nieważne jak na to spojrzeć. Najgorszym rozwiązaniem będzie połączenie z pełną siatką, co daje 2 tryliony potencjalnych dopasowań (4 miliony * 500 000).

Drugi najprawdopodobniej wykonywać szybciej, ponieważ jest to jedno zapytanie.

1

można spróbować

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

ale nie wiem, czy to będzie szybciej. Najpierw spróbowałbym lewego połączenia. Myślę, że twój problem jest bardziej związany z indeksami. Czy masz indeksy na obu polach identyfikatora?

0

Pamiętaj, aby mieć indeks na A.id i inny na B.id.

Co wydaje się podobny trochę dziwne jest to, że dołączysz A.id z B.id. Czy B.id jest kluczem obcym do A lub czy jest to klucz podstawowy B?

+0

B.id to klucz obcy dla A i połowa klucza podstawowego z dwiema kolumnami. – murgatroid99

+0

ma to znaczenie? oczywiście maby struktura danych może być rafactured .. –

+0

Chciałem tylko upewnić się, że join jest w porządku. – phlogratos

1

Twój indeksowanie jest słaba.

Dla wszystkich formach (istnieje, IN, LEFT JOIN) należy mieć indeksy na identyfikatorze w oba stoły

+0

id-s wygląda jak PK, więc zapytanie powinno być szybkie. – Igor

+0

@Igor: Tablica podrzędna ma własny surogat (nie jest tutaj używany, id to kolumna FK) lub id jest częścią klucza złożonego. O ile nie jest to relacja 1: 1 ... Nie można założyć prawidłowych indeksów po obu stronach. – gbn

+0

B.id zdecydowanie nie ma PK, ponieważ istnieje wiele wierszy z tym samym identyfikatorem w B. – phlogratos

0

Jeśli schemat jest mniej więcej tak:

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

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

Jeśli chcesz, aby wszystkie wiersze z tabeli A, które nie mają dzieci w tabeli A dlaczego nie spróbować czegoś takiego:

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

nie testowałem, ale myślę, że gniją. Pamiętaj, aby umieścić wskaźnik nad id

nadzieję, że to pomaga

0

Dlaczego nie spróbować zamiast pustą wartość NULL. W SQL wartość NULL nigdy nie jest prawdziwa w porównaniu z żadną inną wartością, nawet NULL. Wyrażenie zawierające wartość NULL zawsze daje wartość NULL, chyba że w dokumentacji dla operatorów i funkcji związanych z wyrażeniem podano inaczej.

Powiązane problemy