DuckDB GLOB Operator
DuckDB supports the GLOB operator for case-sensitive pattern matching on strings. It also provides a glob() table function that returns files matching a filesystem pattern. Both use the same wildcard syntax — * for zero or more characters, ? for exactly one, and [...] brackets for character sets. This post covers the operator, the table function, and the patterns that work in each.
Wildcards
The * wildcard
* matches zero or more characters. It is the most common pattern for suffix, prefix, or infix matching.
1
2
3
4
5
SELECT 'report.parquet' GLOB '*.parquet'; -- true (suffix match)
SELECT 'report.parquet' GLOB 'report*'; -- true (prefix match)
SELECT 'report_2025.parquet' GLOB 'report*parquet'; -- true (infix)
SELECT 'report' GLOB 'report*'; -- true (* matches empty string)
SELECT 'report.csv' GLOB '*.parquet'; -- false
The ? wildcard
? matches exactly one character — no more, no less.
1
2
3
4
5
SELECT 'a1' GLOB 'a?'; -- true (one character after 'a')
SELECT 'a' GLOB 'a?'; -- false (zero characters after 'a')
SELECT 'a12' GLOB 'a?'; -- false (two characters after 'a')
SELECT 'abc' GLOB '???'; -- true (exactly three characters)
SELECT 'ab' GLOB '???'; -- false (only two characters)
The [...] bracket
Brackets define a character set. The pattern matches if the character at that position is in the set. You can use ranges with -.
1
2
3
4
5
6
7
SELECT 'cat' GLOB '[abc]at'; -- true ('c' is in [abc])
SELECT 'dat' GLOB '[abc]at'; -- false ('d' is not in [abc])
SELECT 'f' GLOB '[a-z]'; -- true (lowercase range)
SELECT 'F' GLOB '[a-z]'; -- false (uppercase not in range)
SELECT 'F' GLOB '[a-zA-Z]'; -- true (combined ranges)
SELECT '5' GLOB '[0-9]'; -- true (digit range)
SELECT 'a' GLOB '[0-9]'; -- false
The [!...] negation
Prefixing the bracket contents with ! inverts the match — the character must not be in the set.
1
2
3
4
SELECT 'x' GLOB '[!abc]'; -- true ('x' is not in [abc])
SELECT 'a' GLOB '[!abc]'; -- false ('a' is in [abc])
SELECT 'A' GLOB '[!a-z]'; -- true (uppercase is not in [a-z])
SELECT 'a' GLOB '[!a-z]'; -- false
Case Sensitivity
Unlike LIKE, GLOB is always case-sensitive. There is no IGLOB.
1
2
3
4
SELECT 'NYC' GLOB 'nyc'; -- false
SELECT 'NYC' GLOB 'NYC'; -- true
SELECT 'NYC' GLOB 'n*'; -- false
SELECT 'NYC' GLOB 'N*'; -- true
Operator Aliases
DuckDB provides ~~~ as a synonym for GLOB, and NOT GLOB for negation:
1
2
3
4
5
SELECT 'test.txt' ~~~ '*.txt'; -- true
SELECT 'test.csv' ~~~ '*.txt'; -- false
SELECT 'a1' ~~~ '[a-z]?'; -- true
SELECT NOT 'test.csv' GLOB '*.txt'; -- true
SELECT NOT 'test.txt' GLOB '*.txt'; -- false
Combining Wildcards
The real power comes from combining *, ?, and [...] in a single pattern:
1
2
3
4
SELECT 'file_a.txt' GLOB 'file_?.tx*'; -- true
SELECT 'log_2025.csv' GLOB '[a-z]*_????.csv'; -- true
SELECT 'x9_data' GLOB '[a-z]?_*'; -- true
SELECT 'X9_data' GLOB '[a-z]?_*'; -- false (case-sensitive)
The pattern [a-z]*_????.csv reads as: starts with a lowercase letter, then any characters, then an underscore, then exactly four characters, then .csv. This matches filenames like log_2025.csv or data_0001.csv.
GLOB in WHERE Clauses
GLOB works in WHERE clauses just like any other predicate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE files (name VARCHAR);
INSERT INTO files VALUES
('report_2024.csv'), ('report_2025.csv'),
('data_jan.parquet'), ('data_feb.parquet'),
('README.md'), ('notes.txt');
SELECT name FROM files WHERE name GLOB '*.parquet';
-- data_jan.parquet
-- data_feb.parquet
SELECT name FROM files WHERE name GLOB '*_20??.csv';
-- report_2024.csv
-- report_2025.csv
SELECT name FROM files WHERE name GLOB '[a-z]*';
-- report_2024.csv, report_2025.csv, data_jan.parquet,
-- data_feb.parquet, notes.txt (5 rows — all lowercase-starting names)
SELECT name FROM files WHERE name GLOB '[!a-z]*';
-- README.md (only the uppercase-starting name)
The glob() Table Function
The glob() table function returns filesystem paths matching a pattern. It uses the same wildcard syntax as the GLOB operator but operates on the local filesystem instead of string values.
1
2
3
SELECT * FROM glob('*.parquet');
SELECT * FROM glob('data/year=*/month=*/*.parquet');
SELECT * FROM glob('/var/log/app-????-??-??.log');
This is useful for discovering files before reading them. DuckDB’s read_parquet() and read_csv() also accept glob patterns directly, so glob() is primarily for inspection:
1
2
3
4
5
SELECT count(*) FROM glob('data/**/*.parquet');
SELECT file FROM glob('exports/report_*.csv')
ORDER BY file DESC
LIMIT 5;
The ** pattern in glob() matches directories recursively, which is not available in the GLOB string operator.
Pattern Reference
| Pattern | Meaning | Example Match |
|---|---|---|
* |
Zero or more characters | *.csv matches data.csv |
? |
Exactly one character | file_?.txt matches file_a.txt |
[abc] |
One character from the set | [abc]at matches cat |
[a-z] |
One character in the range | [a-z] matches f |
[!abc] |
One character NOT in the set | [!abc]at matches dat |
[!a-z] |
One character NOT in the range | [!a-z] matches F |
~~~ |
GLOB synonym | name ~~~ '*.csv' |