แบบสอบถาม SQL ส่งคืนข้อมูลจากหลายตาราง


434

ฉันต้องการทราบสิ่งต่อไปนี้:

  • วิธีรับข้อมูลจากหลายตารางในฐานข้อมูลของฉัน
  • มีวิธีการประเภทใดให้ทำเช่นนี้?
  • ตัวเชื่อมและสหภาพคืออะไรและแตกต่างจากกันอย่างไร
  • เมื่อใดที่ฉันควรใช้แต่ละรายการเปรียบเทียบกับรายการอื่น

ฉันวางแผนที่จะใช้สิ่งนี้ในแอปพลิเคชัน (ตัวอย่างเช่น - PHP) ของฉัน แต่ไม่ต้องการเรียกใช้แบบสอบถามจำนวนมากกับฐานข้อมูลฉันต้องทำอะไรตัวเลือกใดบ้างเพื่อรับข้อมูลจากหลายตารางในแบบสอบถามเดียว

หมายเหตุ: ฉันกำลังเขียนสิ่งนี้เนื่องจากฉันต้องการลิงก์ไปยังคำแนะนำที่เป็นลายลักษณ์อักษรอย่างดีเกี่ยวกับคำถามมากมายที่ฉันพบเจอในคิว PHP อย่างต่อเนื่องดังนั้นฉันจึงสามารถลิงก์ไปยังรายละเอียดเพิ่มเติมได้เมื่อฉันโพสต์คำตอบ

คำตอบครอบคลุมถึงสิ่งต่อไปนี้:

  1. ส่วนที่ 1 - เข้าร่วมและสหภาพ
  2. ส่วนที่ 2 - คำถามย่อย
  3. ส่วนที่ 3 - เทคนิคและรหัสที่มีประสิทธิภาพ
  4. ส่วนที่ 4 - เคียวรีย่อยใน From Clause
  5. ตอนที่ 5 - ถุงผสมของจอห์นทริค

คำตอบ:


469

ส่วนที่ 1 - เข้าร่วมและสหภาพ

คำตอบนี้ครอบคลุมถึง:

  1. ส่วนที่ 1
  2. ส่วนที่ 2
    • คำถามย่อย - สิ่งที่พวกเขาสามารถใช้และสิ่งที่ควรระวัง
    • คาร์ทีเซียนเข้าร่วม AKA - โอ้ความทุกข์ยาก!

มีหลายวิธีในการดึงข้อมูลจากหลายตารางในฐานข้อมูล ในคำตอบนี้ฉันจะใช้ไวยากรณ์การเข้าร่วม ANSI-92 สิ่งนี้อาจแตกต่างจากบทช่วยสอนอื่น ๆ ที่ใช้ไวยากรณ์ ANSI-89 ที่เก่ากว่า (และถ้าคุณคุ้นเคยกับ 89 อาจดูเหมือนใช้งานง่ายกว่า - แต่ทั้งหมดที่ฉันพูดได้คือลองทำ) เพราะง่ายกว่ามากเพื่อทำความเข้าใจเมื่อแบบสอบถามเริ่มซับซ้อนขึ้น ทำไมต้องใช้ มีประสิทธิภาพเพิ่มขึ้นหรือไม่? คำตอบสั้น ๆคือไม่ แต่มันเป็นเรื่องง่ายที่จะอ่านเมื่อคุณได้ใช้มัน ง่ายต่อการอ่านข้อความค้นหาที่เขียนโดยคนอื่นโดยใช้ไวยากรณ์นี้

ฉันจะใช้แนวคิดของนิวเคลียสเล็กซึ่งมีฐานข้อมูลเพื่อติดตามรถยนต์ที่มีอยู่ เจ้าของได้ว่าจ้างคุณเป็นคนคอมพิวเตอร์ไอทีของเขาและคาดหวังว่าคุณจะสามารถวางข้อมูลที่เขาขอไว้เมื่อหมวกหล่น

ฉันสร้างตารางการค้นหาจำนวนหนึ่งซึ่งจะใช้ในตารางสุดท้าย สิ่งนี้จะทำให้เรามีรูปแบบที่เหมาะสมในการทำงานจาก ในการเริ่มต้นฉันจะใช้คำสั่งของฉันกับฐานข้อมูลตัวอย่างที่มีโครงสร้างต่อไปนี้ ฉันจะพยายามคิดถึงข้อผิดพลาดทั่วไปที่เกิดขึ้นเมื่อเริ่มต้นและอธิบายสิ่งที่ผิดพลาดกับพวกเขา - รวมถึงการแสดงวิธีการแก้ไขให้ถูกต้อง

ตารางแรกเป็นเพียงรายการสีเพื่อให้เรารู้ว่าสีอะไรที่เรามีในลานรถ

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)

ตารางยี่ห้อระบุยี่ห้อที่แตกต่างกันของรถยนต์ออก caryard อาจขายได้

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)

ตารางรุ่นจะครอบคลุมรถยนต์ประเภทต่างๆมันจะง่ายกว่าถ้าจะใช้รถยนต์ประเภทต่าง ๆ แทนที่จะเป็นรถรุ่นจริง

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)

และสุดท้ายเพื่อผูกตารางอื่น ๆ ทั้งหมดนี้ตารางที่ผูกทุกอย่างเข้าด้วยกัน จริง ๆ แล้วฟิลด์ ID เป็นหมายเลขลอตที่ไม่ซ้ำกันซึ่งใช้ระบุรถยนต์

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)

