trdsql 1.2.3 常用 CLI 命令与工具详解

trdsql v1.2.3 高频命令实测全回显:文件SQL查询/聚合JOIN、文本类型坑与CAST修正、十余种格式转换与压缩输出、stdin管道/通配符多文件/-q/-t/-a分析器、文件×PostgreSQL库表JOIN与窗口函数、CREATE TABLE AS/INSERT INTO写回SQLite与PostgreSQL、-db命名连接,附9条注意事项与5条排障实录

trdsqltrdsqlSQLCLI工具Go命令行2026/11/03

trdsql 1.2.3 常用 CLI 命令与工具详解

目标:以实测回显逐条讲透 trdsql v1.2.3 的高频命令组合——文件 SQL 查询、格式转换、管道/通配/查询文件、文件×数据库 JOIN、写回 SQLite/PostgreSQL 目标库、文件分析器,并沉淀文本类型、表头行、通配符等真实踩坑实录。参数含义与配置文件机制见前篇《trdsql 1.2.3 配置参数详解》,本文专注"命令怎么用"。

一、环境信息

  • 操作系统:Ubuntu 24.04.5 LTS(x86_64),主机名 trdsql-server,内网 10.89.56.2/24
  • trdsql:v1.2.3(/usr/local/bin/trdsql,官方二进制安装,见 《Ubuntu 24.04 安装 trdsql 1.2.3 指南》)
  • 查询引擎:默认内嵌 SQLite3;PostgreSQL 16.15 数据库服务器 db-server(10.89.56.3,演示账号 root/q1w2e3r4,库 trdsql,下文 DSN 简写为 PGDSN="host=db-server user=root password=q1w2e3r4 dbname=trdsql sslmode=disable")
  • SQLite 文件库:testdata/fruits.db(sqlite3 CLI 预建表 fruits(id,name,price))、testdata/sales.db(trdsql 导出建表 sales)
  • 命名连接配置:~/.config/trdsql/config.json(pdb=postgres、sdb/ldb=sqlite3,见 C1 篇 5.2)
  • 测试数据(/opt/work/testdata/):
文件格式内容
test.csvCSV 无表头1,Orange / 2,Melon / 3,Apple
sales.csvCSV 有表头id,name,price,city 共 8 行
user.csv / hist.csvCSV 无表头用户与访问日期,供 JOIN
test.tsv / test.ltsvTSV/LTSV同 test.csv 三行
test.jsonJSON 对象{"items":[{id,name,price}×3]}
test.jsonlJSON Lines3 行对象
test.yamlYAML3 个对象
sales.csv.gzgzip 压缩 CSV同 sales.csv
tt1.csv / tt2.csvCSV 有表头供通配符多文件实测

二、工具总览

trdsql 一条命令 = 引擎选择 + 输入选项 + SQL + 输出选项 四段拼装:

trdsql [-driver/-dsn/-db 引擎] [-i* 输入] [-ih/-id/-ijq 等] "SQL(表名=文件名)" [-o* 输出/-out 文件]
  • SQL 里文件名即表名:FROM testdata/sales.csv;标准输入用 FROM - 或 FROM stdin;
  • 无表头文件的列名自动为 c1,c2,...,加 -ih 后首行变列名;
  • 所有选项必须在 SQL 之前(Go flag 机制,C1 篇 FAQ Q1 实测);
  • 默认引擎内嵌 SQLite3,-driver postgres 后获得 PostgreSQL 方言与库内表访问能力。

三、前置条件

  1. trdsql 已安装(trdsql -version 回显 trdsql version v1.2.3);
  2. 测试数据就位(上表文件);
  3. 涉及数据库的章节(4.15–4.18)需 PostgreSQL 服务器可达:psql "$PGDSN" -c "SELECT 1;" 有回显;SQLite 章节需 sqlite3 客户端(apt sqlite3,3.45.1)。

四、各命令详解

4.1 基础查询:文件名即表名

$ trdsql "SELECT * FROM testdata/test.csv"
1,Orange
2,Melon
3,Apple

无表头时列名是 c1,c2,可显式引用:SELECT c1,c2 FROM testdata/test.csv。

4.2 表头、过滤与聚合:-ih + WHERE/ORDER/GROUP BY

$ trdsql -ih "SELECT name, price, city FROM testdata/sales.csv WHERE price > 200 ORDER BY price DESC"
Cherry,990,Guangzhou
Banana,90,Guangzhou
Grape,680,Shanghai
Orange,380,Beijing
Apple,250,Beijing
Mango,210,Shanghai

