INNER JOIN - З'єднання таблиць
INNER JOIN - З'єднання таблиць
Проблема: Як отримати дані з кількох таблиць?
У попередньому файлі ми розбили дані на множинні таблиці. Тепер виникає питання: як отримати інформацію з них?
Припустимо, маємо такі таблиці:
-- Кафедри
CREATE TABLE Departments (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(50),
Phone NVARCHAR(20)
);
-- Викладачі
CREATE TABLE Teachers (
Id INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(50),
LastName NVARCHAR(50),
DepartmentId INT, -- FK → Departments.Id
FOREIGN KEY (DepartmentId) REFERENCES Departments(Id)
);
Задача: Отримати список викладачів з назвою їх кафедри (не просто Id!).
Department.Name з таблиці Teachers - цього поля там немає!Рішення: JOIN - операція з'єднання таблиць.
1. Що таке JOIN?
JOIN — операція, що об'єднує рядки з двох або більше таблиць на основі зв'язку між ними.
Концепція: З'єднуємо таблиці по спільному стовпцю (зазвичай PK = FK).
2. INNER JOIN: Базовий синтаксис
2.1. Explicit JOIN (рекомендований)
SELECT column1, column2, ...
FROM table1
INNER JOIN table2
ON table1.column = table2.column;
Приклад:
SELECT
Teachers.FirstName,
Teachers.LastName,
Departments.Name AS DepartmentName
FROM Teachers
INNER JOIN Departments
ON Teachers.DepartmentId = Departments.Id;
2.2. Implicit JOIN (застарілий)
SELECT column1, column2, ...
FROM table1, table2
WHERE table1.column = table2.column;
Той самий приклад (implicit):
SELECT
Teachers.FirstName,
Teachers.LastName,
Departments.Name AS DepartmentName
FROM Teachers, Departments
WHERE Teachers.DepartmentId = Departments.Id;
SELECT T.FirstName, D.Name
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
-- Чітко видно JOIN умову
-- ON відокремлює умову з'єднання від WHERE
SELECT T.FirstName, D.Name
FROM Teachers AS T, Departments AS D
WHERE T.DepartmentId = D.Id;
-- Умова з'єднання змішана з WHERE фільтрами
-- Менш читабельний
3. Як працює INNER JOIN?
3.1. Діаграма Венна
INNER JOIN повертає тільки ті рядки, де є збіг в обох таблицях.
3.2. Візуалізація процесу
Teachers Departments
┌────┬─────────┬────────┐ ┌────┬──────────────┐
│ Id │ Name │ DeptId │ │ Id │ Name │
├────┼─────────┼────────┤ ├────┼──────────────┤
│ 1 │ Sophia │ 1 │──│ 1 │ Software Dev │
│ 2 │ Henry │ 1 │──│ 2 │ Mathematics │
│ 3 │ Emma │ 2 │──│ 3 │ Physics │
│ 4 │ John │ NULL │ └────┴──────────────┘
└────┴─────────┴────────┘
↓ INNER JOIN ↓
Результат (тільки збіги!)
┌─────────┬──────────────┐
│ Name │ DeptName │
├─────────┼──────────────┤
│ Sophia │ Software Dev │
│ Henry │ Software Dev │
│ Emma │ Mathematics │
└─────────┴──────────────┘
John НЕ включений (DeptId = NULL, немає збігу)
Physics НЕ включена (немає викладачів)
4. Анатомія INNER JOIN запиту
SELECT
T.FirstName + ' ' + T.LastName AS FullName, -- 1
D.Name AS DepartmentName, -- 2
D.Phone AS DeptPhone -- 3
FROM Teachers AS T -- 4
INNER JOIN Departments AS D -- 5
ON T.DepartmentId = D.Id -- 6
WHERE T.FirstName LIKE 'S%' -- 7
ORDER BY T.LastName; -- 8
Пояснення:
T. та D.TDepartments з псевдонімом DFK = PK5. Table Aliases (Псевдоніми таблиць)
5.1. Навіщо потрібні?
Проблема: Повні імена таблиць довгі та незручні.
-- ❌ Без псевдонімів (важко читати)
SELECT
Teachers.FirstName,
Teachers.LastName,
Departments.Name
FROM Teachers
INNER JOIN Departments
ON Teachers.DepartmentId = Departments.Id;
Рішення: Використовувати aliases (псевдоніми).
-- ✅ З псевдонімами (читабельно)
SELECT
T.FirstName,
T.LastName,
D.Name
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
5.2. Синтаксис aliases
FROM TableName AS Alias
-- Або без AS
FROM TableName Alias
Приклади:
FROM Teachers AS T -- повний синтаксис
FROM Teachers T -- скорочений (також валідний)
FROM Students AS S
FROM Departments AS Dept -- псевдонім може бути довгим
- Коротко:
T,D,S,G - Описово:
Teach,Dept,Stud,Grp - Уникайте надто загальних:
A,B,C
5.3. Обов'язкове використання
Якщо стовпець існує в обох таблицях, обов'язково вказувати префікс:
-- ❌ Помилка: Ambiguous column name 'Id'
SELECT Id, FirstName, Name
FROM Teachers
INNER JOIN Departments
ON Teachers.DepartmentId = Departments.Id;
-- ✅ Правильно: вказуємо таблицю
SELECT
T.Id AS TeacherId, -- з Teachers
D.Id AS DepartmentId, -- з Departments
T.FirstName,
D.Name AS DepartmentName
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
6. Приклади INNER JOIN
6.1. Простий JOIN двох таблиць
Задача: Показати викладачів з їх кафедрами.
-- Sample data
INSERT INTO Departments (Name, Phone) VALUES
('Software Development', '48-21'),
('Mathematics', '51-33'),
('Physics', '52-44');
INSERT INTO Teachers (FirstName, LastName, DepartmentId) VALUES
('Sophia', 'Nelson', 1),
('Henry', 'MacAlister', 1),
('Emma', 'Kirk', 2);
-- INNER JOIN запит
SELECT
T.FirstName + ' ' + T.LastName AS Teacher,
D.Name AS Department,
D.Phone
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id
ORDER BY D.Name, T.LastName;
Результат:
| Teacher | Department | Phone |
|---|---|---|
| Emma Kirk | Mathematics | 51-33 |
| Henry MacAlister | Software Development | 48-21 |
| Sophia Nelson | Software Development | 48-21 |
6.2. JOIN з додатковими умовами (WHERE)
Задача: Показати викладачів кафедри "Software Development".
SELECT
T.FirstName,
T.LastName,
D.Name AS Department
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id
WHERE D.Name = 'Software Development';
Результат:
| FirstName | LastName | Department |
|---|---|---|
| Sophia | Nelson | Software Development |
| Henry | MacAlister | Software Development |
Логічний порядок виконання:
FROM Teachers- беремо основну таблицюINNER JOIN Departments ON ...- з'єднуємо з DepartmentsWHERE D.Name = ...- фільтруємо результатSELECT- вибираємо стовпці
6.3. JOIN трьох таблиць
Схема БД:
Departments ──1:N── Teachers ──N:M── Subjects
│
TeachersSubjects
(junction table)
Задача: Показати, які предмети викладає кожен викладач.
-- Створюємо Subjects
CREATE TABLE Subjects (
Id INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(50) NOT NULL
);
-- Junction table
CREATE TABLE TeachersSubjects (
TeacherId INT NOT NULL,
SubjectId INT NOT NULL,
PRIMARY KEY (TeacherId, SubjectId),
FOREIGN KEY (TeacherId) REFERENCES Teachers(Id),
FOREIGN KEY (SubjectId) REFERENCES Subjects(Id)
);
-- Sample data
INSERT INTO Subjects (Name) VALUES
('C#'), ('Java'), ('Databases'), ('Web Development');
INSERT INTO TeachersSubjects (TeacherId, SubjectId) VALUES
(1, 1), -- Sophia → C#
(1, 2), -- Sophia → Java
(2, 3), -- Henry → Databases
(3, 1); -- Emma → C#
-- JOIN трьох таблиць
SELECT
T.FirstName + ' ' + T.LastName AS Teacher,
S.Name AS Subject
FROM Teachers AS T
INNER JOIN TeachersSubjects AS TS
ON T.Id = TS.TeacherId
INNER JOIN Subjects AS S
ON TS.SubjectId = S.Id
ORDER BY Teacher, Subject;
Результат:
| Teacher | Subject |
|---|---|
| Emma Kirk | C# |
| Henry MacAlister | Databases |
| Sophia Nelson | C# |
| Sophia Nelson | Java |
Візуалізація з'єднання:
Teachers TeachersSubjects Subjects
┌───┬────────┐ ┌──────┬─────────┐ ┌───┬──────────┐
│ID │ Name │ │T_Id │ S_Id │ │ID │ Name │
├───┼────────┤ ├──────┼─────────┤ ├───┼──────────┤
│1 │Sophia │──│ 1 │ 1 │──┬──│1 │ C# │
│2 │Henry │──│ 1 │ 2 │──┼──│2 │ Java │
│3 │Emma │──│ 2 │ 3 │──┼──│3 │Databases │
└───┴────────┘ │ 3 │ 1 │──┘ └───┴──────────┘
└──────┴─────────┘
6.4. JOIN чотирьох таблиць
Задача: Показати, які предмети викладаються на кожній кафедрі.
SELECT
D.Name AS Department,
S.Name AS Subject,
COUNT(DISTINCT T.Id) AS TeachersCount
FROM Departments AS D
INNER JOIN Teachers AS T
ON D.Id = T.DepartmentId
INNER JOIN TeachersSubjects AS TS
ON T.Id = TS.TeacherId
INNER JOIN Subjects AS S
ON TS.SubjectId = S.Id
GROUP BY D.Name, S.Name
ORDER BY D.Name, S.Name;
Результат:
| Department | Subject | TeachersCount |
|---|---|---|
| Mathematics | C# | 1 |
| Software Development | C# | 1 |
| Software Development | Databases | 1 |
| Software Development | Java | 1 |
7. INNER JOIN з обчисленнями
Задача: Додати вік викладача.
SELECT
T.FirstName + ' ' + T.LastName AS Teacher,
D.Name AS Department,
T.BirthDate,
DATEDIFF(YEAR, T.BirthDate, GETDATE()) AS Age
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id
WHERE DATEDIFF(YEAR, T.BirthDate, GETDATE()) > 30
ORDER BY Age DESC;
8. Декартове представлення (Cartesian Product)
Критична помилка: Забути вказати умову ON!
-- ❌ ПОМИЛКА: Немає ON
SELECT
T.FirstName,
D.Name
FROM Teachers AS T
INNER JOIN Departments AS D;
-- Або implicit:
FROM Teachers, Departments;
Результат: Декартове представлення - кожен рядок з Teachers з'єднується з кожним рядком з Departments!
3 Teachers × 3 Departments = 9 рядків (замість 3!)
Приклад результату:
| FirstName | Name |
|---|---|
| Sophia | Software Development |
| Sophia | Mathematics |
| Sophia | Physics |
| Henry | Software Development |
| Henry | Mathematics |
| Henry | Physics |
| Emma | Software Development |
| Emma | Mathematics |
| Emma | Physics |
- Неправильні дані
- Величезна кількість рядків (Tables: 1000 × 1000 = 1,000,000!)
- Повільні запити
9. Performance Tips
9.1. Використовуйте індекси
-- Створіть індекс на FK стовпці
CREATE INDEX IX_Teachers_DepartmentId
ON Teachers(DepartmentId);
-- JOIN буде швидшим!
9.2. SELECT тільки потрібні стовпці
-- ❌ Погано
SELECT *
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
-- ✅ Добре
SELECT T.FirstName, T.LastName, D.Name
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
9.3. Фільтруйте ДО JOIN (якщо можливо)
-- Якщо фільтр тільки на одну таблицю, краще фільтрувати ДО JOIN
SELECT T.FirstName, D.Name
FROM (
SELECT * FROM Teachers WHERE FirstName LIKE 'S%'
) AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id;
-- Але зазвичай SQL Server оптимізує це автоматично
10. Best Practices
1. Explicit JOIN
-- ✅ Використовуйте
INNER JOIN ... ON
-- ❌ Уникайте
FROM table1, table2 WHERE
2. Aliases завжди
-- Навіть для двох таблиць
FROM Teachers AS T
INNER JOIN Departments AS D
3. Умова ON обов'язкова
-- Завжди вказуйте умову з'єднання
ON T.DepartmentId = D.Id
4. WHERE після ON
-- ON - умова з'єднання
-- WHERE - фільтр результату
INNER JOIN ... ON ...
WHERE condition
11. Практичні завдання
Створіть запит, що показує студентів з назвою їх групи.
Таблиці:
Students (Id, FirstName, LastName, GroupId)Groups (Id, Name)
💡 Розв'язок
SELECT
S.FirstName + ' ' + S.LastName AS Student,
G.Name AS GroupName
FROM Students AS S
INNER JOIN Groups AS G
ON S.GroupId = G.Id
ORDER BY G.Name, S.LastName;
Показати студентів групи "31PR11" з їх віком.
💡 Розв'язок
SELECT
S.FirstName,
S.LastName,
G.Name AS GroupName,
DATEDIFF(YEAR, S.BirthDate, GETDATE()) AS Age
FROM Students AS S
INNER JOIN Groups AS G
ON S.GroupId = G.Id
WHERE G.Name = '31PR11'
ORDER BY Age DESC;
Показати студентів, їх групи та факультети.
Таблиці:
Students (Id, FirstName, LastName, GroupId)Groups (Id, Name, FacultyId)Faculties (Id, Name)
💡 Розв'язок
SELECT
S.FirstName + ' ' + S.LastName AS Student,
G.Name AS GroupName,
F.Name AS Faculty
FROM Students AS S
INNER JOIN Groups AS G
ON S.GroupId = G.Id
INNER JOIN Faculties AS F
ON G.FacultyId = F.Id
ORDER BY F.Name, G.Name, S.LastName;
Порахувати кількість студентів у кожній групі та показати факультет.
💡 Розв'язок
SELECT
G.Name AS GroupName,
F.Name AS Faculty,
COUNT(S.Id) AS StudentsCount
FROM Groups AS G
INNER JOIN Faculties AS F
ON G.FacultyId = F.Id
LEFT JOIN Students AS S -- LEFT, бо може не бути студентів
ON G.Id = S.GroupId
GROUP BY G.Name, F.Name
ORDER BY StudentsCount DESC;
Показати викладачів кафедри "Software Development", які викладають "C#" або "Java".
Таблиці: Teachers, Departments, TeachersSubjects, Subjects
💡 Розв'язок
SELECT DISTINCT
T.FirstName + ' ' + T.LastName AS Teacher,
D.Name AS Department,
S.Name AS Subject
FROM Teachers AS T
INNER JOIN Departments AS D
ON T.DepartmentId = D.Id
INNER JOIN TeachersSubjects AS TS
ON T.Id = TS.TeacherId
INNER JOIN Subjects AS S
ON TS.SubjectId = S.Id
WHERE D.Name = 'Software Development'
AND S.Name IN ('C#', 'Java')
ORDER BY Teacher, Subject;
Резюме
- Синтаксис:
FROM table1 INNER JOIN table2 ON condition - Результат: Тільки збіги з обох таблиць
- ON vs WHERE:
ON- умова з'єднання (PK = FK)WHERE- фільтр результату
- Aliases: Завжди використовуйте для читабельності
- Множинні JOIN: Можна з'єднувати 3, 4, 5+ таблиць
- Performance: Індекси на FK покращують швидкість
- Декартове представлення: Катастрофа, уникайте (завжди вказуйте ON!)
- Отримання даних з пов'язаних таблиць
- Показ "повної картини" (Student + Group + Faculty)
- Коли потрібні тільки збіги
Попередня тема: Зв'язки та нормалізація
Наступна тема: OUTER JOINs - LEFT, RIGHT, FULL