นี่จะทำให้เรามีข้อมูลมากพอ (ฉันหวังว่า) เพื่อปกปิดตัวอย่างด้านล่างของการเข้าร่วมประเภทต่าง ๆ และยังให้ข้อมูลที่เพียงพอเพื่อทำให้พวกเขาคุ้มค่า

ดังนั้นการเข้าสู่กรวดของมันเจ้านายต้องการที่จะรู้รหัสของทุกรถสปอร์ตที่เขามี

นี่คือการรวมสองตารางที่เรียบง่าย เรามีตารางที่ระบุรูปแบบและตารางพร้อมสต็อกที่มีอยู่ในนั้น อย่างที่คุณเห็นข้อมูลในmodelคอลัมน์ของcarsตารางเกี่ยวข้องกับmodelsคอลัมน์ของcarsตารางที่เรามี ตอนนี้เรารู้แล้วว่าตาราง model มี ID 1สำหรับSportsดังนั้นให้เขียนการเข้าร่วม

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

ดังนั้นแบบสอบถามนี้ดูดีใช่ไหม เราได้ระบุสองตารางและมีข้อมูลที่เราต้องการและใช้การเข้าร่วมที่ระบุคอลัมน์ที่จะเข้าร่วมอย่างถูกต้อง

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

โอ้โห! ข้อผิดพลาดในข้อความค้นหาแรกของเรา! ใช่และมันก็เป็นลูกพลัม คุณเห็นแล้วว่าคิวรี่นั้นมีคอลัมน์ที่ถูกต้อง แต่มีบางคอลัมน์อยู่ในทั้งสองตารางดังนั้นฐานข้อมูลจะสับสนว่าคอลัมน์จริงที่เราหมายถึงและที่ใด มีสองวิธีแก้ไขปัญหานี้ อันแรกนั้นดีและเรียบง่ายเราสามารถใช้tableName.columnNameบอกฐานข้อมูลว่าเราหมายถึงอะไรแบบนี้

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)

อีกอันอาจใช้บ่อยกว่าและเรียกว่านามแฝงตาราง ตารางในตัวอย่างนี้มีชื่อที่ง่ายและสั้น แต่พิมพ์สิ่งที่KPI_DAILY_SALES_BY_DEPARTMENTอาจจะเก่าไปอย่างรวดเร็วดังนั้นวิธีง่ายๆคือตั้งชื่อตารางตามนี้:

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

ตอนนี้กลับไปที่คำขอ อย่างที่คุณเห็นว่าเรามีข้อมูลที่เราต้องการ แต่เราก็มีข้อมูลที่ไม่ได้ถามด้วยดังนั้นเราจึงจำเป็นต้องรวมส่วนคำสั่งในการรับรถสปอร์ตตามที่ถูกถามเท่านั้น ตามที่ฉันต้องการวิธีการนามแฝงของตารางแทนที่จะใช้ชื่อตารางซ้ำแล้วซ้ำอีกฉันจะยึดมันจากจุดนี้เป็นต้นไป

เห็นได้ชัดว่าเราต้องเพิ่มส่วนคำสั่งลงในแบบสอบถามของเรา เราสามารถระบุรถยนต์กีฬาทั้งโดยหรือID=1 model='Sports'ในขณะที่ ID ถูกทำดัชนีและคีย์หลัก (และเกิดขึ้นกับการพิมพ์น้อยลง) ให้ใช้ในการสืบค้นของเรา

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)

บิงโก! เจ้านายมีความสุข แน่นอนเป็นเจ้านายและไม่เคยมีความสุขกับสิ่งที่เขาถามหาเขาดูที่ข้อมูลแล้วพูดว่าฉันต้องการสีได้เป็นอย่างดี

ตกลงดังนั้นเรามีส่วนที่ดีของแบบสอบถามของเราที่เขียนไปแล้ว แต่เราจำเป็นต้องใช้ตารางที่สามซึ่งเป็นสี ตอนนี้ตารางข้อมูลหลักของเราจัดcarsเก็บรหัสสีรถยนต์และลิงค์นี้กลับไปที่คอลัมน์รหัสสี ดังนั้นในลักษณะที่คล้ายคลึงกับต้นฉบับเราสามารถเข้าร่วมตารางที่สาม:

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)

ประณามแม้ว่าตารางจะถูกรวมอย่างถูกต้องและมีการเชื่อมโยงคอลัมน์ที่เกี่ยวข้องเราลืมที่จะดึงข้อมูลจริงจากตารางใหม่ที่เราเพิ่งเชื่อมโยง

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)

ใช่แล้วนั่นคือหัวหน้าของเรา ทีนี้มาอธิบายเรื่องนี้ให้ละเอียดหน่อย ตามที่คุณเห็นส่วนfromคำสั่งในคำสั่งของเราเชื่อมโยงตารางหลักของเรา (ฉันมักจะใช้ตารางที่มีข้อมูลมากกว่าตารางการค้นหาหรือส่วนข้อมูล) การสืบค้นจะทำงานได้ดีเช่นเดียวกับตารางที่สลับไปมาทั้งหมด เรากลับมาที่ข้อความค้นหานี้เพื่ออ่านในเวลาไม่กี่เดือนดังนั้นจึงเป็นการดีที่สุดที่จะลองเขียนคำถามที่จะดีและง่ายต่อการเข้าใจ - จัดวางไว้อย่างสังหรณ์ใจใช้การเยื้องที่ดีเพื่อให้ทุกอย่างชัดเจน มันอาจเป็นไปได้ถ้าคุณยังสอนผู้อื่นให้ลองปลูกฝังคุณลักษณะเหล่านี้ในแบบสอบถามของพวกเขา - โดยเฉพาะถ้าคุณจะแก้ไขปัญหาเหล่านั้น

