BigQueryの複数のテーブル&複数の列からデータを抽出するのに、UNPIVOT関数と配列関数が役に立ったので記録しておきます。
状況
BigQueryからこんなデータが抽出できないか、との依頼がありました。
すべてのブログに含まれる文字データから、URLを抽出してほしい。
URLごとに、誰の・どのデータに含まれていたのかも分かるようにして。
ブログパーツは、記事やパーツなどの属性ごとに異なるテーブルに格納されていました。
今回は簡単のため、本文とライター別パーツが2テーブルに格納されているものとします。
列名 | データ型 | 制約 | null |
id | int | 主キー | 不可 |
writer_id | int | 不可 | |
document_id | int | 不可 | |
title | str | 不可 | |
body | str | 可 |
列名 | データ型 | 制約 | null |
id | int | 主キー | 不可 |
writer_id | int | 不可 | |
position_type | int | 不可 | |
text | str | 可 | |
link_url | str | 可 |
上のようなテーブルを用いて、以下のようなテーブルを作るのが目標です。
writer_id | table_name | id | column_name | url |
001 | article | 101 | title | http://hoge.com |
001 | component | 30 | text | https://fuga.co.jp/ |
002 | article | 26 | body | https://piyo.net/piyopiyo/ |
002 | article | 26 | body | http://hogehoge.jp/ |
課題と対策
この抽出にあたり、おおまかには下記2つの課題がありました。
- 複数テーブルの複数列に対して、一様なURL抽出処理が必要。
- 同じデータに含まれるURLは一つとは限らない。1データから複数のURLを抽出する必要がある。
これらの課題を解決するために、以下のような方法を取ることにしました。
- 一様な処理: UNPIVOT関数で、すべての対象列を一列にまとめる。
- 複数URLの抽出: REGEXP_EXTRACT_ALL関数で、1データから複数のURLを配列として抽出。UNNEST関数を使って配列を分解し、1URL1行のテーブルを出力する。
用いたSQL
実際に用いたSQLは下記のようなものです。
declare url_regex default r'https?://(?:[\w-]+\.)+[\w-]+(?:/[\w./?%&=]*)?';
-- URLを捕捉するための正規表現を予め宣言。
-- 1. テーブルごとの捜索対象列を全て縦に並べる。その後、各テーブルをUNION ALLで結合。
with melt_tbl as (
select
member_id
,'article' as table_name
,id
,column_name
,strings
from
blog_article_tbl
,unpivot exclude nulls (
strings -- 捜索対象データが格納される列名
for column_name in (title, body) -- 捜索対象データが含まれる列名を、新規テーブルのcolumn_name列に格納
)
union all
select
member_id
,'component' as table_name
,id
,column_name
,strings
from
blog_component_tbl
,unpivot exclude nulls (
strings
for column_name in (text, link_url)
)
)
-- 2. melt_tblの行ごとに複数のURLを抽出。
,extract_tbl as (
select
member_id
,table_name
,id
,column_name
,regexp_extract_all(strings, url_regex) as urls
from
melt_tbl
)
-- 3. extract_tblの配列を分解し、1行に1URLが含まれるテーブルを出力。
select
member_id
,table_name
,id
,column_name
,url
from
extract_tbl
,unnest(urls) as url -- 配列をunnest
;
補足
- 今回は対象列やテーブル名を明示しましたが、information_schemaを参照できるなら動的に参照することもできそうです。
- URL抽出用の正規表現は、URLに「http://またはhttps://で始まること」「日本語は含まないこと」という前提条件で抽出しています。また、正規表現のロジックは別記事で説明するかもしれません。