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!