Long complex SQL queries aren't always the most readable thing. CTEs (as a gross over simplification) are somewhat akin to a view, but only during that specific query. In effect you can build up multiple different CTEs that reference earlier ones you created, thus making more composable and readable SQL. You can also recursively call them, allowing you to do things that you would otherwise need procedural language for.
The broad structure of a CTE is:
WITH your_cte_name AS ( SELECT foo, bar FROM your_table )
You can chain them together in a basic form such as:
WITH your_cte_name AS ( SELECT foo, bar FROM your_table ), cte_number_2_name AS ( SELECT foo FROM your_cte_name )
You can find a more fully formed below, or read further over at craigkerstiens.com:
WITH user_task AS ( SELECT u.id AS user_id, p.id AS project_id, u.email, array_agg(t.name) AS task_list, p.title FROM project p, task t, user_ u WHERE u.id = t.user_id AND p.id = t.project_id GROUP BY u.id, p.id ), --- Calculates the total task per each project total_task_per_project AS ( SELECT t.project_id, count(*) as task_count FROM task t GROUP BY t.project_id ), --- Calculates the projects per each user task_per_project_per_user AS ( SELECT t.user_id, t.project_id, count(*) as task_count FROM task t GROUP BY t.user_id, t.project_id ), --- Gets user ids that have over 50% of task assigned overloaded_user AS ( SELECT tpu.user_id, tpu.project_id FROM task_per_project_per_user tpu, total_task_per_project pp WHERE tpu.project_id = pp.project_id AND tpu.task_count > (pp.task_count / 2) ) SELECT ut.email, ut.task_list, ut.title FROM user_task ut, overloaded_user ou WHERE ut.user_id = ou.user_id AND ut.project_id = ou.project_id ;