こんにちは!Yokoです。
情報漏洩のニュースが未だに続いていますが、いつ収まるんでしょうかねえ。
個人情報の一つに住所がありますが(個人を特定できちゃう場合ね)、お客様のデータを見ていると、顧客住所がめちゃくちゃで、市区町村に町名まで入っていたり、「1の2」とか「1−2」とか…😩
何でって、入力フォームで顧客が入力したものなので仕方ないのかもしれないけど、エリア分析なんかでは結構ネックになってたりします。
社内の担当者が、ダウンロードしてExcelでメンテするとか、今となっちゃ危なくてできないですよね。
こういうの、裏でAIがひっそり直してくれると安全だなあと思いませんか?
そうなんですよ。今回はQlikのロードスクリプトでAIと連携して、住所の表記ゆれを直して取り込んでくれるっていうワザをご紹介しちゃうんです。QlikのロードスクリプトからOpenAIにデータを渡して、返してもらうって何やら難しそうですが、コツを覚えればできますよ!
悩ましいデータ
冒頭述べたとおり、住所の表記ゆれには色々ありますが、以前の記事で使用した銭湯マップデータはこんな感じです。
途中までは○番○号で入っていて、その後は○-○で入っているとか…とにかく色んなパターンがあるんだけど、AIにとってはこんなのは簡単に処理できるに決まってますよね。
それより、データの受け渡しのほうが面倒です。

今回は、銭湯マップの住所データをロードした際に、住所のゆれがあるままOpenAIに投げて、返ってきたらQlikの住所データに反映させるという処理をロードスクリプト内で完結させるという手法で実現させます。
Qlik OpenAI Connectorを有効化する
Qlik OpenAI Connectorなんてあるの?
データ接続に出てこないんだけど…
って思うかもしれません。
実は、このコネクターは隠れていて、初期状態では見えません😆
有効化する手順は以下のとおりです。
Qlik Cloudの管理画面で、設定 > 機能コントロール を表示し、 機械学習エンドポイントをオンにします。

これで、データ接続を作成する際に、分析ソースで様々なコネクタが表示されるようになります。
僕が機械学習エンドポイントをオンにした直後、分析ソースのコネクタが出るか確認したんですが、デフォルトのまま変化がなかったので、一旦Qlik Cloudからログアウトして、再度ログインしたら見えるようになりました。

データ要件とGPTモデルの選定
今回のデータ要件は以下のとおりと仮定します。
- データは住所のみで、OpenAIからは都道府県、市区町村、町丁目、番地に分割して返してもらう
- 表記ゆれはOpenAI側で修正してもらう
- 住所データの件数は1万件
一般のコネクタ設定はデータソースへの接続情報を入れるだけですが、OpenAI Connectorでは、利用場面を少し意識しておく必要があります。
おそらくGPTにお願いする内容に則して、接続を作成していくほうがベターな気がします。
今回の内容的には高度なモデルである必要は全然ないので、料金が比較的安いGPT-4.1 miniくらいでも処理できるだろうと考えました。
OpenAIの公式サイトからGPT-4.1 miniの情報を見てみると、回答の出力上限(max output tokens)は32,768トークンということなので、回答1件当りのトークンを推定して、一度のバルクで処理する件数を逆算していきます。
これを適正に指定しないと、回答で取り漏れが発生しちゃうので要注意です。
トークンはJSON形式で試算する必要があって、回答の4項目をJSONで受け取ることを想定するとこんな計算になりました。
【目安としたデータ】
{“id”:1,”都道府県”:”東京都”,”市区町村”:”台東区”,”町丁目”:”谷中2丁目”,”番地”:”18-7″}
| 部分 | 内容 | 目安トークン数 |
|---|---|---|
| 記号・英文字 | {"id":1," ":" "," "}, などの区切り記号、id の数字 | 約15〜20 |
| 項目名(日本語) | 都道府県・市区町村・町丁目・番地 | 約6〜10 |
| 値(日本語+数字) | 東京都・台東区・谷中2丁目・18-7 | 約8〜15 |
| 合計 | 約30〜45 | |
理屈からすると約32,000 / 40 = 800ということになるので、一度に800件くらいは処理してくれそうに見えます。
が、実際には全然そうじゃなくて、このまま800件処理すると大量の取り漏れが発生してしまいます。
まあ、安全なところでいきますと、100件くらいに抑えておいたほうが無難というのが現実です。
概ね、この状態で100件×100回処理した場合、入力分も含めて計算すると「120円くらいかな」っていう感じの費用感なので、OpenAIにバルクで渡すデータは100件ずつということにしました。
Qlik OpenAI Connectorを設定する
まずは、OpenAI側の準備から始めます。
OpenAI Platformで、API Keyを発行します。
例えば、下記のような設定でAPI Keyを発行し、そのキーを大事に取っておきましょう。

