# COPY

> COPY 语句用于导入和导出表或数据库的数据。

# COPY

## COPY TABLE
### COPY TO

`COPY TO` 被用来将表的内容导出到文件中，其语法如下：

```sql
COPY tbl TO '/xxx/xxx/output.parquet' WITH (FORMAT = 'parquet');
```

命令以 `COPY` 关键字开始，后面跟着要导出数据的表名（本例中为 `tbl`）。
`TO` 指定导出数据的文件路径和名称（本例中为 `/xxx/xxx/output.parquet`）。

:::tip NOTE
导出的文件会生成在执行该查询的 GreptimeDB 服务端节点上，而不是发起 SQL 的客户端机器上。请确保路径在服务端可访问且可写，或使用 `CONNECTION` 导出到 S3、GCS、Azure Blob Storage 等云存储服务。

在单机部署模式下，本地文件路径受限于 `storage.copy_root` 目录（默认为 `<data_home>/copy`）；在分布式部署模式下，本地文件访问被完全禁止。详情请参阅[迁移本地 SQL 文件访问](/user-guide/deployments-administration/migrate-local-sql-file-access.md)。
:::

例如，可以使用自定义时间戳和日期格式导出数据到 CSV 或 JSON 文件：

```sql
COPY tbl TO '/path/to/file.csv' WITH (
  FORMAT = 'csv',
  TIMESTAMP_FORMAT = '%Y/%m/%d %H:%M:%S',
  DATE_FORMAT = '%Y-%m-%d'
);
```

```sql
COPY tbl TO '/path/to/file.json' WITH (
  FORMAT = 'json',
  TIMESTAMP_FORMAT = '%Y/%m/%d %H:%M:%S',
  DATE_FORMAT = '%Y-%m-%d'
);
```

也可以将数据导出为压缩的 CSV 或 JSON 文件：

```sql
COPY tbl TO '/path/to/file.csv.gz' WITH (
  FORMAT = 'csv',
  COMPRESSION_TYPE = 'gzip'
);
```

:::tip NOTE
使用压缩时，请确保文件扩展名与压缩类型匹配：gzip 使用 `.gz`，zstd 使用 `.zst`，bzip2 使用 `.bz2`，xz 使用 `.xz`。
:::

#### `WITH` 选项

`WITH` 可以添加一些选项，比如文件的 `FORMAT` 用来指定导出文件的格式。本例中的格式为 Parquet，它是一种用于大数据处理的列式存储格式。Parquet 为大数据分析高效地压缩和编码列式数据。

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `FORMAT` | 目标文件格式，例如 JSON, CSV, Parquet  | **是** |
| `START_TIME`/`END_TIME`| 需要导出数据的时间范围，时间范围为左闭右开 | 可选 |
| `COMPRESSION_TYPE` | 导出文件的压缩算法。支持的值：`gzip`、`zstd`、`bzip2`、`xz`。仅支持 CSV 和 JSON 格式。 | 可选 |
| `TIMESTAMP_FORMAT` | 导出 CSV 或 JSON 格式时自定义时间戳列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符（例如 `'%Y-%m-%d %H:%M:%S'`）。支持 CSV 和 JSON 格式。 | 可选 |
| `DATE_FORMAT` | 导出 CSV 或 JSON 格式时自定义日期列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符（例如 `'%Y-%m-%d'`）。支持 CSV 和 JSON 格式。 | 可选 |
| `TIME_FORMAT` | 导出 CSV 或 JSON 格式时自定义时间列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符（例如 `'%H:%M:%S'`）。支持 CSV 和 JSON 格式。 | 可选 |

#### `CONNECTION` 选项

