给运营导个 CSV,在 ksql 里用 \copy 就够了

首页 编程分享 PHP丨JAVA丨OTHER 正文

一只牛博 转载 编程分享 2026-08-15 22:11:09

简介 运营要一份订单数据、产品要个对账表,这活儿最后常常落到开发头上。MySQL 里把查询结果导成文件,第一反应是 SELECT ... INTO OUTFILE


运营要一份订单数据、产品要个对账表,这活儿最后常常落到开发头上。MySQL 里把查询结果导成文件,第一反应是 SELECT ... INTO OUTFILE:可它要 FILE 权限,文件还落在数据库服务器上,开发机上看不到,经常得再登服务器把文件拷出来。

KingbaseES 里有个更顺手的做法——ksql 的 \copy。它是客户端元命令,由 ksql 进程在本机执行:文件直接写在运行 ksql 的这台机器上,普通账号就能用,不碰服务端权限。

这篇在 KingbaseES V009R001C010 环境里验证 \copy 的三件事:把整张表导成 CSV、按条件只导需要的列和行、再把 CSV 导回表;最后用一条不带反斜杠的 COPY 触发报错,说清 \copyCOPY 到底差在哪。演示用普通用户 app_user 连接 app_db 库,表是 app_schema.t_meta_demo

先确认连的是哪里

\copy 跑在当前会话里,用短表名时靠 search_path 找表,所以连进去先把位置和身份确认清楚:

set search_path to app_schema, public;
​
select current_database(), current_user, current_schema();

当前库是 app_db,用户是 app_usersearch_path 第一个 schema 是 app_schema。注意这里是普通用户——这一点到第 6 节会很关键,它正是 \copy 能用、而服务端 COPY 用不了的分界线。

导出整张表

t_meta_demo 整张表导成带列名的 CSV:

\copy t_meta_demo TO '/tmp/orders_full.csv' WITH (FORMAT CSV, HEADER true);

ksql 返回 COPY 5,表示写出了 5 行。在另一个终端 cat 这个文件:

id,order_no,user_name,status,amount,created_at
1,ORD-20240601-001,alice,paid,1280.50,2026-06-12 14:39:09.209084
2,ORD-20240601-002,bob,pending,360.00,2026-06-12 14:39:09.209084
3,ORD-20240601-003,carol,shipped,899.00,2026-06-12 14:39:09.209084
4,ORD-20240601-004,alice,paid,75.20,2026-06-12 14:39:09.209084
5,ORD-20240601-005,dave,refund,2100.00,2026-06-12 14:39:09.209084

几个点:

  • 文件路径 /tmp/orders_full.csv本机路径(运行 ksql 的客户端这台机器),不是数据库服务器上的路径。
  • HEADER true 让第一行输出列名,运营拿到手直接能用。
  • 整表导出会带上所有字段,包括 idcreated_at

MySQL 里做同样的事是 SELECT * FROM ... INTO OUTFILE '/path/orders.csv':文件生成在数据库服务器上,需要 FILE 权限,开发机上还看不到。\copy 没有这些限制——文件落在哪、用什么账号,由运行 ksql 的人决定。另外 \copy 是 ksql 元命令,必须写在一行,不能像普通 SQL 那样折行。

只导需要的列和行

实际场景很少导整表。运营要的往往是「状态 paid 的订单,只要订单号、用户名、金额」。\copy 后面直接跟一段 SELECT 就行:

\copy (select order_no, user_name, amount from t_meta_demo where status = 'paid') TO '/tmp/orders_paid.csv' WITH (FORMAT CSV, HEADER true);

返回 COPY 2,文件内容:

order_no,user_name,amount
ORD-20240601-001,alice,1280.50
ORD-20240601-004,alice,75.20

括号里是任意 SELECT,WHERE、字段筛选、ORDER BYJOIN 都能写。这是最实用的形式:不用建临时表,需要什么直接查出来写进文件。CSV 的列名取自 SELECT 的字段——加了别名就用别名,做模板化导出时这个细节很有用。

准备一份要导入的 CSV

反过来,运营发来一批要补录的新订单,CSV 格式。先用 !(在 ksql 里执行 shell 命令)造一个文件出来:

