kintone から BigQuery への差分取り込み|削除と同じ分の更新の抜けを直す

kintone の更新日時を「>」で比べて BigQuery に差分を取り込むと、削除したレコードが残り、当社の検証では前回と同じ分の更新も抜けました。全件の ID との突き合わせと「>=」・revision での取り直しで直しました。

この記事をシェア:
目次 クリックで開く

この記事の要点

  1. kintone の更新日時が前回より新しいレコードを取る差分では、削除したレコードは返ってきません。当社の検証では、消した1件が BigQuery に残り、kintone の31件に対して32件になりました。
  2. 当社の検証環境では更新日時が分単位で返り、前回の同期と同じ分に更新されたレコードを「>」の条件で取りこぼしました。受注は kintone の12件・4,170,000円に対して、BigQuery は11件・3,720,000円でした。
  3. 全件の $id と突き合わせて消し、「>=」で取り直して revision が大きいときだけ上書きすると、kintone と一致しました。削除の取り残しは件数に、同じ分の取りこぼしは金額にだけ出たので、毎回その両方を比べます。

同じ見積アプリで、差分の取り込みを流した順番と結果

差分を取る(>)5件が返る。消した6番は返らない
MERGE で反映BigQuery 32件、kintone 31件
全件の $id と突き合わせ6番を DELETE して31件
同じ分の更新「>」で0件。受注 11件・3,720,000円
「>=」と revision受注 12件・4,170,000円で kintone と一致

削除の取り残しは件数に、同じ分の取りこぼしは金額にだけ出た

出典:当社の検証

kintone から BigQuery へ毎日データを入れるとき、「更新日時が前回の取り込みより新しいレコードだけを取る」差分は、追加と更新は拾えますが、kintone で削除したレコードは拾えません。当社の検証環境では、もう1つ抜けがありました。更新日時が分単位で返るため、前回の同期と同じ分に更新されたレコードを「>(より大きい)」の条件で取りこぼしました。直し方は、kintone の全件の $id と突き合わせて消すことと、「>=」で取り直して revision(版の番号)で上書きを判定することの2つです。

当社は kintone の業務アプリづくりと、BigQuery を中心にしたデータ基盤の設計・構築を支援している会社です。この記事では、当社の検証用 kintone の架空の見積アプリ(30件)と、それを読み込んだ当社の検証用の Google Cloud プロジェクトの BigQuery(東京リージョン)を使い、kintone 側で更新・追加・削除をしたあとに差分の取り込みを流して、件数と受注の金額がどうずれ、どう直すと kintone と一致したかを載せます(2026年10月14日)。会社名と金額はすべて架空です。初回の読み込み($id の列名のエラーやサブテーブルの扱い)はkintone×BigQuery 連携の記事に書いたので、ここでは2回目からの取り込みだけを扱います。

前提:前回までの30件と、kintone で行った3つの変更

BigQuery には、前回までに取り込んだ見積の見出しの表 kq_header を置きました。列は kintone のレコード ID(kintone_id)、版の番号(revision)、見積番号(quote_no)、状況(status)、合計(total)、更新日時(updated_at)の6つです。中身は30件、うち受注が9件・2,990,000円で、更新日時のいちばん新しい値は 2026-10-13T15:04:00Z でした。時刻は kintone が返す UTC の形のまま書きます(日本時間では10月14日の0時台です)。

この表は、kintone の応答を自動検出で読み込んだ表から作りました。kintone のフィールドコード「更新日時」がそのまま列名になっていて、SQL に 更新日時.value と書くと、次のエラーで止まりました。

Syntax error: Illegal input character "\346"

日本語の列名は、バッククォートで囲んだ名前(引用符付きの識別子)にすると通ります(BigQuery のリファレンス「Lexical structure and syntax」)。表は次の SQL で作りました。

CREATE OR REPLACE TABLE `your-project.blog_demo.kq_header` AS
SELECT kintone_id.value       AS kintone_id,
       kintone_revision.value AS revision,
       quote_no.value         AS quote_no,
       status.value           AS status,
       total.value            AS total,
       `更新日時`.value        AS updated_at
FROM `your-project.blog_demo.kintone_quotes_auto`

次に kintone 側で、数秒のうちに3種類の変更をしました。

