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 rank
FROM employees;
1.2. RANK() – Rank with Gaps for Ties
SELECT
employee_id,
name,
salary,
RANK() OVER (ORDER BY salary DESC) AS rank
FROM employees;
1.3. DENSE_RANK() – Rank Without Gaps
SELECT
employee_id,
name,
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM employees;
1.4. NTILE() – Divide Rows into Buckets
SELECT
employee_id,
name,
salary,
NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM employees;
1.5. LEAD() – Access Next Row’s Value
SELECT
employee_id,
name,
salary,
LEAD(salary, 1) OVER (ORDER BY salary DESC) AS next_salary
FROM employees;
1.6. LAG() – Access Previous Row’s Value
SELECT
employee_id,
name,
salary,
LAG(salary, 1) OVER (ORDER BY salary DESC) AS prev_salary
FROM 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_dept
FROM 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_dept
FROM employees;
1.9. Running Total with SUM() OVER
SELECT
date,
revenue,
SUM(revenue) OVER (ORDER BY date) AS running_total
FROM sales;
1.10. Moving Average
SELECT
date,
revenue,
AVG(revenue) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM 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 e
JOIN 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_sales
FROM sales
GROUP BY product_id;
3.2. Pivot with PIVOT (SQL Server, Oracle)
SELECT *
FROM (
SELECT product_id, region, sales
FROM sales
) AS src
PIVOT (
SUM(sales) FOR region IN ([North], [South], [East], [West])
) AS pvt;
3.3. Unpivot Data
SELECT product_id, region, sales
FROM (
SELECT product_id, north_sales, south_sales, east_sales, west_sales
FROM pivoted_sales
) AS src
UNPIVOT (
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_id
FROM employees e1
JOIN 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.salary
FROM departments d
CROSS 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.department
FROM employees e1
WHERE 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 AS
SELECT * 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_salary
FROM employees
GROUP BY ROLLUP(department, job_title);
6.2. CUBE (All Possible Groupings)
SELECT
department,
job_title,
SUM(salary) AS total_salary
FROM employees
GROUP BY CUBE(department, job_title);
6.3. GROUPING SETS (Custom Groupings)
SELECT
department,
job_title,
SUM(salary) AS total_salary
FROM employees
GROUP 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 age
FROM users;
7.2. Query Nested JSON Arrays
SELECT
id,
json_array_elements(json_data->'skills') AS skill
FROM users;
7.3. XML Parsing
SELECT
id,
xpath('//name/text()', xml_data) AS name,
xpath('//age/text()', xml_data) AS age
FROM 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_name
FROM employees e
FULL 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_employees
INTERSECT
SELECT employee_id FROM high_performers;
9.4. EXCEPT (Find Records in First Query but Not Second)
SELECT employee_id FROM all_employees
EXCEPT
SELECT employee_id FROM terminated_employees;
9.5. UNION ALL (Combine Results with Duplicates)
SELECT name, salary FROM current_employees
UNION ALL
SELECT name, salary FROM former_employees;
10. Advanced Subqueries
10.1. EXISTS (Check for Related Records)
SELECT e.name
FROM employees e
WHERE 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_name
FROM departments d
WHERE 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, salary
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location = 'New York'
);
10.4. ALL (Compare Against All Values in Subquery)
SELECT name, salary
FROM employees
WHERE salary > ALL (
SELECT salary
FROM employees
WHERE department = 'Intern'
);
10.5. ANY/SOME (Compare Against Any Value in Subquery)
SELECT name, salary
FROM employees
WHERE 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 UPDATE
SET name = EXCLUDED.name, salary = EXCLUDED.salary;
11.2. MERGE (Conditional INSERT/UPDATE/DELETE)
MERGE INTO employees e
USING updated_employees ue
ON e.id = ue.id
WHEN MATCHED THEN
UPDATE SET e.name = ue.name, e.salary = ue.salary
WHEN NOT MATCHED THEN
INSERT (id, name, salary) VALUES (ue.id, ue.name, ue.salary);
11.3. DELETE with JOIN
DELETE FROM employees
USING departments
WHERE employees.department_id = departments.department_id
AND departments.location = 'Remote';
11.4. UPDATE from Another Table
UPDATE employees e
SET salary = e.salary * 1.1
FROM departments d
WHERE e.department_id = d.department_id
AND d.budget > 1000000;
12. Database Administration & Meta-Queries
12.1. List All Tables in a Database
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public';
12.2. Find Column Names in a Table
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'employees';
12.3. Check Table Size (PostgreSQL)
SELECT
table_name,
pg_size_pretty(pg_total_relation_size(table_name)) AS size
FROM information_schema.tables
WHERE table_schema = 'public';
12.4. Find Long-Running Queries
SELECT
pid,
query,
now() - query_start AS duration
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC;
12.5. Kill a Running Query
SELECT pg_cancel_backend(pid)
FROM pg_stat_activity
WHERE 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_days
FROM (
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 tag
FROM products;
14.3. Concatenate Rows into String
SELECT
department_id,
string_agg(name, ', ') AS employees
FROM employees
GROUP 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