เป็นไปได้ทั้งหมดที่จะเชื่อมโยงตารางมากขึ้นเรื่อย ๆ ในลักษณะนี้

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

แม้ว่าฉันลืมที่จะรวมตารางที่เราอาจต้องการเข้าร่วมมากกว่าหนึ่งคอลัมน์ในjoinคำชี้แจงนี่คือตัวอย่าง หากmodelsตารางมีโมเดลเฉพาะแบรนด์และดังนั้นจึงมีคอลัมน์brandที่เชื่อมโยงกลับไปที่brandsตารางในIDฟิลด์ก็สามารถทำได้ดังนี้:

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

คุณสามารถดูแบบสอบถามด้านบนไม่เพียง แต่เชื่อมโยงตารางที่รวมเข้ากับตารางหลักcarsแต่ยังระบุการรวมระหว่างตารางที่เข้าร่วมแล้ว หากสิ่งนี้ยังไม่เสร็จสิ้นผลลัพธ์จะถูกเรียกว่าการเข้าร่วมคาร์ทีเซียนซึ่งเป็น dba ที่พูดไม่ดี การรวมคาร์ทีเซียนคือการที่แถวถูกส่งคืนเนื่องจากข้อมูลไม่ได้บอกฐานข้อมูลถึงวิธี จำกัด ผลลัพธ์ดังนั้นแบบสอบถามจะส่งคืนแถวทั้งหมดที่ตรงกับเกณฑ์

ดังนั้นเพื่อให้ตัวอย่างของการเข้าร่วมคาร์ทีเซียนให้เรียกใช้แบบสอบถามต่อไปนี้:

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)

พระเจ้าที่ดีน่าเกลียด แต่เท่าที่เป็นฐานข้อมูลเป็นห่วงก็คือว่าสิ่งที่ถูกถามหา ในแบบสอบถามที่เราถามหาIDจากcarsและจากmodel modelsอย่างไรก็ตามเนื่องจากเราไม่ได้ระบุวิธีเข้าร่วมตารางฐานข้อมูลจึงจับคู่ทุกแถวจากตารางแรกกับทุกแถวจากตารางที่สอง

โอเคเจ้านายกลับมาแล้วและเขาต้องการข้อมูลเพิ่มเติมอีกครั้ง ฉันต้องการรายการเดียวกัน แต่ยังรวมถึง 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=1
    or b.ID=3

ในขณะที่ข้างต้นจะทำงานได้ดีอย่างสมบูรณ์ให้ดูที่มันแตกต่างกันนี้เป็นข้อแก้ตัวที่ดีในการแสดงวิธีการunionทำงานของแบบสอบถาม

เรารู้ว่าสิ่งต่อไปนี้จะคืนรถสปอร์ตทุกคัน:

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

และต่อไปนี้จะส่งคืน 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

ดังนั้นโดยการเพิ่ม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=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)

อย่างที่คุณเห็นผลลัพธ์ของแบบสอบถามแรกจะถูกส่งกลับก่อนตามด้วยผลลัพธ์ของแบบสอบถามที่สอง

ในตัวอย่างนี้แน่นอนว่าจะง่ายกว่ามากในการใช้การสืบค้นแรก แต่การunionสืบค้นสามารถทำได้ดีสำหรับกรณีเฉพาะ พวกเขาเป็นวิธีที่ยอดเยี่ยมในการส่งคืนผลลัพธ์ที่เฉพาะเจาะจงจากตารางจากตารางที่ไม่ได้รวมเข้าด้วยกันได้ง่ายหรือสำหรับตารางที่ไม่เกี่ยวข้องอย่างสมบูรณ์ มีกฎบางอย่างที่ต้องปฏิบัติตามอย่างไรก็ตาม

  • ประเภทคอลัมน์จากแบบสอบถามแรกจะต้องตรงกับประเภทคอลัมน์จากแบบสอบถามอื่น ๆ ด้านล่าง
  • ชื่อของคอลัมน์จากแบบสอบถามแรกจะถูกใช้เพื่อระบุชุดผลลัพธ์ทั้งหมด
  • จำนวนคอลัมน์ในแต่ละแบบสอบถามจะต้องเหมือนกัน

ตอนนี้คุณอาจสงสัยว่าความแตกต่างระหว่างการใช้งานunionกับunion allอะไร unionแบบสอบถามจะลบที่ซ้ำกันในขณะที่union allจะไม่ นี่หมายความว่ามีการใช้งานเล็กน้อยเมื่อใช้unionเกินunion allแต่ผลลัพธ์อาจมีค่า - ฉันจะไม่คาดเดากับสิ่งนั้นในเรื่องนี้

ในบันทึกนี้มันอาจจะคุ้มค่าที่จะต้องสังเกตโน้ตเพิ่มเติมที่นี่

  • หากเราต้องการเรียงลำดับผลลัพธ์เราสามารถใช้order byแต่คุณไม่สามารถใช้ชื่อแทนได้อีก ในแบบสอบถามด้านบนการผนวกorder by a.IDจะส่งผลให้เกิดข้อผิดพลาด - เท่าที่ผลลัพธ์เกี่ยวข้องคอลัมน์จะถูกเรียกIDมากกว่าa.ID- แม้ว่าจะใช้นามแฝงเดียวกันในแบบสอบถามทั้งสอง
  • เราสามารถมีได้เพียงหนึ่งorder byคำสั่งและมันจะต้องเป็นคำสั่งสุดท้าย