データ接続で分析ソースからOpenAIを選択してクリックします。

ここからコネクターの設定を行っていきます。
| 設定項目 | 設定内容 |
|---|---|
| Configuration | OpenAI Chat Completions API |
| OpenAI API Key | <OpenAI Platformで取得したAPI Key> |
| OpenAI Model | GPT-4.1 mini |
| Temperature | 0 |
| MaxTokens | 8000 |

Temperatureというのは0にすると必ず同じ回答をする、1に近づけるとその時によってばらつきのある回答をするっていうもので、今回は毎回同じ回答にしないといけないので0にしました。
(ばらつきのある回答がほしいケースというのは、アイデア出しや文章作成などです)
また、Max Tokensは40トークン × 100件 × 2 = 8,000件としました。最後の×2は、トークンが超えないくらいに設定するため2倍にしたっていうだけですが、これくらいバッファーを見るもののようです。
以上の設定で、OpenAI_Connectionという接続名をつけて保存します。
Qlikロードスクリプトの実装
ちょっと長いスクリプトになっちゃったんで、全体を出してから下で解説しますね。
// ==========================================
// 1. 元データ(1万件想定)の読み込み
// ==========================================
raw_data:
LOAD
RowNo() as id,
住所,
Ceil(RowNo() / 100) as chunk_id // AIの取りこぼしを防ぐため100件ずつ
FROM [lib://<Path>/東京23区銭湯リスト.csv]
(txt, utf8, embedded labels, delimiter is ',', msq);
//100でLOOPする回数
LET vMaxChunk = Peek('chunk_id', -1, 'raw_data');
//空テーブル
address_master:
LOAD * INLINE [id, 都道府県, 市区町村, 町丁目, 番地]; // raw_data と id で紐づく
FOR i = 1 TO $(vMaxChunk)
// 現在のグループ(100件)だけを抽出してJSON化
address_json_tmp:
LOAD
JsonSetEx('{}', '/id', id, '/address', 住所) as json_row
RESIDENT raw_data
WHERE chunk_id = $(i);
// プロンプト定義(JSONを変数展開すると引用符で構文が壊れるため、プロンプトはフィールド側で組み立てる)
LET vSystemPrompt = 'あなたは日本の住所データの正規化の専門家です。入力されたJSON配列の各要素(id, address)について、次のルールで表記ゆれを補正し、分割してください。'
& ' 1.全角英数字・全角記号は半角に統一する。'
& ' 2.丁目の漢数字は算用数字に統一する(例:谷中二丁目→谷中2丁目)。'
& ' 3.番地・号は「18-7」のようにハイフン区切りに統一する(例:18番7号、18番地の7、18ー7→18-7)。'
& ' 4.都道府県が省略されている場合は補完する。'
& ' 5.不要な空白・建物名・部屋番号は除去する。'
& ' 6.判断できない部分は推測せず空文字にする。'
& ' 出力は入力と同じ件数・同じidで、[{"id":1,"都道府県":"東京都","市区町村":"台東区","町丁目":"谷中2丁目","番地":"18-7"}] の形式の有効なJSON配列のみを返してください。解説やマークダウン(```json)は一切不要です。';
// 100件分のJSONをカンマで結合し、プロンプトと合わせて1レコードにする
address_json:
LOAD
'$(vSystemPrompt)' & Chr(10) & '以下のJSON配列データを処理してください: '
& '[' & Concat(json_row, ',') & ']' as text
RESIDENT address_json_tmp;
DROP TABLE address_json_tmp;
// OpenAIコネクタ(分析接続)の呼び出し ※モデル(gpt-4.1-mini)等は接続設定側で指定
response:
LOAD * EXTENSION endpoints.ScriptEval(
'{"RequestType":"endpoint", "endpoint":{"connectionname":"dev:OpenAI_Connection","column":"choices.message.content"}}',
address_json{text}
);
DROP TABLE address_json;
// 返ってきたJSON配列を1件ずつ取り出してマスターに追加(縦に結合)
CONCATENATE (address_master)
LOAD
JsonGet(item, '/id') as id,
JsonGet(item, '/都道府県') as 都道府県,
JsonGet(item, '/市区町村') as 市区町村,
JsonGet(item, '/町丁目') as 町丁目,
JsonGet(item, '/番地') as 番地
;
LOAD
JsonGet(resp, '/' & (IterNo() - 1)) as item
WHILE Len(JsonGet(resp, '/' & (IterNo() - 1))) > 0
;
LOAD
// 念のためマークダウン(```json)が付いて返ってきた場合は除去
Trim(Replace(Replace([choices.message.content], '```json', ''), '```', '')) as resp
RESIDENT response;
DROP TABLE response;
NEXT i| 行数 | 解説 |
|---|---|
| 4〜10行目 | RAWデータを読み込みます。ここではRowNo()をidにしていますが、顧客IDなどでも大丈夫 |
| 12行目 | vMaxChunk:一度に100個のデータを処理した場合、何回LOOPするかを設定します |
| 17行目 | 空テーブルを作成(ここにデータをConcatenateしていきます) |
| 19行目 | FOR 〜NEXT(79行目)をvMaxChunkの回数分LOOPさせます |
| 22〜26行目 | raw_dataを読み込んで、JSON形式のデータに変換します。JsonSetEx関数で一気にJSON化できるのは便利ですね |
| 29〜36行目 | OpenAIに渡すシステムプロンプトを指定します |
| 39〜43行目 | システムプロンプトに続けて、22〜26行目で作成したJSONデータ(100件分)を結合したJSONにして渡す文字列を作成します |
| 48〜52行目 | “OpenAIに接続して回答を取得します。dev:OpenAI_ConnectionはdevというスペースのOpenAIの接続名:OpenAI_Connectionです。 address_json{text}はaddress_jsonテーブルのtextの値を指定しています” |
| 57〜72行目 | マークダウン(“`json)が返ってきた場合は除去します。65〜67行目ではJsonGetで’/’をつけてJSON分解し、それを58〜64行目で5つのカラムに分解してaddress_masterにConcatenateします。 |
リロード結果
上記のスクリプトを実行すると、以下のようにきれいに表記ゆれを修正してデータを取得してくれました。
Json関数がないと大変だな〜
(ついでに.jsonファイルもロードできるといいんだけどなあ…でも、そのうちできるようになるに違いない!)

まとめ
以上、QlikのロードスクリプトからOpenAI Connectorを使って、AI連携してみました!
今回はGPT-4.1 miniを使いましたが、2026年10月現在でも「非推奨」みたいですので、最新の安いモデルで試してみても良いと思います。
今回ご紹介したロードスクリプトを参考にしてもらえれば、リロード実行中にOpenAIにデータを丸投げして、返ってきた回答をQlik内に取り込めちゃうんですよね!
結構使えそうだと思いません?
プロンプトしだいでは、色んなことができそう😄
因みに、今回はOpenAIを使いましたが、Claudeでやりたかったら、AWS(Amazon Bedrock)で実現できるようです。いつかやってみようかな。
最後に、リロード都度、OpenAIで課金されるのも困るので、一旦全件処理したらQVD化して、未処理のデータだけ抽出して処理させるなど工夫すれば、費用も抑えられます😉
ではまた!


