PostgreSQLで複数テーブルを全文検索する方法

PostgreSQLの全文検索機能

最近、「PostgreSQLの全文検索機能を試してみる」という記事がバズっていました。

しかし、内容がPostgreSQL初心者には難しく、Goの話も含まれていて理解しにくい方も多いと思うので再解釈してみました。

まず、以下の全文検索を試すためにPostgreSQLを試せるPlaygroundのWebページで以下のSQL文を実行してダミーのテーブルのデータを作成します。

https://aiven.io/tools/pg-playground

SQL
CREATE TABLE IF NOT EXISTS test (
  id   SERIAL PRIMARY KEY,
  name TEXT,
  age  INT
);

CREATE TABLE IF NOT EXISTS test2 (
  id   SERIAL PRIMARY KEY,
  name TEXT,
  age  INT
);

INSERT INTO test (name, age) VALUES
  ('佐藤', 28),
  ('鈴木', 30),
  ('山田', 26);

INSERT INTO test2 (name, age) VALUES
  ('山本', 29),
  ('中村', 31),
  ('小林', 26);

テーブルが追加されたら、画面右のTablesにtable, table2が表示されます。

次に「WITH t AS ()」でテーブルをまとめて、最後にWHEREで以下の形式で全文検索を実行します。

例えば「山田」を検索する場合は以下のようになり、実行すると「山田」のあるtable_name, id, name, ageが表示されます。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
)
SELECT *
FROM t
WHERE to_tsvector(name || ' ' || age::text) @@ to_tsquery('山田');
table_nameidnameage
test3山田26

ageは数値型なので、age::text にキャストして文字列として検索します。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
)
SELECT *
FROM t
WHERE to_tsvector(name || ' ' || age::text) @@ to_tsquery('28');
table_nameidnameage
test1佐藤28

テーブルが3つある場合はUNION ALLで結合して以下のようになります。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
  UNION ALL
  SELECT 'test3' AS table_name, * FROM test3
)
SELECT *
FROM t
WHERE to_tsvector(name || ' ' || age::text) @@ to_tsquery('山田');

テキストは完全一致のため、to_tsquery('山') だと検出されません。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
)
SELECT *
FROM t
WHERE to_tsvector(name || ' ' || age::text) @@ to_tsquery('');

前方一致の場合はto_tsquery('山:*')で検索できます。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
)
SELECT *
FROM t
WHERE to_tsvector(name || ' ' || age::text) @@ to_tsquery('山:*');

to_tsqueryで部分一致はできないので、部分一致検索はLIKEを使用してください。

SQL
WITH t AS (
  SELECT 'test'  AS table_name, * FROM test
  UNION ALL
  SELECT 'test2' AS table_name, * FROM test2
)
SELECT *
FROM t
WHERE name LIKE '%山%' OR age::text LIKE '%山%';
table_nameidnameage
test3山田26
test21山本29
カテゴリーsql