สำหรับตัวอย่างถัดไปฉันกำลังเพิ่มแถวพิเศษสองสามแถวในตารางของเรา

ฉันเพิ่มลงHoldenในตารางแบรนด์แล้ว ฉันได้เพิ่มแถวcarsที่มีcolorค่า12ซึ่งไม่มีการอ้างอิงในตารางสี

โอเคเจ้านายกลับมาอีกครั้งเห่าร้องขอ - * ฉันต้องการนับแต่ละยี่ห้อที่เราบรรทุกและจำนวนรถยนต์ในนั้น! '- โดยทั่วไปเราเพิ่งไปยังส่วนที่น่าสนใจของการสนทนาของเราและหัวหน้าต้องการทำงานมากขึ้น .

Rightyo สิ่งแรกที่เราต้องทำคือรับรายชื่อแบรนด์ที่เป็นไปได้ทั้งหมด

select
    a.brand
from
    brands a

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

ตอนนี้เมื่อเราเข้าร่วมในตารางรถยนต์ของเราเราได้รับผลดังต่อไปนี้:

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)

แน่นอนว่าเป็นปัญหา - เราไม่เห็นการกล่าวถึงHoldenแบรนด์ที่น่ารักที่ฉันเพิ่มเข้าไป

นี่เป็นเพราะการเข้าร่วมค้นหาการจับคู่แถวในตารางทั้งสอง เนื่องจากไม่มีข้อมูลในรถยนต์ที่เป็นประเภทHoldenจึงไม่ส่งคืน นี่คือที่เราสามารถใช้outerเข้าร่วม สิ่งนี้จะส่งคืนผลลัพธ์ทั้งหมดจากตารางหนึ่งไม่ว่าจะถูกจับคู่ในตารางอื่นหรือไม่:

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)

ตอนนี้เรามีสิ่งนั้นแล้วเราสามารถเพิ่มฟังก์ชั่นการรวมที่น่ารักเพื่อการนับและกำจัดหัวหน้าของเราสักครู่

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)

และด้วยสิ่งนั้นให้ห่างจากเจ้านาย

ตอนนี้หากต้องการอธิบายรายละเอียดเพิ่มเติมบางอย่างการรวมภายนอกอาจเป็นประเภทleftหรือ rightซ้ายหรือขวากำหนดตารางเป็นอย่างเต็มที่รวม A left outer joinจะรวมแถวทั้งหมดจากตารางทางด้านซ้ายในขณะที่ (คุณเดา) right outer joinจะนำผลลัพธ์ทั้งหมดจากตารางด้านขวาไปยังผลลัพธ์

ฐานข้อมูลบางอย่างจะอนุญาตให้ใช้full outer joinซึ่งจะนำผลลัพธ์กลับมา (ไม่ว่าจะตรงกันหรือไม่ก็ตาม) จากทั้งสองตาราง แต่ไม่รองรับในฐานข้อมูลทั้งหมด

ตอนนี้ฉันอาจคิดว่า ณ เวลานี้คุณกำลังสงสัยว่าคุณสามารถผสานประเภทการเข้าร่วมในแบบสอบถามได้หรือไม่และคำตอบคือใช่คุณทำได้จริง ๆ

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)

เหตุใดจึงไม่ผลลัพธ์ที่คาดไว้ เป็นเพราะแม้ว่าเราได้เลือกการรวมภายนอกจากรถยนต์ไปยังแบรนด์ แต่ไม่ได้ระบุในการเข้าร่วมสีดังนั้นการเข้าร่วมโดยเฉพาะจะนำผลลัพธ์ที่ตรงกันในทั้งสองตารางกลับมาใช้

นี่คือข้อความค้นหาที่ใช้งานเพื่อให้ได้ผลลัพธ์ตามที่เราคาดไว้:

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)

อย่างที่เราเห็นเรามีตัวเชื่อมภายนอกสองตัวในแบบสอบถามและผลลัพธ์จะผ่านตามที่คาดไว้

ทีนี้คุณคิดว่าคุณจะเข้าร่วมประเภทไหนได้บ้าง สิ่งที่เกี่ยวกับการแยก?

ไม่ใช่ฐานข้อมูลทั้งหมดที่สนับสนุนintersectionแต่ฐานข้อมูลทั้งหมดจะช่วยให้คุณสร้างจุดตัดผ่านการเข้าร่วม (หรือโครงสร้างที่ดีที่มีคำสั่งอย่างน้อย)

Intersection เป็นประเภทของการเข้าร่วมที่ค่อนข้างคล้ายกับunionที่อธิบายไว้ข้างต้น - แต่ความแตกต่างคือมันจะส่งกลับเฉพาะแถวของข้อมูลที่เหมือนกัน (และฉันหมายถึงเหมือนกัน) ระหว่างแบบสอบถามแบบต่างๆที่เข้าร่วมโดยสหภาพ จะส่งคืนเฉพาะแถวที่เหมือนกันทุกประการ

ตัวอย่างง่ายๆจะเป็นเช่นนี้:

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

ในขณะที่unionแบบสอบถามปกติจะส่งกลับแถวทั้งหมดของตาราง (แบบสอบถามแรกที่ส่งคืนสิ่งใด ๆID>2และรายการที่สองมีID<4) ซึ่งจะส่งผลให้ครบชุดแบบสอบถามตัดกันจะส่งคืนการจับคู่แถวid=3ตามที่ตรงกับเกณฑ์ทั้งสองเท่านั้น

ตอนนี้ถ้าฐานข้อมูลของคุณไม่รองรับการintersectสืบค้นข้อมูลข้างต้นสามารถใช้งานได้อย่างง่ายดายด้วยการสืบค้นดังต่อไปนี้:

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)

