BigQueryでデータ利活用をする際、手動でテーブルを簡単に作成できると便利ですよね。
今回はスプレッドシートからBigQueryのテーブルを作成/データ投入する方法を解説します。
実施スキーム
スプレッドシート上に実行ボタンを設け、
実行ボタン押下でGASスクリプトを実行するようにします。
完成品
スプレッドシート
1行目のC列にBigQueryのプロジェクト番号、
E列にテーブル識別子(プロジェクト名.データセット名.テーブル名)を記入します。
2~4行目には各カラムのデータ型・論理名・物理名を記入します。
今回対応したデータ型はこちら。
- STRING
- INT64
- FLOAT64
- NUMERIC
- BOOLEAN
- DATE
- TIME
- DATETIME
5行目以降には、投入するデータを記入します。
セルに表示されている値(Display Value)を投入するので、関数で出力しても大丈夫です。
スクリプト
GASに登録しているスクリプトです。
/**
* シートのレコード数を計算する関数
* @return {number}
*/
function getRecordCountFromSheet() {
const target_sheet = SpreadsheetApp.getActiveSheet();
let record_count = target_sheet.getLastRow();
const values = target_sheet.getRange(1, 2, record_count, 1).getValues();
for (let i = record_count - 1; i >= 0; i--) {
if (values[i][0] !== '') {
record_count = i + 1;
break;
}
}
return record_count - 4;
}
/**
* シートのカラム数を計算する関数
* @return {number}
*/
function getColumnCountFromSheet() {
const target_sheet = SpreadsheetApp.getActiveSheet();
let column_count = target_sheet.getLastColumn();
const values = target_sheet.getRange(2, 1, 1, column_count).getValues()[0];
for (let i = column_count - 1; i >= 0; i--) {
if (values[i] !== '') {
column_count = i + 1;
break;
}
}
return column_count - 1;
}
/**
* 実行確認ダイアログを表示する関数
*/
function confirmExecute() {
const ui = SpreadsheetApp.getUi();
const title = "実行確認";
const prompt = "テーブルを作成します。\n" + getRecordCountFromSheet() + "件のレコードが登録されます。\nよろしいですか?";
const response = ui.alert(title, prompt, ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
createTableFromSheet();
exportTable();
}
}
/**
* シートの内容でテーブルを再作成する関数
*/
function createTableFromSheet() {
const target_sheet = SpreadsheetApp.getActiveSheet();
const project_number = target_sheet.getRange(1, 3).getValue();
const table_name = target_sheet.getRange(1, 5).getValue();
const column_count = getColumnCountFromSheet();
const data_types = target_sheet.getRange(2, 2, 1, column_count).getValues()[0];
const descriptions = target_sheet.getRange(3, 2, 1, column_count).getValues()[0];
const columns = target_sheet.getRange(4, 2, 1, column_count).getValues()[0];
const records = target_sheet.getRange(5, 2, getRecordCountFromSheet(), column_count).getDisplayValues();
let table_columns = new Array();
columns.forEach((column_name, i) => {
table_columns.push(`${column_name} ${data_types[i]} OPTIONS (description = '${descriptions[i]}')`);
});
const create_sql = "CREATE OR REPLACE TABLE `" + table_name + "` (" + table_columns.toString() + ")";
executeQuery(project_number, create_sql);
let insert_values = new Array();
let insert_sql = "INSERT INTO `" + table_name + "` (" + columns.toString() + ") VALUES";
records.forEach((record, i) => {
let values = new Array();
record.forEach((column_value, j) => {
if (column_value === "") {
values.push("NULL");
} else if (data_types[j] === "STRING" || data_types[j] === "DATE" || data_types[j] === "TIME" || data_types[j] === "DATETIME") {
values.push(`'${column_value}'`);
} else {
values.push(column_value.replace(",", ""));
}
});
insert_values.push("(" + values.toString() + ")");
});
insert_sql += insert_values.toString();
executeQuery(project_number, insert_sql);
}
/**
* テーブルデータをシートへ出力する関数
*/
function exportTable() {
const target_sheet = SpreadsheetApp.getActiveSheet();
const project_number = target_sheet.getRange(1, 3).getValue();
const table_name = target_sheet.getRange(1, 5).getValue();
const select_sql = "SELECT * FROM `" + table_name + "` ORDER BY 1";
const split_size = 10000;
let offset = 0;
let result_count = split_size;
while (result_count == split_size) {
const query = `${select_sql} LIMIT ${split_size} OFFSET ${offset}`;
const query_results = executeQuery(project_number, query);
const table_rows = query_results.getRows();
result_count = query_results.getTotalRows();
if (result_count > 0) {
const result_schema = query_results.getSchema();
// 書式作成
const schema_fields = result_schema["fields"];
let cell_formats = new Array();
for (let i = 0; i < schema_fields.length; i++) {
const field_type = schema_fields[i]["type"];
if (field_type === "STRING") {
cell_formats.push("@");
} else if (field_type === "INTEGER") {
cell_formats.push("#,##0");
} else if (field_type === "FLOAT" || field_type === "NUMERIC") {
cell_formats.push("#,##0.###");
} else if (field_type === "DATE") {
cell_formats.push("yyyy-mm-dd");
} else if (field_type === "TIME") {
cell_formats.push("hh:MM:ss");
} else if (field_type === "DATETIME") {
cell_formats.push("yyyy-mm-dd hh:MM:ss");
} else {
cell_formats.push("");
}
}
let range_formats = new Array();
for (let i = 0; i < result_count; i++) {
range_formats.push(cell_formats);
}
// 値作成
let result_values = new Array();
for (let i = 0; i < result_count; i++) {
const cols = table_rows[i].getF();
let result_row = new Array();
for (let j = 0; j < cols.length; j++) {
result_row.push(cols[j].getV());
}
result_values.push(result_row);
}
target_sheet.getRange(5 + offset, 2, result_count, schema_fields.length).setNumberFormats(range_formats).setValues(result_values);
}
offset += split_size;
}
}
/**
* SQLを実行する関数
* @param {string} project_number - SQL実行プロジェクトのプロジェクト番号
* @param {string} sql - 実行するSQL
* @return {Object} クエリの実行結果
*/
function executeQuery(project_number, sql) {
let query_results;
const resource = {
query: sql,
useLegacySql: false,
timeoutMs: 600000
};
try {
query_results = BigQuery.Jobs.query(resource, project_number);
const job_id = query_results.getJobReference().getJobId();
let continue_flg = query_results.getJobComplete();
while (continue_flg == false) {
Utilities.sleep(3000);
query_results = BigQuery.Jobs.getQueryResults(project_number, job_id);
continue_flg = query_results.getJobComplete();
}
} catch (error) {
Logger.log(error);
Browser.msgBox(error);
return;
}
return query_results;
}
実行結果
実行すると、このようにテーブルが作成されデータ投入されます。
スクリプトの解説
ここからは各関数について解説します。
getRecordCountFromSheet()
シートに記入されているレコード数をカウントする関数です。
今回、シートの行は実際に登録するレコード数よりも多く設けて行番号を振っているため、
レコード数がgetLastRow()
で取得できません。
そのため、2列目の最終行を取得し、ヘッダーの行数をマイナスすることでレコード数を計算しています。
getColumnCountFromSheet()
シートに記入されているカラム数をカウントする関数です。
基本的にはgetLastColumn()
で事足りるのですが、テーブルのカラム数が3カラム以下の場合、
1行目のテーブル名の記入部分が最終列となりエラーが発生してしまいます。
そのため、2行目の最終列を取得し、行番号列をマイナスすることでカラム数を計算しています。
confirmExecute()
「Execute」押下で実行される関数です。
スプレッドシートのUI上に確認ダイアログを表示し、
「はい」押下でcreateTableFromSheet
とexportTable
を実行します。
createTableFromSheet()
テーブル作成/データ投入を行う関数です。
スプレッドシートの記入内容をもとに、
テーブルをCREATE OR REPLACE
するSQLとテーブルへINSERT
するSQLを作成し実行します。
なおデータ投入にはBULK INSERT(1回のINSERTで複数データ投入する方法)をとっています。
exportTable()
テーブルの中身をスプレッドシートに出力する関数です。
クエリ実行時のエラーは画面に返却されますが、
SQLにエラーがなく登録内容やフォーマットに誤りがあった場合には気づくことができないため、
登録実施後に登録結果を同じシートへ反映します。
ただデータを取得してシートに記入するだけだと、
意図しない形式(頭0が落ちる、など)となってしまうことがあるため、
テーブルのデータ型に応じてセルの書式を整えた上で値を出力します。
executeQuery(project_number, sql)
BigQueryへクエリを実行する関数です。
引数として渡されたプロジェクト番号とSQLを使ってクエリを実行し、
実行結果を返却します。