[BigQuery] bq コマンドでテーブルの種類ごとにコピー方法を整理してみた

# はじめに
BigQuery のデータ移行やステージング環境へのコピー作業において、**テーブルの種類によって `bq` コマンドの使い方が異なる**ことに気づきました。

この記事では、次の 4 種類のテーブルごとにコピー方法をまとめています：

- Standard Table（通常のテーブル）
- View（ビュー）
- External Table（外部データ構成テーブル）
- Materialized View（マテリアライズドビュー）

## 通常テーブルのコピー


### 単一テーブルのコピー

```bash
#!/bin/bash

SRC_PROJECT="source-project"
DEST_PROJECT="destination-project"
LOCATION="us"

DATASET="example_dataset"
TABLE="example_table"

# すでに存在する場合はスキップ
if bq show --project_id="$DEST_PROJECT" --format=prettyjson \
     "${DEST_PROJECT}:${DATASET}.${TABLE}" > /dev/null 2>&1; then
  echo "[SKIP]  Table already exists: ${DATASET}.${TABLE}"
else
  bq cp --force --location="$LOCATION" \
    "${SRC_PROJECT}:${DATASET}.${TABLE}" \
    "${DEST_PROJECT}:${DATASET}.${TABLE}" > /dev/null 2>&1

  if [ $? -eq 0 ]; then
    echo "[OK]    Table copied: ${DATASET}.${TABLE}"
  else
    echo "[WARN]  Failed to copy table: ${DATASET}.${TABLE}" >&2
  fi
fi
```

### 複数テーブルのコピー

```bash
#!/bin/bash

SRC_PROJECT="source-project"
DEST_PROJECT="destination-project"
LOCATION="us"

TABLES=(
example_dataset.table1
example_dataset.table2
example_dataset.table3
)

for FULL_TABLE in "${TABLES[@]}"; do
  DATASET=$(echo "$FULL_TABLE" | cut -d'.' -f1)
  TABLE=$(echo "$FULL_TABLE" | cut -d'.' -f2)

  if bq show --project_id="$DEST_PROJECT" --format=prettyjson \
       "${DEST_PROJECT}:${DATASET}.${TABLE}" > /dev/null 2>&1; then
    echo "[SKIP]  Table already exists: ${DATASET}.${TABLE}"
  else
    bq cp --force --location="$LOCATION" \
      "${SRC_PROJECT}:${DATASET}.${TABLE}" \
      "${DEST_PROJECT}:${DATASET}.${TABLE}" > /dev/null 2>&1

    if [ $? -eq 0 ]; then
      echo "[OK]    Table copied: ${DATASET}.${TABLE}"
    else
      echo "[WARN]  Failed to copy table: ${DATASET}.${TABLE}" >&2
    fi
  fi
done
```

### 既存テーブルを上書きしたい場合
```bash
#!/bin/bash

SOURCE_PROJECT="source-project"
TARGET_PROJECT="destination-project"

TABLES=(
"dataset1.table_a"
"dataset2.table_b"
"dataset2.table_c"
)

for table in "${TABLES[@]}"
do
  echo "Copying ${table}..."
  bq cp --force=true "${SOURCE_PROJECT}:${table}" "${TARGET_PROJECT}:${table}"
done


```

## View（ビュー）のコピー
View は bq cp ではコピーできませんが、SQL を再定義すれば作成できます。

```bash
#!/bin/bash

DEST_PROJECT="destination-project"
DEST_DATASET="example_dataset"
DEST_VIEW="example_view"
LOCATION="us"

bq mk \
  --use_legacy_sql=false \
  --location="${LOCATION}" \
  --view '
    SELECT * FROM `source-project.example_dataset.example_table`;
  ' \
  "${DEST_PROJECT}:${DEST_DATASET}.${DEST_VIEW}"
  ```
## 外部データ構成テーブル（External Table）のコピー
外部テーブルは bq cp でコピーできないため、通常テーブルとして SELECT 結果を書き出す形でコピーします。

```bash
#!/bin/bash

SRC_PROJECT="source-project"
SRC_DATASET="external_dataset"
SRC_TABLE="external_table"

DEST_PROJECT="destination-project"
DEST_DATASET="internal_dataset"
DEST_TABLE="copied_table"
LOCATION="us"

echo "[INFO] Copying external table to internal table..."

bq query \
  --use_legacy_sql=false \
  --location="${LOCATION}" \
  --destination_table="${DEST_PROJECT}:${DEST_DATASET}.${DEST_TABLE}" \
  --replace \
  "SELECT * FROM \`${SRC_PROJECT}.${SRC_DATASET}.${SRC_TABLE}\`"

if [ $? -eq 0 ]; then
  echo "[OK]   External table successfully copied to internal table."
else
  echo "[ERROR] Failed to copy external table." >&2
fi
```
## Materialized View（マテリアライズドビュー）のコピー
Materialized View は bq cp ではコピーできません。

❌ NG 例：
```bash
bq cp source-project.dataset.mv destination-project.dataset.mv
# → エラーになる
```
✅ OK：SQL 定義を再利用して bq mk で作り直す
```bash

#!/bin/bash

SRC_PROJECT="source-project"
DEST_PROJECT="destination-project"
DATASET="example_dataset"
VIEW_NAME="example_mv"
LOCATION="us"

QUERY='
SELECT
  *
FROM
  `source-project.example_dataset.source_table`
WHERE
  column_name LIKE "%pattern%"
'

# 既存の View を削除（必要に応じて）
if bq show --project_id="${DEST_PROJECT}" "${DATASET}.${VIEW_NAME}" > /dev/null 2>&1; then
  echo "[WARN] View already exists: ${DEST_PROJECT}.${DATASET}.${VIEW_NAME}"
  bq rm -f -t "${DEST_PROJECT}:${DATASET}.${VIEW_NAME}"
fi
```

### Materialized View を作成
```bash
bq mk \
  --materialized_view \
  --view_query="$QUERY" \
  --use_legacy_sql=false \
  --location="$LOCATION" \
  --time_partitioning_field="partition_column" \
  --clustering_fields="cluster_column" \
  "${DEST_PROJECT}:${DATASET}.${VIEW_NAME}"

if [ $? -eq 0 ]; then
  echo "[OK]   Materialized view created successfully."
else
  echo "[ERROR] Failed to create materialized view." >&2
fi
```
# おわりに
BigQuery のテーブルをコピーする際、テーブルの種類に応じて最適な方法を選ぶことが大切です。

特にステージング環境などにおいて、bq コマンドを活用すれば、簡単かつ柔軟にコピー作業を行うことができます。

参考になれば嬉しいです！

🗒️ 他にも便利な BigQuery Tips があればぜひコメントで教えてください！