Redash APIで動的に結果を取得する


前提

  • Redash 2.0.0+b2990

記事中の記法

  • <host>はRedashのホストを意味します。たとえば、あなたの環境のRedashのGUIにアクセスする際、 https://www.example.com/redash/ にアクセスしているのなら、これがホストです。<host>/api と記載した場合、https://www.example.com/redash/apiを意味します。
  • その他、<>で囲った文字は適宜変数を意味します。<query_id>

User API Keyを取得

メニュー>ユーザー名>API Key

クエリを実行

以下のURIをPOSTすると、クエリを実行できます。

<host>/api/queries/<query_id>/refresh?api_key=<user_api_key>

クエリ中にパラメータがある場合

クエリ中のパラメータはp_<parameter_name>=<value>の形式でURLクエリパラメータとして渡します。
たとえば、

select
  *
from
  users
where
  user_id = {{user_id}}

というクエリを、user_id=12345で実行する場合、POSTするURIは、以下のようになります。

<host>/api/queries/<query_id>/refresh?p_user_id=12345&api_key=<user_api_key>

応答

以下のようなJSONが返ってきます。

{
  "job": {
    "status": 2,
    "error": "",
    "id": "XXXXXXXXXXX",
    "query_result_id": null,
    "updated_at": 0
  }
}

※以降、上記のJSONのjob.idの値を<job_id>として扱います。

ジョブの状態を取得

以下のURIをGETするとジョブの状態を取得できます。

<host>/api/jobs/<job_id>?api_key=<user_api_key>

応答は/api/queries/<query_id>/refreshと同じ形式です。
ジョブが終了していれば、query_result_idに値が設定されます。
query_result_idの値がnullの場合は、ジョブの終了を待つ必要があります。

ジョブの結果を取得

以下のURIをGETすると、ジョブの結果が取得できます。

<host>/api/queries/<query_id>/results/<query_result_id>.json?api_key=<user_api_key>

ファイルの拡張子は.jsonのほかに.csvがあります。

スニペット(GAS用)

…を踏まえたコードをGAS用に書くとこうなります。

/**
 * 
 * @param {String} host https://redash.example.com 的なもの。末尾にスラッシュはつけないでください。
 * @param {String} queryId 123等
 * @param {String} apiKey 
 * @param {any[]} param クエリのパラメータの連想配列
 */
function callRedash(host, queryId, apiKey, param) {
    let queryParams = [`api_key=${apiKey}`]
    if (param) {
        for (let key in param) {
            const queryParam = `p_${key}=${param[key]}`;
            queryParams.push(queryParam);
        }
    }
    const refreshUri = `${host}/api/queries/${queryId}/refresh?${queryParams.join('&')}`;
    const refreshResult = UrlFetchApp.fetch(refreshUri, { "method": "POST" });
    const jobId = JSON.parse(refreshResult).job.id;

    const jobStatusUri = `${host}/api/jobs/${jobId}?api_key=${apiKey}`;
    let queryResultId = null;
    const startMills = new Date().getTime();
    while (true) {
        const endMills = new Date().getTime();
        if (endMills - startMills > (4 * 60 * 1000)) {
            // GASの実行時間上限は5分なので、4分経過した時点で中断します。
            throw new Error("Redashへの問い合わせ開始から4分以上経過したため、中断します。");
        }
        const jobStatus = JSON.parse(UrlFetchApp.fetch(jobStatusUri)).job;
        const status = jobStatus.status;
        if (status === 3 || status === 4) {
            queryResultId = jobStatus.query_result_id;
            break; // 結果を取得できたのでbreak
        }
        Utilities.sleep(5000); //  5秒待機
    }

    const jobResultUri = `${host}/api/queries/${queryId}/results/${queryResultId}.json?api_key=${apiKey}`;
    const result = JSON.parse(UrlFetchApp.fetch(jobResultUri));
    return result.query_result.data.rows;
}