หากคุณต้องการดำเนินการทางแยกข้ามสองตารางที่แตกต่างกันโดยใช้ฐานข้อมูลที่ไม่สนับสนุนการค้นหาทางแยกคุณจะต้องสร้างการเข้าร่วมในทุกคอลัมน์ของตาราง


2
@Fluffeh คำตอบที่ดี ฉันมีข้อเสนอแนะ: ถ้าคุณต้องการทำให้มันเป็น SQL Tutorial คุณจะพลาดที่จะเพิ่ม Venn Diagrams เท่านั้น ฉันเข้าใจได้ทันทีจากซ้ายและขวาเข้าร่วมขอบคุณพวกเขา คำขอส่วนบุคคล: คุณมีการสอนเกี่ยวกับข้อผิดพลาดทั่วไป / การปรับแต่งประสิทธิภาพหรือไม่?
StrayChild01

25
พุทโธ่. ล้อเลื่อนของฉันเสีย คำถามและคำตอบที่ดี ฉันหวังว่าฉันจะสามารถลงคะแนน 10 ครั้งนี้
Amal Murali

3
ฮิฮิขอบคุณสำหรับการตอบรับเชิงบวก เลื่อนไปเรื่อย ๆ นี่เป็นเพียงคำตอบแรก ดังนั้นกล่าวว่าคำตอบของฉันเป็นเวลานานเกินไปให้พอดีมันกลายเป็น "คำตอบ" อย่างใดอย่างหนึ่งเพื่อให้ฉันได้ใช้ไม่กี่ :)
Fluffeh

7
สุจริตฉันคิดว่าคำตอบนี้จะต้องสั้นลงบ้าง
einpoklum

บทความที่ยอดเยี่ยม ฐานข้อมูลร่วม 101
maqs

101

ตกลงฉันพบว่าโพสต์นี้น่าสนใจมากและฉันต้องการแบ่งปันความรู้ของฉันเกี่ยวกับการสร้างแบบสอบถาม ขอบคุณสำหรับFluffehนี้ คนอื่น ๆ ที่อาจอ่านบทความนี้และอาจรู้สึกว่าฉันผิด 101% สามารถแก้ไขและวิจารณ์คำตอบของฉันได้ ( สุจริตฉันรู้สึกขอบคุณมากที่แก้ไขข้อผิดพลาดของฉัน )

ฉันจะโพสต์คำถามที่พบบ่อยในMySQLแท็ก


Trick No. 1 ( แถวที่ตรงกับหลายเงื่อนไข )

รับสคีมานี้

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');

คำถาม

ค้นหาภาพยนตร์ทั้งหมดที่เป็นของทั้งสอง อย่างComedyและRomanceประเภท

สารละลาย

บางครั้งคำถามนี้อาจซับซ้อนมาก ดูเหมือนว่าคำค้นหาเช่นนี้จะเป็นคำตอบ: -

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

การสาธิต SQLFiddle

ซึ่งแน่นอนผิดมากเพราะมันผลิตไม่มีผล คำอธิบายของเรื่องนี้ก็คือว่ามีเพียงคนเดียวคือค่าที่ถูกต้องของCategoryNameในแต่ละแถว ตัวอย่างเช่นเงื่อนไขแรกคืนค่าจริงเงื่อนไขที่สองเป็นเท็จเสมอ ดังนั้นโดยใช้ANDโอเปอเรเตอร์เงื่อนไขทั้งสองควรเป็นจริง มิฉะนั้นจะเป็นเท็จ แบบสอบถามอื่นเป็นเช่นนี้

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

การสาธิต SQLFiddle

และผลที่ได้ยังคงไม่ถูกต้องเพราะมันตรงกับการบันทึกที่มีอย่างน้อยcategoryNameหนึ่งในการแข่งขันบน ทางออกที่แท้จริง จะเป็นโดยการนับจำนวนของการบันทึกกรณีต่อภาพยนตร์ จำนวนอินสแตนซ์ควรตรงกับจำนวนรวมของค่าที่กำหนดในเงื่อนไข

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 (คำตอบ)


Trick No. 2 ( บันทึกสูงสุดสำหรับแต่ละรายการ )

รับสคี

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');

คำถาม

ค้นหาเวอร์ชันล่าสุดของซอฟต์แวร์แต่ละตัว แสดงคอลัมน์ต่อไปนี้: SoftwareName, Descriptions, LatestVersion( จากคอลัมน์ VersionNo )DateReleased

สารละลาย

นักพัฒนา SQL บางคนใช้MAX()ฟังก์ชั่นรวมโดยไม่ตั้งใจ พวกเขามีแนวโน้มที่จะสร้างเช่นนี้

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

การสาธิต SQLFiddle

( RDBMS ส่วนใหญ่สร้างข้อผิดพลาดทางไวยากรณ์ในเรื่องนี้เพราะไม่ได้ระบุคอลัมน์ที่ไม่รวมบางส่วนในส่วนgroup byคำสั่ง ) ผลลัพธ์จะสร้างความถูกต้องLatestVersionในแต่ละซอฟต์แวร์ แต่เห็นได้ชัดว่าDateReleasedไม่ถูกต้อง MySQLไม่รองรับWindow FunctionsและCommon Table Expressionยังเป็น RDBMS บางอย่างแล้ว วิธีแก้ปัญหาในปัญหานี้คือการสร้างวิธีการsubqueryที่ได้รับสูงสุดแต่ละรายการversionNoในแต่ละซอฟต์แวร์และหลังจากนั้นจะเข้าร่วมในตารางอื่น ๆ

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 (คำตอบ)


