# 数据源

> 在 DuckLocal 中打开 CSV、TSV、Parquet、JSON、Excel 文件、文件夹、通配符或 DuckDB 数据库，附加更多数据库，从访达打开文件并用快速查看预览。

Source: https://ducklocal.app/docs/zh/data-sources

**[English](../data-sources.md)**

DuckLocal 把文件变成视图，挂到内存中的 DuckDB 连接上；打开了数据库文件时，就挂到那个数据库文件里。旁边还可以再附加其他数据库。不需要任何配置。

## 打开数据的方式

以下入口都走同一套解析规则：

1. 命令行中的路径：`ducklocal ./logs/ billing.parquet`
2. 标题栏的 **打开** 菜单：**打开文件**、**打开文件夹**，或 **输入路径或通配符**——后者会打开 **打开数据** 对话框，可以手动输入路径，也可以用系统文件选择器选取
3. 把文件或文件夹拖到窗口的任意位置
4. 在 Mac 上，由访达交给 DuckLocal 的文件，见[从访达打开](#open-from-finder)

如果想在当前数据库旁边再加一个数据库、而不是替换它，用 **打开 → 附加数据库…**，见[附加数据库](#attached-databases)。

在 macOS 上，还可以在访达里双击打开 Parquet 和 DuckDB 文件；选中 Parquet、DuckDB、CSV、TSV 或 JSON Lines 文件按空格键，“快速查看”会显示它的前几行，不用启动 DuckLocal。

## 支持的文件格式

扩展名不区分大小写。

| 扩展名 | 读取方式 |
| --- | --- |
| `.csv`, `.tsv`, `.txt` | `read_csv_auto` 视图 |
| `.parquet` | `read_parquet` 视图 |
| `.json`, `.ndjson`, `.jsonl` | `read_json_auto` 视图 |
| `.xlsx`, `.xls`, `.xlsb`, `.ods` | 每个工作表导入为一张表 |

`.json.gz`、`.csv.gz` 这类 gzip 文本也算数据文件，适用于 `schema`、`source` block 和挂载文件；`logs.json.gz` 会成为名为 `logs` 的视图。0.4.0 之前，`ducklocal schema` 会把这种文件当作数据库尝试打开；如果你用的是旧版，请在 SQL 里用 `read_json` 或 `read_csv` 读取。

其他扩展名都不算数据文件，参见[数据库文件](#database-files)。

工作簿是唯一会转换为**表**而非视图的格式，并且会导入**所有工作表**。第一个工作表以文件名主干（去掉扩展名的部分）命名，其余工作表命名为 `{主干}_{工作表名}`，空工作表会跳过。列类型根据单元格内容推断：整数为 `BIGINT`，整数与浮点数混合为 `DOUBLE`，日期为 `TIMESTAMP`，其他混合或无法识别的内容为 `VARCHAR`；空单元格导入为 `NULL`。

## 文件夹与通配符

**文件夹**会递归遍历，其中每个数据文件都变成一个视图。遍历遵循两条规则：

- 跳过隐藏条目（名字以 `.` 开头），也不进入其下的子目录。
- 不跟随符号链接。指向上层目录的链接会让遍历没完没了；而链接指向的目录，通常会在它实际所在的位置遍历到。

文件按排序后的顺序挂载，所以同一个文件夹每次得到的工作区都一样。

**通配符**可以包含 `*`（任意一串字符）和 `?`（恰好一个字符）。匹配按路径逐段、直接在文件系统上进行，所以通配符可以跨目录：

```bash
ducklocal './data/2026-0*/*.parquet'
```

没有转义字符。`**` 没有特殊含义，效果和单个 `*` 一样，不会递归。通配符匹配到的目录会被忽略，只挂载文件。这里同样跳过隐藏条目。

不加引号的通配符会先由 shell 展开，DuckLocal 拿到的是展开后的结果。这样没问题。只有想让 DuckLocal 自己展开时才需要加引号——比如 shell 匹配不到文件、不加引号就会报错的情况。

## 一次读取多个 CSV

DuckLocal 挂载的每个文件都是独立的视图，schema 各自嗅探，所以两个结构不一致的 CSV 互不干扰。麻烦出在一条 SQL 同时读取多个文件的时候：

```sql
SELECT * FROM read_csv('data/*.csv', union_by_name = true)
```

- **各文件嗅探出的类型可能对不上。** 读取函数根据样本推断列类型。某列在其他文件里都是数值，只在一个文件里混进了字符串 `'NULL'`，整个读取就会报转换错误。这时在 `SELECT` 里做 cast 已经来不及，因为出错的是读取这一步。应该把所有列按文本读入，再在 SQL 里转换：

  ```sql
  SELECT try_cast(amount AS DOUBLE) AS amount
  FROM read_csv('data/*.csv', union_by_name = true, all_varchar = true)
  ```

  `union_by_name = true` 还能兼容列不完全相同的文件。
- **字符串 `'NULL'` 不是 `NULL`。** CSV 里写着的 `'NULL'`（或 `'N/A'`、`'null'`）只是普通文本，`WHERE amount IS NOT NULL` 过滤不掉。可以用 `NULLIF(amount, 'NULL')`，或者 `WHERE amount <> 'NULL'`。

两种修法可以叠加：`try_cast(NULLIF(amount, 'NULL') AS DOUBLE)`。

## 数据库文件 {#database-files}

DuckLocal 同一时间只有一个**主数据库**：要么在内存里，要么是你打开的 DuckDB 文件。数据文件变成主数据库里的视图。此外还可以在旁边[附加其他数据库](#attached-databases)。

只要路径**存在**且不是数据文件，就会作为主数据库打开，不会挂载成视图。扩展名无所谓：`.db`、`.duckdb`，甚至 `.md` 路径都会当作数据库，能不能真正打开由 DuckDB 决定。

由此有几点需要知道：

- **只有“打开数据”对话框能新建数据库。** 输入一个以 `.duckdb` 结尾、还不存在的路径，DuckLocal 会创建这个数据库，缺少的父目录也一并创建。其他入口——命令行、文件选择器、拖放、访达——遇到不存在的路径都会报 `文件不存在`。
- **打开数据库会替换主数据库。** 已登记的文件和附加的数据库会重新挂到新连接上。S3 凭据不会保留，需要重新配置。
- **文件打不开时**，DuckLocal 会说明原因（见[下文](#when-a-database-will-not-open)）。启动时它会退回内存模式的工作区，不会启动失败；运行期间打开失败，原来的工作区保持不变。
- **已经附加在主数据库旁边的文件**，会先分离，再作为主数据库打开；打开失败的话，附加会恢复原样。打开的正是当前主数据库文件时，直接沿用，不会重复打开。

打开数据库文件后再挂载数据文件，会**把这些视图写进那个数据库文件**，视图会一直保存在那里，不只存在于本次会话。

### 数据库文件打不开时 {#when-a-database-will-not-open}

DuckDB 拒绝打开某个文件时，DuckLocal 会自己读取文件开头的几个字节；如果能从中看出原因，就把原因和解决办法放在 DuckDB 原始报错的上方。DuckDB 的原始报错保留在下面，方便查看确切错误。说明文字跟随界面语言。

| 文件是什么 | DuckLocal 的说明 |
| --- | --- |
| 由比 DuckLocal 内置版本**更新的 DuckDB** 写入的 DuckDB 文件 | 写明两个版本号，并给出一段 SQL，在那个较新的 DuckDB 里运行，把文件复制成 DuckLocal 能读的格式（见下） |
| 来自 **v0.9 或更早版本**的 DuckDB 文件 | 用原来的 DuckDB 执行 `EXPORT DATABASE 'dir'`，再在 DuckLocal 里执行 `IMPORT DATABASE 'dir'` |
| **SQLite** 数据库 | 它不是 DuckDB 文件，应该附加它——用[附加数据库…](#attached-databases)，或者 `INSTALL sqlite; ATTACH '…' (TYPE sqlite);` |
| 其他**不是 DuckDB 文件**的东西 | CSV、Parquet、JSON、Excel 文件请作为数据文件打开 |

对于较新 DuckDB 写的文件，提示里会填好文件路径和 DuckLocal 内置的 DuckDB 版本。在创建该文件的那个 DuckDB 里运行，然后打开 `compat.duckdb`：

```sql
ATTACH '/path/to/file.duckdb' AS src (READ_ONLY);
ATTACH 'compat.duckdb' AS dst (STORAGE_VERSION 'v2.0.0');
COPY FROM DATABASE src TO dst;
```

DuckDB v1.2 之前写入的文件不记录是哪个版本创建的。其他原因的失败——文件被别的程序锁住、没有读取权限等——只显示 DuckDB 的原始报错。参见[常见问题排查](troubleshooting.md#opening-a-database)。

### 哪些 DuckDB 能读这个数据库 {#storage-version}

打开数据库文件后，状态栏会显示它的路径。把鼠标悬停在上面，可以看到文件的存储格式，也就是能读它的最低 DuckDB 版本，例如 **存储格式：DuckDB v1.0.0+ 可读**；文件里有记录时，还会显示创建它的版本（**由 DuckDB v1.4.3 创建**）。把文件交给使用旧版 DuckDB 的人之前，先看一眼这里。

DuckLocal 用的是 DuckDB 2.0，但它新建的数据库文件按 DuckDB 1.x 的格式写入（**DuckDB v1.0.0+ 可读**），所以 DuckDB 1.x 的命令行、Python 或 DBeaver 仍然能打开。其他程序按 DuckDB 2.0 自己的格式（**v2.0.0+**）写的文件也能打开，并保持原格式：1.x 读不了这种文件。

## 附加数据库 {#attached-databases}

把另一个 DuckDB 或 SQLite 文件附加在主数据库旁边，就能跨库查询——比如把一份快照和今天的数据放在一起比。

1. 打开标题栏的 **打开** 菜单，选择 **附加数据库…**。
2. 输入路径，或点击 **浏览** 选择文件。
3. 除非确实要修改这个文件，否则保持勾选 **只读**。它默认是勾选的。
4. 点击 **附加**。

数据库会根据文件名得到一个**别名**，用 `别名.表`（或 `别名.schema.表`）查询其中的表：

```sql
-- snap-2026-10-01.duckdb 附加后的别名是 snap_2026_10_01
SELECT t.id, t.amount, s.amount AS amount_last_week
FROM orders t
JOIN snap_2026_10_01.orders s USING (id);
```

别名的生成规则：

- 取文件名主干，其中不是 ASCII 字母或数字的字符都换成 `_`。
- 不以字母开头时，前面加 `db_`：`2026-10 logs.duckdb` 变成 `db_2026_10_logs`。
- 名字已被占用时加序号：`snap`，然后是 `snap_2`。不会使用 `main`。
- 以后再次附加同一个文件，会拿回它原来的别名，针对它写好的 SQL 照样能跑。

其他细节：

- **SQLite 文件**根据文件内容识别，通过 DuckDB 的 `sqlite` 扩展附加。
- **取消勾选“只读”**会以可写方式附加。这时如果 DuckDB 路径不存在，会新建一个空数据库；勾选只读时，附加会失败。
- **同一个文件不会附加两次。** 附加一个已经打开的文件（不论它是主数据库还是已附加的数据库），会提示 *这个数据库已经打开了，名为 …。*

### 在侧栏中 {#in-the-sidebar}

所有数据库都出现在 schema 树里，主数据库和附加的数据库一样，空数据库也列出。数据库名旁边会视情况显示 **只读** 或 **sqlite**，鼠标悬停可看到对应的文件。

右键点击数据库：

- **设为默认数据库**——执行 `USE`，之后不带库名的表名都在这里查找。状态栏会显示当前在哪里查找。
- **分离数据库**——分离并忘掉它，下次不会再自动附加。如果它正是默认数据库，会先切回主数据库。文件本身不受影响。

右键菜单的其余选项见 [Schema 浏览与历史](schema-and-history.md)。

### 跨连接保留 {#kept-across-connections}

附加的数据库连同别名和只读设置，存放在 DuckLocal 自己的历史数据库里，每次建立新连接时都会重新附加：下次启动时、打开另一个数据库后、以及选择 **新建内存数据库** 后。

- 文件**不存在**的数据库会悄悄跳过。登记仍然保留，文件回来后它也会回来——DuckLocal 不会在原位置建一个空数据库。
- **已经打开**的数据库（不论是作为主数据库还是别的方式）不会再附加一次。
- 如果新的主数据库里已经有同名的数据库，DuckLocal 会提示 *无法附加 …：名称 … 已被当前数据库占用。*

## 从访达打开 {#open-from-finder}

在 Mac 上，访达可以直接把文件交给 DuckLocal：

| 文件 | 双击 | “打开方式”，或拖到程序坞图标上 |
| --- | --- | --- |
| `.parquet` | 在 DuckLocal 中打开 | 可以 |
| `.duckdb`、`.ddb` | 在 DuckLocal 中打开 | 可以 |
| `.dash` | 在 DuckLocal 中打开 | 可以 |
| `.csv`、`.tsv`、`.json`、`.jsonl`、`.ndjson`、`.xlsx`、`.xls` | 仍由原来打开它们的应用处理（Numbers、Excel、文本编辑器） | 可以 |

DuckLocal 只把自己注册为 Mac 上没有其他应用认领的格式的默认应用。如果 Parquet 或 DuckDB 文件已经被别的应用占了，就在 **打开方式** 里选 DuckLocal；或者在访达里用 **显示简介 → 打开方式 → 全部更改…** 把它设为默认。

从访达来的文件，处理方式和拖到窗口上完全一样：数据文件变成当前工作区里的视图，数据库文件作为主数据库打开，`.dash` 文件作为[仪表盘](dashboards.md)标签页打开。如果 DuckLocal 没在运行，会先启动，等它自己的启动流程完成后再打开这个文件。

## 快速查看（Quick Look）预览 {#quick-look}

在 Mac 上，在访达里选中数据文件后按**空格键**，不用打开任何应用就能看到它的列和前几行。预览功能内置在 DuckLocal.app 里，不需要另外安装。和应用本身一样，需要 macOS 12 或更新版本。

| 文件 | 预览内容 |
| --- | --- |
| `.parquet` | 列和类型、前 50 行，以及文件尾部（footer）里的信息：行数、行组数、压缩方式、写入程序 |
| `.duckdb`、`.ddb` | 表的数量、存储格式（**Readable by DuckDB …**），以及最大的五张表，每张表附估算行数和前 50 行 |
| `.csv`、`.tsv` | 列及嗅探出的类型、前 50 行——以表格显示，而不是纯文本 |
| `.jsonl`、`.ndjson` | 列和前 50 行 |

普通 `.json` 和 Excel 文件仍由 macOS 自带的预览处理。

预览会做什么、不会做什么：

- 从不写入。数据库以只读方式附加。
- 从不扫描整个文件：行数据用 `LIMIT` 读取，只有格式本身存了行数时才显示行数（Parquet 的文件尾部、数据库的估算值）。
- 不下载任何东西：既不安装也不加载 DuckDB 扩展。
- 超过 200 个字符的单元格会被截断。预览页面的文字是英文。
- 读不了的文件会显示一页说明原因。

按空格键后没有出现 DuckLocal 的预览时：

1. 确认 DuckLocal 在 **应用程序** 文件夹里，并启动一次。macOS 第一次见到这个应用时才会登记预览功能。
2. 在终端里重置快速查看：`qlmanage -r`，然后再试一次。
3. 在“系统设置”的扩展设置里，确认快速查看下的 **DuckLocal Preview** 已开启。

## 命名

文件对应的视图以文件名主干命名，并整理成合法的标识符。名字在连接中已被占用时，DuckLocal 会加上序号：`events`，然后是 `events_2`、`events_3`。所以两个文件即使在不同目录、主干同名，也会得到不同的视图。

判断是否重复，看的是写出来的路径文本，所以 `./a.csv` 和 `a.csv` 算两个不同的文件。

## 限制

- **每次打开最多 256 个数据文件。** 上限按一次请求里的所有路径合计，不按文件夹分别计算。这样即使不小心把整个用户主目录拖进来，连接也不会被占上好几分钟。达到上限时，DuckLocal 会给出提示，只挂载前 256 个文件。
- 启动时重新挂载上次会话的文件，不受这个上限限制。

## 管理已注册的数据源

已注册的文件列在侧栏的**本地文件**分组里，每个文件显示视图名、文件名和行数。选中文件会生成一条查询它的 `SELECT` 语句。

点文件行上的 **×**，确认后即可移除。移除会删除视图、清除这条注册；**文件本身不受任何影响。**

**刷新 schema** 会重新加载目录（catalog）和已注册的文件。

## 会被记住的内容

已注册文件的清单（路径、视图名和类型）存放在 DuckLocal 自己的历史数据库里，下次启动时重新挂载，所以关掉时是什么工作区，再打开还是什么工作区。[附加的数据库](#kept-across-connections)也一样。主数据库文件本身*不会*记住：除非指定了数据库，每次启动都从内存模式开始。

这些数据在磁盘上的位置，参见[设置与应用数据](settings-and-data.md)。
