MySQL Query Advanced: 25+ Teknik Query Lanjutan untuk Developer Professional
Pernah ngerasain frustasi karena MySQL query yang lambat, atau bingung bagaimana ekstrak data complex dari database? Kalau SELECT dasar sudah dikuasai, sekarang saatnya naik level ke query advanced MySQL! Teknik-teknik ini bakal bikin kamu seperti ninja database – bisa solve problem complex dengan query yang elegant dan efficient.
Artikel ini adalah panduan komprehensif untuk teknik query lanjutan MySQL yang digunakan developer professional sehari-hari. Dari window functions sampai query optimization, dari complex joins sampai advanced analytics – semua akan kita bahas dengan contoh nyata. Siap untuk transformasi skill MySQL-mu?
Fundamental yang Harus Dikuasai Sebelum Melanjutkan
Sebelum masuk ke advanced topics, pastikan kamu sudah comfortable dengan:
- Basic SELECT, INSERT, UPDATE, DELETE
- JOIN operations (INNER, LEFT, RIGHT)
- GROUP BY dan aggregate functions (COUNT, SUM, AVG)
- Indexes dan basic query optimization
1. Window Functions – Game Changer untuk Analytical Queries
Window functions memungkinkan perhitungan across rows tanpa mengelompokkan data seperti GROUP BY.
ROW_NUMBER() – Penomoran Baris
-- Beri nomor urut untuk setiap employee per department
SELECT
employee_id,
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank_in_dept
FROM employees;
-- Hasil:
-- employee_id | name | department | salary | rank_in_dept
-- 101 | John | IT | 8000 | 1
-- 102 | Jane | IT | 7500 | 2
-- 103 | Bob | HR | 6000 | 1
RANK() dan DENSE_RANK() – Ranking dengan Handle Ties
-- Bandingkan RANK vs DENSE_RANK
SELECT
product_id,
product_name,
sales,
RANK() OVER (ORDER BY sales DESC) as rank,
DENSE_RANK() OVER (ORDER BY sales DESC) as dense_rank
FROM products;
-- Jika ada sales yang sama:
-- product_id | product_name | sales | rank | dense_rank
-- 1 | Laptop | 100 | 1 | 1
-- 2 | Phone | 100 | 1 | 1 (Sama rank)
-- 3 | Tablet | 90 | 3 | 2 (DENSE_RANK tidak skip)
-- 4 | Mouse | 80 | 4 | 3
LEAD() dan LAG() – Akses Data Sebelum/Sesudah
-- Bandingkan sales bulan ini dengan bulan sebelumnya
SELECT
month,
sales,
LAG(sales) OVER (ORDER BY month) as previous_month_sales,
LEAD(sales) OVER (ORDER BY month) as next_month_sales,
sales - LAG(sales) OVER (ORDER BY month) as growth
FROM monthly_sales;
-- Hasil:
-- month | sales | previous_month_sales | next_month_sales | growth
-- 2024-01 | 1000 | NULL | 1200 | NULL
-- 2024-02 | 1200 | 1000 | 1100 | 200
-- 2024-03 | 1100 | 1200 | NULL | -100
2. Common Table Expressions (CTEs) – Query yang Lebih Readable
CTEs membuat complex queries lebih mudah dibaca dan di-maintain.
Basic CTE Example
-- Hitung average salary per department, lalu cari employees di atas average
WITH department_stats AS (
SELECT
department,
AVG(salary) as avg_salary
FROM employees
GROUP BY department
),
high_earners AS (
SELECT
e.name,
e.department,
e.salary,
ds.avg_salary
FROM employees e
JOIN department_stats ds ON e.department = ds.department
WHERE e.salary > ds.avg_salary
)
SELECT * FROM high_earners ORDER BY department, salary DESC;
-- Lebih readable daripada subquery nested!
Recursive CTE untuk Hierarchical Data
-- Contoh organizational hierarchy
WITH RECURSIVE employee_hierarchy AS (
-- Anchor member: CEO (tidak punya manager)
SELECT
employee_id,
name,
title,
manager_id,
0 as level,
CAST(name AS CHAR(1000)) as hierarchy_path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Recursive member: subordinates
SELECT
e.employee_id,
e.name,
e.title,
e.manager_id,
eh.level + 1,
CONCAT(eh.hierarchy_path, ' -> ', e.name)
FROM employees e
INNER JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT
employee_id,
name,
title,
level,
hierarchy_path
FROM employee_hierarchy
ORDER BY hierarchy_path;
-- Hasil akan menampilkan seluruh struktur organisasi
3. Advanced JOIN Techniques
Beyond basic INNER JOIN, MySQL punya beberapa JOIN techniques yang powerful.
SELF JOIN untuk Comparative Analysis
-- Cari employees yang bergabung di bulan yang sama
SELECT
e1.name as employee1,
e2.name as employee2,
DATE_FORMAT(e1.hire_date, '%Y-%m') as join_month
FROM employees e1
JOIN employees e2
ON DATE_FORMAT(e1.hire_date, '%Y-%m') = DATE_FORMAT(e2.hire_date, '%Y-%m')
AND e1.employee_id < e2.employee_id -- Hindari duplicate pairs
ORDER BY join_month, employee1;
-- Cari products yang sering dibeli bersama (market basket analysis)
SELECT
p1.product_name as product_a,
p2.product_name as product_b,
COUNT(*) as times_bought_together
FROM order_items oi1
JOIN order_items oi2
ON oi1.order_id = oi2.order_id
AND oi1.product_id < oi2.product_id JOIN products p1 ON oi1.product_id = p1.product_id JOIN products p2 ON oi2.product_id = p2.product_id GROUP BY p1.product_name, p2.product_name HAVING COUNT(*) > 5
ORDER BY times_bought_together DESC;
CROSS JOIN untuk Generating Data
-- Generate calendar dates untuk tahun 2024
WITH numbers AS (
SELECT 0 as n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION
SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
),
dates AS (
SELECT
DATE('2024-01-01') + INTERVAL (t4.n * 1000 + t3.n * 100 + t2.n * 10 + t1.n) DAY as date
FROM numbers t1
CROSS JOIN numbers t2
CROSS JOIN numbers t3
CROSS JOIN numbers t4
WHERE DATE('2024-01-01') + INTERVAL (t4.n * 1000 + t3.n * 100 + t2.n * 10 + t1.n) DAY < '2025-01-01'
)
SELECT date FROM dates ORDER BY date;
-- Generate report untuk semua kombinasi product dan region (meski tidak ada sales)
SELECT
p.product_name,
r.region_name,
COALESCE(SUM(s.amount), 0) as total_sales
FROM products p
CROSS JOIN regions r
LEFT JOIN sales s ON p.product_id = s.product_id AND r.region_id = s.region_id
GROUP BY p.product_name, r.region_name
ORDER BY p.product_name, r.region_name;
4. Advanced Subquery Techniques
Subquery yang tepat bisa solve problem complex dengan elegant.
Correlated Subqueries
-- Cari employees dengan salary di atas average department mereka
SELECT
e1.name,
e1.department,
e1.salary
FROM employees e1
WHERE e1.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e1.department -- Correlated subquery
);
-- Cari products yang belum pernah dijual bulan ini
SELECT
p.product_name
FROM products p
WHERE NOT EXISTS (
SELECT 1
FROM sales s
WHERE s.product_id = p.product_id
AND YEAR(s.sale_date) = YEAR(CURRENT_DATE)
AND MONTH(s.sale_date) = MONTH(CURRENT_DATE)
);
Derived Tables (Subquery di FROM Clause)
-- Hitung running total sales per bulan
SELECT
month,
sales,
SUM(sales) OVER (ORDER BY month) as running_total,
AVG(sales) OVER (ORDER BY month ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg_3months
FROM (
SELECT
DATE_FORMAT(sale_date, '%Y-%m') as month,
SUM(amount) as sales
FROM sales
GROUP BY DATE_FORMAT(sale_date, '%Y-%m')
) monthly_sales
ORDER BY month;
-- Complex aggregation dengan multiple levels
SELECT
dept_stats.department,
dept_stats.avg_salary,
dept_stats.max_salary,
(SELECT COUNT(*) FROM employees e2 WHERE e2.department = dept_stats.department) as employee_count
FROM (
SELECT
department,
AVG(salary) as avg_salary,
MAX(salary) as max_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 5000
) dept_stats
ORDER BY dept_stats.avg_salary DESC;
5. JSON Functions – Working dengan Semi-Structured Data
MySQL 5.7+ support JSON data type dengan functions yang powerful.
JSON Extraction dan Manipulation
-- Contoh table dengan JSON column
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
attributes JSON,
created_at TIMESTAMP
);
-- Insert data JSON
INSERT INTO products VALUES (
1,
'Smartphone',
'{"brand": "Samsung", "specs": {"ram": "8GB", "storage": "128GB"}, "colors": ["black", "blue"]}',
NOW()
);
-- Extract values dari JSON
SELECT
name,
attributes->>"$.brand" as brand,
attributes->>"$.specs.ram" as ram,
attributes->>"$.specs.storage" as storage,
JSON_LENGTH(attributes->>"$.colors") as color_count
FROM products;
-- Query berdasarkan JSON content
SELECT name, attributes
FROM products
WHERE attributes->>"$.brand" = 'Samsung'
AND JSON_CONTAINS(attributes->>"$.colors", '"blue"');
-- Update JSON data
UPDATE products
SET attributes = JSON_SET(
attributes,
'$.specs.ram', '12GB',
'$.price', 799.99
)
WHERE id = 1;
6. Full-Text Search – Advanced Text Searching
Untuk implementasi search yang powerful beyond LIKE operator.
Setup Full-Text Search
-- Buat table dengan full-text index
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200),
content TEXT,
author VARCHAR(100),
created_at TIMESTAMP,
FULLTEXT(title, content)
) ENGINE=InnoDB;
-- Insert sample data
INSERT INTO articles (title, content, author) VALUES
('MySQL Performance Tips', 'Learn how to optimize your MySQL queries...', 'John Doe'),
('Advanced SQL Techniques', 'Window functions and CTEs can solve complex problems...', 'Jane Smith');
-- Basic full-text search
SELECT
title,
content,
MATCH(title, content) AGAINST('performance optimization') as relevance_score
FROM articles
WHERE MATCH(title, content) AGAINST('performance optimization')
ORDER BY relevance_score DESC;
-- Boolean mode untuk advanced search
SELECT title, content
FROM articles
WHERE MATCH(title, content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);
-- Query expansion untuk synonym search
SELECT title, content
FROM articles
WHERE MATCH(title, content) AGAINST('database' WITH QUERY EXPANSION);
7. Query Optimization Advanced Techniques
Teknik optimisasi untuk query yang high-performance.
EXPLAIN ANALYZE untuk Query Analysis
-- Analyze query execution plan
EXPLAIN ANALYZE
SELECT
c.customer_name,
COUNT(o.order_id) as order_count,
SUM(oi.quantity * oi.unit_price) as total_spent
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date >= '2024-01-01'
GROUP BY c.customer_id, c.customer_name
HAVING COUNT(o.order_id) > 5
ORDER BY total_spent DESC;
-- Hasil EXPLAIN akan menunjukkan:
-- * Index usage
-- * Join types
-- * Rows examined
-- * Potential bottlenecks
-- Optimized version dengan proper indexing
ALTER TABLE orders ADD INDEX idx_customer_date (customer_id, order_date);
ALTER TABLE order_items ADD INDEX idx_order (order_id);
-- Gunakan covering index dimana possible
EXPLAIN ANALYZE
SELECT
c.customer_name,
o.order_count,
o.total_spent
FROM customers c
JOIN (
SELECT
customer_id,
COUNT(*) as order_count,
SUM(total_amount) as total_spent
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY customer_id
HAVING COUNT(*) > 5
) o ON c.customer_id = o.customer_id
ORDER BY o.total_spent DESC;
Query Hints untuk Force Optimization
-- Force index usage
SELECT *
FROM orders USE INDEX (idx_customer_date)
WHERE customer_id = 123
AND order_date BETWEEN '2024-01-01' AND '2024-12-31';
-- Force join order
SELECT STRAIGHT_JOIN
c.customer_name,
o.order_date,
o.total_amount
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE c.country = 'Indonesia'
ORDER BY o.order_date DESC;
-- Optimizer hints untuk specific strategies
SELECT /*+ MAX_EXECUTION_TIME(1000) */
customer_id,
COUNT(*) as order_count
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 10;
8. Advanced Data Analysis Queries
Queries untuk business intelligence dan analytics.
Cohort Analysis
-- Cohort analysis untuk user retention
WITH user_cohorts AS (
SELECT
user_id,
DATE_FORMAT(MIN(created_at), '%Y-%m-01') as cohort_month
FROM users
GROUP BY user_id
),
monthly_activity AS (
SELECT
u.user_id,
c.cohort_month,
DATE_FORMAT(u.created_at, '%Y-%m-01') as activity_month,
TIMESTAMPDIFF(MONTH, c.cohort_month, u.created_at) as month_number
FROM users u
JOIN user_cohorts c ON u.user_id = c.user_id
WHERE u.created_at >= c.cohort_month
)
SELECT
cohort_month,
month_number,
COUNT(DISTINCT user_id) as active_users,
ROUND(100.0 * COUNT(DISTINCT user_id) / FIRST_VALUE(COUNT(DISTINCT user_id))
OVER (PARTITION BY cohort_month ORDER BY month_number), 2) as retention_rate
FROM monthly_activity
GROUP BY cohort_month, month_number
ORDER BY cohort_month, month_number;
Running Totals dan Moving Averages
-- Financial report dengan running totals
SELECT
date,
revenue,
SUM(revenue) OVER (ORDER BY date) as running_total,
AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as moving_avg_7days,
LAG(revenue, 7) OVER (ORDER BY date) as revenue_7days_ago,
ROUND(100.0 * (revenue - LAG(revenue, 7) OVER (ORDER BY date)) /
LAG(revenue, 7) OVER (ORDER BY date), 2) as growth_7days
FROM daily_revenue
ORDER BY date;
9. Stored Procedures untuk Complex Business Logic
Encapsulate complex operations dalam stored procedures.
Advanced Stored Procedure Example
DELIMITER //
CREATE PROCEDURE GenerateMonthlySalesReport(
IN p_year INT,
IN p_month INT,
OUT p_total_sales DECIMAL(10,2),
OUT p_top_product VARCHAR(100)
)
BEGIN
DECLARE v_done INT DEFAULT FALSE;
DECLARE v_product_name VARCHAR(100);
DECLARE v_product_sales DECIMAL(10,2);
-- Temporary table untuk intermediate results
CREATE TEMPORARY TABLE temp_product_sales (
product_name VARCHAR(100),
total_sales DECIMAL(10,2)
);
-- Calculate total sales
SELECT COALESCE(SUM(oi.quantity * oi.unit_price), 0)
INTO p_total_sales
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE YEAR(o.order_date) = p_year
AND MONTH(o.order_date) = p_month;
-- Get product-wise sales
INSERT INTO temp_product_sales
SELECT
p.product_name,
SUM(oi.quantity * oi.unit_price) as total_sales
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE YEAR(o.order_date) = p_year
AND MONTH(o.order_date) = p_month
GROUP BY p.product_name
ORDER BY total_sales DESC;
-- Get top product
SELECT product_name INTO p_top_product
FROM temp_product_sales
ORDER BY total_sales DESC
LIMIT 1;
-- Cleanup
DROP TEMPORARY TABLE temp_product_sales;
END//
DELIMITER ;
-- Usage
CALL GenerateMonthlySalesReport(2024, 1, @total_sales, @top_product);
SELECT @total_sales as total_sales, @top_product as top_product;
10. Security dan Performance Best Practices
Prepared Statements untuk Security
-- Prevent SQL injection dengan prepared statements
PREPARE get_user_orders FROM '
SELECT o.order_id, o.order_date, o.total_amount
FROM orders o
JOIN users u ON o.user_id = u.user_id
WHERE u.username = ? AND o.order_date BETWEEN ? AND ?
';
SET @username = 'john_doe';
SET @start_date = '2024-01-01';
SET @end_date = '2024-12-31';
EXECUTE get_user_orders USING @username, @start_date, @end_date;
DEALLOCATE PREPARE get_user_orders;
Transaction Management untuk Data Integrity
-- Complex operation dengan transaction
START TRANSACTION;
-- Deduct inventory
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = 123 AND stock_quantity >= 1;
-- Check if update was successful
IF ROW_COUNT() = 0 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient stock';
END IF;
-- Create order
INSERT INTO orders (user_id, total_amount, status)
VALUES (456, 99.99, 'pending');
SET @order_id = LAST_INSERT_ID();
-- Add order items
INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (@order_id, 123, 1, 99.99);
COMMIT;
Performance Monitoring Queries
Query untuk Monitor Database Performance
-- Cari slow queries
SELECT
query,
execution_time,
rows_examined,
rows_sent
FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 1 DAY
ORDER BY execution_time DESC
LIMIT 10;
-- Check index usage statistics
SELECT
TABLE_NAME,
INDEX_NAME,
SEQ_IN_INDEX,
COLUMN_NAME,
CARDINALITY
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;
-- Monitor table sizes dan growth
SELECT
TABLE_NAME,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS size_mb,
TABLE_ROWS,
AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database'
ORDER BY size_mb DESC;
Kesimpulan: Mastery melalui Practice
Menguasai MySQL query advanced techniques membutuhkan practice dan pengalaman nyata. Mulailah dengan:
- Practice dengan dataset real yang complex
- Analyze query performance dengan EXPLAIN
- Experiment dengan different approaches untuk problem yang sama
- Pelajari dari query patterns yang digunakan di production
Dengan menguasai teknik-teknik ini, kamu akan mampu menyelesaikan problem data yang complex dengan solutions yang efficient dan maintainable. Happy querying!