! printf 'order_no,user_name,status,amount\nORD-20240602-001,eve,paid,560.00\nORD-20240602-002,frank,pending,120.50\nORD-20240602-003,grace,shipped,3200.00\n' > /tmp/orders_import.csv

cat 确认内容:

order_no,user_name,status,amount
ORD-20240602-001,eve,paid,560.00
ORD-20240602-002,frank,pending,120.50
ORD-20240602-003,grace,shipped,3200.00

这份 CSV 只给了 order_nouser_namestatusamount 四列,没有 idcreated_at——它们一个走自增序列、一个走 now() 默认值,不需要在文件里提供。

把 CSV 导进表

导入时用括号指定 CSV 的列对应表里的哪几列:

\copy t_meta_demo (order_no, user_name, status, amount) FROM '/tmp/orders_import.csv' WITH (FORMAT CSV, HEADER true);

返回 COPY 3。查一下结果(这里开了展开模式,字段竖排更好认):

select * from t_meta_demo order by id;

原来的 5 条加上新导入的 3 条,一共 8 条。新数据的 id 由序列自动生成、created_at 自动填入导入时刻的时间——CSV 里没提供这两列,它们各自走了默认值。

两个关键点:

  • 列名括号 (order_no, user_name, status, amount) :告诉 ksql CSV 里的数据按顺序填进这几列,没列出来的列走默认值。不写这个括号时,ksql 会按表的全部列顺序去对 CSV,列数对不上就报错。
  • HEADER true 在导入时的作用和导出相反:导出时它让第一行输出列名;导入时它告诉 ksql「第一行是列名,跳过、别当数据」。同一个选项,两个方向各管一头。

MySQL 里对应的是 LOAD DATA INFILE,同样在服务端执行、文件要在服务器上,除非加 LOCAL 关键字走客户端。\copy 不挑这些,文件在 ksql 所在的机器上就能读。

\copy 和 COPY 只差一个反斜杠,执行的人完全不同

把前面成功的命令去掉反斜杠,用同一个 app_user 再跑一次:

COPY t_meta_demo TO '/tmp/test_server_copy.csv' WITH (FORMAT CSV, HEADER true);

这次直接报错:

ERROR:  must be superuser or a member of the sys_write_server_files role to COPY to a file
HINT:  Anyone can COPY to stdout or from stdin. ksql's \copy command also works for anyone.

同一张表、同样的路径和选项,加不加反斜杠,结果天差地别:

  • COPY(不带反斜杠)是 SQL 命令,由数据库服务端执行。 它要往文件写,路径是服务器上的路径,需要超级用户或 sys_write_server_files 角色。app_user 是普通用户,直接被拦下。
  • \copy(带反斜杠)是 ksql 元命令,由 ksql 进程在本机执行。 路径是客户端本机路径,用的是当前操作系统账号的文件权限,跟数据库里有没有特权无关——这正是前面几节普通用户都能跑通的原因。

报错的 HINT 把这层关系说得很直白:Anyone can COPY to stdout or from stdin. ksql's \copy command also works for anyone.——谁都能用 \copy

小结

记一条就够:看反斜杠

  • \copy:客户端命令,文件在你运行 ksql 的机器上,用你的系统账号权限,普通数据库用户就能用。日常给运营导数据、补录数据,用它。
  • COPY:服务端命令,文件在数据库服务器上,要超级用户或 sys_write_server_files 角色。它是 DBA 在服务端做批量迁移时用的。

开发日常最顺手的一条,是 \copy (SELECT ...) TO '文件' WITH (FORMAT CSV, HEADER true):条件、字段自己挑,HEADER true 带上列名,一条命令把运营要的数据落到本地。

最后一个编码上的坑:如果 CSV 是 Windows 下 Excel 导出的,多半是 GBK 编码,直接导入导出中文会乱码。在 WITH 里加 ENCODING 'GBK' 指定文件编码即可,不用先转码。

这也是 \copy 和 MySQL SELECT INTO OUTFILE / LOAD DATA INFILE 最本质的区别:不是语法不同,而是执行的那一端不同——一个在客户端,一个在服务端。想清楚文件该落在哪、用谁的权限,选哪个就清楚了。

转载链接:https://juejin.cn/post/7673897632196919348


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云