Skip to content

Latest commit

 

History

190 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DuckDBExcelAddin

DuckDB | Windows | Excel 365 | Excel 2024 | Excel 2021 | License

A native Microsoft Excel XLL add-in for querying Excel ranges with DuckDB SQL and parameter binding.

Screenshot

Screenshot

Quick Start

=DUCKDB.EXEC(
"SELECT cif, SUM(amount)
 FROM xlrange(1)
 GROUP BY cif",
A1:D10000
)

Query Excel ranges directly with DuckDB SQL and return the result as a dynamic array.

Features

  • Native DuckDB integration
  • Query Excel ranges
  • Parameter binding from Excel values
  • Asynchronous execution
  • No .NET runtime required

Status

Stable

Core functionality is considered stable and suitable for production use. Future releases will prioritize backward compatibility with existing workbooks.

Comparison with xlDuckDB

This project was inspired by xlDuckDB, an XLL add-in that integrates DuckDB with Microsoft Excel.

Feature DuckDBExcelAddin xlDuckDB
Native Excel formula experience ✅ ✅
Dynamic array (spill) results ✅ ✅
Query external files ✅ ✅
Query Excel ranges ✅ ✅
Parameter binding from Excel values ✅ ❌
xlrange type inference options ✅ ❌
Helpers for Excel date and time values ✅ ❌
Async execution ✅ ❓
XLL implementation ✅ ✅
.NET free ✅ ❌
Runtime DuckDB DLL upgrade ✅ ❓

Comparison based on publicly documented features available at the time of writing.

Installation

Requirements

  • Microsoft Excel 64-bit with Dynamic Array (Spill Range) support:
    • Microsoft 365 Excel
    • Excel 2024
    • Excel 2021
  • DuckDB 1.5.x (duckdb.dll)

Enable the Add-in

Copy these files to the same folder:

DuckDBExcelAddIn.xll
duckdb.dll

Open the XLL directly, or add it through the Excel Add-ins dialog.

DuckDB Upgrade

DuckDB is loaded dynamically at runtime.

Upgrading DuckDB generally requires only replacing:

duckdb.dll

with a newer compatible version.

Tutorial

Query Excel Ranges

  • Excel ranges are exposed as xlrange() table function:
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange(1)
 WHERE cif = 10001",
A1:D100
)
  • With inference options:
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange(1, sample=50)
 WHERE cif = 10001",
A1:D100
)
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange(1, all_varchar=true)
 WHERE cif = 10001",
A1:D100
)

Query External Files

  • Use database file as default database:
=DUCKDB.EXECA(
"Path\db.duckdb",
"SELECT * FROM table_name;"
)
  • Use DuckDB read_xxx() functions:
=DUCKDB.EXEC(
"SELECT *
 FROM read_duckdb('Path\db.duckdb', table_name='mytable')
 LIMIT 100"
)

Parameter Binding

  • Auto-incremented parameters:
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange(?)
 WHERE cif = ?",
A1:D100,
1,
10001
)
  • Positional parameters:
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange($1)
 WHERE cif = $2",
A1:D100,
1,
10001
)
  • Dynamic range selector:
=DUCKDB.EXEC(
"SELECT *
 FROM xlrange(?)
 WHERE cif = ?",
A1:D100,
F1:I200,
2,
10001
)
  • Dynamic file reader:
=DUCKDB.EXEC(
"SELECT *
 FROM read_duckdb(?, table_name=?)
 LIMIT 100",
"Path\db.duckdb",
"mytable"
)
  • Dynamic column selector:
=DUCKDB.EXEC(
"SELECT columns(?)
 FROM read_duckdb(?, table_name=?)
 LIMIT 100",
"column1",
"Path\db.duckdb",
"mytable"
)
  • Dynamic table selector:
=DUCKDB.EXEC(
"CREATE TABLE mytable(col1) AS
 SELECT * FROM range(10);
 SELECT *
 FROM query_table(?)",
"mytable"
)

Excel Date and Time Helpers

Adds scalar functions to convert Excel date and time values stored as DOUBLE to DuckDB DATE, TIME and TIMESTAMP:

=DUCKDB.EXEC(
"SELECT xldate(date_col)
 FROM xlrange(1)",
A1:D100
)
=DUCKDB.EXEC(
"SELECT xltime(time_col)
 FROM xlrange(1)",
A1:D100
)
=DUCKDB.EXEC(
"SELECT xldatetime(datetime_col)
 FROM xlrange(1)",
A1:D100
)

Architecture

DuckDBExcelAddin executes SQL inside the Excel process using the embedded DuckDB engine.

Excel ranges are exposed to DuckDB through the xlrange() table function, allowing worksheet data to participate in SQL queries.

SQL statements are extracted, then each statement is prepared, bound with parameters, and executed.