kintone 側で行った変更(2026-10-13 15:58 UTC ごろ)
変更対象中身
更新$id 2・3・4状況を「提出済」から「受注」へ。revision は1から2に
追加$id 31・32状況は「提出前」、合計は各60,000円
削除$id 6状況が「失注」、合計60,000円のレコード

差分は5件。消した6番は返ってこない

前回のいちばん新しい更新日時より後に変わったレコードを、レコードを一括取得する API(GET /k/v1/records.json)で取りました。クエリでは更新日時を「>」「>=」などで比べられます(cybozu developer network「クエリの書き方」)。日時は、kintone が返す形(末尾に Z の付いた UTC)のまま書きました。fields には、この確認に使う4つのフィールドだけを指定しました。BigQuery に入れるときは、MERGE で使う見積番号(quote_no)や合計(total)など、表に入れる列も fields に足します。

GET /k/v1/records.json
app:    (見積アプリの ID)
query:  更新日時 > "2026-10-13T15:04:00Z"
fields: ["$id", "$revision", "更新日時", "status"]

返ったのは5件でした。更新した2・3・4(revision 2、受注)と、追加した31・32(revision 1)です。削除した6番は入っていません。kintone から消えたレコードは、条件に合うかどうかを比べる対象そのものに無いからです。5件の更新日時は、どれも 2026-10-13T15:58:00Z と秒が00の形でした。この点は後の節で効いてきます。

3種類の変更のうち、差分の問い合わせで返ったもの

更新した3件

$id 2・3・4

  • 返った
  • revision 2、状況は受注
  • 更新日時は 15:58:00Z

追加した2件

$id 31・32

  • 返った
  • revision 1、状況は提出前
  • 更新日時は 15:58:00Z

削除した1件

$id 6

  • 返らない
  • kintone に無いので、問い合わせの結果に出てこない

出典:当社の検証

この5件を、BigQuery の MERGE で kq_header に反映しました。kintone_id で照合し、一致した行は revision が大きいときだけ上書きし、表に無い行は足します。検証では5件を SQL の中に値で書きましたが、毎日の運用なら、差分を作業用の表に読み込んで USING に指定する形になります。

MERGE `your-project.blog_demo.kq_header` T
USING (SELECT * FROM UNNEST([
  STRUCT(2 AS kintone_id, 2 AS revision, 'Q-0002' AS quote_no, '受注' AS status,
         140000 AS total, TIMESTAMP '2026-10-13 15:58:00+00' AS updated_at),
  STRUCT(3, 2, 'Q-0003', '受注', 400000, TIMESTAMP '2026-10-13 15:58:00+00'),
  -- 4・31・32 も同じ形で続く
  STRUCT(32, 1, 'Q-0032', '提出前', 60000, TIMESTAMP '2026-10-13 15:58:00+00')])) S
ON T.kintone_id = S.kintone_id
WHEN MATCHED AND S.revision > T.revision THEN
  UPDATE SET revision = S.revision, status = S.status,
             total = S.total, updated_at = S.updated_at
WHEN NOT MATCHED THEN
  INSERT (kintone_id, revision, quote_no, status, total, updated_at)
  VALUES (S.kintone_id, S.revision, S.quote_no, S.status, S.total, S.updated_at)

反映したあとの kq_header は32件になりました。kintone は6番を消したので31件です。受注は12件・3,980,000円で、kintone と同じでした。6番は失注の見積だったので、受注の件数と金額は動かず、ずれは件数にだけ出ました。受注の合計だけを見ていたら、この取り残しには気づけません。取り残しが積もると、見積の総数や失注の件数を数える集計が、kintone より多く出るようになります。

削除は、全件の $id と突き合わせて消す

消えたレコードを差分の問い合わせで見つける方法は無いので、kintone に今あるレコードの $id を全部取り、BigQuery の側に「kintone に無い ID」が残っていないかを見ます。検証では、同じ API の fields に $id だけを指定し、totalCount を true にしました。totalCount を true にすると、条件に合うレコードの件数も一緒に返ります(cybozu developer network「レコードを一括取得する」)。

GET /k/v1/records.json
app:        (見積アプリの ID)
fields:     ["$id"]
totalCount: true

返ったのは31件(1〜5と7〜32)で、応答は $id だけでした。この31個の ID を BigQuery に渡して、kintone に無い行を探しました。

