ここで

実行環境
••
•
作業中テーブルの現状
前回、HTML# \d kenpg_seesaa_list Table "public.kenpg_seesaa_list" +-----------+------+-----------+ | Column | Type | Modifiers | +-----------+------+-----------+ | ymd | date | not null | | url | text | not null | | title | text | not null | | tags | text | not null | | html_file | text | not null | | html | text | not null | | body | text | |<- extracted +-----------+------+-----------+ Indexes: "kenpg_seesaa_list_pkey" PRIMARY KEY, btree (ymd) "kenpg_seesaa_list_url_idx" UNIQUE, btree (url)

↓
# select ymd, left(body, 100) from kenpg_seesaa_list order by random(); +------------+------------------------------------------------------------------------------------------------------------- | ymd | left +------------+------------------------------------------------------------------------------------------------------------- | 2013-08-11 | ), r (ary, len, rid, ymd, body) as ( (select ary, array_length(ary, 1), 1, ymd, body from a, kenpg_seesaa_list1. 今日やったこと総務省の次世代統計利用システムのメッシュ | 2014-03-02 | 2014/01/22 以降何回か紹介したように、東芝のノー | 2014-03-06 | 2013/05/23 と PostgreSQL カンファレンス 2013 での | 2013-07-16 |追記(2015/01/29))|()', '', 'gi'], ['((class|style)="[^"]*")|(width="*\d+"*)', '', 'gi'], ['( *■ *)|(target="_blank")', '', 'gi'], [' +(>| src=| />)', '\1', 'gi'], ['
もっと簡単な方法がありまし ... # select ymd, regexp_matches(body, 'SELECT.{100}') from kenpg_seesaa_list order by random(); +------------+------------------------------------------------------------------------------------------------------------- | ymd | regexp_matche +------------+------------------------------------------------------------------------------------------------------------- | 2014-07-24 | {"SELECT 139.58 x -- 経度
, 35.84 y -- 緯度
, 4612 srid | 2015-01-17 | {"SELECT ST_AsText(ST_Intersection(g1, g2))
FROM ST_GeomFromText('POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))') | 2015-01-20 | {"SELECT 2013 iso_year, 52 iso_week
UNION ALL SELECT 2014, 1
), b AS (
SELECT *
// ここに CSV を貼り付ける
// 行頭インデントしたらクエリ内の正規表現で | 2014-10-13 | {"SELECT now();¥n\";
setupWindow(tr(\"QPg SQL Client\"));
statusBar()->hide();
cr ...複数の文字列置換を一度に行うクエリ
ここではSELECT 文で置換後の文字列を得るまでを考え、実データを UPDATE する部分は置いときます。
(1)置換する数が少なければ、原始的にregexp_replace を入れ子にするのが ↓ 簡単。実際これで済ますことも結構あったり。 select regexp_replace( regexp_replace( regexp_replace(column, pattern, rep,option ), pattern, rep,option ), pattern, rep,option ) from foo;
(2)上の難点は、「この置換は正規表現でなくreplace 関数で済むので、変えたい」などの部分が出た時に面倒なこと。関数と引数が離れてるので。そこで置換操作を FROM の後に置くと ↓ それはし易くなります。 select t3 from foo, regexp_replace(column, pattern, rep,option ) as t1, regexp_replace(t1, pattern, rep,option ) as t2,replace(t2, pattern, rep) as t3;
どちらにしても、置換パターンの追加・削除・入れ替えが面倒です。実際クエリを作っていく時、最も必要なのがそういう試行錯誤。
(3)そこで今回は ↓ こうしました。使う関数はregexp_replace に一本化し、置換パターンを 2 次元配列に入れて WITH RECURSIVE で順番に実行。クエリは一気に長くなったけど、正規表現の試行錯誤が 2 次元配列の操作だけで楽にできます。 with recursive a (ary) as ( select array[ [pattern, rep,option ], [pattern, rep,option ], [pattern, rep,option ] ] ), r (ary, len, rid, column) as ( select ary, array_length(ary, 1), 0, column from a, foo union all select ary, len, rid + 1, regexp_replace(column, ary[rid][1], ary[rid][2], ary[rid][3]) from r where rid <= len + 1 ) select ymd, left(body, 100) from r where rid = len + 1;
(4)上の2 次元配列を JSONB に変換して行うと ↓ こんな感じ。配列型と違って 2 次元配列の要素を取り出すと 1 次元になるので、それを使ってます。添字がゼロ始まりなのも配列型と違う点(下記の赤い部分が変化)。 with recursive a (jsonb) as ( select to_jsonb(array[ [pattern, rep,option ], [pattern, rep,option ], [pattern, rep,option ] ]) ), r (jsonb, len, rid, column) as ( select jsonb, jsonb_array_length(jsonb), 0, column from a, foo union all select jsonb, len, rid + 1, regexp_replace(column, js->>0, js->>1, js->>2) from r, cast(jsonb->rid as jsonb) as js whererid <= len ) select column from r whererid = len;
再帰クエリはどうしても長くなりがち。普通にループを使えるストアド関数を作る方が簡単だけど、SQLだけでアドホックに実行できるのは利点です。 配列型と
以下、JSONBJSON 型の比較(定義時の書きやすさ) 型を含めて JSON と略します。定義の際、配列は正規表現パターンをシングルクォートするだけなので楽。JSON は全体をシングルで囲って文字列をダブルで囲むため、パターンがダブルクォートを含む場合(特に今回の HTML)はエスケープが必要になって見づらい。だから前項のクエリ(4)は、配列で定義した後 to_jsonb 関数で変換しました。この関数はとても有難い存在。 -- ARRAY # select array['style="[^"]*"', ''] :: text[]; +-------------------------+ | {"style=\"[^\"]*\"",""} | +-------------------------+ -- JSONB # select '["style=\" [^\" ]*\" ", ""]' :: jsonb; +--------------------------+ | ["style=\"[^\"]*\"", ""] | +--------------------------+配列型と
書きやすさは配列JSON 型の比較(多次元配列の扱い) >JSON な一方、多次元配列の扱いは逆。というか PostgreSQL の多次元配列はちょっと異質で「要素を取り出しても次元は同じ」。普通はできる「2 次元配列 → 各要素を取り出して 1 次元配列 → 添字 1 つで各要素にアクセス」ができません。
JSON型は普通と同じで ↓ 要素を取り出すごとに次元が下がっていきます。-> は JSON の配列に添字でアクセスする PostgreSQL の演算子。 # select '[ [ "a", "b" ], [ "c", "d"] ]' :: jsonb; +--------------------------+ | [["a", "b"], ["c", "d"]] | +--------------------------+ # select '[ [ "a", "b" ], [ "c", "d"] ]' :: jsonb -> 0; +------------+ | ["a", "b"] | +------------+ # select '[ [ "a", "b" ], [ "c", "d"] ]' :: jsonb -> 0 -> 0; +----------+ | "a" | +----------+
一方、PostgreSQLの多次元配列は独特。例えば 2 次元配列から 1 レベル下を取り出す添字を [■:■][■:■] と書く必要があり、取り出しても次元は変わらず。[■][■] でアクセスして初めてスカラになります。↓ # select array[ [ 'a', 'b' ], [ 'c', 'd'] ]; +---------------+ | {{a,b},{c,d}} | +---------------+ # select (array[ [ 'a', 'b' ], [ 'c', 'd'] ])[1]; -- NG +-------+ | | +-------+ # select (array[ [ 'a', 'b' ], [ 'c', 'd'] ])[1:1][1:2]; +---------+ | {{a,b}} | +---------+ # select (array[ [ 'a', 'b' ], [ 'c', 'd'] ])[1:1][1]; +-------+ | {{a}} | +-------+ # select (array[ [ 'a', 'b' ], [ 'c', 'd'] ])[1][1]); +--------+ | a | +--------+
今回はどちらでも大差なかったけど、途中で1 次元配列も何らか使う場合は JSON の方が便利な気がします。 実際のクエリと結果
まずSELECT 文で正規表現をいろいろ試し、かつ複数組の置換がきちんと行われているか確認します。↓ は元テーブルからランダムに 1 行取り出し、再帰クエリでの各行(元の文字列 + 一つ一つの置換結果)を出すもの。 # with recursive a (ary) as ( select array[ ['(<(div|span)[^>]*>)|(
', e'\n', 'gi'], ['\t', ' ', 'g'], ['(^\s+)|(\s+$)', '', 'g'], ['〜', '~', 'g'] ]order by random() limit 1 ) union all select ary, len, rid + 1, ymd, regexp_replace(body, ary[rid][1], ary[rid][2], ary[rid][3]) from r where rid <= len + 1 ) select rid, len, length(body), left(body, 100) from r; +-----+-----+--------+------------------------------------------------------------------- | rid | len | length | +-----+-----+--------+------------------------------------------------------------------- | 1 | 8 | 3877 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 2 | 8 | 3556 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 3 | 8 | 3454 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 4 | 8 | 3418 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 5 | 8 | 3413 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 6 | 8 | 3088 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 8 | 8 | 3145 | ある時系列データを処理する中、任意の年・月の全日付(初日〜末日)を | 9 | 8 | 3145 | ある時系列データを処理する中、任意の年・月の全日付(初日~末日)を | 10 | 8 | | +-----+-----+--------+------------------------------------------------------------------- (10 rows)

見やすさのため、クエリの最後で文字長と先頭
とりあえず今回はこのパターンで良しとし、新しい列を作って置換結果を保存します。元テーブルとの結合は主キー
# alter table kenpg_seesaa_list add body2 text; # with recursive a (ary) as ( select array[ ['(<(div|span)[^>]*>)|(