The result of the final statement is materialized and returned to Excel as a dynamic array (spill range).

References

Parameter Binding

Type Support Note
Auto incremented ? ✅
Positional $1 ✅ Must reset parameter index for each statement.
Named $param ❌ Excel doesn't support named parameters.

Formulas

All Formulas

Function Since Syntax Purpose Equivalent xlDuckDB Formula
DUCKDB.EXEC / DUCKDB.EXEC.ASYNC =DUCKDB.EXEC(sql, [range1], [range2], ..., [param1], [param2], ...) Execute SQL using in-memory database. =DuckDbQuery(sql,, range)
DUCKDB.EXECX / DUCKDB.EXECX.ASYNC =DUCKDB.EXECX([init_sql], sql, [range1], [range2], ..., [param1], [param2], ...) Execute initialization SQL, then main SQL using in-memory database.
DUCKDB.EXECA / DUCKDB.EXECA.ASYNC 1.1.0 =DUCKDB.EXECA([db_file_path], sql, [range1], [range2], ..., [param1], [param2], ...) Execute SQL using a DuckDB file as the default database. =DuckDbQuery(sql, dbfilepath, range)
DUCKDB.EXECAX / DUCKDB.EXECAX.ASYNC 1.1.0 =DUCKDB.EXECAX([db_file_path], [init_sql], sql, [range1], [range2], ..., [param1], [param2], ...) EXECA plus initialization SQL.
DUCKDB.INFO =DUCKDB.INFO() Return add-in and DuckDB runtime information.

Formula Parameters

  • Only sql is required, other parameters can be ignored. For example: =DUCKDB.EXECA( , A1).
  • Ranges are exposed to xlrange() and must appear before bound parameters.
  • When multiple SQL statements are supplied, all statements are executed sequentially, but only the result of the final statement is returned to Excel.
  • Asynchronous formulas do not block Excel recalculation, but they introduce overhead due to thread creation and deep copying of worksheet ranges.
  • Initialization SQL is executed before the main query and can be used to define reusable macros, views, or other helper objects.
  • Parameters are not bound in initialization SQL.

xlrange

Exposes Excel ranges as a table function.

Supports column projection pushdown since version 1.7.0.

Syntax:

xlrange(
  index,
  sample=30,
  all_varchar=false,
  header=true,
  strict=true,
  ignore_errors=false
)

Options

Parameter Since Status Default Description
index 🔴 Required 1-based position of an Excel range passed to formula.
all_varchar 🟢 Optional false - When true, all values are returned as VARCHAR and type inference is disabled.
- When false, column type is inferred.
sample 🟢 Optional 30 Number of data rows used for type inference.
- A value of 0 samples all data rows.
- Ignored when all_varchar=true.
header 1.2.0 🟢 Optional true - When true, the first row is interpreted as column names.
- When false, column names are generated as column_0, column_1, ...
strict 1.3.0 🟢 Optional true - When true, column names must be non-empty and unique; otherwise, an error is raised.
- When false, empty column names are generated as unnamed_0, unnamed_1, and so on. Duplicated names are renamed as name, name_1, name_2, and so on.
- Ignored when header=false.
ignore_errors 1.5.0 🟢 Optional false - When false an error is raised if values incompatible with inferred type are encountered.
- When true incompatible values are silently converted to NULL.

Inference Strategy

  1. Scan for the first non-empty value and use its type as the candidate column type.
Value Inferred As
Whole numbers within the INT32 range
and their text representations(*)
INTEGER
Other finite numbers and their
text representations(*)
DOUBLE
TRUE/FALSE and recognized text
representations(*), including
T/F, YES/NO, and Y/N, case-insensitive
BOOLEAN
Other text VARCHAR
Empty, Excel error Ignored during inference

(*)Text values with leading or trailing whitespace are trimmed during inference.

  1. Sample the remaining rows up to the configured sample limit.

  2. If incompatible types are encountered, the column type is promoted to DOUBLE or VARCHAR.

Examples can be found in examples\test_cases.xlsx.

Out-of-Sample Conversion

If sample is less than number of data rows, out-of-sample values are converted to inferred type. An error is returned if conversion fails.

Inferred type Compatible value
INTEGER Same as inference rule
TRUE/FALSE-like values are incompatible
DOUBLE Same as inference rule
BOOLEAN Same as inference rule, plus
0/1-like values are compatible
VARCHAR Text, number, TRUE/FALSE

Examples can be found in examples\test_cases.xlsx.

Date and Time Helpers

Function Purpose Syntax
xldate Convert an Excel serial date value to a DuckDB DATE. xldate(value)
xltime Convert the fractional portion of an Excel serial value to a DuckDB TIME. xltime(value)
xldatetime Convert an Excel serial datetime value to a DuckDB TIMESTAMP. xldatetime(value)

