Query this Blog with DuckDB
Written
I wanted to have some fun with DuckDB, and for some reason, I thought it would be fun to query my blog using it. So, I added two endpoints for querying the current posts and resource pages as CSV files. This is already enough for DuckDB, because it has CSV parsing and an HTTP client built-in:
Note: I used .mode html in the DuckDB CLI to give me a headstart on formatting the query results.
Query (DuckDB CLI)
SELECT title, date, link
FROM 'https://australorp.dev/blog.csv'
ORDER BY date DESC
Results
| title | date | link |
|---|---|---|
| Query this Blog with DuckDB | 2026-06-25 | https://australorp.dev/blog/query-this-blog |
| Lists or Tables? | 2026-06-24 | https://australorp.dev/blog/lists-vs-tables |
| Write Game UIs like a Caveman | 2026-06-23 | https://australorp.dev/blog/write-game-ui-like-a-caveman |
| Being a Professional Programmer | 2026-05-04 | https://australorp.dev/blog/professional-programmer-talk |
| Godot Virtual Joystick | 2026-04-22 | https://australorp.dev/blog/godot-virtual-joystick |
| Making a Lichess TV Viewer with Hyperscript | 2026-04-16 | https://australorp.dev/blog/lichess-tv-hyperscript |
I love how easy DuckDB makes it to query a CSV. Let’s do one more for fun. Let’s find the largest pages by the length of their source Markdown files.
Query (DuckDB CLI)
WITH pages AS (
SELECT title, markdown
FROM 'https://australorp.dev/resources.csv'
UNION
SELECT title, markdown
FROM 'https://australorp.dev/blog.csv'
)
SELECT title, length(markdown) as length
FROM pages
ORDER BY length(markdown) DESC
Results
| title | length |
|---|---|
| Write Game UIs like a Caveman | 9525 |
| Lists or Tables? | 8170 |
| Being a Professional Programmer | 5817 |
| Programming | 3719 |
| Query this Blog with DuckDB | 3009 |
| Web Design | 1938 |
| Romans | 1045 |
| Making a Lichess TV Viewer with Hyperscript | 880 |
| Godot Virtual Joystick | 603 |
Nice. The length of the Markdown source is not a great way to estimate the length or reading time of the page, but it’s not the worst way either.
Serving the pages as CSV files almost feels like a poor man’s RSS. Since I am using Astro, it was very easy to add. All I had to do was make a new static file endpoint that queries my blog collection. I wrote the CSV escaping and formatting myself because I hate adding new dependencies if I don’t have to.
import { getCollection } from 'astro:content'
export async function GET(_) {
const posts = await getCollection('blog')
const data = csv(posts.map(x => [
x.id,
`https://australorp.dev/blog/${x.id}`,
x.data.title,
x.data.date,
x.data.tags.join(','),
x.body,
]))
return new Response(data)
}
function csv(rows) {
const data = rows
.map(row =>
row.map(field => formatField(field)).join(',')
)
data.unshift(['id','link','title','date','tags','markdown'])
return data.join('\n')
}
function formatField(x) {
return `"${escape(x)}"`
}
function escape(x) {
return x.replaceAll('"', '""')
}
That’s all I have for now. Go download DuckDB and write some queries against my blog.