SELECT kintone_id, quote_no, status, total
FROM `your-project.blog_demo.kq_header`
WHERE kintone_id NOT IN UNNEST([1, 2, 3, 4, 5, 7, 8, 9, 10, /* … */ 31, 32])
-- 結果:6 | Q-0006 | 失注 | 60000

見つかったのは6番の1件だけでした。同じ条件で DELETE を流すと kq_header は31件になり、件数も、受注12件・3,980,000円も、この時点の kintone と一致しました。BigQuery の DELETE には WHERE が必須です(BigQuery のリファレンス「DML statements」)。

DELETE FROM `your-project.blog_demo.kq_header`
WHERE kintone_id NOT IN UNNEST([1, 2, 3, 4, 5, 7, 8, 9, 10, /* … */ 31, 32])

BigQuery の見積の件数(kintone は変更後に31件)

前回までの取り込み30件
差分を MERGE したあと32件
全件の $id で DELETE したあと31件
kintone(変更後)31件

出典:当社の検証。6番は失注の見積なので、受注の件数と金額は、BigQuery のどの時点もその時点の kintone と同じだった

全件の ID を取るときに気をつけること

取り出す件数の決まりは初回の読み込みと同じです。1回で取れるのは500件まで(limit を書かなければ100件)、offset は10,000まで、order by を書かなければレコード ID の降順で返ります(「クエリの書き方」)。1万件のアプリなら、$id だけを500件ずつ取って20回のリクエストです。1万件を超えるアプリの区切り方($id で区切る、カーソル API)はkintone×BigQuery 連携の記事にまとめています。

気をつけたいのは、ID の一覧が途中で欠けたときです。ページ送りの途中で失敗した一覧と突き合わせると、取れなかった分まで「kintone に無い」と判定して、BigQuery の行をまとめて消してしまいます。誤って消した行は、kintone 側で更新されない限り、差分の取り込みでは戻ってきません。消す前に、集めた ID の数が totalCount と一致しているかを確かめ、消す件数がふだんより多い日は止めて人が見る作りをお勧めします。逆向き(kintone にあって BigQuery に無い ID)も同じ一覧で探せるので、追加の取りこぼしにも気づけます。

MERGE の1本で消す書き方もある

MERGE には、ソースに無いターゲットの行を消す WHEN NOT MATCHED BY SOURCE THEN DELETE という句があります(BigQuery のリファレンス「DML statements」)。ソースが全件の一覧なら、更新・追加・削除を1本で済ませられます。反対に、ソースが差分だけのときにこの句を書くと、差分に入っていない行がすべて消えます。差分を反映する MERGE と、全件の ID との突き合わせは、別の文に分けておく方が安全です。

Webhook の削除の通知だけでは、拾えない削除がある

kintone の Webhook は、レコードの削除を外部に通知できます。通知の種類は DELETE_RECORD で、レコード番号(recordId)、削除した日時(deletedAt)、削除した人が送られます(kintone ヘルプ「kintoneの操作で送信されるWebhookの通知内容」)。通知を受けた側で、その番号の行を BigQuery から消せば、翌日の突き合わせを待たずに反映できます。

ただし、kintone ヘルプの Webhook の設定のページには、通知が送られない操作が挙がっています。Excel や CSV のファイルを読み込んでのレコードの操作、複数のレコードの一括削除、複数のレコードを一括で操作する REST API です。通知はドメイン単位で1分間に60回までで、61回目からの操作は通知されません。ライトコースでは Webhook を使えません(kintone ヘルプ「Webhookを設定する」)。

一覧からまとめて消した場合や、ほかの仕組みが REST API でまとめて消した場合は通知が来ないので、Webhook を使う場合も、全件の $id との突き合わせは残すことをお勧めします。BigQuery の側にも決まりがあり、1つの表への UPDATE・DELETE・MERGE は同時に2本まで動き、待ちは20本までで、それを超えた文は失敗します(BigQuery「データ操作言語(DML)でデータを変換する」)。通知1件ごとに DELETE を流すより、通知をためて間隔を空けてまとめて消す形が向いています。

削除を拾う2つの方法

全件の $id と突き合わせる

