Skip to content
Reference / DuckDB Macros
Reference

DuckDB Macros

A macro gives a name to an expression, or to a whole parameterized query. It is DuckDB's answer to 'can I write a function for this?'

A macro names a piece of SQL so you can use it by that name afterwards. There are two kinds, and they are used in different places: a scalar macro stands in for an expression and goes wherever a value goes, while a table macro stands in for a query and goes in the FROM clause.

A scalar macro
create or replace macro gross_margin(revenue, cost) as
case
  when revenue = 0 then null
  else round((revenue - cost) / revenue, 3)
end;

select product_name,
       gross_margin(price, cost) as margin
from products
order by margin desc, product_name
limit 5;

Every place that CASE used to be copied, there is now a name that says what it means. When the definition of margin changes, it changes in one place.

Table macros

A table macro is the piece a view cannot do: a saved query that takes arguments. A view is fixed once created; a table macro is a view with parameters.

A query you can pass arguments to
create or replace macro top_products(n) as table
select p.product_name,
       sum(oi.quantity * oi.unit_price) as revenue
from order_items oi
join products p on p.product_id = oi.product_id
group by p.product_name
order by revenue desc, p.product_name
limit n;

select * from top_products(3);

Call it in the FROM clause like a table, including in joins and subqueries. Calling one as if it were a value gets you Function "top_products" is a table function but it was used as a scalar function, which is the clearest signal that you reached for the wrong kind.

Which one to use

Scalar macro Table macro
Stands in for An expression A whole query
Used in SELECT, WHERE, anywhere a value fits FROM
Returns One value per row A result set
Declared with AS expression AS TABLE select ...

Macro vs view vs CTE

All three save you from repeating SQL, and the deciding question is whether the saved thing needs to change per use.

  • CTE: one statement, no object created. Fine until you need it in a second query.
  • View: reusable, but frozen. Filter it at the call site with a WHERE.
  • Table macro: reusable and parameterized, when the argument changes something a WHERE cannot reach, like a LIMIT or which column is grouped.

In practice a view plus a WHERE covers most cases and is more portable. Reach for a table macro when the parameter shapes the query rather than filters the rows.

Parameters, defaults, and overloads

Macro parameters can be left untyped when you want the body to work across compatible input types, or given explicit DuckDB types when you want binding to be stricter. The macro's result type is inferred from the expression. Because the macro body is expanded at the call site, type errors often point into that expanded expression.

Typed and default parameters
create or replace macro discount(
  price decimal(10, 2),
  pct := 10
) as price * (1 - pct / 100.0);

select discount(50.00)        as default_discount,
       discount(50.00, 20)    as custom_discount;

DuckDB also supports macro overloading. Put the overloads in the same CREATE MACRO statement; a later CREATE OR REPLACE MACRO replaces the existing macro definition rather than appending another overload.

Two overloads under one name
create or replace macro add_tax
  (amount) as amount * 1.06,
  (amount, rate) as amount * (1 + rate);

What macros cannot do

  • Recursion. A macro cannot call itself while its own definition is being bound.
  • Procedural logic. No loops, procedural variables, or statement blocks. A scalar macro expands to an expression; a table macro expands to a table-producing query.
  • Side effects. A macro body is not a place for INSERT, UPDATE, or DDL statements. For what replaces a procedure that changes data, see stored procedures.

A scalar macro can contain a subquery over a table, which is more than the name suggests: create macro order_count() as (select count(*) from orders) works.

Inspecting them

Read a macro back
select function_name,
       function_type,
       parameters,
       macro_definition
from duckdb_functions()
where function_name in ('gross_margin', 'top_products')
order by function_name

function_type is macro for scalar and table_macro for table macros, which is worth checking when a call fails and you are not sure which kind you created.

Remove one with DROP MACRO, which works on both kinds. DROP MACRO TABLE only accepts table macros.

Dialect note

Macros are a DuckDB feature and do not port. The nearest equivalent elsewhere is a user-defined function, such as PostgreSQL's CREATE FUNCTION ... LANGUAGE sql, or a SQL Server inline table-valued function. Both declare typed parameters where a macro's are untyped, and both live in the catalog with their own permissions. DuckDB accepts CREATE FUNCTION as a spelling of CREATE MACRO, but it is still a macro and not a procedural UDF. DuckDB has no CREATE PROCEDURE; scripts needing loops or variables belong in the host language.

Where they earn their keep

  • A business definition repeated across queries (margin, active customer, billable session) where everyone must compute it the same way.
  • An expression too fiddly to retype correctly, like a regex extraction or a nested coalesce.
  • A report shape run with different arguments: top N, one region, one month.

Used once, a macro is indirection with no payoff, so write the expression. Used in six queries by four people, it is the difference between one definition of margin and four.

Ready to run it?

Open the DuckDB playground with the matching dataset and query already filled in.