นั่นก็คือ ฉันจะโพสต์อีกครั้งทันทีที่ฉันจำคำถามที่พบบ่อยอื่น ๆบนMySQLแท็ก ขอบคุณที่อ่านบทความเล็กน้อย ฉันหวังว่าคุณจะได้รับความรู้เล็ก ๆ น้อย ๆ จากนี้

อัพเดท 1


เคล็ดลับหมายเลข 3 (ค้นหาระเบียนล่าสุดระหว่างสอง ID )

รับสคี

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');

คำถาม

ค้นหาการสนทนาล่าสุดระหว่างผู้ใช้สองคน

สารละลาย

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
)

การสาธิต SQLFiddle


! น่ากลัว คำเตือนจอห์นโซลูชั่นแรกของคุณใช้งานได้เฉพาะเพราะมีข้อ จำกัด ที่ไม่ซ้ำกันในสองฟิลด์ คุณอาจใช้วิธีแก้ไขปัญหาทั่วไปเพิ่มเติมเพื่อช่วยแก้ไขปัญหาทั่วไป ในความคิดของฉันเพียงการแก้ปัญหาคือการทำเลือกสำหรับแต่ละและcomedy ไม่เหมาะแล้ว ..romanceHaving
nawfal

@nawfal ไม่ได้จริงๆถ้าไม่ได้เพิ่มข้อ จำกัด ที่ไม่ซ้ำกันคุณต้องเพิ่มdistinctคำสั่งSQLFiddle Demo ที่มีอยู่ : D
John Woo

63

ส่วนที่ 2 - คำถามย่อย

โอเคตอนนี้หัวหน้าระเบิดอีกครั้ง - ฉันต้องการรายชื่อรถยนต์ทั้งหมดของเราที่มีตราสินค้าและจำนวนทั้งหมดของแบรนด์นั้นที่เรามี!

นี่เป็นโอกาสที่ดีในการใช้เคล็ดลับต่อไปในกระเป๋าสารพัด SQL ของเรา - แบบสอบถามย่อย หากคุณไม่คุ้นเคยกับคำนั้นแบบสอบถามย่อยคือแบบสอบถามที่ทำงานภายในแบบสอบถามอื่น มีหลายวิธีในการใช้

สำหรับคำขอของเราก่อนอื่นให้รวบรวมคำง่ายๆที่จะแสดงรายการรถยนต์แต่ละคันและแบรนด์:

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

ตอนนี้ถ้าเราต้องการเพียงแค่นับรถยนต์ที่เรียงลำดับตามยี่ห้อเราก็สามารถเขียนสิ่งนี้ได้:

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

ดังนั้นเราควรจะสามารถเพิ่มฟังก์ชั่นการนับในแบบสอบถามต้นฉบับของเราได้ใช่ไหม

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)

น่าเศร้าที่เราไม่สามารถทำเช่นนั้นได้ เหตุผลก็คือเมื่อเราเพิ่มในรหัสรถ (คอลัมน์ a.ID) เราจะต้องเพิ่มเข้าไปในกลุ่มโดย - ดังนั้นตอนนี้เมื่อฟังก์ชั่นการนับงานมีเพียงหนึ่ง ID ที่ตรงกันต่อ ID

นี่คือที่ที่เราสามารถใช้เคียวรีย่อยได้ แต่ในความเป็นจริงเราสามารถทำเคียวรีย่อยสองประเภทที่แตกต่างกันโดยสิ้นเชิงซึ่งจะให้ผลลัพธ์ที่เหมือนกันกับที่เราต้องการ ข้อแรกคือการใส่ข้อความค้นหาย่อยไว้ในselectประโยค ซึ่งหมายความว่าทุกครั้งที่เราได้รับแถวข้อมูลแบบสอบถามย่อยจะหายไปรับคอลัมน์ของข้อมูลแล้วนำไปเก็บไว้ในแถวข้อมูลของเรา

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)

และบำ! นี่จะทำให้เรา หากคุณสังเกตว่าแบบสอบถามย่อยนี้จะต้องทำงานสำหรับข้อมูลทุกแถวที่เราส่งคืน แม้แต่ในตัวอย่างเล็ก ๆ น้อย ๆ นี้เรามีเพียงห้ายี่ห้อรถที่แตกต่างกัน แต่คำถามย่อยวิ่งสิบเอ็ดครั้งเนื่องจากเรามีข้อมูลสิบเอ็ดแถวที่เรากลับมา ดังนั้นในกรณีนี้ดูเหมือนว่าวิธีเขียนโค้ดที่มีประสิทธิภาพที่สุด

สำหรับวิธีอื่นให้เรียกใช้แบบสอบถามย่อยและแกล้งทำเป็นว่าเป็นตาราง:

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)

ตกลงดังนั้นเราจึงมีผลลัพธ์เดียวกัน (สั่งแตกต่างกันเล็กน้อย - ดูเหมือนว่าฐานข้อมูลต้องการส่งคืนผลลัพธ์ที่เรียงลำดับตามคอลัมน์แรกที่เราเลือกในครั้งนี้) - แต่ตัวเลขที่ถูกต้องเหมือนกัน

