For loops for dynamic SQL in dbt - Time & Space Complexity
We want to understand how the time needed to run dynamic SQL with for loops changes as the input grows.
How does the number of loop runs affect the total work done?
Analyze the time complexity of the following code snippet.
{% set tables = ['sales', 'customers', 'products'] %}
{% for table in tables %}
{{
run_query(
"""
SELECT COUNT(*) FROM {{ table }}
"""
)
}}
{% endfor %}
This code runs a SQL query for each table in a list, counting rows dynamically.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Running a SQL query inside a for loop.
- How many times: Once for each table in the list.
As the number of tables increases, the number of queries grows the same way.
| Input Size (n) | Approx. Operations |
|---|---|
| 3 | 3 queries run |
| 10 | 10 queries run |
| 100 | 100 queries run |
Pattern observation: The work grows directly with the number of tables.
Time Complexity: O(n)
This means if you double the number of tables, the total queries run will also double.
[X] Wrong: "Running queries inside a loop is always constant time because each query is separate."
[OK] Correct: Each query adds to total time, so more tables mean more queries and more total work.
Understanding how loops affect query counts helps you write efficient data transformations and explain your code clearly.
"What if we nested another loop inside to run queries for each column in each table? How would the time complexity change?"
Practice
{% for %} loops in dbt SQL models?Solution
Step 1: Understand the role of
The{% for %}in dbt{% for %}loop is used to repeat code blocks dynamically in dbt SQL models.Step 2: Identify the benefit of repeating code
Repeating code patterns saves time and reduces errors by avoiding manual copy-pasting.Final Answer:
To repeat SQL code patterns dynamically and reduce manual repetition -> Option DQuick Check:
For loops = dynamic repetition [OK]
- Thinking for loops create tables
- Confusing dbt loops with Python loops
- Believing loops stop queries
Solution
Step 1: Recall dbt Jinja syntax for loops
dbt uses Jinja templating where loops start with{% for variable in sequence %}.Step 2: Compare options to correct syntax
Only {% for item in list %} matches the correct Jinja for loop syntax.Final Answer:
{% for item in list %} -> Option CQuick Check:
dbt for loop syntax = {% for %} [OK]
- Using {{ }} instead of {% %} for loops
- Using wrong keywords like 'loop'
- Using angle brackets instead of curly braces
{% for col in ['id', 'name', 'age'] %}SELECT {{ col }} FROM users{% if not loop.last %} UNION ALL {% endif %}{% endfor %}Solution
Step 1: Understand the loop iteration
The loop runs over ['id', 'name', 'age'], producing three SELECT statements.Step 2: Check the UNION ALL insertion
Between each SELECT except the last, 'UNION ALL' is added to combine results.Final Answer:
SELECT id FROM users UNION ALL SELECT name FROM users UNION ALL SELECT age FROM users -> Option AQuick Check:
Loop + UNION ALL = combined selects [OK]
- Ignoring the UNION ALL between selects
- Assuming all columns selected in one query
- Thinking commas separate selects
{% for col in columns %}SELECT {{ col }} FROM users{% endfor %}Assuming
columns = ['id', 'name'].Solution
Step 1: Analyze the loop output
The loop outputs two SELECT statements back to back without any separator.Step 2: Identify missing SQL syntax
Without UNION ALL or a separator, the SQL is invalid and will cause errors.Final Answer:
Missing UNION ALL or separator between SELECT statements -> Option BQuick Check:
Multiple SELECTs need UNION ALL [OK]
- Forgetting to add UNION ALL
- Using wrong loop syntax
- Misnaming variables inside loop
Solution
Step 1: Filter columns inside the for loop
{% for col in columns if col %}SELECT {{ col }} FROM users{% if not loop.last %} UNION ALL {% endif %}{% endfor %} usesfor col in columns if colto skip empty or None values correctly.Step 2: Add UNION ALL only between valid selects
{% for col in columns if col %}SELECT {{ col }} FROM users{% if not loop.last %} UNION ALL {% endif %}{% endfor %} adds UNION ALL only if not the last valid column, avoiding trailing UNION ALL.Final Answer:
{% for col in columns if col %}SELECT {{ col }} FROM users{% if not loop.last %} UNION ALL {% endif %}{% endfor %} -> Option AQuick Check:
Filter in loop + conditional UNION ALL = correct [OK]
- Using incorrect if condition syntax
- Adding UNION ALL after last select
- Not filtering out empty or None columns
Ready to prove it?
Pass the test and this pattern moves unknown
