Zapytanie SQL zwraca dane z wielu tabel


434

Chciałbym wiedzieć, co następuje:

  • jak uzyskać dane z wielu tabel w mojej bazie danych?
  • jakie są metody, aby to zrobić?
  • czym są połączenia i związki i czym się różnią?
  • Kiedy powinienem używać każdego z nich w porównaniu do innych?

Planuję użyć tego w mojej aplikacji (na przykład - PHP), ale nie chcę uruchamiać wielu zapytań do bazy danych, jakie opcje muszę uzyskać z wielu tabel w jednym zapytaniu?

Uwaga: Piszę to, ponieważ chciałbym móc połączyć się z dobrze napisanym przewodnikiem na temat wielu pytań, które ciągle napotykam w kolejce PHP, więc mogę połączyć się z tym, aby uzyskać więcej szczegółów, kiedy zamieszczam odpowiedź.

Odpowiedzi obejmują następujące kwestie:

  1. Część 1 - Połączenia i związki
  2. Część 2 - podzapytania
  3. Część 3 - Sztuczki i skuteczny kod
  4. Część 4 - podzapytania w klauzuli From
  5. Część 5 - Mieszany worek sztuczek Johna

Odpowiedzi:


469

Część 1 - Połączenia i związki

Ta odpowiedź obejmuje:

  1. Część 1
    • Łączenie dwóch lub więcej tabel za pomocą łączenia wewnętrznego ( dodatkowe informacje można znaleźć we wpisie na Wikipedii )
    • Jak korzystać z kwerendy związkowej
    • Lewe i prawe połączenia zewnętrzne (ta odpowiedź StackOverflow doskonale nadaje się do opisywania rodzajów połączeń)
    • Przecinaj zapytania (i jak je reprodukować, jeśli twoja baza danych ich nie obsługuje) - jest to funkcja SQL-Server ( patrz informacje ) i po części dlatego napisałem to wszystko .
  2. Część 2
    • Podkwerendy - czym są, gdzie można ich używać i na co uważać
    • Kartezjan dołącza do AKA - Och, nędza!

Istnieje wiele sposobów wyszukiwania danych z wielu tabel w bazie danych. W tej odpowiedzi użyję składni złączenia ANSI-92. Może się to różnić od wielu innych samouczków, które używają starszej składni ANSI-89 (a jeśli jesteś przyzwyczajony do 89, może wydawać się znacznie mniej intuicyjny - ale wszystko, co mogę powiedzieć, to wypróbować), ponieważ jest to o wiele łatwiejsze aby zrozumieć, kiedy zapytania stają się bardziej złożone. Po co z tego korzystać? Czy jest wzrost wydajności? Krótka odpowiedź nie jest, ale jest łatwiejsze do odczytania po przyzwyczaić się do niego. Łatwiej jest czytać zapytania napisane przez innych ludzi za pomocą tej składni.

Zamierzam również wykorzystać koncepcję małego caryarda, który ma bazę danych do śledzenia dostępnych samochodów. Właściciel wynajął cię jako swojego informatyka i oczekuje, że będziesz w stanie upuścić mu dane, o które prosi po kropli kapelusza.

Zrobiłem wiele tabel odnośników, które będą używane przy stole finałowym. To da nam rozsądny model do pracy. Na początek będę uruchamiać moje zapytania na przykładowej bazie danych o następującej strukturze. Spróbuję wymyślić typowe błędy popełniane na początku i wyjaśnić, co się z nimi dzieje - a także oczywiście pokażę, jak je naprawić.

Pierwszy stół to po prostu lista kolorów, dzięki czemu wiemy, jakie kolory mamy na podwórzu samochodowym.

mysql> create table colors(id int(3) not null auto_increment primary key, 
    -> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql> insert into colors (color, paint) values ('Red', 'Metallic'), 
    -> ('Green', 'Gloss'), ('Blue', 'Metallic'), 
    -> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from colors;
+----+-------+----------+
| id | color | paint    |
+----+-------+----------+
|  1 | Red   | Metallic |
|  2 | Green | Gloss    |
|  3 | Blue  | Metallic |
|  4 | White | Gloss    |
|  5 | Black | Gloss    |
+----+-------+----------+
5 rows in set (0.00 sec)

Tabela marek identyfikuje różne marki samochodów, które caryard mógłby sprzedać.

mysql> create table brands (id int(3) not null auto_increment primary key, 
    -> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| brand | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> insert into brands (brand) values ('Ford'), ('Toyota'), 
    -> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from brands;
+----+--------+
| id | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  3 | Nissan |
|  4 | Smart  |
|  5 | BMW    |
+----+--------+
5 rows in set (0.00 sec)

Tabela modeli obejmie różne typy samochodów, łatwiej będzie korzystać z różnych typów samochodów niż z rzeczywistych modeli samochodów.

mysql> create table models (id int(3) not null auto_increment primary key, 
    -> model varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| model | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from models;
+----+--------+
| id | model  |
+----+--------+
|  1 | Sports |
|  2 | Sedan  |
|  3 | 4WD    |
|  4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)

I wreszcie, aby związać wszystkie te inne stoły, stół, który łączy wszystko razem. Pole ID jest w rzeczywistości unikalnym numerem partii używanym do identyfikacji samochodów.

mysql> create table cars (id int(3) not null auto_increment primary key, 
    -> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type   | Null | Key | Default | Extra          |
+-------+--------+------+-----+---------+----------------+
| id    | int(3) | NO   | PRI | NULL    | auto_increment |
| color | int(3) | YES  |     | NULL    |                |
| brand | int(3) | YES  |     | NULL    |                |
| model | int(3) | YES  |     | NULL    |                |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), 
    -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
|  1 |     1 |     2 |     1 |
|  2 |     3 |     1 |     2 |
|  3 |     5 |     3 |     1 |
|  4 |     4 |     4 |     2 |
|  5 |     2 |     2 |     3 |
|  6 |     3 |     5 |     4 |
|  7 |     4 |     1 |     3 |
|  8 |     2 |     2 |     1 |
|  9 |     5 |     2 |     3 |
| 10 |     4 |     5 |     1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)

To da nam wystarczającą ilość danych (mam nadzieję), aby zilustrować poniższe przykłady różnych rodzajów złączeń, a także dostarczy wystarczającą ilość danych, aby były warte zachodu.

Wchodząc w sedno, szef chce poznać identyfikatory wszystkich samochodów sportowych, które ma .

To proste połączenie dwóch tabel. Mamy tabelę, która identyfikuje model i tabelę z dostępnymi zapasami. Jak widać, dane w modelkolumnie carstabeli odnoszą się do modelskolumny carstabeli, którą mamy. Teraz wiemy, że tabela modeli ma identyfikator 1dla, Sportswięc napiszmy złączenie.

select
    ID,
    model
from
    cars
        join models
            on model=ID

Więc to zapytanie wygląda dobrze, prawda? Zidentyfikowaliśmy dwie tabele i zawierają potrzebne informacje, a następnie używamy złączenia, które poprawnie identyfikuje kolumny, które należy dołączyć.

ERROR 1052 (23000): Column 'ID' in field list is ambiguous

Och nie! Błąd w naszym pierwszym zapytaniu! Tak, i to jest śliwka. Widzisz, zapytanie rzeczywiście ma odpowiednie kolumny, ale niektóre z nich istnieją w obu tabelach, więc baza danych jest zdezorientowana co do tego, którą kolumnę mamy na myśli i gdzie. Istnieją dwa rozwiązania tego problemu. Pierwszy jest ładny i prosty, możemy użyć tableName.columnNamebazy danych, aby dokładnie powiedzieć, co mamy na myśli, w następujący sposób:

select
    cars.ID,
    models.model
from
    cars
        join models
            on cars.model=models.ID

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
|  2 | Sedan  |
|  4 | Sedan  |
|  5 | 4WD    |
|  7 | 4WD    |
|  9 | 4WD    |
|  6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)

Drugi jest prawdopodobnie częściej używany i nazywany jest aliasingiem tabel. Tabele w tym przykładzie mają ładne i krótkie proste nazwy, ale wpisanie czegoś takiego KPI_DAILY_SALES_BY_DEPARTMENTprawdopodobnie szybko się zestarzeje, więc prostym sposobem jest pseudonimowanie tabeli w ten sposób:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID

Wróćmy do żądania. Jak widać, mamy informacje, których potrzebujemy, ale mamy również informacje, o które nie poprosiliśmy, dlatego w oświadczeniu musimy zawrzeć klauzulę „where”, aby uzyskać tylko samochody sportowe, o które poproszono. Ponieważ wolę metodę aliasu tabeli niż używanie nazw tabel w kółko, odtąd będę się jej trzymał.

Oczywiście musimy dodać klauzulę where do naszego zapytania. Możemy zidentyfikować samochody sportowe za pomocą ID=1lub model='Sports'. Ponieważ identyfikator jest indeksowany, a klucz podstawowy (i zdarza się, że mniej się pisze), użyjmy go w naszym zapytaniu.

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Bingo! Szef jest szczęśliwy. Oczywiście, będąc szefem i nigdy nie będąc zadowolonym z tego, o co prosił, patrzy na informacje, a następnie mówi, że chcę też kolorów .

Okej, więc duża część naszego zapytania jest już napisana, ale musimy użyć trzeciej tabeli, która jest kolorami. Teraz nasza główna tabela informacyjna carsprzechowuje identyfikator koloru samochodu, który prowadzi do kolumny identyfikatora kolorów. Tak więc, podobnie jak oryginał, możemy dołączyć do trzeciego stołu:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Cholera, chociaż tabela została poprawnie połączona i powiązane kolumny zostały połączone, zapomnieliśmy pobrać rzeczywiste informacje z nowej tabeli, którą właśnie połączyliśmy.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)