ดังนั้นอะไรคือความแตกต่างระหว่างสอง - และเมื่อเราควรใช้แบบสอบถามย่อยแต่ละประเภท? ก่อนอื่นให้ตรวจสอบให้แน่ใจว่าเราเข้าใจว่าการสืบค้นที่สองนั้นทำงานอย่างไร เราเลือกสองตารางในส่วนfromคำสั่งของเราแล้วเขียนแบบสอบถามและบอกฐานข้อมูลว่าจริงๆแล้วมันเป็นตารางแทนซึ่งฐานข้อมูลมีความสุขอย่างสมบูรณ์ มีสามารถเป็นประโยชน์บางอย่างที่จะใช้วิธีการนี้ (เช่นเดียวกับข้อ จำกัด บางอย่าง) สำคัญที่สุดก็คือว่าแบบสอบถามย่อยนี้วิ่งครั้งเดียว หากฐานข้อมูลของเรามีข้อมูลจำนวนมากอาจมีการปรับปรุงอย่างมากในวิธีแรก อย่างไรก็ตามในขณะที่เรากำลังใช้สิ่งนี้เป็นตารางเราจะต้องนำแถวข้อมูลเข้ามาเพิ่มเติมเพื่อให้พวกเขาสามารถเข้าร่วมกลับไปยังแถวข้อมูลของเราได้ เราก็ต้องแน่ใจว่ามีเพียงพอแถวข้อมูลถ้าเราจะใช้การเข้าร่วมแบบง่ายในแบบสอบถามด้านบน หากคุณจำการเข้าร่วมจะดึงเฉพาะแถวที่มีข้อมูลที่ตรงกันทั้งสองด้านของการเข้าร่วม หากเราไม่ระวังนี่อาจส่งผลให้ข้อมูลที่ถูกต้องไม่ถูกส่งคืนจากตารางรถยนต์ของเราหากไม่มีแถวที่ตรงกันในแบบสอบถามย่อยนี้

ตอนนี้เมื่อมองย้อนกลับไปยังคิวรีย่อยแรกมีข้อ จำกัด เช่นกัน เพราะเรามีการดึงข้อมูลกลับเข้ามาในแถวเดียวเราสามารถเฉพาะดึงกลับหนึ่งแถวของข้อมูล subqueries ใช้ในselectประโยคของแบบสอบถามมากมักจะใช้เฉพาะฟังก์ชันการรวมเช่นsum, count, maxหรืออื่นรวมฟังก์ชั่นที่คล้ายกัน พวกเขาไม่จำเป็นต้องทำ แต่นั่นมักจะเป็นวิธีการเขียน

ดังนั้นก่อนที่เราจะเดินหน้าต่อไปให้เราดูอย่างรวดเร็วว่าเราสามารถใช้แบบสอบถามย่อยได้ที่ไหน เราสามารถใช้มันในwhereข้อ - ตอนนี้ตัวอย่างนี้มีการประดิษฐ์เล็กน้อยในฐานข้อมูลของเรามีวิธีที่ดีกว่าในการรับข้อมูลต่อไปนี้ แต่เมื่อเห็นว่ามันเป็นเพียงตัวอย่างให้ดูได้:

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

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

สิ่งนี้จะส่งคืนรายการรหัสแบรนด์และชื่อแบรนด์ให้เรา (คอลัมน์ที่สองจะถูกเพิ่มเพื่อแสดงแบรนด์เท่านั้น) ที่มีตัวอักษรoในชื่อ

ตอนนี้เราสามารถใช้ผลลัพธ์ของแบบสอบถามนี้ในส่วนคำสั่งนี้:

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)

อย่างที่คุณเห็นแม้ว่าแบบสอบถามย่อยจะส่งคืน ID แบรนด์สามรายการ แต่ตารางรถยนต์ของเรามีเพียงรายการสองรายการเท่านั้น

ในกรณีนี้สำหรับรายละเอียดเพิ่มเติมแบบสอบถามย่อยจะทำงานเหมือนกับว่าเราเขียนโค้ดต่อไปนี้:

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)

อีกครั้งคุณสามารถดูว่าแบบสอบถามย่อย vs อินพุตด้วยตนเองมีการเปลี่ยนแปลงลำดับของแถวอย่างไรเมื่อกลับจากฐานข้อมูล

ขณะที่เรากำลังพูดถึงคำถามย่อยให้ดูว่ามีอะไรอื่นที่เราสามารถทำได้ด้วยแบบสอบถามย่อย:

  • คุณสามารถวางแบบสอบถามย่อยภายในแบบสอบถามย่อยอื่น ๆ และอื่น ๆ มีขีด จำกัด ซึ่งขึ้นอยู่กับฐานข้อมูลของคุณ แต่มีฟังก์ชั่นการเรียกซ้ำของโปรแกรมเมอร์ที่บ้าและบ้าคลั่งคนส่วนใหญ่จะไม่ถึงขีด จำกัด นั้น
  • คุณสามารถวางแบบสอบถามย่อยจำนวนหนึ่งไว้ในแบบสอบถามเดี่ยวสองสามselectประโยคในบางfromประโยคในประโยคย่อยและอีกสองสามwhereประโยค - โปรดจำไว้ว่าแต่ละคำถามที่คุณใส่ไว้นั้นทำให้แบบสอบถามของคุณซับซ้อนและมีแนวโน้มที่จะใช้เวลานานกว่า ปฏิบัติ

หากคุณจำเป็นต้องเขียนโค้ดที่มีประสิทธิภาพจะเป็นประโยชน์ในการเขียนแบบสอบถามหลายวิธีและดู (โดยกำหนดเวลาหรือโดยใช้อธิบายแผน) ซึ่งเป็นแบบสอบถามที่ดีที่สุดเพื่อให้ได้ผลลัพธ์ของคุณ วิธีแรกที่ใช้งานได้อาจไม่ใช่วิธีที่ดีที่สุดในการทำ