Known Limitations

Excel Number Formats

Formulas cannot access cell number formats.

When using xlrange, numeric header cells formatted as dates or times are registered using their underlying numeric values rather than their displayed formats. For example, an Excel date displayed as December 31, 2026 may be registered as the column name 46387.

Workaround: Convert Excel date to text with =TEXT(value, date_format) formula first.

Excel Worksheet Limits

Results are returned as Excel Dynamic Arrays.

Excel limits apply:

Limit Value
Rows 1,048,576
Columns 16,384
String length 32,767

Queries exceeding these limits are not supported.

Excel Number Types

Large DuckDB numeric types (BIGINT, HUGEINT, DECIMAL) may lose precision when converted to Excel numbers (DOUBLE).

Excel Range-Based Data Exchange

Data is exchanged through Excel ranges.

  • Excel worksheet limits apply.
  • Large datasets may consume significant memory.
  • Input and output data must fit within Excel worksheets.

For large datasets, query DuckDB-supported sources directly (Parquet, CSV, DuckDB databases, etc.) and return only the required results to Excel.

SELECT *
FROM read_parquet('large_dataset.parquet')
LIMIT 1000

DuckDB Composite Types

The following DuckDB types are not supported as Excel results:

  • LIST
  • STRUCT
  • MAP
  • UNION
  • VARIANT

Workaround: Convert or serialize the value to VARCHAR before returning it to Excel.

xlrange Row Filter Pushdown

Currently, xlrange does not support row filter pushdown because the DuckDB C API does not expose pushed filters to table functions.

Dynamic PIVOT with Parameter Binding

Currently, a dynamic PIVOT, meaning a PIVOT without an IN clause, cannot use parameters in its source query. This is a limitation of DuckDB’s dynamic PIVOT implementation.

For example:

=DUCKDB.EXEC(
  "PIVOT xlrange(?) ON col1 USING sum(col2) GROUP BY col0",
  A1:C5,
  1
)

Returns:

Parser Error: PIVOT statements with pivot elements extracted from the data cannot have parameters in their source...

Using a CTE does not bypass the restriction:

=DUCKDB.EXEC(
  "WITH cte AS (from xlrange(?))
   PIVOT cte ON col1 USING sum(col2) GROUP BY col0",
  A1:C5,
  1
)

This may return an internal error such as:

INTERNAL Error: Attempted to dereference unique_ptr that is NULL! ...

Workaround:

  • Specify the pivot values explicitly with an IN clause.
  • Store the parameter value in a DuckDB variable and reference the variable from the query, where applicable.
  • Materialize the parameterized source in a temporary table or temporary view, and then pivot that object without parameters.

Troubleshooting

Issue Verify
Add-in fails to load - Excel is 64-bit
- duckdb.dll is located next to DuckDBExcelAddIn.xll
- duckdb.dll version is 1.5.x
- The add-in is not blocked
#VALUE! returned - The add-in is loaded
- Dynamic Arrays are supported
- Input ranges and Result size do not exceed Excel limits
#SPILL! error - The destination spill range is empty
- There are enough rows and columns available to display the result

Building

Build Requirements

  • Excel XLL SDK
  • DuckDB C API (duckdb.h)
  • uthash by troydhanson and maintained Arthur O'Dwyer

Toolchain

Development and testing are performed primarily using w64devkit (MinGW-w64).

Other toolchains such as Visual Studio (MSVC) may work but are currently unverified.

Contributions and testing reports are welcome.

Build Notes

XLCALL.H XLOPER struct contain a member named:

bool

which conflicts with C bool keyword.

You need to edit the header and rename the member to, for example:

xbool

and update the corresponding references in FRAMEWRK.C.

This modification only affects local compilation and does not affect runtime behavior because XLOPER is never used.

Build Instruction

  • Development
make EXCEL_SDK_PATH=<excel-sdk> DUCKDB_INC_PATH=<duckdb-include> xll
  • Release
make ADDIN_VERSION=vx.x.x EXCEL_SDK_PATH=<excel-sdk> DUCKDB_INC_PATH=<duckdb-include> xll

Tests

The release package includes a workbook containing examples and regression tests for the major features of DuckDBExcelAddin:

examples\test_cases.xlsx

Acknowledgements

Special thanks to the DuckDB team and contributors for creating an exceptional embedded analytical database.

This project was inspired by xlDuckDB, particularly its formula-based integration approach and the xlrange concept.

Additional thanks to:

  • uthash by troydhanson and maintained Arthur O'Dwyer
  • Microsoft Excel XLL SDK

License

MIT License

About

A native Microsoft Excel XLL add-in for querying Excel ranges with DuckDB SQL and parameter binding.

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages