SQL Joins: Complete Gids met Praktische Voorbeelden
Leer alle SQL join types: INNER, LEFT, RIGHT, FULL, CROSS en SELF JOIN. Met praktische voorbeelden en wanneer je welke join gebruikt.
Zoek je SQL experts?
Vind ervaren Data Analysts en Database Developers voor je data projecten
Inhoudsopgave
- Wat zijn SQL Joins?
- INNER JOIN - Alleen matching records
- LEFT JOIN - Alle records van linker tabel
- RIGHT JOIN - Alle records van rechter tabel
- FULL OUTER JOIN - Alle records van beide tabellen
- CROSS JOIN - Cartesiaans product
- SELF JOIN - Tabel met zichzelf joinen
- Multiple Joins - Meerdere tabellen
- Performance tips en best practices
- Praktijkvoorbeelden uit echte scenario's
1. Wat zijn SQL Joins?
SQL Join Definitie
SQL Joins worden gebruikt om rijen uit twee of meer tabellen te combineren op basis van een gerelateerde kolom tussen hen. Dit stelt je in staat om data uit meerdere tabellen in één query op te halen.
Relational Databases
Tabellen zijn verbonden via primary en foreign keys
Data Normalization
Voorkomt data duplicatie en zorgt voor consistentie
Query Flexibility
Haal complexe datasets op met één query
Performance
Efficiëntere data retrieval dan meerdere queries
| Join Type | Wanneer gebruiken? | Resultaat | Visualisatie |
|---|---|---|---|
| INNER JOIN | Alleen records met matches in beide tabellen | Overlap van beide tabellen | ∩ (Intersection) |
| LEFT JOIN | Alle records van linker tabel + matches van rechter | Volledige linker tabel | ← (Volledig links) |
| RIGHT JOIN | Alle records van rechter tabel + matches van linker | Volledige rechter tabel | → (Volledig rechts) |
| FULL JOIN | Alle records van beide tabellen | Volledige unie van beide tabellen | ∪ (Union) |
| CROSS JOIN | Alle mogelijke combinaties van rijen | Cartesiaans product | × (Multiplication) |
| SELF JOIN | Tabel met zichzelf joinen | Vergelijk rijen binnen dezelfde tabel | ↺ (Self reference) |
Voorbeeld database structuur
Voor deze tutorial gebruiken we een eenvoudige e-commerce database:
-- Database schema voor e-commerce
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100),
registration_date DATE
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
total_amount DECIMAL(10, 2),
status VARCHAR(20),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
category VARCHAR(50),
price DECIMAL(10, 2),
stock_quantity INT
);
CREATE TABLE order_items (
order_item_id INT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10, 2),
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
2. INNER JOIN - Alleen matching records
INNER JOIN Concept
INNER JOIN retourneert alleen de rijen waar er een match is in beide tabellen. Rijen zonder match worden uitgesloten van het resultaat.
INNER JOIN Basis Syntax
-- Basis INNER JOIN syntax
SELECT
table1.column1,
table1.column2,
table2.column1,
table2.column2
FROM table1
INNER JOIN table2
ON table1.matching_column = table2.matching_column;
Praktijkvoorbeeld: Klanten en Orders
-- Haal alle klanten op die minstens één order hebben geplaatst
SELECT
c.customer_id,
c.first_name,
c.last_name,
c.email,
o.order_id,
o.order_date,
o.total_amount,
o.status
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
ORDER BY o.order_date DESC;
INNER JOIN met WHERE clause
-- Klanten met orders in 2025
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
COUNT(o.order_id) AS total_orders,
SUM(o.total_amount) AS total_spent,
MAX(o.order_date) AS last_order_date
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.order_date >= '2025-01-01'
AND o.status = 'Delivered'
GROUP BY
c.customer_id,
c.first_name,
c.last_name
HAVING COUNT(o.order_id) >= 2
ORDER BY total_spent DESC;
3. LEFT JOIN - Alle records van linker tabel
LEFT JOIN Concept
LEFT JOIN (of LEFT OUTER JOIN) retourneert alle rijen van de linker tabel, en de matched rijen van de rechter tabel. Als er geen match is, worden NULL waarden geretourneerd voor de rechter tabel.
LEFT JOIN Basis Syntax
-- Basis LEFT JOIN syntax
SELECT
left_table.column1,
left_table.column2,
right_table.column1,
right_table.column2
FROM left_table
LEFT JOIN right_table
ON left_table.matching_column = right_table.matching_column;
Praktijkvoorbeeld: Alle klanten met hun orders
-- Haal ALLE klanten op, ook die zonder orders
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.email,
c.registration_date,
o.order_id,
o.order_date,
o.total_amount,
o.status,
-- Toon 'No Orders' als klant geen orders heeft
CASE
WHEN o.order_id IS NULL THEN 'No Orders'
ELSE 'Has Orders'
END AS order_status
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
ORDER BY
o.order_date DESC NULLS LAST,
c.registration_date;
LEFT JOIN om niet-aankoopgedrag te vinden
-- Klanten die geregistreerd zijn maar NOOIT een order hebben geplaatst
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.email,
c.registration_date,
'No orders placed' AS status,
-- Dagen sinds registratie
DATEDIFF(DAY, c.registration_date, GETDATE()) AS days_since_registration
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL
AND c.registration_date < DATEADD(MONTH, -3, GETDATE()) -- Meer dan 3 maanden geleden
ORDER BY c.registration_date DESC;
Team nodig voor data projecten?
Vind ervaren SQL Developers en Data Analysts gespecialiseerd in database management
4. RIGHT JOIN - Alle records van rechter tabel
RIGHT JOIN Concept
RIGHT JOIN (of RIGHT OUTER JOIN) retourneert alle rijen van de rechter tabel, en de matched rijen van de linker tabel. Als er geen match is, worden NULL waarden geretourneerd voor de linker tabel.
RIGHT JOIN Basis Syntax
-- Basis RIGHT JOIN syntax
SELECT
left_table.column1,
left_table.column2,
right_table.column1,
right_table.column2
FROM left_table
RIGHT JOIN right_table
ON left_table.matching_column = right_table.matching_column;
Praktijkvoorbeeld: Orders zonder klantgegevens
-- Vind orders waar klantgegevens mogelijk ontbreken in customers tabel
SELECT
o.order_id,
o.order_date,
o.total_amount,
o.status,
o.customer_id AS order_customer_id,
c.customer_id AS customer_table_id,
c.first_name,
c.last_name,
CASE
WHEN c.customer_id IS NULL THEN 'Customer Missing'
ELSE 'Customer Found'
END AS customer_status
FROM customers c
RIGHT JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.customer_id IS NULL
AND o.order_date >= '2025-01-01'
ORDER BY o.order_date DESC;
5. FULL OUTER JOIN - Alle records van beide tabellen
FULL JOIN Concept
FULL OUTER JOIN retourneert alle rijen wanneer er een match is in één van de tabellen. Het combineert de resultaten van zowel LEFT als RIGHT joins.
FULL JOIN Basis Syntax
-- Basis FULL OUTER JOIN syntax
SELECT
table1.column1,
table1.column2,
table2.column1,
table2.column2
FROM table1
FULL OUTER JOIN table2
ON table1.matching_column = table2.matching_column;
Praktijkvoorbeeld: Complete data audit
-- Vind alle data inconsistencies tussen customers en orders
SELECT
COALESCE(c.customer_id, o.customer_id) AS customer_id,
CASE
WHEN c.customer_id IS NOT NULL AND o.customer_id IS NOT NULL THEN 'Match in Both'
WHEN c.customer_id IS NOT NULL AND o.customer_id IS NULL THEN 'Only in Customers'
WHEN c.customer_id IS NULL AND o.customer_id IS NOT NULL THEN 'Only in Orders'
END AS data_status,
c.first_name,
c.last_name,
c.email,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS total_spent,
MAX(o.order_date) AS last_order_date
FROM customers c
FULL OUTER JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY
COALESCE(c.customer_id, o.customer_id),
c.first_name,
c.last_name,
c.email
ORDER BY
CASE
WHEN c.customer_id IS NOT NULL AND o.customer_id IS NOT NULL THEN 1
WHEN c.customer_id IS NOT NULL AND o.customer_id IS NULL THEN 2
ELSE 3
END,
order_count DESC;
6. CROSS JOIN - Cartesiaans product
CROSS JOIN Concept
CROSS JOIN retourneert het Cartesiaans product van beide tabellen - elke rij van de eerste tabel wordt gecombineerd met elke rij van de tweede tabel.
CROSS JOIN Basis Syntax
-- Basis CROSS JOIN syntax
SELECT
table1.column1,
table1.column2,
table2.column1,
table2.column2
FROM table1
CROSS JOIN table2;
Praktijkvoorbeeld: Prijsmatrix genereren
-- Genereer een prijsmatrix voor alle product-korting combinaties
CREATE TABLE discounts (
discount_id INT PRIMARY KEY,
discount_name VARCHAR(50),
discount_percentage DECIMAL(5, 2)
);
-- Voeg voorbeeld discount data toe
INSERT INTO discounts VALUES (1, 'Summer Sale', 10.00);
INSERT INTO discounts VALUES (2, 'Black Friday', 20.00);
INSERT INTO discounts VALUES (3, 'Christmas Special', 15.00);
INSERT INTO discounts VALUES (4, 'New Year', 25.00);
-- Genereer prijsmatrix
SELECT
p.product_id,
p.product_name,
p.category,
p.price AS original_price,
d.discount_name,
d.discount_percentage,
-- Bereken verkoopprijs na korting
ROUND(p.price * (1 - d.discount_percentage/100), 2) AS discounted_price,
-- Bereken kortingsbedrag
ROUND(p.price * (d.discount_percentage/100), 2) AS discount_amount
FROM products p
CROSS JOIN discounts d
WHERE p.category = 'Electronics' -- Filter voor specifieke categorie
ORDER BY
p.product_name,
d.discount_percentage DESC;
7. SELF JOIN - Tabel met zichzelf joinen
SELF JOIN Concept
SELF JOIN is een reguliere join maar de tabel wordt met zichzelf gejoined. Dit is handig voor hiërarchische data of voor het vergelijken van rijen binnen dezelfde tabel.
SELF JOIN Basis Syntax
-- Basis SELF JOIN syntax
SELECT
a.column1,
a.column2,
b.column1,
b.column2
FROM table_name a
JOIN table_name b
ON a.matching_column = b.matching_column;
Praktijkvoorbeeld: Employee hierarchy
-- Maak employees tabel met manager relaties
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
position VARCHAR(50),
department VARCHAR(50),
salary DECIMAL(10, 2),
manager_id INT,
hire_date DATE,
FOREIGN KEY (manager_id) REFERENCES employees(employee_id)
);
-- Vind alle werknemers en hun managers
SELECT
e.employee_id AS employee_id,
CONCAT(e.first_name, ' ', e.last_name) AS employee_name,
e.position AS employee_position,
e.department AS department,
e.salary AS employee_salary,
m.employee_id AS manager_id,
CONCAT(m.first_name, ' ', m.last_name) AS manager_name,
m.position AS manager_position,
-- Bereken salaris verschil met manager
e.salary - m.salary AS salary_difference_from_manager
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.employee_id
ORDER BY
e.department,
e.position,
e.salary DESC;
SELF JOIN voor data quality checks
-- Vind mogelijk duplicate customer records
SELECT
c1.customer_id AS customer1_id,
c1.first_name AS customer1_first_name,
c1.last_name AS customer1_last_name,
c1.email AS customer1_email,
c2.customer_id AS customer2_id,
c2.first_name AS customer2_first_name,
c2.last_name AS customer2_last_name,
c2.email AS customer2_email,
-- Bereken string similarity scores
CASE
WHEN c1.email = c2.email THEN 'Exact Email Match'
WHEN LOWER(c1.first_name) = LOWER(c2.first_name)
AND LOWER(c1.last_name) = LOWER(c2.last_name)
THEN 'Exact Name Match'
WHEN SOUNDEX(c1.first_name) = SOUNDEX(c2.first_name)
AND SOUNDEX(c1.last_name) = SOUNDEX(c2.last_name)
THEN 'Similar Name (Soundex)'
ELSE 'Potential Duplicate'
END AS match_type,
-- Toon match confidence
CASE
WHEN c1.email = c2.email THEN 100
WHEN LOWER(c1.first_name) = LOWER(c2.first_name)
AND LOWER(c1.last_name) = LOWER(c2.last_name)
THEN 90
WHEN SOUNDEX(c1.first_name) = SOUNDEX(c2.first_name)
AND SOUNDEX(c1.last_name) = SOUNDEX(c2.last_name)
THEN 70
ELSE 50
END AS match_confidence
FROM customers c1
INNER JOIN customers c2
ON c1.customer_id < c2.customer_id -- Voorkom dubbele paren en self matches
WHERE (
c1.email = c2.email -- Exact email match
OR (
LOWER(c1.first_name) = LOWER(c2.first_name)
AND LOWER(c1.last_name) = LOWER(c2.last_name)
) -- Exact name match
OR (
SOUNDEX(c1.first_name) = SOUNDEX(c2.first_name)
AND SOUNDEX(c1.last_name) = SOUNDEX(c2.last_name)
) -- Similar names
)
ORDER BY match_confidence DESC, c1.customer_id;
Klaar voor database projecten?
Vind de juiste experts of plaats je SQL/Database vacature
8. Multiple Joins - Meerdere tabellen
Multiple Joins Concept
Multiple Joins laten je toe om drie of meer tabellen in één query te combineren. Dit is essentieel voor complexe data retrieval uit genormaliseerde databases.
Drie tabellen joinen: Klant → Order → Product
-- Complete sales analysis met alle gerelateerde data
SELECT
-- Klant informatie
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.email,
c.registration_date,
-- Order informatie
o.order_id,
o.order_date,
o.total_amount AS order_total,
o.status AS order_status,
-- Product informatie
p.product_id,
p.product_name,
p.category,
-- Order item details
oi.quantity,
oi.unit_price,
-- Bereken regel totaal
oi.quantity * oi.unit_price AS line_total,
-- Calculated fields
CASE
WHEN o.status = 'Delivered' THEN 'Completed'
WHEN o.status IN ('Pending', 'Processing') THEN 'In Progress'
WHEN o.status = 'Cancelled' THEN 'Cancelled'
ELSE 'Other'
END AS order_category,
-- Customer segmentation based on spending
CASE
WHEN SUM(o.total_amount) OVER (PARTITION BY c.customer_id) > 1000 THEN 'VIP Customer'
WHEN SUM(o.total_amount) OVER (PARTITION BY c.customer_id) > 500 THEN 'Regular Customer'
ELSE 'New Customer'
END AS customer_segment
FROM customers c
INNER JOIN orders o
ON c.customer_id = o.customer_id
INNER JOIN order_items oi
ON o.order_id = oi.order_id
INNER JOIN products p
ON oi.product_id = p.product_id
WHERE o.order_date >= '2025-01-01'
AND o.status != 'Cancelled'
ORDER BY
o.order_date DESC,
c.last_name,
c.first_name;
Complexe business intelligence query
-- Geavanceerde sales rapport met meerdere joins en aggregaties
WITH CustomerSales AS (
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.registration_date,
COUNT(DISTINCT o.order_id) AS total_orders,
SUM(o.total_amount) AS total_spent,
MAX(o.order_date) AS last_order_date,
AVG(o.total_amount) AS avg_order_value,
DATEDIFF(DAY, MAX(o.order_date), GETDATE()) AS days_since_last_order
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
GROUP BY
c.customer_id,
c.first_name,
c.last_name,
c.registration_date
),
ProductPerformance AS (
SELECT
p.product_id,
p.product_name,
p.category,
p.price,
SUM(oi.quantity) AS total_units_sold,
SUM(oi.quantity * oi.unit_price) AS total_revenue,
COUNT(DISTINCT o.order_id) AS times_ordered,
COUNT(DISTINCT o.customer_id) AS unique_customers
FROM products p
LEFT JOIN order_items oi
ON p.product_id = oi.product_id
LEFT JOIN orders o
ON oi.order_id = o.order_id
GROUP BY
p.product_id,
p.product_name,
p.category,
p.price
)
SELECT
cs.customer_name,
cs.total_orders,
cs.total_spent,
cs.avg_order_value,
cs.days_since_last_order,
-- Customer status based on activity
CASE
WHEN cs.days_since_last_order < 30 THEN 'Active'
WHEN cs.days_since_last_order < 90 THEN 'At Risk'
ELSE 'Inactive'
END AS customer_status,
-- Top product gekocht door deze klant
(
SELECT TOP 1 pp.product_name
FROM orders o2
INNER JOIN order_items oi2
ON o2.order_id = oi2.order_id
INNER JOIN products pp
ON oi2.product_id = pp.product_id
WHERE o2.customer_id = cs.customer_id
GROUP BY pp.product_name
ORDER BY SUM(oi2.quantity) DESC
) AS favorite_product,
-- Recente order details
(
SELECT TOP 1 CONCAT(
'Order #',
CAST(o3.order_id AS VARCHAR),
' - ',
FORMAT(o3.order_date, 'dd/MM/yyyy'),
' - €',
CAST(o3.total_amount AS VARCHAR(10))
)
FROM orders o3
WHERE o3.customer_id = cs.customer_id
ORDER BY o3.order_date DESC
) AS latest_order
FROM CustomerSales cs
WHERE cs.total_orders > 0
ORDER BY cs.total_spent DESC;
9. Performance tips en best practices
SQL Join Performance Optimalisatie
Indexes gebruiken
- Creëer indexes op join columns
- Gebruik composite indexes voor multi-column joins
- Monitor index usage en performance
Efficient Filteren
- Filter vroeg met WHERE clause
- Vermijd functions in join conditions
- Gebruik EXISTS() ipv IN() voor subqueries
Query Optimalisatie
- LIMIT results voor testing
- Vermijd SELECT *
- Gebruik EXPLAIN/EXPLAIN ANALYZE
Database Design
- Normaliseer data waar mogelijk
- Denormaliseer voor read-heavy workloads
- Gebruik partitioning voor grote tabellen
Index creatie voor join optimalisatie
-- Creëer indexes voor veelgebruikte join columns
-- 1. Primary key indexes (meestal automatisch gecreëerd)
CREATE INDEX idx_customers_id ON customers(customer_id);
CREATE INDEX idx_orders_id ON orders(order_id);
CREATE INDEX idx_products_id ON products(product_id);
-- 2. Foreign key indexes (cruciaal voor join performance)
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
-- 3. Composite indexes voor vaak samen gebruikte columns
CREATE INDEX idx_orders_date_status ON orders(order_date, status);
CREATE INDEX idx_products_category_price ON products(category, price);
CREATE INDEX idx_customers_name_email ON customers(last_name, first_name, email);
-- 4. Covering indexes voor vaak uitgevoerde queries
CREATE INDEX idx_customer_orders_summary ON orders(customer_id, order_date, total_amount, status)
INCLUDE (order_id); -- SQL Server syntax
-- Voor PostgreSQL/Mysql zou het zijn:
-- CREATE INDEX idx_customer_orders_summary ON orders(customer_id, order_date, total_amount, status);
-- 5. Monitor index usage
-- SQL Server
SELECT
OBJECT_NAME(s.object_id) AS table_name,
i.name AS index_name,
s.user_seeks,
s.user_scans,
s.user_lookups,
s.user_updates,
s.last_user_seek,
s.last_user_scan
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i
ON s.object_id = i.object_id
AND s.index_id = i.index_id
WHERE OBJECT_NAME(s.object_id) = 'orders'
ORDER BY s.user_seeks + s.user_scans DESC;
Performance comparison: Goede vs Slechte practices
-- ❌ SLECHTE PRACTICE: Gebruik van functions in join conditions
SELECT c.*, o.*
FROM customers c
INNER JOIN orders o
ON UPPER(c.email) = UPPER(o.customer_email) -- Function op join column
WHERE YEAR(o.order_date) = 2025; -- Function in WHERE clause
-- ✅ GOEDE PRACTICE: Vermijd functions, gebruik indexed columns
SELECT c.*, o.*
FROM customers c
INNER JOIN orders o
ON c.email = o.customer_email -- Direct column match
WHERE o.order_date >= '2025-01-01'
AND o.order_date < '2026-01-01'; -- Range query op indexed column
-- ❌ SLECHTE PRACTICE: Gebruik van SELECT *
SELECT *
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id;
-- ✅ GOEDE PRACTICE: SELECT alleen nodig columns
SELECT
c.customer_id,
c.first_name,
c.last_name,
o.order_date,
p.product_name,
oi.quantity,
oi.unit_price
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date >= '2025-01-01';
10. Praktijkvoorbeelden uit echte scenario's
E-commerce Sales Dashboard Query
-- Complete sales dashboard query voor management rapportage
WITH MonthlySales AS (
SELECT
-- Maand aggregatie
FORMAT(o.order_date, 'yyyy-MM') AS sales_month,
DATEPART(YEAR, o.order_date) AS sales_year,
DATEPART(MONTH, o.order_date) AS sales_month_num,
-- Sales metrics
COUNT(DISTINCT o.order_id) AS total_orders,
COUNT(DISTINCT o.customer_id) AS unique_customers,
SUM(o.total_amount) AS total_revenue,
AVG(o.total_amount) AS avg_order_value,
-- Product metrics
COUNT(DISTINCT oi.product_id) AS unique_products_sold,
SUM(oi.quantity) AS total_units_sold,
-- Customer metrics
COUNT(DISTINCT CASE WHEN c.registration_date >= DATEADD(MONTH, -1, o.order_date)
THEN c.customer_id END) AS new_customers
FROM orders o
INNER JOIN order_items oi
ON o.order_id = oi.order_id
LEFT JOIN customers c
ON o.customer_id = c.customer_id
WHERE o.status = 'Delivered'
AND o.order_date >= DATEADD(YEAR, -1, GETDATE())
GROUP BY
FORMAT(o.order_date, 'yyyy-MM'),
DATEPART(YEAR, o.order_date),
DATEPART(MONTH, o.order_date)
),
CustomerLifetimeValue AS (
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.registration_date,
COUNT(DISTINCT o.order_id) AS lifetime_orders,
SUM(o.total_amount) AS lifetime_value,
DATEDIFF(DAY, c.registration_date, GETDATE()) AS customer_age_days,
-- Bereken Customer Lifetime Value per dag
CASE
WHEN DATEDIFF(DAY, c.registration_date, GETDATE()) > 0
THEN SUM(o.total_amount) / DATEDIFF(DAY, c.registration_date, GETDATE())
ELSE 0
END AS clv_per_day
FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE o.status = 'Delivered' OR o.status IS NULL
GROUP BY
c.customer_id,
c.first_name,
c.last_name,
c.registration_date
),
TopProducts AS (
SELECT
p.category,
p.product_name,
SUM(oi.quantity) AS units_sold,
SUM(oi.quantity * oi.unit_price) AS revenue,
RANK() OVER (PARTITION BY p.category ORDER BY SUM(oi.quantity) DESC) AS rank_in_category
FROM products p
INNER JOIN order_items oi
ON p.product_id = oi.product_id
INNER JOIN orders o
ON oi.order_id = o.order_id
WHERE o.order_date >= DATEADD(MONTH, -3, GETDATE())
AND o.status = 'Delivered'
GROUP BY p.category, p.product_name
)
SELECT
-- Monthly sales summary
ms.sales_month,
ms.total_orders,
ms.unique_customers,
ms.total_revenue,
ms.avg_order_value,
ms.total_units_sold,
ms.new_customers,
-- Month-over-month growth
LAG(ms.total_revenue) OVER (ORDER BY ms.sales_month) AS previous_month_revenue,
CASE
WHEN LAG(ms.total_revenue) OVER (ORDER BY ms.sales_month) > 0
THEN ((ms.total_revenue - LAG(ms.total_revenue) OVER (ORDER BY ms.sales_month)) /
LAG(ms.total_revenue) OVER (ORDER BY ms.sales_month)) * 100
ELSE NULL
END AS revenue_growth_percentage,
-- Top 3 customers by CLV
(
SELECT STRING_AGG(clv.customer_name, ', ') WITHIN GROUP (ORDER BY clv.lifetime_value DESC)
FROM (
SELECT TOP 3 customer_name, lifetime_value
FROM CustomerLifetimeValue
WHERE lifetime_value > 0
ORDER BY lifetime_value DESC
) clv
) AS top_3_customers,
-- Top product per category
(
SELECT STRING_AGG(CONCAT(tp.category, ': ', tp.product_name), ' | ')
FROM TopProducts tp
WHERE tp.rank_in_category = 1
) AS top_products_by_category
FROM MonthlySales ms
ORDER BY ms.sales_month DESC;
Klaar om SQL te masteren?
Vind database professionals of plaats je vacature voor SQL projecten
Conclusie en volgende stappen
SQL Joins zijn een fundamentele vaardigheid voor iedereen die met databases werkt. Je hebt nu geleerd:
- Alle join types: INNER, LEFT, RIGHT, FULL, CROSS en SELF JOIN
- Praktische toepassingen: Echte wereld voorbeelden en use cases
- Performance optimalisatie: Indexes, query optimalisatie en best practices
- Complexe queries: Multiple joins, CTEs en window functions
- Business intelligence: Sales analysis, customer segmentation en reporting
Volgende stappen:
- Oefen met je eigen database om joins onder de knie te krijgen
- Leer geavanceerde SQL features: Window Functions, CTEs, Pivot Tables
- Bestudeer query execution plans voor performance tuning
- Implementeer database indexing strategies
- Volg onze advanced SQL en database optimization tutorials