Racja, to na chwilę szef od naszych pleców. Teraz wyjaśnię to trochę bardziej szczegółowo. Jak widać, fromklauzula w naszym zestawieniu łączy naszą główną tabelę (często używam tabeli zawierającej informacje zamiast tabeli odnośników lub wymiarów. Zapytanie działałoby równie dobrze z przełączonymi tabelami, ale mniej sensowne, gdy wrócimy do tego zapytania, aby je przeczytać za kilka miesięcy, więc często najlepiej jest napisać zapytanie, które będzie miłe i łatwe do zrozumienia - ułóż je intuicyjnie, użyj ładnego wcięcia, aby wszystko było tak jasne, jak może być. Jeśli będziesz uczyć innych, spróbuj zaszczepić te cechy w ich zapytaniach - szczególnie jeśli będziesz je rozwiązywał.

W ten sposób można nadal łączyć coraz więcej tabel.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Chociaż zapomniałem dołączyć tabelę, w której moglibyśmy chcieć dołączyć więcej niż jedną kolumnę do joininstrukcji, oto przykład. Jeśli modelstabela zawiera modele specyficzne dla marki, a zatem ma również kolumnę o nazwie, brandktóra łączy się z brandstabelą w IDpolu, można to zrobić w następujący sposób:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
            and b.brand=d.ID
where
    b.ID=1

Widzisz, powyższe zapytanie nie tylko łączy połączone tabele z carstabelą główną , ale także określa połączenia między już połączonymi tabelami. Jeśli tego nie zrobiono, wynik nazywany jest łączeniem kartezjańskim - co oznacza, że ​​dba mówi źle. Sprzężenie kartezjańskie to takie, w którym zwracane są wiersze, ponieważ informacje nie informują bazy danych, jak ograniczyć wyniki, więc zapytanie zwraca wszystkie wiersze spełniające kryteria.

Aby podać przykład sprzężenia kartezjańskiego, uruchommy następujące zapytanie:

select
    a.ID,
    b.model
from
    cars a
        join models b

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  1 | Sedan  |
|  1 | 4WD    |
|  1 | Luxury |
|  2 | Sports |
|  2 | Sedan  |
|  2 | 4WD    |
|  2 | Luxury |
|  3 | Sports |
|  3 | Sedan  |
|  3 | 4WD    |
|  3 | Luxury |
|  4 | Sports |
|  4 | Sedan  |
|  4 | 4WD    |
|  4 | Luxury |
|  5 | Sports |
|  5 | Sedan  |
|  5 | 4WD    |
|  5 | Luxury |
|  6 | Sports |
|  6 | Sedan  |
|  6 | 4WD    |
|  6 | Luxury |
|  7 | Sports |
|  7 | Sedan  |
|  7 | 4WD    |
|  7 | Luxury |
|  8 | Sports |
|  8 | Sedan  |
|  8 | 4WD    |
|  8 | Luxury |
|  9 | Sports |
|  9 | Sedan  |
|  9 | 4WD    |
|  9 | Luxury |
| 10 | Sports |
| 10 | Sedan  |
| 10 | 4WD    |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)

Dobry Boże, to brzydkie. Jednak jeśli chodzi o bazę danych, to dokładnie o to poproszono. W zapytaniu poprosiliśmy o podanie IDod carsi modelod models. Ponieważ jednak nie określiliśmy sposobu łączenia tabel, baza danych dopasowała każdy wiersz z pierwszej tabeli do każdego wiersza z drugiej tabeli.

Okej, więc szef wrócił i znowu chce więcej informacji. Chcę tę samą listę, ale także zawierać w niej 4WD .

To jednak daje nam doskonałą wymówkę, aby spojrzeć na dwa różne sposoby osiągnięcia tego celu. Możemy dodać kolejny warunek do klauzuli where:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
    or b.ID=3

Chociaż powyższe będzie działać doskonale, spójrzmy na to inaczej, jest to świetna wymówka, aby pokazać, jak działa unionzapytanie.

Wiemy, że następujące produkty zwrócą wszystkie samochody sportowe:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

A poniższe zwróci wszystkie 4WD:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

Dodając union allmiędzy nimi klauzulę, wyniki drugiego zapytania zostaną dołączone do wyników pierwszego zapytania.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
union all
select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
|  5 | 4WD    | Green |
|  7 | 4WD    | White |
|  9 | 4WD    | Black |
+----+--------+-------+
7 rows in set (0.00 sec)

Jak widać, wyniki pierwszego zapytania są zwracane jako pierwsze, a następnie wyniki drugiego zapytania.

W tym przykładzie byłoby oczywiście o wiele łatwiej po prostu użyć pierwszego zapytania, ale unionzapytania mogą być świetne w określonych przypadkach. Są świetnym sposobem na zwrócenie określonych wyników z tabel z tabel, które nie dają się łatwo połączyć - lub, w tym przypadku, całkowicie niepowiązanych tabel. Należy jednak przestrzegać kilku zasad.

  • Typy kolumn z pierwszego zapytania muszą pasować do typów kolumn z każdego innego zapytania poniżej.
  • Nazwy kolumn z pierwszego zapytania zostaną wykorzystane do zidentyfikowania całego zestawu wyników.
  • Liczba kolumn w każdym zapytaniu musi być taka sama.

Być może zastanawiasz się, jaka jest różnica między używaniem uniona union all. unionZapytania usunie duplikaty, póki union allnie będzie. Oznacza to, że podczas korzystania z unionnadmiernej wydajności występuje niewielki spadek wydajności, union allale wyniki mogą być tego warte - nie będę jednak spekulował na ten temat.

W tej notatce warto zwrócić uwagę na kilka dodatkowych uwag.

  • Jeśli chcielibyśmy zamówić wyniki, możemy użyć, order byale nie możesz już używać aliasu. W powyższym zapytaniu dodanie an order by a.IDspowoduje błąd - jeśli chodzi o wyniki, kolumna jest wywoływana IDzamiast a.ID- mimo że w obu zapytaniach zastosowano ten sam alias.
  • Możemy mieć tylko jedno order byoświadczenie, które musi być ostatnim oświadczeniem.

Do kolejnych przykładów dodam kilka dodatkowych wierszy do naszych tabel.

Dodałem Holdendo tabeli marek. Dodałem również wiersz, carsktóry ma colorwartość 12- która nie ma odniesienia w tabeli kolorów.

Okej, szef znowu wrócił, szczekając prośbami - * Chcę liczbę każdej przewożonej marki i liczbę samochodów w niej! ”- Typowo, przechodzimy do interesującej części naszej dyskusji, a szef chce więcej pracy .

Rightyo, więc pierwszą rzeczą, którą musimy zrobić, to uzyskać pełną listę możliwych marek.

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

Teraz, gdy dołączymy to do naszej tabeli samochodów, otrzymamy następujący wynik:

select
    a.brand
from
    brands a
        join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Nissan |
| Smart  |
| Toyota |
+--------+
5 rows in set (0.00 sec)

Co oczywiście stanowi problem - nie widzimy wzmianki o uroczej Holdenmarce, którą dodałem.

Jest tak, ponieważ złączenie szuka pasujących wierszy w obu tabelach. Ponieważ w typach samochodów nie ma danych, Holdennie są one zwracane. Tutaj możemy użyć outerzłączenia. Spowoduje to zwrócenie wszystkich wyników z jednej tabeli, niezależnie od tego, czy zostaną one dopasowane w drugiej tabeli, czy nie:

select
    a.brand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Holden |
| Nissan |
| Smart  |
| Toyota |
+--------+
6 rows in set (0.00 sec)

Teraz, gdy to mamy, możemy dodać uroczą funkcję agregującą, aby policzyć i na chwilę odrzucić bossa.

select
    a.brand,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+--------------+
| brand  | countOfBrand |
+--------+--------------+
| BMW    |            2 |
| Ford   |            2 |
| Holden |            0 |
| Nissan |            1 |
| Smart  |            1 |
| Toyota |            5 |
+--------+--------------+
6 rows in set (0.00 sec)

A wraz z tym szef odchodzi.

Aby wyjaśnić to bardziej szczegółowo, sprzężenia zewnętrzne mogą być typu leftlub right. Lewy lub prawy określa, która tabela jest w pełni uwzględniona. A left outer joinobejmie wszystkie wiersze z tabeli po lewej stronie, podczas gdy (zgadłeś) a a right outer joinprzenosi wszystkie wyniki z tabeli po prawej stronie do wyników.

Niektóre bazy danych pozwolą na full outer joinprzywrócenie wyników (dopasowanych lub nie) z obu tabel, ale nie jest to obsługiwane we wszystkich bazach danych.

Prawdopodobnie w tym momencie myślę, że zastanawiasz się, czy możesz scalić typy złączeń w zapytaniu - a odpowiedź brzmi tak, absolutnie możesz.

select
    b.brand,
    c.color,
    count(a.id) as countOfBrand
from
    cars a
        right outer join brands b
            on b.ID=a.brand
        join colors c
            on a.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)

Dlaczego więc nie są to oczekiwane wyniki? Dzieje się tak, ponieważ chociaż wybraliśmy łączenie zewnętrzne od samochodów do marek, nie zostało ono określone w łączeniu z kolorami - więc to konkretne połączenie przyniesie tylko wyniki, które pasują do obu tabel.

Oto zapytanie, które zadziałałoby, aby uzyskać oczekiwane wyniki:

select
    a.brand,
    c.color,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
        left outer join colors c
            on b.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Holden | NULL  |            0 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| Toyota | NULL  |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)

Jak widać, w zapytaniu mamy dwa sprzężenia zewnętrzne, a wyniki są zgodne z oczekiwaniami.

A co powiesz na inne rodzaje złączeń, o które pytasz? Co z skrzyżowaniami?

Cóż, nie wszystkie bazy danych obsługują, intersectionale prawie wszystkie bazy danych pozwolą ci stworzyć skrzyżowanie poprzez sprzężenie (lub dobrze skonstruowaną instrukcję co najmniej).

Przecięcie jest rodzajem łączenia nieco podobnym do unionopisanego powyżej - ale różnica polega na tym, że zwraca tylko wiersze danych, które są identyczne (i mam na myśli identyczne) między różnymi pojedynczymi zapytaniami połączonymi przez związek. Zwrócone zostaną tylko wiersze identyczne pod każdym względem.

Prostym przykładem byłby taki:

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

Podczas gdy normalne unionzapytanie zwróci wszystkie wiersze tabeli (pierwsze zapytanie zwraca wszystko, ID>2a drugie ma coś ID<4), co da pełny zestaw, zapytanie przecinające zwróci tylko dopasowanie wiersza, id=3ponieważ spełnia oba kryteria.

Teraz, jeśli twoja baza danych nie obsługuje intersectzapytania, powyższe można łatwo osiągnąć za pomocą następującego zapytania:

select
    a.ID,
    a.color,
    a.paint
from
    colors a
        join colors b
            on a.ID=b.ID
where
    a.ID>2
    and b.ID<4

+----+-------+----------+
| ID | color | paint    |
+----+-------+----------+
|  3 | Blue  | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)

Jeśli chcesz wykonać przecięcie między dwoma różnymi tabelami przy użyciu bazy danych, która z natury nie obsługuje zapytania o przecięcie, musisz utworzyć złączenie w każdej kolumnie tabeli.


2
@Fluffeh Ładne odpowiedzi. Mam sugestię: jeśli chcesz uczynić go zabójczym samouczkiem SQL, brakuje tylko dodawania diagramów Venna; Dzięki nim zrozumiałem od razu lewe i prawe połączenia. Osobista prośba: Czy masz jakiś poradnik na temat typowych błędów / strojenia wydajności?
StrayChild01,

25
O mój. Moje kółko przewijania jest zepsute. Świetne pytanie i odpowiedź. Chciałbym móc to zagłosować 10 razy.
Amal Murali,

