
Contents
実行環境 関連リンクと過去記事 PostgreSQL 用の汎用ウェブアクセス関数を、PL/Python で作成 作った関数のテスト(以上、1月4日) PL/Python で作ったウェブアクセス関数を、PL/v8 から使う(1月5日) WebAPI の例として、MediaWiki API を使ってみる PL/v8 で MediaWiki API の「タイトル → pageid」検索関数を作る(以上、1月7日) 関数の改良(ⅰ)JSON を処理して pageid を直接返す 改良したものの、テーブルの複数行に対しては使いにくい(以上、1月8日) - 関数の改良(ⅱ)複数行に対するウェブアクセスを、ウィンドウ関数で
1 回にまとめる - 関数のテスト
: 取得した pageid から Wikipedia の記事本文を確認 - 関数のテスト
: 一括取得数を多めにしてみる - 関数のテスト
: 検索語(英語)の大文字・小文字について - 今後の課題
関数の改良(ⅱ)複数行に対するウェブアクセスを、ウィンドウ関数で 1 回にまとめる
ウィンドウ関数の一般的な使い方からは外れますが、今回やりたい「ウェブアクセスはselect * from cities; +--------+ | city | +--------+ | 東京 | | 大阪 | | 札幌幌 | -- 検索結果なしのテスト用 | 仙台 | | 広島 | | 北九州 | | 福岡 | +--------+ (7 rows)
最終形のイメージは ↓ こんな感じ。結果は昨日の複雑なクエリと同様ですが、検索語の列(city)をストアドに渡すだけで、ストアド内で
select city, plv8_winf_expected(city) over() from cities; +--------+----------+ | city | pageid | +--------+----------+ | 東京 | 2126632 | | 大阪 | 3211539 | | 札幌幌 | -1 | | 仙台 | 3211582 | | 広島 | 3211568 | | 北九州 | 16326328 | | 福岡 | 9336564 | +--------+----------+ (7 rows)
まず前段、検索語を配列にまとめて
1
create or replace function plv8_MediaWiki_ex3(wiki_title text) returns json language plv8 window immutable as $$// prepare window function object var w = plv8.get_window_object();// do nothing at the second row later if (w.get_current_position() > 0) return null;// construct an array of keywords var wiki_titles = []; for(var i = 0; i < w.get_partition_row_count(); i++) { wiki_titles.push(w.get_func_arg_in_partition(0, i, w.SEEK_HEAD, false)); }// followed by getting data from MediaWiki API and converting it to JSON // as well as the function in http://kenpg.bitbucket.org/blog/201601/08.html var url = 'https://en.wikipedia.org/w/api.php?action=query&format=json&titles=' + encodeURIComponent(wiki_titles.join('|')), sql = "select convert_from(plpy_urlopen('" + url + "'), 'utf8')"; res = plv8.execute(sql), objTmp1 = JSON.parse(res[0]['convert_from']); if (!objTmp1) return null;// search a pageid matching each title var objTmp2 = objTmp1['query']['pages'], res = {}; for (var i = 0; i < wiki_titles.length; i++) { var titleTarget = wiki_titles[i]; for (var pid in objTmp2) { var objCand = objTmp2[ pid ]; if (objCand['title'] === titleTarget) { res[ titleTarget ] = pid; break; } } } return res; $$;
↓ 実行結果。1
select *, plv8_MediaWiki_ex3(city) over() from cities; +--------+--------------------------------------------------------------------------------------------------------------------------+ | city | plv8_mediawiki_ex3 | +--------+--------------------------------------------------------------------------------------------------------------------------+ | 東京 | {"東京":"2126632","大阪":"3211539","札幌幌":"-1","仙台":"3211582","広島":"3211568","北九州":"16326328","福岡":"9336564"} | | 大阪 | | | 札幌幌 | | | 仙台 | | | 広島 | | | 北九州 | | | 福岡 | | +--------+--------------------------------------------------------------------------------------------------------------------------+ (7 rows)

↓ 処理追加後。これが今回の一応の完成形です。先ほど
create or replace function plv8_MediaWiki_ex4(wiki_title text) returns json language plv8 window immutable as $$// prepare window function object var w = plv8.get_window_object();// at the first row if (w.get_current_position() === 0) {// construct an array of keywords var wiki_titles = []; for(var i = 0; i < w.get_partition_row_count(); i++) { wiki_titles.push(w.get_func_arg_in_partition(0, i, w.SEEK_HEAD, false)); }// followed by getting data from MediaWiki API and converting it to JSON // as well as the function in http://kenpg.bitbucket.org/blog/201601/08.html var url = 'https://en.wikipedia.org/w/api.php?action=query&format=json&titles=' + encodeURIComponent(wiki_titles.join('|')), sql = "select convert_from(plpy_urlopen('" + url + "'), 'utf8')"; res = plv8.execute(sql), objTmp1 = JSON.parse(res[0]['convert_from']); if (!objTmp1) { w.set_partition_local(null); } else {// search a pageid matching each title var objTmp2 = objTmp1['query']['pages']; objRes = {}; for (var i = 0; i < wiki_titles.length; i++) { var titleTarget = wiki_titles[i]; for (var pageid in objTmp2) { var objCand = objTmp2[ pageid ]; if (objCand['title'] === titleTarget) { objRes[ titleTarget ] = pageid; break; } } } w.set_partition_local(objRes); } }// output result (pageid) var obj = w.get_partition_local(); if (!obj) { return null; } else { var title_curr = w.get_func_arg_in_partition(0, 0, w.SEEK_CURRENT, false); return obj[ title_curr ]; } $$;
↓ 実行結果。コンソール画像では確認のため元テーブル表示も入ってます。ストアドの出力列名を分かりやすく
select *, plv8_MediaWiki_ex4(city) over() from cities; +--------+--------------------+ | city | plv8_mediawiki_ex4 | +--------+--------------------+ | 東京 | 2126632 | | 大阪 | 3211539 | | 札幌幌 | -1 | | 仙台 | 3211582 | | 広島 | 3211568 | | 北九州 | 16326328 | | 福岡 | 9336564 | +--------+--------------------+ (7 rows)

