Chapter 07

持久化数据库与 DML

从内存数据库到持久化文件;CREATE/INSERT/UPDATE/DELETE;宏、视图与多数据库管理

7.1 持久化数据库文件

DuckDB 支持将数据库持久化到单个 .duckdb 文件中(类似 SQLite 的 .sqlite 文件)。DuckDB 的文件格式与 SQLite 不同,是自研的列式存储格式,不能互相使用。

SQL-- CLI:打开或创建持久化数据库
duckdb analytics.duckdb

-- 连接到数据库后,所有操作自动持久化
CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR);
INSERT INTO users VALUES (1, 'Alice');
-- 关闭后重新打开,数据仍然存在

-- 查看数据库文件版本信息
SELECT * FROM duckdb_databases();
SELECT * FROM duckdb_tables();
⚠️

DuckDB 文件版本注意 DuckDB 文件格式在主版本之间可能不兼容(如 v0.10 的文件不能被 v0.9 读取)。生产环境请确保所有客户端使用相同的 DuckDB 版本。如需迁移,可通过 EXPORT DATABASE 导出 SQL 脚本再重新导入。

7.2 DDL:创建表与数据类型

SQLCREATE TABLE orders (
    order_id    BIGINT PRIMARY KEY,
    user_id     INTEGER NOT NULL,
    product     VARCHAR(200) NOT NULL,
    amount      DECIMAL(10, 2) CHECK (amount > 0),
    status      VARCHAR DEFAULT 'pending',
    tags        VARCHAR[],               -- 数组列
    metadata    JSON,                    -- JSON 列
    created_at  TIMESTAMPTZ DEFAULT now()
);

-- CREATE TABLE AS(从查询结果创建表)
CREATE TABLE orders_2024 AS
    SELECT * FROM orders WHERE year(created_at) = 2024;

-- 从文件直接创建表
CREATE TABLE raw_sales AS SELECT * FROM 'sales.parquet';

-- 修改表结构
ALTER TABLE orders ADD COLUMN region VARCHAR;
ALTER TABLE orders DROP COLUMN tags;
ALTER TABLE orders RENAME COLUMN product TO product_name;

7.3 DML:增删改查

SQL-- INSERT 单行
INSERT INTO orders (order_id, user_id, product, amount)
VALUES (1001, 42, 'MacBook Pro', 15999.00);

-- INSERT 批量(性能更好)
INSERT INTO orders (order_id, user_id, product, amount)
VALUES
    (1002, 43, 'iPhone 16', 8999),
    (1003, 44, 'AirPods Pro', 1999);

-- INSERT FROM SELECT(从查询结果插入)
INSERT INTO orders_archive
SELECT * FROM orders WHERE year(created_at) < 2023;

-- INSERT OR REPLACE(冲突时替换)
INSERT OR REPLACE INTO orders VALUES (1001, 42, 'MacBook Air', 12999);

-- UPDATE
UPDATE orders
SET status = 'shipped', region = '华东'
WHERE order_id = 1001;

-- DELETE
DELETE FROM orders
WHERE status = 'cancelled'
  AND created_at < current_date() - INTERVAL '30 days';

-- UPSERT(INSERT ON CONFLICT DO UPDATE)
INSERT INTO user_stats (user_id, total_orders)
VALUES (42, 1)
ON CONFLICT (user_id) DO UPDATE
    SET total_orders = total_orders + EXCLUDED.total_orders;

7.4 事务支持

SQL-- 显式事务
BEGIN;

UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;

COMMIT;   -- 提交事务
-- 或
ROLLBACK; -- 回滚事务

-- Savepoint(部分回滚)
BEGIN;
INSERT INTO orders VALUES (...);
SAVEPOINT sp1;
UPDATE inventory SET stock = stock - 1;
ROLLBACK TO sp1;  -- 只回滚到 sp1,INSERT 保留
COMMIT;

7.5 序列与自增

SQL-- 创建序列
CREATE SEQUENCE order_seq START 1000 INCREMENT 1;

-- 使用序列
INSERT INTO orders (order_id, product)
VALUES (nextval('order_seq'), 'iPhone');

-- SEQUENCE 自增类型(简写)
CREATE TABLE products (
    id          INTEGER DEFAULT nextval('seq_products_id'),
    name        VARCHAR
);

-- DuckDB 1.0+ 支持 BIGINT AUTO_INCREMENT
CREATE TABLE events (
    id     BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name   VARCHAR
);

7.6 视图(VIEW)

SQL-- 创建视图
CREATE VIEW active_orders AS
    SELECT * FROM orders WHERE status NOT IN ('cancelled', 'refunded');

-- 查询视图(和表一样)
SELECT COUNT(*) FROM active_orders;

-- 删除视图
DROP VIEW active_orders;

-- 视图直接包装文件(无需先建表!)
CREATE VIEW raw_logs AS
    SELECT * FROM read_csv_auto('logs/*.csv');

-- 这样就可以用表名方式查询文件
SELECT date_trunc('hour', timestamp), COUNT(*)
FROM raw_logs
GROUP BY 1;

7.7 宏 MACRO(存储过程替代品)

DuckDB 使用 MACRO 实现可复用的 SQL 代码,比存储过程更简洁,分为标量宏和表宏:

SQL-- 标量宏:接受参数返回标量值
CREATE MACRO clamp(x, lo, hi) AS
    CASE
        WHEN x < lo THEN lo
        WHEN x > hi THEN hi
        ELSE x
    END;

SELECT clamp(150, 0, 100);   -- 返回 100
SELECT clamp(-5, 0, 100);   -- 返回 0

-- 表宏:接受参数返回表结果
CREATE MACRO get_top_products(n := 10) AS TABLE
    SELECT product, SUM(amount) AS revenue
    FROM orders
    GROUP BY product
    ORDER BY revenue DESC
    LIMIT n;

-- 使用表宏
SELECT * FROM get_top_products();       -- 默认 Top 10
SELECT * FROM get_top_products(n := 5); -- 自定义 Top 5

7.8 ATTACH 多数据库

DuckDB 支持同时连接多个数据库文件,甚至可以跨库 JOIN!

SQL-- 附加其他 DuckDB 文件
ATTACH 'sales_2023.duckdb' AS db2023;
ATTACH 'sales_2024.duckdb' AS db2024 (READ_ONLY);

-- 跨库查询(db名.表名)
SELECT * FROM db2023.orders
UNION ALL
SELECT * FROM db2024.orders;

-- 查看已附加的数据库
SHOW DATABASES;

-- 切换默认数据库
USE db2024;

-- 分离数据库
DETACH db2023;

-- 附加 SQLite 数据库(跨库 JOIN!)
INSTALL sqlite; LOAD sqlite;
ATTACH 'legacy.sqlite' AS sqlite_db (TYPE SQLITE);
SELECT d.*, s.legacy_data
FROM duckdb_table d
JOIN sqlite_db.old_table s ON d.id = s.id;
💡

本章小结 DuckDB 支持完整的持久化文件模式,.duckdb 文件格式是列式存储(不同于 SQLite 的行存),不同版本可能不兼容。支持标准 DDL/DML、事务(BEGIN/COMMIT/ROLLBACK)、序列、视图、UPSERT。MACRO 是轻量级的可复用 SQL 代码单元,分标量宏和表宏。ATTACH 允许同时连接多个数据库文件,甚至可以跨 DuckDB/SQLite 库 JOIN。