3
Hehe, dzięki za pozytywne opinie. Przewijaj dalej, to była tylko pierwsza odpowiedź. Więc powiedziałem, że moja odpowiedź jest za długa, aby zmieścić się w jednej „odpowiedzi”, więc musiałem użyć kilku :)
Fluffeh,

7
Szczerze mówiąc, uważam, że ta odpowiedź wymaga nieco skrócenia.
einpoklum,

Doskonały artykuł. Baza danych dołącza do 101.
maqs

101

Ok, uważam ten post za bardzo interesujący i chciałbym podzielić się swoją wiedzą na temat tworzenia zapytania. Dzięki za to Fluffeh . Inni, którzy mogą to przeczytać i czuć, że się mylę, mogą w 101% edytować i krytykować moją odpowiedź. ( Szczerze mówiąc, czuję się bardzo wdzięczny za sprostowanie mój błąd (s). )

Zamieszczę niektóre często zadawane pytania w MySQLtagu.


Sztuczka nr 1 ( wiersze pasujące do wielu warunków )

Biorąc pod uwagę ten schemat

CREATE TABLE MovieList
(
    ID INT,
    MovieName VARCHAR(25),
    CONSTRAINT ml_pk PRIMARY KEY (ID),
    CONSTRAINT ml_uq UNIQUE (MovieName)
);

INSERT INTO MovieList VALUES (1, 'American Pie');
INSERT INTO MovieList VALUES (2, 'The Notebook');
INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa');
INSERT INTO MovieList VALUES (4, 'Mr. Bean');
INSERT INTO MovieList VALUES (5, 'Expendables 2');

CREATE TABLE CategoryList
(
    MovieID INT,
    CategoryName VARCHAR(25),
    CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName),
    CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID)
);

INSERT INTO CategoryList VALUES (1, 'Comedy');
INSERT INTO CategoryList VALUES (1, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Drama');
INSERT INTO CategoryList VALUES (3, 'Documentary');
INSERT INTO CategoryList VALUES (4, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Action');

PYTANIE

Znajdź wszystkie filmy należące przynajmniej do obu kategorii Comedyi Romancekategorii.

Rozwiązanie

To pytanie może być czasami bardzo trudne. Może się wydawać, że takie zapytanie będzie odpowiedzią:

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName = 'Comedy' AND
        b.CategoryName = 'Romance'

Demo SQLFiddle

co jest zdecydowanie bardzo błędne, ponieważ nie daje żadnego rezultatu . Wyjaśnienie tego jest takie, że CategoryNamew każdym wierszu jest tylko jedna poprawna wartość . Na przykład pierwszy warunek zwraca wartość true , drugi warunek jest zawsze fałszywy. Tak więc, używającAND operatora oba warunki powinny być spełnione; w przeciwnym razie będzie to fałsz. Kolejne zapytanie jest takie,

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')

Demo SQLFiddle

a wynik jest nadal niepoprawny, ponieważ pasuje do rekordu, który ma co najmniej jedno dopasowanie w categoryName. Prawdziwym rozwiązaniem byłoby przez zliczenie liczby wystąpień na rekordowo filmu . Liczba instancji powinna być zgodna z całkowitą liczbą wartości podanych w warunku.

SELECT  a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')
GROUP BY a.MovieName
HAVING COUNT(*) = 2

SQLFiddle Demo (odpowiedź)


Sztuczka nr 2 ( maksymalny rekord dla każdego wpisu )

Biorąc pod uwagę schemat

CREATE TABLE Software
(
    ID INT,
    SoftwareName VARCHAR(25),
    Descriptions VARCHAR(150),
    CONSTRAINT sw_pk PRIMARY KEY (ID),
    CONSTRAINT sw_uq UNIQUE (SoftwareName)  
);

INSERT INTO Software VALUES (1,'PaintMe','used for photo editing');
INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world');
INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words');

CREATE TABLE VersionList
(
    SoftwareID INT,
    VersionNo INT,
    DateReleased DATE,
    CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo),
    CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID)
);

INSERT INTO VersionList VALUES (3, 2, '2009-12-01');
INSERT INTO VersionList VALUES (3, 1, '2009-11-01');
INSERT INTO VersionList VALUES (3, 3, '2010-01-01');
INSERT INTO VersionList VALUES (2, 2, '2010-12-01');
INSERT INTO VersionList VALUES (2, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 3, '2011-12-01');
INSERT INTO VersionList VALUES (1, 2, '2010-12-01');
INSERT INTO VersionList VALUES (1, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 4, '2012-12-01');

PYTANIE

Znajdź najnowszą wersję każdego oprogramowania. Wyświetlane są następujące kolumny: SoftwareName, Descriptions, LatestVersion( z kolumny VersionNo )DateReleased

Rozwiązanie

Niektórzy programiści SQL błędnie używają MAX()funkcji agregujących. Zwykle tworzą tak,

SELECT  a.SoftwareName, a.Descriptions,
        MAX(b.VersionNo) AS LatestVersion, b.DateReleased
FROM    Software a
        INNER JOIN VersionList b
            ON a.ID = b.SoftwareID
GROUP BY a.ID
ORDER BY a.ID

Demo SQLFiddle

( większość RDBMS generuje błąd składniowy z tego powodu, że nie określono niektórych niezagregowanych kolumn w group byklauzuli ) wynik daje prawidłowe LatestVersionw każdym oprogramowaniu, ale oczywiście DateReleasedsą one nieprawidłowe. MySQLnie obsługuje, Window Functionsa Common Table Expressionjak już niektóre RDBMS już. Obejściem tego problemu jest utworzenie wartości, subqueryktóra pobiera indywidualne maksimum versionNodla każdego oprogramowania, a następnie łączy się z innymi tabelami.

SELECT  a.SoftwareName, a.Descriptions,
        b.LatestVersion, c.DateReleased
FROM    Software a
        INNER JOIN
        (
            SELECT  SoftwareID, MAX(VersionNO) LatestVersion
            FROM    VersionList
            GROUP BY SoftwareID
        ) b ON a.ID = b.SoftwareID
        INNER JOIN VersionList c
            ON  c.SoftwareID = b.SoftwareID AND
                c.VersionNO = b.LatestVersion
GROUP BY a.ID
ORDER BY a.ID

SQLFiddle Demo (odpowiedź)


Tak to było. Będę publikować kolejne, gdy tylko przypomnę sobie inne FAQ na temat MySQLtagu. Dziękujemy za przeczytanie tego małego artykułu. Mam nadzieję, że masz przynajmniej trochę wiedzy na ten temat.

AKTUALIZACJA 1


Sztuczka nr 3 ( Znajdowanie najnowszego rekordu między dwoma identyfikatorami )

Biorąc pod uwagę schemat

CREATE TABLE userList
(
    ID INT,
    NAME VARCHAR(20),
    CONSTRAINT us_pk PRIMARY KEY (ID),
    CONSTRAINT us_uq UNIQUE (NAME)  
);

INSERT INTO userList VALUES (1, 'Fluffeh');
INSERT INTO userList VALUES (2, 'John Woo');
INSERT INTO userList VALUES (3, 'hims056');

CREATE TABLE CONVERSATION
(
    ID INT,
    FROM_ID INT,
    TO_ID INT,
    MESSAGE VARCHAR(250),
    DeliveryDate DATE
);

INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01');
INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02');
INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03');
INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04');
INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05');
INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05');

PYTANIE

Znajdź najnowszą rozmowę między dwoma użytkownikami.

Rozwiązanie

SELECT    b.Name SenderName,
          c.Name RecipientName,
          a.Message,
          a.DeliveryDate
FROM      Conversation a
          INNER JOIN userList b
            ON a.From_ID = b.ID
          INNER JOIN userList c
            ON a.To_ID = c.ID
WHERE     (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate)
IN
(
    SELECT  LEAST(FROM_ID, TO_ID) minFROM,
            GREATEST(FROM_ID, TO_ID) maxTo,
            MAX(DeliveryDate) maxDate
    FROM    Conversation
    GROUP BY minFROM, maxTo
)

Demo SQLFiddle


Niesamowite! Uwaga John, twoje pierwsze rozwiązanie działa tylko dlatego, że istnieje wyjątkowe ograniczenie na dwóch polach. Mogłeś użyć bardziej ogólnego rozwiązania, aby rozwiązać typowy problem. Moim zdaniem jedynym rozwiązaniem jest dokonanie indywidualnych wyborów dla comedyi romance. Havingnie pasuje to ...
nawfal

@nawfal nie bardzo, jeśli nie dodano unikalnego ograniczenia, musisz dodać distinctklauzulę mającą SQLFiddle Demo : D
John Woo,

63

Część 2 - podzapytania

Okej, teraz szef znowu się włamał - chcę listę wszystkich naszych samochodów z marką i razem ile ich mamy!

To świetna okazja, aby użyć następnej sztuczki w naszej torbie dodatków SQL - podzapytania. Jeśli nie znasz tego terminu, podzapytanie to zapytanie uruchamiane w innym zapytaniu. Istnieje wiele różnych sposobów ich wykorzystania.

Na nasze życzenie, najpierw zestawmy proste zapytanie, które będzie zawierało listę każdego samochodu i marki:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Teraz, jeśli chcielibyśmy po prostu uzyskać liczbę samochodów posortowanych według marki, moglibyśmy oczywiście napisać to:

select
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    b.brand

+--------+-----------+
| brand  | countCars |
+--------+-----------+
| BMW    |         2 |
| Ford   |         2 |
| Nissan |         1 |
| Smart  |         1 |
| Toyota |         5 |
+--------+-----------+

Więc powinniśmy móc po prostu dodać funkcję count do naszego oryginalnego zapytania, prawda?

select
    a.ID,
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    a.ID,
    b.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         1 |
|  2 | Ford   |         1 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         1 |
|  6 | BMW    |         1 |
|  7 | Ford   |         1 |
|  8 | Toyota |         1 |
|  9 | Toyota |         1 |
| 10 | BMW    |         1 |
| 11 | Toyota |         1 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Niestety nie możemy tego zrobić. Powodem jest to, że kiedy dodajemy identyfikator samochodu (kolumna a.ID), musimy dodać go do grupy do - więc teraz, gdy działa funkcja liczenia, istnieje tylko jeden identyfikator dopasowany do identyfikatora.

Tutaj możemy jednak użyć podkwerendy - w rzeczywistości możemy wykonać dwa całkowicie różne typy podkwerendy, które zwrócą te same wyniki, których potrzebujemy do tego. Pierwszym z nich jest po prostu umieszczenie podzapytania w selectklauzuli. Oznacza to, że za każdym razem, gdy otrzymujemy wiersz danych, podkwerenda zostanie uruchomiona, zdobędzie kolumnę danych, a następnie wstawi ją do naszego wiersza danych.

select
    a.ID,
    b.brand,
    (
    select
        count(c.ID)
    from
        cars c
    where
        a.brand=c.brand
    ) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  2 | Ford   |         2 |
|  7 | Ford   |         2 |
|  1 | Toyota |         5 |
|  5 | Toyota |         5 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 11 | Toyota |         5 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  6 | BMW    |         2 |
| 10 | BMW    |         2 |
+----+--------+-----------+
11 rows in set (0.00 sec)

I Bam !, to by nam zrobiło. Jeśli jednak zauważyłeś, to zapytanie będzie musiało być uruchomione dla każdego zwracanego wiersza danych. Nawet w tym małym przykładzie mamy tylko pięć różnych marek samochodów, ale podzapytanie przebiegało jedenaście razy, ponieważ mamy jedenaście wierszy danych, które zwracamy. Tak więc w tym przypadku nie wydaje się to najbardziej wydajnym sposobem pisania kodu.

Dla innego podejścia, uruchommy podkwerendę i udawajmy, że jest to tabela:

select
    a.ID,
    b.brand,
    d.countCars
from
    cars a
        join brands b
            on a.brand=b.ID
        join
            (
            select
                c.brand,
                count(c.ID) as countCars
            from
                cars c
            group by
                c.brand
            ) d
            on a.brand=d.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         5 |
|  2 | Ford   |         2 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         5 |
|  6 | BMW    |         2 |
|  7 | Ford   |         2 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 10 | BMW    |         2 |
| 11 | Toyota |         5 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Ok, więc mamy te same wyniki (uporządkowane nieco inaczej - wygląda na to, że baza danych chciała zwrócić wyniki uporządkowane według pierwszej kolumny, którą wybraliśmy tym razem) - ale te same poprawne liczby.

Jaka jest różnica między nimi - i kiedy powinniśmy używać każdego typu podzapytania? Po pierwsze, upewnijmy się, że rozumiemy, jak działa to drugie zapytanie. W fromklauzuli naszego zapytania wybraliśmy dwie tabele , a następnie napisaliśmy zapytanie i powiedzieliśmy bazie danych, że w rzeczywistości jest to tabela - z której baza danych jest całkowicie zadowolona. Nie mogą być pewne korzyści z korzystania z tej metody (jak również pewne ograniczenia). Najważniejsze jest to, że to podzapytanie prowadziło wiersze danych, jeśli będziemy używać prostego łączenia, jak w powyższym zapytaniu. Jeśli sobie przypomnisz, łączenie spowoduje wycofanie tylko wierszy, które mają pasujące dane po obu stronach złączenia. Jeśli nie będziemy ostrożni, może to spowodować, że prawidłowe dane nie zostaną zwrócone z tabeli naszych samochodów, jeśli w tym podzapytaniu nie będzie pasującego wiersza. raz . Gdyby nasza baza danych zawierała dużą ilość danych, może być ogromna poprawa w stosunku do pierwszej metody. Ponieważ jednak używamy tego jako tabeli, musimy wprowadzić dodatkowe wiersze danych - aby mogły one zostać ponownie połączone z naszymi wierszami danych. Musimy również mieć pewność, że jest ich wystarczająco dużo

Teraz, patrząc wstecz na pierwsze podzapytanie, istnieją również pewne ograniczenia. ponieważ ściągamy dane z powrotem do jednego wiersza, możemy wycofać TYLKO jeden wiersz danych. Podzapytania używane w selectklauzuli kwerendy bardzo często używać tylko funkcji, takich jak kruszywa sum, count, maxlub innej podobnej funkcji zbiorczej. Nie muszą , ale często tak się pisze.

Zanim przejdziemy dalej, rzućmy okiem, gdzie jeszcze możemy użyć podzapytania. Możemy użyć tego w whereklauzuli - teraz ten przykład jest nieco spreparowany, ponieważ w naszej bazie danych są lepsze sposoby na uzyskanie następujących danych, ale ponieważ jest to tylko przykład, spójrzmy:

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

To zwraca nam listę identyfikatorów marek i nazw marek (druga kolumna została dodana tylko w celu pokazania marek), które zawierają literę ow nazwie.

Teraz możemy użyć wyników tego zapytania w klauzuli where to:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in
        (
        select
            ID
        from
            brands
        where
            brand like '%o%'
        )

+----+--------+
| ID | brand  |
+----+--------+
|  2 | Ford   |
|  7 | Ford   |
|  1 | Toyota |
|  5 | Toyota |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Jak widać, nawet jeśli podzapytanie zwracało trzy identyfikatory marek, w naszej tabeli samochodów były wpisy tylko dla dwóch z nich.

W tym przypadku, dla dalszych szczegółów, podzapytanie działa tak, jakbyśmy napisali następujący kod:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in (1,2,6)

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Toyota |
|  2 | Ford   |
|  5 | Toyota |
|  7 | Ford   |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Ponownie można zobaczyć, w jaki sposób podzapytanie w porównaniu z ręcznymi danymi wejściowymi zmieniło kolejność wierszy podczas powrotu z bazy danych.

Podczas omawiania podkwerend, zobaczmy, co jeszcze możemy zrobić z podkwerendami:

  • Możesz umieścić podkwerendę w innym podkwerendie itd. I tak dalej. Istnieje limit, który zależy od twojej bazy danych, ale bez rekurencyjnych funkcji jakiegoś szalonego i maniakalnego programisty, większość ludzi nigdy nie przekroczy tego limitu.
  • Możesz umieścić wiele podkwerend w jednym zapytaniu, kilka w selectklauzuli, niektóre w fromklauzuli i kilka innych w whereklauzuli - pamiętaj tylko, że każde z nich powoduje, że zapytanie jest bardziej złożone i może potrwać dłużej wykonać.