↓ 出力列名と型を分かりやすくする例。ついでに
select *, plv8_MediaWiki_ex4(city) over() :: text :: int as pageid from cities order by pageid; +--------+----------+ | city | pageid | +--------+----------+ | 札幌幌 | -1 | | 東京 | 2126632 | | 大阪 | 3211539 | | 広島 | 3211568 | | 仙台 | 3211582 | | 福岡 | 9336564 | | 北九州 | 16326328 | +--------+----------+ (7 rows)
関数のテスト : 取得した pageid から Wikipedia の記事本文を確認
まず、得られたhttps://en.wikipedia.org/w/index.php?curid=(関数が返すpageid)

次にエラー処理の確認。ネットワーク非接続だと ↓ 全て
select *, plv8_MediaWiki_ex4(city) over() from cities; NOTICE: URLError or HTTPError. failed to reach a server CONTEXT: PL/Python function "plpy_urlopen" SQL statement "select convert_from(plpy_urlopen('https://en.wikipedia.org/w/api.php?a ction=query&format=json&titles=%E6%9D%B1%E4%BA%AC%7C%E5%A4%A7%E9%98%AA%7C%E6%9C%AD%E5 %B9%8C%E5%B9%8C%7C%E4%BB%99%E5%8F%B0%7C%E5%BA%83%E5%B3%B6%7C%E5%8C%97%E4%B9%9D%E5%B7% 9E%7C%E7%A6%8F%E5%B2%A1'), 'utf8')" +--------+--------------------+ | city | plv8_mediawiki_ex4 | +--------+--------------------+ | 東京 | | | 大阪 | | | 札幌幌 | | | 仙台 | | | 広島 | | | 北九州 | | | 福岡 | | +--------+--------------------+ (7 rows)

関数のテスト : 一括取得数を多めにしてみる
今までは一括検索といっても数件程度でしたが、MediaWiki APISpecifying titles through the query string (either through titles or pageids) is limited to 50 titles per query (or 500 for those with the apihighlimits right, usually bots and sysops).
検索語は何でもいいですが、どっかからデータを持ってくるのが面倒なのでクエリ上でランダムな英字
create table random3chars as with a (str) as ( select string_agg(chr(cast(ceil(random() * 26) + 64 as int)), '') from generate_series(1, 500) as gs ) select distinct (regexp_matches(str, '.{3}', 'g'))[1] from a; select * from random3chars; +----------------+ | regexp_matches | +----------------+ | ETB | | GNJ | | EPW | | GDL | | DGL | | OSP | | PDM | | BJF | | PVH | | HCL | | BIF | | JTN | | EVO | | CZV | (...)

↓ 試しに
with a as ( select regexp_matches as word from random3chars order by random() limit 20 ) select *, plv8_MediaWiki_ex4(word) over() from a; +------+--------------------+ | word | plv8_mediawiki_ex4 | +------+--------------------+ | UVA | 38443306 | | IZJ | -1 | | XYT | -2 | | DDP | 1709559 | | CMU | 61137 | | BTC | 1010613 | | ERF | 658194 | | XKN | -3 | | KKO | -4 | | ETB | 612729 | | AWI | 3613552 | | IIV | -5 | | HMO | 4278947 | | EDM | 9967 | | YJR | -6 | | NQF | 3754357 | | VPB | 1828379 | | KST | 6180744 | | JKU | 1267051 | | KRF | 1115400 | +------+--------------------+ (20 rows)

次に、最大
with a as ( select regexp_matches as word from random3chars order by random() limit 100 ) select row_number() over(), word, plv8_MediaWiki_ex4(word) over() from a;



関数のテスト : 検索語(英語)の大文字・小文字について
最後に、これは今回の関数というよりselect *, plv8_MediaWiki_ex4(word) over() from unnest('{ PostgreSQL, MySQL, Postgresql, Mysql, postgresql, mysql }' :: text[]) as word; +------------+--------------------+ | word | plv8_mediawiki_ex4 | +------------+--------------------+ | PostgreSQL | 23824 | | MySQL | 19545 | | Postgresql | 253356 | | Mysql | 378657 | | postgresql | | | mysql | | +------------+--------------------+ (6 rows)

これには
今後の課題
MediaWiki APIMediaWiki API
最後に、今回は「PL/v8