検証で試した方法

  • kintone の今の ID を全部取り、BigQuery に残る ID を消す
  • 一覧からの一括削除や、一括の REST API で消したものも拾える
  • 取得は件数に比例して重くなる(1回500件まで)

試したか:試した(31件と突き合わせて1件を消した)

Webhook の削除の通知

kintone ヘルプの記載から

  • DELETE_RECORD で recordId と deletedAt が届く
  • 消した直後に反映できる
  • 一覧からの一括削除や、一括の REST API での削除は通知されない
  • 通知はドメインで1分60回まで

試したか:試していない

Webhook を使う場合も、全件の突き合わせは残す

出典:当社の検証、kintone ヘルプ「Webhookを設定する」、「kintoneの操作で送信されるWebhookの通知内容」

同じ「分」の更新を「>」で取りこぼす

削除を直したあと、もう1つの抜けを確かめました。前回の同期と同じ分に、別のレコードが更新された場合です。

同じ分に2件が更新されたときの順番(時刻は UTC)

15:59:01 ごろ 7番を更新受注から失注へ(revision 2)。kintone の更新日時は 15:59:00Z
15:59:02 同期17番を取り込み、「前回」として 15:59:00Z を記録
15:59:06 8番を更新失注から受注へ(revision 2)。更新日時は同じ 15:59:00Z
次の同期 更新日時 > 15:59:00Z返ったのは0件。8番は BigQuery で失注のまま

出典:当社の検証

15時59分01秒ごろに7番を「受注」から「失注」に変え、その直後の同期で7番を取り込みました。このとき「前回の同期」として記録した、いちばん新しい更新日時は 15:59:00Z です。その5秒ほど後に8番を「失注」から「受注」に変えると、8番の更新日時も 15:59:00Z でした。次の同期で 更新日時 > "2026-10-13T15:59:00Z" と問い合わせると、返ったのは0件です。8番の更新日時は前回の記録と同じ値なので、「より大きい」の条件から外れました。

このときの BigQuery は31件・受注11件・3,720,000円で、8番は失注のままでした。kintone は、8番の更新のあと変更が無く、あとで全件を数えると31件・受注12件・4,170,000円でした。件数は合っているのに、受注は1件・450,000円少ない状態です。今度は件数を見ていても気づけず、金額を比べて初めて分かります。

受注の合計(7番と8番を更新したあと)

kintone4,170,000円
BigQuery(「>」で同期)3,720,000円
BigQuery(「>=」と revision で同期)4,170,000円

出典:当社の検証。件数はどれも31件。差の450,000円は8番の合計

秒が00で返った:当社の環境での観察と、公式に書いてあること

この検証では、差分で返った更新日時がすべて秒00の形でした。15時58分10秒ごろの更新が 15:58:00Z、15時59分06秒の更新が 15:59:00Z です。cybozu developer network の「kintone REST APIの共通仕様」では、取得するときの日時の形式は YYYY-MM-DDTHH:MM:SSZ で、日時を登録・更新するときに秒を指定すると秒は無視される、とあります。自動で記録される更新日時が秒をどう持つかは、当社が読んだ範囲の公式の資料では見つけられませんでした。ここでは、当社の検証環境で分単位で返った、という観察として書いています。

秒まで返る場合でも、前回の記録とちょうど同じ時刻の更新は「>」では外れます。境目の値を含めて取り直す作りにしておけば、時刻の細かさに頼らずに済みます。

「>=」で取り直し、revision が大きいときだけ上書きする

条件を 更新日時 >= "2026-10-13T15:59:00Z" に変えると、2件が返りました。前回すでに取り込んだ7番と、取りこぼした8番です。境目を含めると、前回取り込んだレコードがもう一度返るので、同じものを二重に扱わない仕組みが要ります。

そこで使ったのが revision です。kintone のレコードは、更新すると revision の番号が変わり、検証では1回の更新で1から2になりました。レコードを更新する API は、応答で更新後のリビジョン番号を返します(「レコードを更新する」)。プロセス管理のステータスを更新したときは、アクションの実行とステータスの更新の2つの操作が記録されるため、リビジョンは2つ増えます(「1件のレコードのステータスを更新する」)。1つずつ増えるとは限らないので、比べるのは「次の番号か」ではなく「大きいか」です。MERGE の一致の条件に「ソースの revision がターゲットより大きいとき」を足しておけば、前回と同じ版の7番は上書きされず、新しい版の8番だけが入ります。

