跪拜 Guibai
← Back to the summary

The Backslash That Decides Where Your CSV Lands: ksql's \copy vs. COPY

Operations wants an order report, product wants a reconciliation table — these tasks often end up on the developer's desk. When exporting query results from MySQL, the first instinct is SELECT ... INTO OUTFILE: but it requires FILE privilege, and the file lands on the database server, invisible from the dev machine, often requiring another login to copy the file out.

KingbaseES offers a smoother approach — ksql's \copy. It's a client-side meta-command executed by the ksql process on the local machine: the file is written directly on the machine running ksql, usable by ordinary accounts without touching server-side permissions.

This article verifies three things about \copy in a KingbaseES V009R001C010 environment: exporting an entire table to CSV, exporting only required columns and rows based on conditions, and importing a CSV back into a table. Finally, a COPY command without the backslash triggers an error, clarifying the exact difference between \copy and COPY. The demo uses an ordinary user app_user connected to the app_db database, with the table app_schema.t_meta_demo.

First, confirm where you're connected

\copy runs within the current session and relies on search_path to find tables when using short table names, so first confirm the location and identity upon connecting:

set search_path to app_schema, public;

select current_database(), current_user, current_schema();

Connection confirmation

The current database is app_db, the user is app_user, and the first schema in search_path is app_schema. Note that this is an ordinary user — this point becomes critical in section 6, as it is the dividing line where \copy works but server-side COPY does not.

Exporting an entire table

Export the entire t_meta_demo table as a CSV with column headers:

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

ksql returns COPY 5, indicating 5 rows were written. cat this file in another terminal:

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

copy export full table csv

A few points:

In MySQL, the equivalent is SELECT * FROM ... INTO OUTFILE '/path/orders.csv': the file is generated on the database server, requires FILE privilege, and is invisible from the dev machine. \copy has none of these restrictions — where the file lands and which account is used are decided by the person running ksql. Also, \copy is a ksql meta-command and must be written on a single line; it cannot be wrapped like ordinary SQL.

Exporting only required columns and rows

Real-world scenarios rarely require exporting an entire table. Operations often wants "orders with status 'paid', only order number, username, and amount." Just follow \copy with a SELECT statement:

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

Returns COPY 2, file content:

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

copy export query results

The parentheses contain any SELECT — WHERE, field filtering, ORDER BY, JOIN can all be written. This is the most practical form: no need to create temporary tables; just query what you need and write it directly to a file. CSV column names are taken from the SELECT fields — using aliases applies them, a useful detail for templated exports.

Preparing a CSV for import

Conversely, operations sends a batch of new orders to be added, in CSV format. First, use ! (execute shell command within ksql) to create a file:

! 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 to confirm content:

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

Prepare import csv file

This CSV only provides four columns: order_no, user_name, status, amount. It lacks id and created_at — one uses an auto-increment sequence, the other uses a now() default value, so they don't need to be provided in the file.

Importing CSV into a table

When importing, use parentheses to specify which table columns the CSV columns correspond to:

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

Returns COPY 3. Check the result (here using expanded mode, fields displayed vertically for better readability):

select * from t_meta_demo order by id;

copy import csv data

The original 5 rows plus the newly imported 3 rows total 8 rows. The new data's id is automatically generated by the sequence, and created_at is automatically filled with the import timestamp — these two columns were not provided in the CSV, so they each used their default values.

Two key points:

In MySQL, the counterpart is LOAD DATA INFILE, which also executes server-side and requires the file to be on the server, unless the LOCAL keyword is added for client-side operation. \copy doesn't care about these distinctions; the file just needs to be readable on the machine where ksql runs.

\copy and COPY differ by just a backslash, but the executor is completely different

Remove the backslash from the previously successful command and run it again with the same app_user:

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

This time it immediately errors:

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 server-side error comparison

Same table, same path and options, but with or without the backslash, the results are worlds apart:

The error HINT states this relationship plainly: Anyone can COPY to stdout or from stdin. ksql's \copy command also works for anyone. — anyone can use \copy.

Summary

Just remember one thing: look for the backslash.

The most convenient command for daily development is \copy (SELECT ...) TO 'file' WITH (FORMAT CSV, HEADER true): choose your own conditions and fields, HEADER true includes column names, one command lands the data operations needs locally.

One final encoding pitfall: if the CSV is exported from Excel on Windows, it's likely GBK encoded. Directly importing or exporting Chinese characters will result in garbled text. Add ENCODING 'GBK' in the WITH clause to specify the file encoding; no need to transcode first.

This is also the most fundamental difference between \copy and MySQL's SELECT INTO OUTFILE / LOAD DATA INFILE: it's not a syntax difference, but which end executes it — one on the client, one on the server. Figure out where the file should land and whose permissions to use, and the choice becomes clear.