$ trdsql -ih "SELECT city, count(*) AS cnt, sum(price) AS total FROM testdata/sales.csv GROUP BY city ORDER BY total DESC"
Guangzhou,2,1080
Shanghai,3,1010
Beijing,3,780

⚠️ 注意:第一条的过滤与排序都不对——Banana,90 不仅通过了 > 200,还排在 Grape,680 之前。原因见 4.3:文本列按字典序比较('90' > '200' 成立,'150'/'120' 反而不成立)。GROUP BY 的 sum(price) 恰因 SQLite 对纯数字文本做隐式数值转换而正确(1080/1010/780)。

4.3 文本类型坑与修正:CAST / ::int

trdsql 把 CSV 列全部按 text 装载(-a 实测 Data types 见 4.13),数值比较/排序必须显式转换:

# SQLite 引擎:CAST
$ trdsql -ih "SELECT name, CAST(price AS INTEGER) p FROM testdata/sales.csv WHERE CAST(price AS INTEGER) > 200 ORDER BY p DESC"
name,p
Cherry,990
Grape,680
Orange,380
Apple,250
Mango,210

# PostgreSQL 引擎::: 类型转换
$ trdsql -db pdb -ih "SELECT city, count(*) AS n, SUM(price::int) AS total FROM testdata/sales.csv GROUP BY city ORDER BY total DESC"
city,n,total
Guangzhou,2,1080
Shanghai,3,1010
Beijing,3,780

第一条 Banana,90 已正确消失、排序正确;第二条用 PG 引擎直接输出数值列并带表头(-oh 见 4.6)。

4.4 文件 × 文件 JOIN

$ trdsql "SELECT u.c1,u.c2,h.c2 FROM testdata/user.csv AS u LEFT JOIN testdata/hist.csv AS h ON(u.c1=h.c1)"
1,userA,2026-10-01
2,userB,2026-10-01
2,userB,2026-10-02

两个文件各成一张临时表,JOIN 语法与所选引擎一致。

4.5 标准输入管道:FROM - / FROM stdin

$ cat testdata/test.csv | trdsql -icsv -otsv "SELECT * FROM -"
1	Orange
2	Melon
3	Apple

$ cat testdata/test.csv | trdsql "SELECT * FROM stdin"
1,Orange
2,Melon
3,Apple

表名写 - 或 stdin 均可;管道输入建议显式 -icsv(stdin 无扩展名可猜,实测不写时按 CSV 处理)。

4.6 输出格式转换全家族

# CSV → JSON
$ trdsql -ih -ojson "SELECT name,price FROM testdata/sales.csv LIMIT 2"
[
  {
    "name": "Orange",
    "price": "380"
  },
  {
    "name": "Melon",
    "price": "120"
  }
]

# CSV → JSON Lines / LTSV
$ trdsql -ih -ojsonl "SELECT name,price FROM testdata/sales.csv LIMIT 2"
{"name":"Orange","price":"380"}
{"name":"Melon","price":"120"}

$ trdsql -ih -oltsv "SELECT name,price FROM testdata/sales.csv LIMIT 2"
name:Orange	price:380
name:Melon	price:120

# CSV → Markdown 表格 / ASCII 表格 / 垂直格式
$ trdsql -ih -omd "SELECT name,price FROM testdata/sales.csv LIMIT 3"
|  name  | price |
|--------|-------|
| Orange |   380 |
| Melon  |   120 |
| Apple  |   250 |

$ trdsql -ih -ovf "SELECT name,price FROM testdata/sales.csv WHERE city='Beijing' LIMIT 2"
---[ 1]------------------------
   name | Orange
  price | 380
---[ 2]------------------------
   name | Apple
  price | 250

# CSV → TBLN(注释头风格,实测 v1.2.3 两种引擎一致)
$ trdsql -ih -otbln "SELECT id,name FROM testdata/sales.csv LIMIT 2"
; name: | id | name |
; type: | text | text |
| 1 | Orange |
| 2 | Melon |

# 其他输入格式 → CSV/TSV
$ trdsql -ijson "SELECT * FROM testdata/test.jsonl"
1,Orange,380
2,Melon,120
3,Apple,250

$ trdsql -iltsv -otsv "SELECT * FROM testdata/test.ltsv"
id	name	price
1	Orange	380
2	Melon	120
3	Apple	250

$ trdsql -iyaml "SELECT * FROM testdata/test.yaml"
1,Orange,380
2,Melon,120
3,Apple,250

输出侧控制项实测:

$ trdsql -otsv -oh "SELECT * FROM testdata/test.csv"     # 输出表头
c1	c2
1	Orange
2	Melon
3	Apple

$ trdsql -ih -od ";" "SELECT name,city FROM testdata/sales.csv LIMIT 2"   # 换分隔符
Orange;Beijing
Melon;Shanghai

$ trdsql -ih -oaq "SELECT name,city FROM testdata/sales.csv LIMIT 2"      # 全字段引号
"Orange","Beijing"
"Melon","Shanghai"

$ trdsql -ih -oraw "SELECT name, price FROM testdata/sales.csv LIMIT 2"   # raw 无引号
Orange,380
Melon,120

$ trdsql -ih -inum -omd "SELECT name,price FROM testdata/sales.csv LIMIT 2"  # 行号列
| row_number |  name  | price |
|------------|--------|-------|
|          1 | Orange |   380 |
|          2 | Melon  |   120 |

4.7 输出到文件与压缩:-out / -oz

-out 按文件名后缀自动猜格式;-oz gz|bz2|zstd|lz4|xz 叠加压缩:

$ trdsql -ih -out out_sales.json "SELECT name,price FROM testdata/sales.csv LIMIT 2"
$ cat out_sales.json
[
  {
    "name": "Orange",
    "price": "380"
  },
  {
    "name": "Melon",
    "price": "120"
  }
]

$ trdsql -ih -out out_sales.csv.gz -oz gz "SELECT name,price FROM testdata/sales.csv LIMIT 2"
$ ls -la out_sales.csv.gz
-rw-r--r-- 1 root root 45 Nov  3 04:28 out_sales.csv.gz
$ gunzip -c out_sales.csv.gz
Orange,380
Melon,120

$ trdsql -ih -out out_sales.csv.zst -oz zstd "SELECT name FROM testdata/sales.csv LIMIT 2"
$ ls -la out_sales.csv.zst
-rw-r--r-- 1 root root 26 Nov  3 04:34 out_sales.csv.zst

4.8 压缩输入透明读取

.gz/.bz2/.zst/.lz4/.xz 输入自动解压(-ig 默认开启):

$ trdsql -ih "SELECT * FROM testdata/sales.csv.gz LIMIT 3"
id,name,price,city
1,Orange,380,Beijing
2,Melon,120,Shanghai
3,Apple,250,Beijing

4.9 JSON 层级取数:-ijq 与 表名::".jq表达式"

# 方式一:-ijq 标志
$ trdsql -ijson -ijq ".items" "SELECT * FROM -" < testdata/test.json
1,Orange,380
2,Melon,120
3,Apple,250

# 方式二:表名后缀(jq 表达式必须用双引号包住)
$ trdsql -oat 'SELECT id,name,price FROM testdata/test.json::".items"'
+----+--------+-------+
| id |  name  | price |
+----+--------+-------+
|  1 | Orange |   380 |
|  2 | Melon  |   120 |
|  3 | Apple  |   250 |
+----+--------+-------+

⚠️ 注意:官方 README 示例 SELECT c1,c2 FROM test.json::.items 直接照抄会报错(实测):

$ trdsql -oltsv "SELECT c1,c2 FROM testdata/test.json::.items"
2026-11-03 04:26:59 export: no such column: c1 [SELECT c1,c2 FROM testdata/test.json::jq1]

jq 取出的对象数组列名即 JSON 键(id/name/price),不是 c1,c2;c1,c2 只适用于"数组的数组"型 JSON。

4.10 通配符多文件

$ trdsql -ih "SELECT * FROM testdata/tt*.csv"
1,test1
2,test2
id,name
3,test3

⚠️ 注意:-ih 只把第一个文件的首行当表头,第二个文件的表头行 id,name 作为数据混入结果(实测回显第 3 行)。多文件同构场景要么全部无表头,要么用 SQL 过滤 WHERE id <> 'id',或逐文件处理。

4.11 整文件格式转换:-t

不写 SQL、整文件转格式时用 -t:

$ trdsql -otsv -t testdata/test.csv
1	Orange
2	Melon
3	Apple

4.12 SQL 存文件:-q

$ printf "SELECT name, price FROM testdata/sales.csv WHERE city = 'Beijing' ORDER BY CAST(price AS INT) DESC;\n" > q.sql
$ trdsql -ih -q q.sql
Orange,380
Apple,250
Peach,150

⚠️ 注意:-q 文件里的 SQL 同样受表头规则约束——实测不加 -ih 时按列名 name 引用会直接报错(SQLite 引擎回显 export: no such column: name,PostgreSQL 引擎回显 pq: column "name" does not exist)。