MERGE `your-project.blog_demo.kq_header` T
USING (SELECT * FROM UNNEST([
  STRUCT(7 AS kintone_id, 2 AS revision, 'Q-0007' AS quote_no, '失注' AS status,
         260000 AS total, TIMESTAMP '2026-10-13 15:59:00+00' AS updated_at),
  STRUCT(8, 2, 'Q-0008', '受注', 450000, TIMESTAMP '2026-10-13 15:59:00+00')])) S
ON T.kintone_id = S.kintone_id
WHEN MATCHED AND S.revision > T.revision THEN   -- 7番は同じ版なので上書きしない
  UPDATE SET revision = S.revision, status = S.status,
             total = S.total, updated_at = S.updated_at
WHEN NOT MATCHED THEN
  INSERT (kintone_id, revision, quote_no, status, total, updated_at)
  VALUES (S.kintone_id, S.revision, S.quote_no, S.status, S.total, S.updated_at)

流したあとの BigQuery は31件・受注12件・4,170,000円で、8番は受注になりました。同じ時点の kintone の全件を取って数えた値(31件・受注12件・4,170,000円)と一致しています。

上書きするかどうかを更新日時で決めないのは、同じ分の更新は更新日時が同じ値になり、どちらが新しいかを決められないためです。もう1つ、UPDATE の句がある MERGE では、ソースの複数の行がターゲットの同じ行に一致し、その行を更新・削除しようとすると、UPDATE/MERGE must match at most one source row for each target row のエラーで止まります(BigQuery のリファレンス「DML statements」)。境目を含めて取った差分を何回分かためてから MERGE する作りなら、同じ kintone_id のうち revision が最大の1行だけを残してから流します。

差分が500件を超える日は、1回のリクエストでは取り切れません。続きを offset か $id で区切って取る作りが要ります。

毎回、件数と合計の両方を kintone と比べる

取り込みのたびに、BigQuery と kintone の数を並べると次のとおりです。

取り込みのたびの BigQuery と kintone の数(当社の検証、2026年10月14日)
時点件数(BigQuery/kintone)受注(BigQuery)受注(kintone)
前回までの取り込み30/309件
2,990,000円
9件
2,990,000円
差分5件を MERGE したあと32/3112件
3,980,000円
12件
3,980,000円
全件の $id と突き合わせて DELETE したあと31/3112件
3,980,000円
12件
3,980,000円
7番・8番の更新後、「>」で同期したあと31/3111件
3,720,000円
12件
4,170,000円
「>=」と revision で同期したあと31/3112件
4,170,000円
12件
4,170,000円

kintone の列のうち、「差分5件を MERGE したあと」と「『>』で同期したあと」は、その時点で数えた記録がありません。次に数えた時点(すぐ下の行)までに kintone の変更は無いので、その値を置いています。

削除の取り残しは件数にだけ出て、受注の金額には出ませんでした。同じ分の取りこぼしは反対に、金額にだけ出て、件数には出ませんでした。どちらか一方だけを比べていると、もう片方のずれを見落とします。

比べる数は、kintone の側でも取れるものにします。件数は、レコードを一括取得する API の totalCount で返り、query で status in ("受注") のように絞った件数も取れます。この API の応答はレコードと件数なので、合計の金額は、検証では全件を取って足しました(「レコードを一括取得する」)。BigQuery の側は COUNT(*) と SUM(IF(status = '受注', total, 0)) で同じ数を出します。

ずれ方から、抜けた原因を引く

件数が kintone より多い

検証で起きた(32件/31件)

  • 削除の取り残し
  • 全件の $id と突き合わせて、kintone に無い行を消す

件数は合い、金額がずれる

検証で起きた(3,720,000円/4,170,000円)

  • 同じ分の更新の取りこぼし
  • 「>=」で取り直し、revision が大きいときだけ上書きする

件数が kintone より少ない

この検証では起きていない

  • 追加の取りこぼしか、消しすぎ
  • kintone にあって BigQuery に無い ID を探す

出典:当社の検証。3列目は当社の考え方で、検証はしていない

費用:小さな表でも1本10 MB、大きくなると表全体を数える