`COPY TO` 支持导出数据到云存储上。详情请参考 [连接 S3](#连接-s3)、[连接 GCS](#连接-gcs) 或 [连接 Azure Blob Storage](#连接-azure-blob-storage)。

### COPY FROM

`COPY FROM` 被用来将文件中的数据导入到表中，其语法如下：

```sql
COPY [<db>.]<table_name>
FROM { '<path>/[<filename>]' }
[ [ WITH ]
 (
   [ FORMAT =  { 'CSV' | 'JSON' | 'PARQUET' | 'ORC' } ]
   [ PATTERN = '<regex_pattern>' ]
 )
]
[LIMIT NUM]
```

命令以 `COPY` 关键字开始，后面跟着要导入数据的表名。

#### `WITH` 选项

`FORMAT` 指定导入文件的格式，本例中为 Parquet。

选项 `PATTERN` 允许使用通配符（如 *）指定匹配某种模式的多个输入文件。例如，你可以使用以下语法导入目录（必须是绝对路径）"/path/to/folder" 中文件名包含 `parquet` 的所有文件：

```sql
COPY tbl FROM '/path/to/folder/' WITH (FORMAT = 'parquet', PATTERN = '.*parquet.*');
```

例如，如果你只有一个文件需要导入，可以使用下方的语法：

```sql
COPY tbl FROM '/path/to/folder/xxx.parquet' WITH (FORMAT = 'parquet');
```

也可以从压缩的 CSV 或 JSON 文件导入数据：

```sql
COPY tbl FROM '/path/to/file.csv.gz' WITH (
  FORMAT = 'csv',
  COMPRESSION_TYPE = 'gzip'
);
```

导入 CSV 数据时，可以设置 `SKIP_BAD_RECORDS = 'true'` 跳过解析或类型转换错误的行并继续导入：

```sql
COPY tbl FROM '/path/to/file.csv' WITH (
  FORMAT = 'csv',
  SKIP_BAD_RECORDS = 'true'
);
```

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `FORMAT` | 目标文件格式，例如 JSON, CSV, Parquet, ORC  | **是** |
| `PATTERN` | 使用正则匹配文件，例如 `*_today.parquet` | 可选 |
| `COMPRESSION_TYPE` | 导入文件的压缩算法。支持的值：`gzip`、`zstd`、`bzip2`、`xz`。仅支持 CSV 和 JSON 格式。 | 可选 |
| `HEADERS` | CSV 文件是否包含表头。支持的值：`true`、`false`。默认值为 `true`。 | 可选 |
| `SKIP_BAD_RECORDS` | 导入 CSV 时是否跳过解析或类型转换错误的行。支持的值：`true`、`false`。默认值为 `false`。仅支持 CSV 格式。 | 可选 |
| `STRICT_HEADERS` | 导入前是否根据表结构校验 CSV 表头。支持的值：`true`、`false`。默认值为 `false`。仅支持 CSV 格式，且要求 `HEADERS = 'true'`。 | 可选 |

:::tip NOTE
默认情况下，CSV 会按包含表头的方式解析，并按列名匹配表字段。

如果要导入不包含表头的 CSV 文件，请设置 `HEADERS = 'false'`。此时会按目标表的字段顺序进行位置映射。你可以使用 `SHOW CREATE TABLE <table_name>` 查看表字段顺序。
:::

#### STRICT_HEADERS 选项

默认的表头匹配是宽松的：表里没有的 CSV 列会被忽略，CSV 里没有的表字段则走正常的插入和默认值处理。

设置 `STRICT_HEADERS = 'true'` 会在读取数据之前先校验表头。出现以下任一情况都会导入失败：

- CSV 里有表中不存在的列；
- 表中有 CSV 里没有的列；
- CSV 表头里有重复的列名。

```sql
COPY monitor FROM 'monitor.csv' WITH (FORMAT = 'CSV', STRICT_HEADERS = 'true');
```

不匹配时会一次性报出三类问题：

```
ERROR:  CSV header mismatch in path: monitor.csv, unknown columns: ["extra"], missing columns: [], duplicate columns: []
```

:::warning
缺列检查针对表的**全部**列，包括可空列和有默认值的列。CSV 少了其中任何一列都会被拒绝。

`STRICT_HEADERS = 'true'` 不能和 `HEADERS = 'false'` 同时使用——无表头的 CSV 没有表头可校验，这个组合会返回 `strict_headers=true requires headers=true`。
:::

#### Connection 选项

`COPY FROM` 同样支持从云存储上导入数据。详情请参考 [连接 S3](#连接-s3)、[连接 GCS](#连接-gcs) 或 [连接 Azure Blob Storage](#连接-azure-blob-storage)。

#### LIMIT 选项

可以通过 `LIMIT` 手动限制一次插入的最大行数。

### 连接 S3

你可以从 S3 导入/导出数据

```sql
-- COPY FROM
COPY tbl FROM '<URL>' WITH (FORMAT = 'parquet') CONNECTION(REGION = 'us-west-2');

-- COPY TO
COPY tbl TO '<URL>' WITH (FORMAT = 'parquet') CONNECTION(REGION = 'us-west-2');
```

#### URL

注意：你应该使用 `S3://bucket/key-name` 指定文件。下方的例子展示了正确的格式：

```
S3://my-bucket/data.parquet
```

另一种方式是使用 Virtual-hosted–style（`ENABLE_VIRTUAL_HOST_STYLE` 需要设置成 `true`），例如：

```
https://bucket-name.s3.region-code.amazonaws.com/key-name
```

:::tip NOTE
可以在 S3 控制台上点击 `Copy S3 URI` 或者 `COPY URL` 直接获取对应的 URL/HTTP 前缀或者完整路径。
:::

#### 选项

你可以设置这些 **CONNECTION** 选项：

| 选项  | 描述  | 是否必需  |
|---|---|---|
| `REGION` | AWS region 名称，例如 `us-west-2` | **是** |
| `ENDPOINT`  | The bucket endpoint，例如 `s3.us-west-2.amazonaws.com`  | 可选 |
| `ACCESS_KEY_ID` | 用于连接 AWS S3 兼容的对象存储的访问密钥 ID | 可选 |
| `SECRET_ACCESS_KEY` | 用于连接 AWS S3 兼容的对象存储的秘密访问密钥  | 可选 |
| `ENABLE_VIRTUAL_HOST_STYLE` | 如果你使用 virtual hosting 的方式来定位 bucket，将该选项设置为 "true" | 可选 |
| `SESSION_TOKEN` | 用于连接 AWS S3 服务的临时凭证。 | 可选 |

### 连接 GCS

你可以从 Google Cloud Storage (GCS) 导入/导出数据：

```sql
-- COPY FROM
COPY tbl FROM 'gcs://<bucket>/<path>' WITH (FORMAT = 'parquet') CONNECTION(SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write');

-- COPY TO
COPY tbl TO 'gcs://<bucket>/<path>' WITH (FORMAT = 'parquet') CONNECTION(SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write');
```

#### URL

使用 `gcs://bucket/path` 指定文件或目录，例如：

```
gcs://my-bucket/data.parquet
```

#### 选项

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `SCOPE` | GCS 访问范围，例如 `https://www.googleapis.com/auth/devstorage.read_write` | 可选 |
| `CREDENTIAL` | JSON 格式的服务账号凭证内容 | 可选 |
| `ENDPOINT` | GCS 服务的 Endpoint | 可选 |

### 连接 Azure Blob Storage

你可以从 Azure Blob Storage 导入/导出数据：

```sql
-- COPY FROM
COPY tbl FROM 'azblob://<container>/<path>' WITH (FORMAT = 'parquet') CONNECTION(ACCOUNT_NAME = 'my-account', ACCOUNT_KEY = 'my-key');

-- COPY TO
COPY tbl TO 'azblob://<container>/<path>' WITH (FORMAT = 'parquet') CONNECTION(ACCOUNT_NAME = 'my-account', ACCOUNT_KEY = 'my-key');
```

#### URL

使用 `azblob://container/path` 指定文件或目录，例如：

```
azblob://my-container/data.parquet
```

#### 选项

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `ACCOUNT_NAME` | Azure 存储账号名称 | 可选 |
| `ACCOUNT_KEY` | Azure 存储账号密钥 | 可选 |
| `ENDPOINT` | Azure Blob Storage 服务的 Endpoint | 可选 |
| `SAS_TOKEN` | Azure Blob Storage 的共享访问签名（SAS）令牌 | 可选 |

## COPY 查询结果

你可以使用 `COPY` 语句将查询结果导出到文件中。语法如下：

```sql
COPY (<QUERY>) TO '<PATH>' WITH (FORMAT = { 'CSV' | 'JSON' | 'PARQUET' });
```

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `QUERY` | 要执行的 SQL SELECT 语句 | **是** |
| `PATH` | 输出文件的路径 | **是** |
| `FORMAT` | 输出文件格式：'CSV'、'JSON' 或 'PARQUET' | **是** |
| `COMPRESSION_TYPE` | 导出文件的压缩算法。支持的值：`gzip`、`zstd`、`bzip2`、`xz`。仅支持 CSV 和 JSON 格式。 | 可选 |
| `TIMESTAMP_FORMAT` | 导出 CSV 或 JSON 格式时自定义时间戳列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符。支持 CSV 和 JSON 格式。 | 可选 |
| `DATE_FORMAT` | 导出 CSV 或 JSON 格式时自定义日期列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符。支持 CSV 和 JSON 格式。 | 可选 |
| `TIME_FORMAT` | 导出 CSV 或 JSON 格式时自定义时间列的格式。使用 [strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 格式说明符。支持 CSV 和 JSON 格式。 | 可选 |

### PostgreSQL `COPY ... TO STDOUT`

当你通过 PostgreSQL 协议连接时，可以使用 `COPY (<QUERY>) TO STDOUT` 将查询结果流式传输到客户端。这在你希望由 PostgreSQL 客户端（例如 `psql`）处理输出，而不是在 GreptimeDB 服务端写文件时很有用。选项部分同时支持 PostgreSQL 兼容的 `WITH (...)` 和直接使用 `(...)` 两种写法。

```sql
COPY (SELECT * FROM tbl WHERE host = 'host1') TO STDOUT;
COPY (SELECT * FROM tbl WHERE host = 'host1') TO STDOUT WITH (FORMAT csv);
COPY (SELECT * FROM tbl WHERE host = 'host1') TO STDOUT (FORMAT binary);
```

- `FORMAT` 支持 PostgreSQL 文本格式（省略时默认使用）、`csv` 和 `binary`。
- `COPY ... TO STDOUT` 仅在 PostgreSQL 协议下支持导出查询结果，也就是 `COPY (<QUERY>)`。

例如，在 `psql` 中可以使用 `\copy` 将流式结果保存到客户端本地文件：

```sql
\copy (SELECT * FROM tbl WHERE host = 'host1') TO '/tmp/file.csv' WITH (FORMAT csv)
```

例如，以下语句将查询结果导出到 CSV 文件中：

```sql
COPY (SELECT * FROM tbl WHERE host = 'host1') TO '/path/to/file.csv' WITH (FORMAT = 'csv');
```

也可以将查询结果导出为压缩文件：

```sql
COPY (SELECT * FROM tbl WHERE host = 'host1') TO '/path/to/file.json.gz' WITH (
  FORMAT = 'json',
  COMPRESSION_TYPE = 'gzip'
);
```

也可以在导出到 CSV 或 JSON 时指定自定义日期和时间格式：

```sql
COPY (SELECT * FROM tbl WHERE host = 'host1') TO '/path/to/file.csv' WITH (
  FORMAT = 'csv',
  TIMESTAMP_FORMAT = '%m-%d-%Y %H:%M:%S',
  DATE_FORMAT = '%Y/%m/%d'
);
```

```sql
COPY (SELECT * FROM tbl WHERE host = 'host1') TO '/path/to/file.json' WITH (
  FORMAT = 'json',
  TIMESTAMP_FORMAT = '%m-%d-%Y %H:%M:%S',
  DATE_FORMAT = '%Y/%m/%d'
);
```

## COPY DATABASE

`COPY` 语句除可以导入/导出表之外，也可以导入/导出指定的数据库，其语法如下：

```sql
COPY DATABASE <db_name>
  [TO | FROM] '<PATH>'
  WITH (
    FORMAT =  { 'CSV' | 'JSON' | 'PARQUET' }
    START_TIME = "<START TIMESTAMP>",
    END_TIME = "<END TIMESTAMP>",
    PARALLELISM = <number>
  )
  [CONNECTION(
    REGION = "<REGION NAME>",
    ENDPOINT = "<ENDPOINT>",
    ACCESS_KEY_ID = "<ACCESS KEY>",
    SECRET_ACCESS_KEY = "<SECRET KEY>",
    ENABLE_VIRTUAL_HOST_STYLE = "[true | false]",
  )]
```

| 选项  | 描述  | 是否必需 |
|---|---|---|
| `FORMAT` | 目标文件格式，例如 JSON, CSV, Parquet  | **是** |
| `START_TIME`/`END_TIME`| 需要导出数据的时间范围，时间范围为左闭右开 | 可选 |
| `PARALLELISM` | 并行处理的表数量。例如，数据库包含 30 个表且 `PARALLELISM` 设置为 8 时，将同时处理 8 个表。默认值为 CPU 核心总数，最小值为 1。 | 可选 |

> - 当导入/导出表时，`<PATH>` 参数必须以 `/` 结尾；
> - COPY DATABASE 同样可以通过 `CONNECTION` 参数将数据导入/导出的路径指向 S3、GCS、Azure Blob Storage 等对象存储

### 示例

```sql
-- 将 public 数据库中所有数据导出到 /tmp/export/ 目录下
COPY DATABASE public TO '/tmp/export/' WITH (FORMAT='parquet');

-- 使用 4 个并行操作导出所有表数据
COPY DATABASE public TO '/tmp/export/' WITH (FORMAT='parquet', PARALLELISM=4);

-- 将 public 数据库中时间范围在 2022-04-11 08:00:00 到 2022-04-11 09:00:00 之间的数据导出到 /tmp/export/ 目录下
COPY DATABASE public TO '/tmp/export/' WITH (FORMAT='parquet', START_TIME='2022-04-11 08:00:00', END_TIME='2022-04-11 09:00:00');

-- 从 /tmp/export/ 目录恢复 public 数据库的数据
COPY DATABASE public FROM '/tmp/export/' WITH (FORMAT='parquet');

-- 使用 8 个并行操作导入数据
COPY DATABASE public FROM '/tmp/export/' WITH (FORMAT='parquet', PARALLELISM=8);
```

## Windows 平台上的路径

请注意，在 Windows 平台上执行 `COPY`/`COPY DATABASE` 语句时，请使用 `/` 代替 `<PATH>` 中的 `\`，如 `C:/some_dir/output.parquet`。

```sql
-- 下列语句将会报错
COPY tbl TO 'C:\xxx\xxx\output.parquet' WITH (FORMAT = 'parquet');

-- 下列语句能够正常执行
COPY tbl TO 'C:/xxx/xxx/output.parquet' WITH (FORMAT = 'parquet');
```