สำคัญมากสำหรับผู้พัฒนาใหม่: แบบสอบถามย่อยอาจทำงานหนึ่งครั้งสำหรับทุกผลลัพธ์เว้นแต่คุณจะสามารถใช้แบบสอบถามย่อยเป็นการเข้าร่วม (แสดงไว้ด้านบน)
Xeoncross

59

ส่วนที่ 3 - เทคนิคและรหัสที่มีประสิทธิภาพ

MySQL in () มีประสิทธิภาพ

ฉันคิดว่าฉันจะเพิ่มบิตพิเศษสำหรับเคล็ดลับและลูกเล่นที่เกิดขึ้น

คำถามหนึ่งที่ฉันเห็นว่าค่อนข้างยุติธรรมคือฉันจะหาแถวที่ไม่ตรงกันจากสองตารางได้อย่างไรและฉันเห็นคำตอบที่ได้รับการยอมรับมากที่สุดเช่นเดียวกับสิ่งต่อไปนี้ (ตามตารางรถยนต์และแบรนด์ของเรา - ซึ่งมีโฮลเดนระบุว่าเป็น แบรนด์ แต่ไม่ปรากฏในตารางรถยนต์):

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

และใช่มันจะทำงาน

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

อย่างไรก็ตามไม่มีประสิทธิภาพในฐานข้อมูลบางอย่าง นี่คือลิงก์ไปยังคำถาม Stack Overflow ที่ถามถึงและนี่คือบทความเชิงลึกที่ยอดเยี่ยมหากคุณต้องการเข้าไปสู่ความกล้าหาญ

คำตอบสั้น ๆ คือหากเครื่องมือเพิ่มประสิทธิภาพไม่สามารถจัดการได้อย่างมีประสิทธิภาพอาจเป็นการดีกว่าถ้าคุณใช้แบบสอบถามดังต่อไปนี้เพื่อให้ได้แถวที่ไม่ตรงกัน:

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)

อัปเดตตารางด้วยตารางเดียวกันในแบบสอบถามย่อย

Ahhh คนแก่อีกคน แต่เป็นคนดี - คนแก่ คุณไม่สามารถระบุ 'แบรนด์' ตารางเป้าหมายสำหรับการอัปเดตในส่วนคำสั่งได้

MySQL จะไม่อนุญาตให้คุณเรียกใช้update...แบบสอบถามด้วยการเลือกย่อยในตารางเดียวกัน ตอนนี้คุณอาจจะคิดว่าทำไมไม่เพียงตบมันเข้าไปในที่ที่ถูกต้องประโยค? แต่ถ้าคุณต้องการอัปเดตเฉพาะแถวที่มีmax()วันที่จำนวนแถวอื่น ๆ คุณไม่สามารถทำเช่นนั้นได้อย่างแน่นอนในประโยคที่

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

ดังนั้นเราไม่สามารถทำเช่นนั้นใช่มั้ย ไม่อย่างนั้น มีวิธีหลีกเลี่ยงไม่ได้ที่ผู้ใช้จำนวนมากอย่างแปลกใจไม่รู้ - แม้ว่ามันจะมีการแฮกเกอร์บางอย่างที่คุณจะต้องให้ความสนใจ

คุณสามารถติดแบบสอบถามย่อยภายในแบบสอบถามย่อยอื่นซึ่งทำให้มีช่องว่างเพียงพอระหว่างแบบสอบถามทั้งสองเพื่อให้มันทำงานได้ อย่างไรก็ตามโปรดทราบว่ามันอาจปลอดภัยที่สุดในการติดแบบสอบถามภายในธุรกรรม - ซึ่งจะป้องกันการเปลี่ยนแปลงอื่น ๆ ที่เกิดขึ้นกับตารางในขณะที่แบบสอบถามกำลังทำงานอยู่

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
เพียงแค่ต้องการที่จะทราบว่าการก่อสร้าง WHERE NOT EXISTS () นั้นค่อนข้างเหมือนกันจาก 'ประสิทธิภาพในการมอง' แต่ในความเห็นของฉันง่ายต่อการอ่าน / ทำความเข้าใจ จากนั้นอีกครั้งความรู้ของฉันถูก จำกัด ที่ MSSQL และฉันไม่สามารถให้คำมั่นสัญญาได้หากสิ่งเดียวกันนี้เป็นจริงในแพลตฟอร์มอื่น
deroby

ฉันเพิ่งลองเปรียบเทียบประเภทนี้เมื่อวันก่อนโดยที่ NOT IN () มีรายการเกี่ยวกับ ID หลายร้อย ID และไม่มีความแตกต่างระหว่างรุ่นนั้นและรุ่นเข้าร่วมของข้อความค้นหา บางทีมันอาจสร้างความแตกต่างเมื่อคุณก้าวเข้าสู่พันหรือพันล้าน
Buttle Butkus

18

คุณสามารถใช้แนวคิดของแบบสอบถามหลายรายการในคำหลักจาก ให้ฉันแสดงตัวอย่างหนึ่ง:

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

คุณสามารถใช้ตารางได้มากเท่าที่คุณต้องการ ใช้การรวมด้านนอกและการรวมที่ใดก็ได้ที่จำเป็นแม้ในคิวรีย่อยของตาราง

นั่นเป็นวิธีที่ง่ายมากในการเชื่อมโยงกับตารางและฟิลด์ได้มาก


8

ความหวังนี้ทำให้มันพบตารางในขณะที่คุณกำลังอ่านเรื่องราว:

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    |                |
+-------+-------------+------+-----+---------+----------------+
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.