この検証で BigQuery に流した SQL は、表の作成・MERGE・DELETE・確認の SELECT を合わせて11本でした。処理した量は1本あたり531〜1,584バイトでしたが、課金の対象の量はどれも最低の量でした。公式の表記は10 MB ですが、記録では10,485,760バイト(10 MiB)で数えられていて、11本で110 MiB です。オンデマンドの料金は処理したバイト数で数え、MB 単位で切り上げ、クエリが参照する表ごと・クエリごとに最低10 MB、毎月1 TiB までは無料です(BigQuery の料金)。

表が大きくなると効いてくるのは、DML の数え方です。パーティションの無い表では、UPDATE・DELETE と、UPDATE か DELETE の句を含む MERGE は、文そのものが読んだ量に、変更する前の表の大きさを足して数えます。INSERT の句だけの MERGE は、文が読んだ量だけです。パーティション分割テーブルなら、足すのは変更するパーティションの大きさです(BigQuery のリファレンス「DML statements」のオンデマンドの処理量)。毎日の MERGE と DELETE は、そのたびに見出しの表全体の大きさを数えることになります。

保存の料金にも関わります。90日続けて変更の無い表やパーティションには、長期保存の安い料金がかかりますが、DML で表を変えるとそのタイマーが0に戻ります(BigQuery の料金)。パーティションの無い表の行を毎日 MERGE や DELETE で変えていれば、長期保存の料金にはなりません。単価と見積もりの考え方はBigQuery でデータ統合基盤を構築する費用に書いています。

この検証で確かめていないこと

検証は30件のアプリで、kintone 側の変更は2回、1回の差分は最大5件でした。次のことは試していません。

  • 明細(サブテーブル)の差分。親のレコードが更新されたら、その親の明細を BigQuery 側で消して入れ直す作りが考えられますが、明細の行の ID での照合も含めて確かめていません。明細の表の形はkintone×BigQuery 連携の記事に書いています。
  • Webhook、カーソル API、500件を超える差分、1日の API リクエスト数の上限への到達。
  • MERGE の WHEN NOT MATCHED BY SOURCE の句と、ソースの重複で出る MERGE のエラー。
  • 逆向きの照合(kintone にあって BigQuery に無い ID)。この検証で確かめたのは、BigQuery に残った側だけです。
  • 連携ツールの差分の設定。ツールを使う場合も、削除をどう扱うか、境目の時刻を含めるかは、各ツールの資料で確かめる必要があります。
  • 毎日の自動実行(スケジュール)と、失敗したときの再実行。

今動いている差分の取り込みがあるなら、今日1回、件数と合計を比べる

差分で毎日入れている仕組みがすでにあるなら、今日、kintone と BigQuery の件数と、いちばんよく見る合計(受注の金額など)を1回比べてみてください。どちらがずれているかで、上の図のどちらの抜けかが分かります。これから作る場合や委託先に作ってもらう場合は、削除をどう反映するか、境目の時刻を含めるか、照合の数をどこで見るかの3つを、仕様書に書いておくと、出来上がったものを確かめやすくなります。

kintone のアプリの作りから、BigQuery の表の持ち方と照合の仕組みまで一緒に考えたい場合は、当社のデータ分析・活用の支援で承ります。kintone の REST API の使い方はkintone REST API 連携の記事にまとめています。

検証の数字は、当社の検証用 kintone と検証用の Google Cloud プロジェクト(東京リージョン)で2026年10月14日に動かした結果で、データはすべて架空です。kintone と BigQuery の仕様・料金は、同じ日に公式の資料で確かめたものです。

kintone導入・内製化支援

kintoneを現場の業務に合わせて設計・構築し、Excelや紙業務の置き換え、他システムとのデータ連携、社内メンバー自身が改修・運用できる内製化までを支援します。

AT
aurant technologies 編集

上場企業からスタートアップまで、数多くのデータ分析基盤構築・AI導入プロジェクトを主導。単なる技術提供にとどまらず、MA/CRM(Salesforce, Hubspot, kintone, LINE)導入によるマーケティング最適化やバックオフィス業務の自動化など、常に「事業数値(売上・利益)」に直結する改善実績多数。

この記事が役に立ったらシェア:

kintoneの資料

「kintone vs Salesforce 判定マニュアル」

kintone導入・内製化支援の内容を見る