4.13 文件分析器:-a / -A

拿到陌生文件先 -a 探测:

$ trdsql -a testdata/sales.csv
File name: testdata/sales.csv
File type: CSV
Number of columns: 4
Data types: [text,text,text,text]
Column names: [c1,c2,c3,c4]
Suggested SQL:
SELECT c1,c2,c3,c4 FROM testdata/sales.csv

Data types 全为 text 即 4.3 文本类型坑的官方"自供"。-A 只给建议 SQL 不给探测信息,且是一组命令(实测 4 条,name 因属 SQL 保留字被自动加引号):

$ trdsql -ih -A testdata/sales.csv
trdsql -ih "SELECT id, \"name\", price, city FROM testdata/sales.csv"
trdsql -ih "SELECT id, \"name\", price, city FROM testdata/sales.csv WHERE id = '1'"
trdsql -ih "SELECT id, count(id) FROM testdata/sales.csv GROUP BY id"
trdsql -ih "SELECT id, \"name\", price, city FROM testdata/sales.csv ORDER BY id LIMIT 10"

4.14 多查询串执行:分号分隔

$ trdsql "UPDATE testdata/test.csv SET c2='banana' WHERE c1='3'; SELECT * FROM testdata/test.csv"
1,Orange
2,Melon
3,banana

⚠️ 注意:UPDATE 只改内存临时表,原文件不变(实测执行后 cat testdata/test.csv 仍为 3,Apple)。多查询共享同一临时表,常用于"清洗→查询"两段式。

4.15 查询已有 SQLite 文件

# fruits.db 由 sqlite3 CLI 预建:CREATE TABLE fruits(id INTEGER PRIMARY KEY, name TEXT, price INTEGER)
$ trdsql -driver sqlite3 -dsn testdata/fruits.db "SELECT name, price FROM fruits WHERE price > 200 ORDER BY price DESC"
Orange,380
Apple,250

DSN 即库文件路径;此时 SQL 跑在真实 .db 文件上,列有真实类型(INTEGER),无 4.3 文本坑。

4.16 导出 CSV 到全新 SQLite 库

$ rm -f testdata/sales.db
$ trdsql -ih -driver sqlite3 -dsn testdata/sales.db "CREATE TABLE sales AS SELECT * FROM testdata/sales.csv"

$ sqlite3 testdata/sales.db ".tables"; sqlite3 testdata/sales.db "SELECT COUNT(*) FROM sales;"
sales
8

导入后列类型仍是 TEXT(CREATE TABLE AS 按源文本推断,实测):

$ sqlite3 testdata/sales.db "PRAGMA table_info(sales);"
0|id|TEXT|0||0
1|name|TEXT|0||0
2|price|TEXT|0||0
3|city|TEXT|0||0

查询时同样要 CAST(实测):

$ trdsql -driver sqlite3 -dsn testdata/sales.db "SELECT city, SUM(CAST(price AS INT)) AS total FROM sales GROUP BY city ORDER BY total DESC"
Guangzhou,1080
Shanghai,1010
Beijing,780

4.17 PostgreSQL 引擎:JOIN 库内表、窗口函数、生成序列

前置:在 db-server 上建参照表(实测):

$ psql "host=db-server user=root password=q1w2e3r4 dbname=trdsql" -c "CREATE TABLE colors(id int, name text); INSERT INTO colors VALUES(1,'orange'),(2,'green'),(3,'red');" -c "SELECT * FROM colors;"
CREATE TABLE
INSERT 0 3
 id |  name
----+--------
  1 | orange
  2 | green
  3 | red
(3 rows)

文件 × 库内表 JOIN(CSV 临时表与 colors 直接关联,::int 走 PG 方言):

$ trdsql -driver postgres -dsn "$PGDSN" "SELECT t.c1,t.c2,c.name FROM testdata/test.csv AS t LEFT JOIN colors AS c ON (t.c1::int = c.id)"
1,Orange,orange
2,Melon,green
3,Apple,red

窗口函数(SQLite3 引擎不支持 generate_series、窗口写法受限,PG 引擎全量可用):

$ trdsql -driver postgres -dsn "$PGDSN" "SELECT row_number() OVER (ORDER BY c2), c1, c2 FROM testdata/test.csv"
1,1,Orange
2,2,Melon
3,3,Apple

$ trdsql -driver postgres -dsn "$PGDSN" "SELECT generate_series(1,3);"
generate_series
1
2
3

⚠️ 注意:-driver postgres 时 FROM testdata/sales.csv 的计数含表头行(实测 count(*)=9),加 -ih 后为 8——引擎切换不改变装载语义。

4.18 导入 CSV 到 PostgreSQL 目标库

建表导入(CREATE TABLE ... AS SELECT,列类型由 :: 转换决定):

$ trdsql -driver postgres -dsn "$PGDSN" "CREATE TABLE fruits (id, name) AS SELECT c1::int, c2 FROM testdata/test.csv"

$ psql "host=db-server user=root password=q1w2e3r4 dbname=trdsql" -c "SELECT * FROM fruits;"
 id |  name
----+--------
  1 | Orange
  2 | Melon
  3 | Apple
(3 rows)

追加导入(先建好带类型目标表,再 INSERT INTO ... SELECT,实测用 -db pdb 命名连接):

$ psql "host=db-server user=root password=q1w2e3r4 dbname=trdsql sslmode=disable" -c "CREATE TABLE sales(id int, name text, price int, city text);"
CREATE TABLE
$ trdsql -db pdb -ih "INSERT INTO sales SELECT id::int, name, price::int, city FROM testdata/sales.csv"
$ psql "host=db-server user=root password=q1w2e3r4 dbname=trdsql sslmode=disable" -c "SELECT city, sum(price) FROM sales GROUP BY city ORDER BY 2 DESC;"
   city    | sum
-----------+------
 Guangzhou | 1080
 Shanghai  | 1010
 Beijing   |  780
(3 rows)

price 落表即为 int(sum 无需再转换)——这是 trdsql 相对 csvq/dsq/OctoSQL/textql 的独有强项:一条命令完成"读文本→转换类型→写库"。

4.19 命名连接速查:-db / -dblist

配置文件 ~/.config/trdsql/config.json 就位后(结构见 C1 篇 5.2):

$ trdsql -dblist
pdb:postgres
ldb:sqlite3
sdb:sqlite3

$ trdsql -db pdb "SELECT name FROM fruits ORDER BY id"
Orange
Melon
Apple

$ trdsql -db sdb "SELECT name, price FROM fruits WHERE price > 200"
Orange,380
Apple,250

同名表 fruits 在 pdb(PostgreSQL)与 sdb(SQLite 文件)中各有一份,-db 即路由开关。

五、注意事项

  • 选项必须在 SQL 之前:SQL 之后的 -otsv 等会被拼进语句报 near "-": syntax error(C1 篇 FAQ Q1 实测);
  • 列默认全 text:数值过滤/排序一律 CAST(x AS INTEGER)(SQLite)或 x::int(PostgreSQL),见 4.3;
  • -ih 决定计数口径:不加时表头行算数据(count(*) 多 1,4.17 实测 9 vs 8);
  • 通配符多文件只认第一个表头(4.10);
  • UPDATE/DELETE 只作用于内存临时表,不回写文件(4.14);
  • -debug 打印 DSN 明文(含密码),日志外发前脱敏(C1 篇 5.3);
  • config.json 的默认 db 会改变报错前缀(export: vs pq:),排障时先确认生效引擎(C1 篇 FAQ Q3);
  • TBLN 输出为注释头风格(; name: / ; type: 行,4.6 实测),与部分文档的 YAML 头示例不同,以所用版本实测为准。

六、常见问题

Q1: SELECT ... FROM x.csv 数值条件"不生效"(90 通过了 > 200)?

A: 文本字典序比较(4.2 实测复现、4.3 修正)。加 -ih 不解决问题,必须显式 CAST/::。

Q2: count(*) 比行数多 1?

A: 表头行被当数据装载。-ih 修正(4.17 实测 9→8),或 -is 1 跳过首行(不建列名)。

Q3: test.json::.items 报 no such column: c1?

A: 两个叠加原因(4.9 实测):jq 表达式应写成 ".items"(表名内双引号);且对象数组的列名是 JSON 键(id/name/price),不是 c1,c2。

Q4: 多文件 FROM *.csv 结果里混进 id,name 行?

A: 只有第一个文件的首行被当表头(4.10 实测)。同构多文件建议先合并或 SQL 过滤。

Q5: -db pdb -ih "SELECT name, price FROM fruits" 报 pq: column "price" does not exist?

A: 不是 bug:PostgreSQL 里 4.18 导入的 fruits 表只有 (id, name) 两列,而 SQLite 文件里的 fruits 有 price——换引擎即换表结构(实测报错原样见 C1 篇 FAQ Q3)。先 -db pdb "SELECT * FROM fruits LIMIT 1" 看列再写 SQL。

七、参考链接