Jeśli potrzebujesz napisać jakiś wydajny kod, może być korzystne napisanie zapytania na kilka sposobów i sprawdzenie (przez czas lub za pomocą planu wyjaśniania), które jest optymalnym zapytaniem, aby uzyskać wyniki. Pierwszy sposób, który działa, nie zawsze może być najlepszym sposobem.


Bardzo ważne dla nowych deweloperów: podkwerendy prawdopodobnie uruchamiają się raz dla każdego wyniku, chyba że można użyć kwerendy jako sprzężenia (jak pokazano powyżej).
Xeoncross,

59

Część 3 - Sztuczki i skuteczny kod

Wydajność MySQL w ()

Pomyślałem, że dodam kilka dodatkowych bitów, aby znaleźć wskazówki i triki.

Jedno pytanie, które, jak się wydaje, pojawiło się dość często , brzmi: Jak uzyskać niepasujące wiersze z dwóch tabel i widzę odpowiedź najczęściej akceptowaną jako coś w tym rodzaju (na podstawie tabeli naszych samochodów i marek - na której Holden wymieniono jako marka, ale nie pojawia się w tabeli samochodów):

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

I tak to zadziała.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

Jednak to nie skuteczny w niektórych danych. Oto link do pytania o przepełnienie stosu z pytaniem o to, a tutaj jest artykuł o doskonałej szczegółowości, jeśli chcesz dostać się w szczegóły.

Krótka odpowiedź brzmi: jeśli optymalizator nie poradzi sobie z nią skutecznie, może być znacznie lepiej użyć zapytania takiego jak poniżej, aby uzyskać niepasujące wiersze:

select
    a.brand
from
    brands a
        left join cars b
            on a.id=b.brand
where
    b.brand is null

+--------+
| brand  |
+--------+
| Holden |
+--------+
1 row in set (0.00 sec)

Zaktualizuj tabelę z tą samą tabelą w podzapytaniu

Ahhh, kolejny oldie ale goodie - stary Nie można określić „marek” tabeli docelowej do aktualizacji w klauzuli FROM .

MySQL nie pozwoli ci uruchomić update...zapytania z podselekcją w tej samej tabeli. Być może zastanawiasz się, dlaczego po prostu nie wpaść w klauzulę „gdzie”, prawda? Ale co, jeśli chcesz zaktualizować tylko wiersz z max()datą między wieloma innymi wierszami? Nie można tego dokładnie zrobić w klauzuli where.

update 
    brands 
set 
    brand='Holden' 
where 
    id=
        (select 
            id 
        from 
            brands 
        where 
            id=6);
ERROR 1093 (HY000): You can't specify target table 'brands' 
for update in FROM clause

Więc nie możemy tego zrobić? Nie do końca. Istnieje zaskakujące obejście, o którym zaskakująco duża liczba użytkowników nie wie - chociaż zawiera pewne hakery, na które trzeba zwrócić uwagę.

Możesz umieścić podkwerendę w innym podkwerendie, co spowoduje dostateczną przerwę między dwoma zapytaniami, aby działało. Należy jednak pamiętać, że najbezpieczniej jest trzymać zapytanie w transakcji - zapobiegnie to wszelkim innym zmianom w tabelach podczas działania zapytania.

update 
    brands 
set 
    brand='Holden' 
where id=
    (select 
        id 
    from 
        (select 
            id 
        from 
            brands 
        where 
            id=6
        ) 
    as updateTable);

Query OK, 0 rows affected (0.02 sec)
Rows matched: 1  Changed: 0  Warnings: 0

3
Chcę tylko zauważyć, że konstrukcja WHERE NOT EXISTS () jest prawie identyczna z „punktu widzenia wydajności”, ale moim zdaniem znacznie łatwiejsza do odczytania / zrozumienia. Z drugiej strony moja wiedza ogranicza się do MSSQL i nie mogę ślubować, jeśli to samo dotyczy innych platform.
deroby

Właśnie tego dnia porównałem ten typ porównania, w którym NOT IN () miał listę około kilkuset identyfikatorów i nie było różnicy między nim a wersją łączącą kwerendy. Być może robi to różnicę, gdy wstaniesz do tysięcy lub miliardów.
Buttle Butkus

18

Można użyć koncepcji wielu zapytań w słowie kluczowym FROM. Pokażę ci jeden przykład:

SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY    
FROM  (
          SELECT c.id cnty,l.name
          FROM   county c, location l
          WHERE  c.id=l.county_id AND l.end_Date IS NOT NULL
      ) c_loc, emp e 
      INNER JOIN dept d ON e.deptno =d.id
      LEFT JOIN 
      ( 
         SELECT l.id lappy, c.name cmpy
         FROM   laptop l, company c
         WHERE l.make = c.name
      ) lap ON e.cmpy_id=lap.cmpy

Możesz użyć tyle tabel, ile chcesz. Używaj złączeń zewnętrznych i łączeń tam, gdzie jest to konieczne, nawet wewnątrz podkwerend tabel.

Jest to bardzo łatwa metoda angażowania tylu tabel i pól.


8

Mam nadzieję, że dzięki temu znajdziesz tabele podczas czytania:

jsfiddle

mysql> show columns from colors;                                                         
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+           
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.