Views, CTEs & Recursive Queries

View, CTE ও recursive query

Read: ~40 min Hard 16 practice problems Live SQLite runner

1. The Quest for Reusable SQL

Real production queries can run to hundreds of lines. Without a way to break them into named pieces, those queries become unreadable, untestable, and impossible to maintain. SQL gives us three tools, in roughly increasing power:

  • Views — saved queries, stored in the database catalog. Anyone with permission can SELECT from them as if they were tables.
  • CTEs (the WITH clause) — named subqueries scoped to a single statement.
  • Recursive CTEs — CTEs that reference themselves, letting SQL solve hierarchy, graph traversal, and series-generation problems that no flat join can.
বড় বাস্তব query একশো-দুশো লাইন পর্যন্ত হতে পারে। সেগুলোকে অংশ-অংশ করে নাম দিয়ে গুছিয়ে রাখার তিনটি উপায় — View (database-এ সংরক্ষিত query), CTE (একটি statement-এর ভেতরে নামকরণ করা subquery), এবং Recursive CTE (নিজেকে ডেকে hierarchy বা graph traverse করে)।
Mental model View = a "function" stored in the database, callable from anywhere. CTE = a "local variable" inside one query. Recursive CTE = a "loop" expressed as set algebra.

View হলো database-এ রাখা function, CTE হলো এক query-র ভেতরে local variable, আর recursive CTE হলো লুপ — কিন্তু set-এর ভাষায় লেখা।

2. Views — Saved Queries That Look Like Tables

A view is created with CREATE VIEW name AS <SELECT...>. It stores no data — it stores the definition. Every time you query the view, the engine substitutes its definition into your query and runs the combined thing.

View তৈরি হয় CREATE VIEW name AS SELECT ... দিয়ে। View-তে কোনো data থাকে না — শুধু query-র সংজ্ঞা থাকে। যখনই আপনি view থেকে SELECT করেন, database সেই সংজ্ঞা বসিয়ে query চালায়।
view_basic.sql
-- Define a view of CSE students with GPA >= 3.7
CREATE VIEW cse_honours AS
SELECT id, name, gpa
FROM students
WHERE dept = 'CSE' AND gpa >= 3.7;

-- Use the view exactly like a table
SELECT * FROM cse_honours ORDER BY gpa DESC;

To remove a view, use DROP VIEW name. To redefine, drop and recreate (most engines also accept CREATE OR REPLACE VIEW; SQLite uses the drop-and-create pattern).

2.1 — Why use views?

  • Encapsulation: business rules ("what counts as 'honours'?") live in one place.
  • Security: hide sensitive columns and grant SELECT only on the view.
  • Compatibility: rename the underlying table without breaking client code.

2.2 — Read-only vs updatable views

Some views, especially those built on a single table without aggregation, are updatable — you can INSERT, UPDATE, or DELETE through them and the engine pushes the change to the underlying table. Views with JOIN, GROUP BY, DISTINCT, set ops, or window functions are typically read-only.

single-table-এর সরল view সাধারণত updatable — INSERT/UPDATE/DELETE চলে এবং সেটি underlying table-এ যায়। কিন্তু JOIN/GROUP BY/DISTINCT/window function থাকলে অধিকাংশ database তা read-only বানিয়ে দেয়। SQLite-এ পুরোনো-নিয়মে CREATE VIEW সরাসরি updatable নয় — INSTEAD OF trigger লিখে updatable করা যায়।
FeatureViewCTE
Persists in DB?Yes — until DROPpedNo — only inside one statement
Reusable across queries?YesNo
Recursion?Indirect (call a recursive CTE)Yes — direct
Permissioned?Yes — own GRANTNo — uses caller's perms

3. CTEs — Local Subqueries with Names

A Common Table Expression (CTE) is introduced with the WITH keyword. It gives a name to a subquery that you can then reference one or more times in the main query — much like a local variable in a programming language.

WITH name AS (SELECT ...) দিয়ে subquery-কে একটি নাম দেওয়া হয়, এরপর সেই নাম ব্যবহার করে মূল query লেখা যায়। এটি statement-এর ভেতরেই কেবল live থাকে — query শেষ হলেই হারিয়ে যায়।
cte_basic.sql
WITH spend AS (
    SELECT cid, SUM(amount) AS total_tk
    FROM orders
    GROUP BY cid
)
SELECT c.name, s.total_tk
FROM spend s
JOIN customers c ON c.id = s.cid
WHERE s.total_tk > 500
ORDER BY s.total_tk DESC;

3.1 — Chained CTEs

You can declare multiple CTEs in one WITH block, and a later CTE may refer to an earlier one. This is the gold-standard way to structure complex analytical queries:

chained_cte.sql
WITH
monthly AS (
    SELECT cid, substr(ts,1,7) AS ym, SUM(amount) AS m_tk
    FROM orders
    GROUP BY cid, substr(ts,1,7)
),
peaks AS (
    SELECT cid, MAX(m_tk) AS peak
    FROM monthly
    GROUP BY cid
)
SELECT c.name, p.peak AS peak_month_tk
FROM peaks p
JOIN customers c ON c.id = p.cid
ORDER BY p.peak DESC;
Style tip Read chained CTEs top-to-bottom like a pipeline: each CTE transforms the previous one. The final SELECT is the "presentation" step. This is much more debuggable than one giant nested query.

Chained CTE-গুলোকে একটি pipeline-এর মতো পড়ুন — প্রতিটি ধাপ আগেরটার ওপর কাজ করে। বিশাল nested query-র চেয়ে এটি অনেক সহজে পরীক্ষা ও debug করা যায়।

4. Recursive CTEs — Letting SQL Loop

A recursive CTE consists of two parts joined by UNION ALL:

  1. Anchor — the base case, a non-recursive query that produces the starting set.
  2. Recursive step — a query that references the CTE itself, producing the next set from the current one.

The engine evaluates the anchor, then repeatedly applies the recursive step using the rows produced in the previous iteration as input, until no new rows are produced. The accumulated rows become the CTE's value.

Recursive CTE-তে দুটি অংশ থাকে — anchor (base case) আর recursive step (নিজেকে ব্যবহার করে নতুন row তৈরি)। দুটোকে UNION ALL দিয়ে জোড়া হয়। যতক্ষণ না নতুন row আসা বন্ধ হয়, ততক্ষণ এটি চলতে থাকে। SQLite ৩.৮.৩ থেকে এটি সমর্থন করে।

4.1 — Generating numbers 1..10

numbers.sql
WITH RECURSIVE n(i) AS (
    SELECT 1                          -- anchor
    UNION ALL
    SELECT i + 1 FROM n WHERE i < 10  -- recursive step
)
SELECT i FROM n;

4.2 — Walking an org chart

org_chart.sql
WITH RECURSIVE org(id, name, manager_id, level, path) AS (
    -- anchor: the CEO has no manager
    SELECT id, name, manager_id, 0, name
    FROM   employees
    WHERE  manager_id IS NULL
    UNION ALL
    -- step: each employee is one level below their manager
    SELECT e.id, e.name, e.manager_id,
           o.level + 1,
           o.path || ' > ' || e.name
    FROM   employees e
    JOIN   org       o ON e.manager_id = o.id
)
SELECT level, name, path FROM org ORDER BY level, name;

4.3 — Traversing a category tree

The same pattern works for any parent-child structure: file systems, comment threads, product categories, family trees.

tree.sql
WITH RECURSIVE tree(id, name, depth, label) AS (
    SELECT id, name, 0, name
    FROM   category WHERE parent IS NULL
    UNION ALL
    SELECT c.id, c.name, t.depth+1,
           printf('%s/%s', t.label, c.name)
    FROM   category c
    JOIN   tree     t ON c.parent = t.id
)
SELECT depth, label FROM tree ORDER BY label;
Infinite recursion If your data has a cycle, or your recursive step does not eventually fail to add new rows, the CTE runs forever (or until the engine's safety limit kills it). Always include a termination condition — most often a WHERE clause on a depth counter.

data-তে cycle থাকলে বা termination না থাকলে recursive CTE অনন্তকাল চলবে। সবসময় depth-limit বা cycle-detection রাখুন।

5. Views & CTEs — Performance Realities

A view is just a query definition; running SELECT ... FROM v WHERE ... usually inlines the view and lets the optimizer push your filter down. Views are essentially free.

CTEs are subtler. In SQLite, a CTE is materialized by default if it's referenced more than once or if it's recursive. That means it's computed into a temporary table once. If it's referenced once and is non-recursive, the engine usually inlines it. You can force materialization with the MATERIALIZED hint or prevent it with NOT MATERIALIZED in newer SQLite builds.

View সাধারণত query-র সাথে inline হয়ে যায়, optimizer filter push করতে পারে — তাই খরচ প্রায় শূন্য। CTE-তে SQLite বহু-বার ব্যবহার বা recursive হলে temporary table-এ materialize করে; নাহলে inline করে। নতুন SQLite-এ MATERIALIZED/NOT MATERIALIZED hint দিয়ে নিয়ন্ত্রণ করা যায়।

✅ View — Best for (সেরা ব্যবহার)

  • Cross-query reuse and permission control
  • Hiding sensitive columns / business rules
  • Stable interface above evolving tables

✅ CTE — Best for (সেরা ব্যবহার)

  • Readability inside one big query
  • Recursion / hierarchy traversal
  • Multi-step transformations as a pipeline

6. Cheat Sheet (এক নজরে)

ConstructSyntaxLifetime
Create a viewCREATE VIEW v AS SELECT ...;Until DROP VIEW
Drop a viewDROP VIEW v;—
One CTEWITH t AS (...) SELECT ...;One statement
Chained CTEsWITH a AS (...), b AS (... a ...) SELECT ...;One statement
Recursive CTEWITH RECURSIVE r(...) AS (anchor UNION ALL step) SELECT ...;One statement

7. Practice Problems

16 problems on views, simple CTEs, chained CTEs, and recursion.

১৬টি অনুশীলনী — view, simple CTE, chained CTE ও recursion-এর ওপর। প্রতিটির runnable answer আছে।
  1. Create a view top_students with students having GPA >= 3.8, then query it.
    GPA >= 3.8 student-দের একটি view বানিয়ে query করুন।
    ✨ Show Answer
    a1.sql
    CREATE VIEW top_students AS
    SELECT id,name,gpa FROM students WHERE gpa>=3.8;
    SELECT * FROM top_students ORDER BY gpa DESC;
  2. Drop the view from problem 1.
    আগের view-টি drop করুন।
    ✨ Show Answer
    a2.sql
    DROP VIEW top_students;
    SELECT name FROM sqlite_master WHERE type='view';
  3. Use a CTE to compute total spend per customer, then list those above 500 BDT.
    CTE দিয়ে customer-wise total ও ৫০০-এর বেশি ফিল্টার।
    ✨ Show Answer
    a3.sql
    WITH spend AS (
        SELECT cid,SUM(amount) AS tot FROM orders GROUP BY cid
    )
    SELECT c.name,s.tot
    FROM spend s
    JOIN customers c ON c.id=s.cid
    WHERE s.tot>500;
  4. Generate the integers 1 to 20 with a recursive CTE.
    Recursive CTE-তে ১ থেকে ২০ পর্যন্ত সংখ্যা তৈরি করুন।
    ✨ Show Answer
    a4.sql
    WITH RECURSIVE n(i) AS (
      SELECT 1
      UNION ALL
      SELECT i+1 FROM n WHERE i<20
    )
    SELECT i FROM n;
  5. Generate every date from 2025-01-01 to 2025-01-10 using recursion.
    2025-01-01 থেকে 2025-01-10 পর্যন্ত প্রতিটি তারিখ।
    ✨ Show Answer
    a5.sql
    WITH RECURSIVE d(day) AS (
      SELECT date('2025-01-01')
      UNION ALL
      SELECT date(day,'+1 day') FROM d WHERE day < date('2025-01-10')
    )
    SELECT day FROM d;
  6. List every employee's chain of command up to the CEO using a recursive CTE.
    প্রত্যেক employee-এর CEO পর্যন্ত chain তৈরি করুন।
    ✨ Show Answer
    a6.sql
    WITH RECURSIVE chain(id,name,path) AS (
      SELECT id,name,name FROM employees
      UNION ALL
      SELECT c.id,c.name,c.path||' -> '||m.name
      FROM chain c
      JOIN employees e ON e.id=c.id
      JOIN employees m ON m.id=e.manager_id
    )
    SELECT name,path FROM chain
    WHERE path LIKE '%Karim'
    ORDER BY name;
  7. Walk the category tree: list every leaf category along with its full path.
    Category tree-এ প্রতিটি leaf-এর সম্পূর্ণ path।
    ✨ Show Answer
    a7.sql
    WITH RECURSIVE t(id,name,label) AS (
      SELECT id,name,name FROM category WHERE parent IS NULL
      UNION ALL
      SELECT c.id,c.name,t.label||'/'||c.name
      FROM category c JOIN t ON c.parent=t.id
    )
    SELECT label FROM t
    WHERE NOT EXISTS (SELECT 1 FROM category WHERE parent=t.id)
    ORDER BY label;
  8. Compute Fibonacci numbers F(1)..F(15) with a recursive CTE.
    Recursive CTE দিয়ে Fibonacci F(1) থেকে F(15)।
    ✨ Show Answer
    a8.sql
    WITH RECURSIVE fib(n,a,b) AS (
      SELECT 1,0,1
      UNION ALL
      SELECT n+1,b,a+b FROM fib WHERE n<15
    )
    SELECT n,b AS fib FROM fib;
  9. Use chained CTEs: monthly totals → yearly totals from orders.
    Chained CTE: মাসিক → বার্ষিক total।
    ✨ Show Answer
    a9.sql
    WITH
    monthly AS (
      SELECT substr(ts,1,7) AS ym, SUM(amount) AS m FROM orders GROUP BY 1
    ),
    yearly AS (
      SELECT substr(ym,1,4) AS y, SUM(m) AS total FROM monthly GROUP BY 1
    )
    SELECT * FROM yearly ORDER BY y;
  10. Create a view that hides salary but shows everything else of employees.
    salary বাদ দিয়ে employees-এর বাকি সব কিছু দেখায় — এমন view।
    ✨ Show Answer
    a10.sql
    CREATE VIEW employees_public AS
    SELECT id,name,dept FROM employees;
    SELECT * FROM employees_public;
  11. Find every descendant (direct or indirect) of category id = 1 with a recursive CTE.
    id=1 category-র সব descendant।
    ✨ Show Answer
    a11.sql
    WITH RECURSIVE desc(id,name) AS (
      SELECT id,name FROM category WHERE parent=1
      UNION ALL
      SELECT c.id,c.name
      FROM category c JOIN desc d ON c.parent=d.id
    )
    SELECT * FROM desc ORDER BY id;
  12. In one sentence, what is the difference between a view and a CTE?
    View ও CTE-এর মূল পার্থক্য কী?
    ✨ Show Answer

    Answer: A view is a saved query that persists in the database catalog and is reusable across statements; a CTE is a named subquery that lives only inside the single statement that defines it.

    View database-এ সংরক্ষিত — যেকোনো query থেকে ডাকা যায়। CTE শুধু সেই একটি statement-এর ভেতরেই থাকে।

  13. Use a CTE to identify customers whose biggest order exceeds 2× their average.
    CTE দিয়ে — যেসব customer-এর সর্বোচ্চ order তাদের গড়ের ২ গুণ ছাড়িয়েছে।
    ✨ Show Answer
    a13.sql
    WITH stats AS (
      SELECT cid,AVG(amount) AS avg_a,MAX(amount) AS max_a
      FROM orders GROUP BY cid
    )
    SELECT * FROM stats WHERE max_a > 2*avg_a;
  14. Generate the multiplication table for 7 (1×7 to 10×7) with recursion.
    ৭-এর নামতা ১ থেকে ১০ — recursive CTE-তে।
    ✨ Show Answer
    a14.sql
    WITH RECURSIVE t(i) AS (
      SELECT 1 UNION ALL SELECT i+1 FROM t WHERE i<10
    )
    SELECT i,i*7 AS result FROM t;
  15. Why might a recursive CTE without a termination condition cause an infinite loop?
    termination না থাকলে recursive CTE-তে infinite loop কেন হয়?
    ✨ Show Answer

    Answer: The engine keeps applying the recursive step using the previous iteration's rows. If the step always produces at least one new row (or the data has a cycle and you don't track visited rows), the iteration never empties — so the engine keeps adding rows until memory or a built-in safety limit aborts it.

    প্রতিটি iteration-এ নতুন row আসতে থাকলে, বা data-তে cycle থাকলে, পুনরাবৃত্তি বন্ধ হয় না — memory শেষ না হওয়া পর্যন্ত চলতে থাকে।

  16. Build an org chart view org_with_level using a recursive CTE wrapped in a view.
    Recursive CTE-কে view-তে মুড়ে org_with_level বানান।
    ✨ Show Answer
    a16.sql
    CREATE VIEW org_with_level AS
    WITH RECURSIVE o(id,name,level) AS (
      SELECT id,name,0 FROM employees WHERE manager_id IS NULL
      UNION ALL
      SELECT e.id,e.name,o.level+1
      FROM employees e JOIN o ON e.manager_id=o.id
    )
    SELECT * FROM o;
    SELECT * FROM org_with_level ORDER BY level,name;

Summary — Module 19

A view is a saved query that persists in the catalog — perfect for encapsulation, security, and stable interfaces. A CTE (the WITH clause) is a named, statement-scoped subquery that turns one giant nested query into a readable pipeline of named steps. A recursive CTE = anchor + recursive step joined by UNION ALL — it is how SQL handles hierarchies, graph traversal, and series generation. Always include a termination condition; cycles in the data require explicit cycle detection.

View database-এ সংরক্ষিত query — encapsulation ও security-এর জন্য আদর্শ। CTE মানে WITH-এর সাহায্যে নাম দেওয়া subquery, এক statement-এর ভেতর pipeline-এর মতো কাজ করে। Recursive CTE (anchor + step) দিয়ে hierarchy বা series তৈরি করা যায় — কিন্তু termination শর্ত অবশ্যই রাখতে হবে, না হলে অনন্ত লুপ।

Next Module → Window Functions — analytics-এর প্রাণ।