SQL is a powerful language for managing and querying relational databases. While basic queries like SELECT
, INSERT
, UPDATE
, and DELETE
are essential, mastering advanced SQL techniques can significantly enhance your ability to analyze data, optimize performance, and solve complex problems.
In this article, we’ll explore 50 advanced SQL queries that cover window functions, recursive CTEs, pivoting, performance optimization, and more.
1. Window Functions (Analytical Queries)
Window functions allow computations across a set of table rows related to the current row.
1.1. ROW_NUMBER() – Assign a Unique Row Number
SELECT employee_id, name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rankFROM employees;
1.2. RANK() – Rank with Gaps for Ties
SELECT employee_id, name, salary, RANK() OVER (ORDER BY salary DESC) AS rankFROM employees;
1.3. DENSE_RANK() – Rank Without Gaps
SELECT employee_id, name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rankFROM employees;
1.4. NTILE() – Divide Rows into Buckets
SELECT employee_id, name, salary, NTILE(4) OVER (ORDER BY salary DESC) AS quartileFROM employees;
1.5. LEAD() – Access Next Row’s Value
SELECT employee_id, name, salary, LEAD(salary, 1) OVER (ORDER BY salary DESC) AS next_salaryFROM employees;
1.6. LAG() – Access Previous Row’s Value
SELECT employee_id, name, salary, LAG(salary, 1) OVER (ORDER BY salary DESC) AS prev_salaryFROM employees;
1.7. FIRST_VALUE() – Get First Value in a Window
SELECT employee_id, name, salary, FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary DESC) AS highest_in_deptFROM employees;
1.8. LAST_VALUE() – Get Last Value in a Window
SELECT employee_id, name, salary, LAST_VALUE(salary) OVER ( PARTITION BY department ORDER BY salary DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS lowest_in_deptFROM employees;
1.9. Running Total with SUM() OVER
SELECT date, revenue, SUM(revenue) OVER (ORDER BY date) AS running_totalFROM sales;
1.10. Moving Average
SELECT date, revenue, AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avgFROM sales;
2. Common Table Expressions (CTEs) and Recursive Queries
CTEs improve readability and allow recursive operations.
2.1. Basic CTE
WITH high_earners AS ( SELECT * FROM employees WHERE salary > 100000)SELECT * FROM high_earners;
2.2. Recursive CTE (Hierarchical Data)
WITH RECURSIVE employee_hierarchy AS ( -- Base case: CEO (no manager) SELECT id, name, manager_id, 1 AS level FROM employees WHERE manager_id IS NULL
UNION ALL
-- Recursive case: Employees with managers SELECT e.id, e.name, e.manager_id, eh.level + 1 FROM employees e JOIN employee_hierarchy eh ON e.manager_id = eh.id)SELECT * FROM employee_hierarchy;
2.3. Multiple CTEs in a Single Query
WITH dept_stats AS ( SELECT department, AVG(salary) AS avg_salary FROM employees GROUP BY department ), high_paying_depts AS ( SELECT department FROM dept_stats WHERE avg_salary > 80000 )SELECT e.*FROM employees eJOIN high_paying_depts hpd ON e.department = hpd.department;
3. Pivoting and Unpivoting Data
3.1. Pivot with CASE
SELECT product_id, SUM(CASE WHEN region = 'North' THEN sales ELSE 0 END) AS north_sales, SUM(CASE WHEN region = 'South' THEN sales ELSE 0 END) AS south_sales, SUM(CASE WHEN region = 'East' THEN sales ELSE 0 END) AS east_sales, SUM(CASE WHEN region = 'West' THEN sales ELSE 0 END) AS west_salesFROM salesGROUP BY product_id;
3.2. Pivot with PIVOT (SQL Server, Oracle)
SELECT *FROM ( SELECT product_id, region, sales FROM sales) AS srcPIVOT ( SUM(sales) FOR region IN ([North], [South], [East], [West])) AS pvt;
3.3. Unpivot Data
SELECT product_id, region, salesFROM ( SELECT product_id, north_sales, south_sales, east_sales, west_sales FROM pivoted_sales) AS srcUNPIVOT ( sales FOR region IN (north_sales, south_sales, east_sales, west_sales)) AS unpvt;
4. Advanced Joins and Subqueries
4.1. Self-Join (Find Employees with Same Manager)
SELECT e1.name AS employee1, e2.name AS employee2, e1.manager_idFROM employees e1JOIN employees e2 ON e1.manager_id = e2.manager_id AND e1.id < e2.id;
4.2. Lateral Join (PostgreSQL)
SELECT d.department_name, e.name, e.salaryFROM departments dCROSS JOIN LATERAL ( SELECT name, salary FROM employees WHERE department_id = d.id ORDER BY salary DESC LIMIT 3) e;
4.3. Correlated Subquery (Find Employees Earning Above Avg in Dept)
SELECT e1.name, e1.salary, e1.departmentFROM employees e1WHERE e1.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e2.department = e1.department);
5. Performance Optimization
5.1. Index Hinting (Force Index Usage)
SELECT * FROM employees WITH (INDEX(idx_salary)) WHERE salary > 50000;
5.2. Query Plan Analysis (EXPLAIN)
EXPLAIN ANALYZE SELECT * FROM employees WHERE department = 'Engineering';
5.3. Materialized Views (Precompute Expensive Queries)
CREATE MATERIALIZED VIEW mv_high_earners ASSELECT * FROM employees WHERE salary > 100000;
REFRESH MATERIALIZED VIEW mv_high_earners;
6. Advanced Aggregations
6.1. ROLLUP (Hierarchical Grouping)
SELECT department, job_title, SUM(salary) AS total_salaryFROM employeesGROUP BY ROLLUP(department, job_title);
6.2. CUBE (All Possible Groupings)
SELECT department, job_title, SUM(salary) AS total_salaryFROM employeesGROUP BY CUBE(department, job_title);
6.3. GROUPING SETS (Custom Groupings)
SELECT department, job_title, SUM(salary) AS total_salaryFROM employeesGROUP BY GROUPING SETS ( (department, job_title), (department), (job_title), ());
7. JSON and XML Handling
7.1. Extract JSON Fields
SELECT id, json_data->>'name' AS name, json_data->>'age' AS ageFROM users;
7.2. Query Nested JSON Arrays
SELECT id, json_array_elements(json_data->'skills') AS skillFROM users;
7.3. XML Parsing
SELECT id, xpath('//name/text()', xml_data) AS name, xpath('//age/text()', xml_data) AS ageFROM users;
8. Dynamic SQL
8.1. Execute Dynamic Query (SQL Injection Safe)
EXECUTE format('SELECT * FROM %I WHERE salary > %L', 'employees', 50000);
8.2. Generate and Run SQL in a Loop
DO $$DECLARE query TEXT;BEGIN FOR i IN 1..10 LOOP query := format('INSERT INTO logs (message) VALUES (%L)', 'Log ' || i); EXECUTE query; END LOOP;END $$;
9. Advanced Joins and Set Operations
9.1. FULL OUTER JOIN (Find All Matches and Non-Matches)
SELECT e.employee_id, e.name, d.department_nameFROM employees eFULL OUTER JOIN departments d ON e.department_id = d.department_id;
9.2. NATURAL JOIN (Join on Columns with Same Name)
SELECT * FROM employees NATURAL JOIN departments;
9.3. INTERSECT (Find Common Records Between Two Queries)
SELECT employee_id FROM full_time_employeesINTERSECTSELECT employee_id FROM high_performers;
9.4. EXCEPT (Find Records in First Query but Not Second)
SELECT employee_id FROM all_employeesEXCEPTSELECT employee_id FROM terminated_employees;
9.5. UNION ALL (Combine Results with Duplicates)
SELECT name, salary FROM current_employeesUNION ALLSELECT name, salary FROM former_employees;
10. Advanced Subqueries
10.1. EXISTS (Check for Related Records)
SELECT e.nameFROM employees eWHERE EXISTS ( SELECT 1 FROM sales s WHERE s.employee_id = e.employee_id AND s.amount > 10000);
10.2. NOT EXISTS (Find Records Without Related Data)
SELECT d.department_nameFROM departments dWHERE NOT EXISTS ( SELECT 1 FROM employees e WHERE e.department_id = d.department_id);
10.3. IN with Subquery (Filter Based on Another Query)
SELECT name, salaryFROM employeesWHERE department_id IN ( SELECT department_id FROM departments WHERE location = 'New York');
10.4. ALL (Compare Against All Values in Subquery)
SELECT name, salaryFROM employeesWHERE salary > ALL ( SELECT salary FROM employees WHERE department = 'Intern');
10.5. ANY/SOME (Compare Against Any Value in Subquery)
SELECT name, salaryFROM employeesWHERE salary > ANY ( SELECT salary FROM employees WHERE department = 'Management');
11. Advanced Data Modification
11.1. UPSERT (INSERT or UPDATE on Conflict)
INSERT INTO employees (id, name, salary)VALUES (101, 'John Doe', 75000)ON CONFLICT (id) DO UPDATESET name = EXCLUDED.name, salary = EXCLUDED.salary;
11.2. MERGE (Conditional INSERT/UPDATE/DELETE)
MERGE INTO employees eUSING updated_employees ueON e.id = ue.idWHEN MATCHED THEN UPDATE SET e.name = ue.name, e.salary = ue.salaryWHEN NOT MATCHED THEN INSERT (id, name, salary) VALUES (ue.id, ue.name, ue.salary);
11.3. DELETE with JOIN
DELETE FROM employeesUSING departmentsWHERE employees.department_id = departments.department_idAND departments.location = 'Remote';
11.4. UPDATE from Another Table
UPDATE employees eSET salary = e.salary * 1.1FROM departments dWHERE e.department_id = d.department_idAND d.budget > 1000000;
12. Database Administration & Meta-Queries
12.1. List All Tables in a Database
SELECT table_nameFROM information_schema.tablesWHERE table_schema = 'public';
12.2. Find Column Names in a Table
SELECT column_name, data_typeFROM information_schema.columnsWHERE table_name = 'employees';
12.3. Check Table Size (PostgreSQL)
SELECT table_name, pg_size_pretty(pg_total_relation_size(table_name)) AS sizeFROM information_schema.tablesWHERE table_schema = 'public';
12.4. Find Long-Running Queries
SELECT pid, query, now() - query_start AS durationFROM pg_stat_activityWHERE state = 'active'ORDER BY duration DESC;
12.5. Kill a Running Query
SELECT pg_cancel_backend(pid)FROM pg_stat_activityWHERE query LIKE '%long_running_query%';
13. Advanced Date & Time Operations
13.1. Generate Date Series
SELECT generate_series( '2023-01-01'::date, '2023-12-31'::date, '1 day'::interval) AS date;
13.2. Calculate Business Days Between Dates
SELECT date1, date2, COUNT(*) FILTER (WHERE EXTRACT(DOW FROM day) BETWEEN 1 AND 5) AS business_daysFROM ( SELECT '2023-01-01'::date AS date1, '2023-01-31'::date AS date2, generate_series( '2023-01-01'::date, '2023-01-31'::date, '1 day'::interval ) AS day) t;
13.3. Find Last Day of Month
SELECT date_trunc('month', current_date) + INTERVAL '1 month - 1 day' AS last_day_of_month;
14. Advanced String Manipulation
14.1. Regex Extract
SELECT regexp_matches(email, '([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+)\.([A-Za-z]{2,})')FROM users;
14.2. Split String into Rows
SELECT id, unnest(string_to_array(tags, ',')) AS tagFROM products;
14.3. Concatenate Rows into String
SELECT department_id, string_agg(name, ', ') AS employeesFROM employeesGROUP BY department_id;
15. Advanced Security & Permissions
15.1. Grant Column-Level Permissions
GRANT SELECT (name, email) ON employees TO analyst_role;
15.2. Create a Read-Only User
CREATE USER readonly WITH PASSWORD 'secure_password';GRANT CONNECT ON DATABASE mydb TO readonly;GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
Conclusion
With these 20 additional advanced SQL queries, we now have a complete list of 50 essential SQL techniques covering:
✅ Window Functions
✅ CTEs & Recursive Queries
✅ Pivoting & Unpivoting
✅ Advanced Joins & Subqueries
✅ Performance Optimization
✅ JSON/XML Handling
✅ Dynamic SQL
✅ Database Administration
Any Questions?
Contact me on any of my communication channels: