はじめに
ベクトル検索ってなんや?と思い調べてみました。
これを書いているのは以下の人物です。
- 普段はSQLで業務をしている
- リレーショナルデータベース(RDB)が思考の土台になっている
- 「ベクトル検索」「embedding」「RAG」という単語は聞くが、自分の知識と地続きで理解したい中堅エンジニア
ゴールはひとつだけです。
「ベクトル検索は、RDBでいうと結局なんなのか」を、SQLの語彙で説明できるようになる
新しい概念をゼロから覚えるのではなく、「いつものSQL」とどこが同じで・どこが違うのか、という対比で進めます。
元の知識からという形で確認する方が、自分の中で腹落ちするかなと思いましての形式です。
1. まず「いつものSQL検索」を振り返る
普段、こういう検索をします。
-- 完全一致
SELECT * FROM faq WHERE question = '休暇の申請方法';
-- 部分一致(あいまい検索)
SELECT * FROM faq WHERE question LIKE '%休暇%';
-- 全文検索(少し賢い)
SELECT * FROM faq WHERE MATCH(question) AGAINST('休暇 申請');
これらはすべて、文字・単語が一致するかを見ています。
正確で速く、結果の理由も説明しやすい、慣れ親しんだ仕組みです。
ただ、限界もあるよなと思っていて。たとえばユーザーが
「有給を取りたいときの手続き」
と検索しても、休暇の申請方法 という行は 1件もヒットしません。
文字が一致しないからです。
でも人間が見れば、この2つはほぼ同じ意味ですよね。
つまり従来のSQL検索は、
- 「文字が一致しているか」は得意
- 「意味が近いか」は苦手
という性質を持っている、と整理できそうです。
この「意味が近いものを探したい」を解決するのが、ベクトル検索なのかなと。
2. 「意味」を数字にする = ベクトル(embedding)
調べてみると、ベクトル検索の出発点はとてもシンプルな発想でした。
文章の「意味」を、数値の並び(ベクトル)に変換する
たとえば、こうなります。
「休暇の申請方法」 → [0.21, -0.83, 0.05, ... ] ← 例えば1536個の数値の並び
この数値の並びを embedding(埋め込み) と呼ぶそうです。ポイントは、この数値がでたらめではないこと。
意味が近い文章ほど、ベクトル(数値の並び)も近くなるように作られている
- 「休暇の申請方法」
- 「有給を取りたいときの手続き」
この2つは文字は違いますが、ベクトルとしては近い場所に配置される、というイメージです。
そもそも embedding って何?(正直ここで一番つまずいた)
正直に言うと、自分はここが一番ピンと来ませんでした。
「意味を数字にする」って、何をどうやって数字にしとるんや…? と。
調べて腹落ちしたのは、こういうイメージでした。
embedding = そのものの「特徴」を、いくつもの軸で点数づけしたもの
たとえば、いろいろな単語を「3つの軸」で点数づけしてみるとします(あくまでイメージ用の例です)。
| 単語 | 生き物っぽさ | 乗り物っぽさ | 食べられる度 |
|---|---|---|---|
| 犬 | 0.9 | 0.0 | 0.1 |
| 猫 | 0.9 | 0.0 | 0.1 |
| 自動車 | 0.0 | 0.9 | 0.0 |
| りんご | 0.2 | 0.0 | 0.9 |
こうすると、各単語が「数字の並び」で表せます。
- 犬 =
[0.9, 0.0, 0.1] - 猫 =
[0.9, 0.0, 0.1] - 自動車 =
[0.0, 0.9, 0.0]
犬と猫は数字がほぼ同じ → 意味が近い。
犬と自動車は数字が全然違う → 意味が遠い。
これがembedding(埋め込み)の正体でした。「意味の特徴」を、座標(数字の並び)として“埋め込む”から、この名前なんですね。
じゃあ、その軸や数字は誰が決めてるの?
ここも気になった所です。さっきは「生き物っぽさ」みたいに人間が軸を決めましたが、本物のembeddingでは事情が違うそうで。
- 軸は 数百〜数千個 ある
- しかも各軸が何を表しているのかは、人間には言葉で説明できない(AIが勝手に作った軸)
- 数字を決めているのは、大量の文章で学習した AIモデル
ざっくり言うと、AIは「似た文脈で使われる言葉どうしは、似た数字になる」ように学習しているとのこと。だから「有給」と「休暇」のように、文字は違うけど近い使われ方をする言葉は、自然と近い数字になる、と。
別のたとえだと、地図の緯度・経度が近いかなと思いました。東京と横浜は座標が近いから「地理的に近い」と判断できますよね。embeddingは、それの“意味バージョン”の座標を、文章ごとに割り振っている――そんなイメージで、自分はようやく納得できました。
RDB的に言うと
難しく考えなくてよさそうです。RDBの頭で言い換えると、
1行 = 1文書 に対して、「意味を表す数値列(ベクトル列)」という新しいカラムが1本増えた
というだけのことかなと。
| id | content | embedding |
|---|---|---|
| 1 | 休暇の申請方法 | [0.21, -0.83, 0.05, ...] |
| 2 | 経費精算のやり方 | [0.55, 0.10, -0.40, ...] |
| 3 | 有給休暇の取り方 | [0.20, -0.81, 0.07, ...] |
id=1 と id=3 は意味が近いので、ベクトルの数値も近い並びになっている、という具合です。
3. ベクトル検索とは =「近いベクトルを上位N件」
ここが個人的に一番ハッとした所です。
| 従来のSQL検索 | ベクトル検索 | |
|---|---|---|
| 判定 | 一致する / しない(YES/NO) | どれくらい近いか(距離) |
| 取り方 | 条件に合う行を取る | 近い順に並べて上位を取る |
腹落ちポイントを一言で言うと、
ベクトル検索は、SQLでいうと
ORDER BY 距離 LIMIT Nである
完全一致の WHERE ではなく、「近い順に並べて上位N件」という発想に変わるわけですね。
-- pgvector(PostgreSQL拡張)での例
SELECT id, content
FROM documents
ORDER BY embedding <=> '[0.20, -0.80, 0.07, ...]' -- ← 検索したい文章のベクトル
LIMIT 5;
<=> は「コサイン距離」を計算する演算子だそうです。
要は「2つのベクトルがどれだけ似た向きを向いているか」を測っている、と。
距離・類似度の代表例
- コサイン類似度 / コサイン距離 … ベクトルの「向き」の近さ。文章検索で最もよく使うらしい
- ユークリッド距離(L2) … 座標上の直線距離
細かい使い分けは後回しにします。
まずは「一致ではなく近さ順 + 上位N件」という発想転換をします。
イメージ(2次元に簡略化)
実際は数百〜数千次元ですが、無理やり2次元に潰すとこうなるイメージです。
休暇の申請方法 ●
● 有給休暇の取り方 ← この2つは近い(=意味が近い)
経費精算のやり方 ● ← 別の意味なので離れている
「有給を取りたいときの手続き」をベクトル化すると、左上の固まりの近くに落ちます。だから文字が一致しなくても、近い文書を拾えるんですね。
4. 曖昧(あいまい)検索とどう違うのか
RDB畑の人がまず引っかかるのが、ここだと思います。自分も最初そうでした。
「ベクトル検索 = ただの賢い曖昧検索(LIKE / あいまい検索)でしょ?」
調べてみると、実は これは別物 でした。混同するとRAGの設計を間違えそうなので、ここははっきり区別しておきます。
曖昧検索(おさらい)
曖昧検索と呼ばれるものは、だいたい次のいずれかかなと思います。
-
LIKE '%...%'による部分一致 - ワイルドカード検索
- 表記ゆれ・打ち間違い(タイプミス)を許容する fuzzy 検索(編集距離 / Levenshtein など)
これらに共通するのは、見ているのが「文字の形・並び」 だという点です。
意味は見ていません。
決定的な違い:「見ているもの」が違う
| 観点 | 曖昧検索(LIKE / fuzzy) | ベクトル検索 |
|---|---|---|
| 見ているもの | 文字の形・並び(表記) | 意味 |
| 強い場面 | 表記ゆれ・打ち間違い・部分一致 | 言い換え・同義語・文脈 |
| 「有給を取りたい」→「休暇の申請方法」 | ✗ ヒットしない | ○ ヒットする |
| 「休假」(誤字)→「休暇」 | ○ ヒットしうる | △ 拾えるが本来の目的ではない |
| 仕組み | 文字列の比較 | ベクトルの距離計算 |
具体例で見ると、イメージしやすいかなと思います。
- 「休假」(誤字)で「休暇」を探したい → これは曖昧検索(fuzzy)の得意分野。文字が1字違うだけなので、文字ベースで拾える
- 「有給を取りたいときの手続き」で「休暇の申請方法」を探したい → 文字が一致しないので曖昧検索ではヒットしない。意味が近いのでベクトル検索なら拾える
一言でまとめると、
曖昧検索は 「文字のゆれ」 に強い。ベクトル検索は 「意味のゆれ」 に強い。
向いている仕事が違うだけで、対立する技術ではなさそうです。実務では両方を併用して結果を統合する ハイブリッド検索(キーワード検索+ベクトル検索)もよく使われるみたいです。
5. AI/LLMとどう関係するのか
ここで、ようやくAI/LLMが2つの役割で登場します。
役割①:ベクトルを作るのはAIモデル(embeddingモデル)
「文章 → ベクトル」に変換しているのは、学習済みのAIモデルでした。
- 「意味が近いと数値も近くなる」という性質は、このモデルが大量の文章を学習した結果として備わっている
- 例:OpenAIのembeddingモデル、各種オープンソースモデルなど
- モデルによって出力されるベクトルの次元数が決まっている(例:1536次元)
つまり、ベクトルの「質」はモデル次第ということですね。ここがAIの前処理として効いてくる部分かなと思います。
役割②:LLM(生成AI)と組み合わせる = RAG
ChatGPTのようなLLMは賢いですが、
- 社内のドキュメント
- 最新の情報
- 特定業務のローカルなルール
は知りません。そこで、こういう流れを組むそうです。
- ユーザーの質問をベクトル化する
- ベクトル検索で「関連しそうな社内文書」を上位N件取ってくる
- その文書をLLMに渡して「これを参考に答えて」と指示する
- LLMが、渡された文書を根拠にして回答を生成する
この仕組みを RAG(Retrieval-Augmented Generation / 検索拡張生成) と呼ぶとのこと。
RDBエンジニアの視点
ここで注目したいのが、RAGの「Retrieval(検索)」の部分です。
RAGの心臓部である「関連文書の検索」は、まさにDBの仕事
ベクトル検索はRAGの土台になっていて、ここがRDBエンジニアの腕の見せ所になりそうです。「AIだから自分には関係ない」ではなく、むしろ得意分野の延長なんだなと、ここで少し安心しました。
6. DBとしてはどうなるのか(テーブルの実体)
RDBの頭で一番気になるのは、たぶんここですよね。自分もそうでした。
「で、結局 テーブル はどうなるの?」
結論から言うと、
テーブルはそのまま存在します。普通のテーブルに、ベクトル型のカラムが1本増えるだけです。
魔法のような別世界ではなく、いつものテーブル設計の延長でした。
6-1. テーブルの実体
PostgreSQL + pgvector(PostgreSQLの拡張)を例にすると、こうなります。
-- 拡張を有効化
CREATE EXTENSION vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
doc_id INT, -- 元文書のID
chunk_no INT, -- 文書内で何番目の塊か
content TEXT, -- 本文(チャンク)
department TEXT, -- メタデータ(部署など、普通のカラム)
created_at TIMESTAMP,
embedding vector(1536) -- 意味を表すベクトル(floatが1536個入る箱)
);
ポイントは次の3つかなと思います。
-
embeddingは専用の型ですが、テーブルの中の1カラムにすぎません -
departmentやcreated_atといった普通のカラムも今まで通り共存できます - 主キー・外部キーなど、RDBの作法はそのまま使えます
6-2. 「1行 = 1文書」とは限らない(チャンク)
実務では、長い文書をまるごと1行に入れることは少なく、適切な長さに**分割(チャンク化)**して 1チャンク = 1行 にすることが多いそうです。doc_id と chunk_no で元文書に紐づける。これはまさにテーブル設計の話なので、しっくりきました。
元文書(PDF / Wiki ページ)
↓ 適当な長さに分割
チャンク1, チャンク2, チャンク3 ...
↓ それぞれをベクトル化
各チャンクを1行ずつ documents テーブルへ INSERT
6-3. 検索は普通のSQLと「合成」できる
ここがRDBエンジニア的に一番嬉しかった所です。メタデータの WHERE と、ベクトルの ORDER BY を1本のSQLで組み合わせられます。
SELECT id, content
FROM documents
WHERE department = '人事部' -- ① 普通のWHEREで母集団を絞る
AND created_at >= '2025-01-01'
ORDER BY embedding <=> :query_vector -- ② その中で「意味が近い順」に並べる
LIMIT 5;
これは「人事部の、今年以降の文書の中から、意味が近いものトップ5」を1本のSQLで表現しています(メタデータフィルタ付きベクトル検索)。WHEREで絞ってからORDER BYで並べる、という構造は、いつものSQLそのものですよね。
6-4. INSERT時・検索時にAIモデルが動く(DBは「数」を比べるだけ)
ここは個人的に一番の勘所だと思いました。
DB自体は「意味を理解」していません。保存された数値ベクトルどうしの距離を、速く計算して並べているだけです。
-
INSERT時:あらかじめembeddingモデルで
contentをベクトル化し、その数値列を一緒にINSERTする -
検索時:検索したい文字列をベクトル化してから、
ORDER BYに渡す - DBの仕事:渡されたベクトルと、各行のベクトルの距離を計算して近い順に並べる
つまり「賢さ(意味の理解)」はモデル側に閉じていて、DBは今まで通り「速く正確に処理する」役割に徹しているわけです。RDBの世界観のまま理解できる、というのが分かって、だいぶ気が楽になりました。
6-5. インデックスだけは目的が違う
ひとつだけ毛色が違うのがインデックスでした。完全一致で使うB-treeとは目的が異なり、ベクトル検索では 近似最近傍探索(ANN) 用のインデックスを使うそうです。
-- コサイン距離向けのHNSWインデックス
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops);
- HNSW … グラフ構造を使う。精度・速度のバランスが良い
- IVFFlat … クラスタに分けて探す。構築が軽い
「全件を正確に比較する」のではなく「だいたい近いものを高速に見つける」ための索引、という点だけ押さえておけばよさそうです。CREATE INDEX の対象である、という枠組み自体は同じでした。
6-6. 専用ベクトルDBとの関係
Pinecone・Weaviate・Milvus・Qdrant といった専用ベクトルDBもあるみたいですが、テーブル・SQL・インデックスという既存スキルがそのまま活きる pgvector から入るのが、RDB畑の自分には一番理解しやすかったです。
7. 何が変わって、何は変わらないのか(まとめ)
完全一致・曖昧検索・ベクトル検索を並べると、立ち位置がはっきりしてきました。
| 観点 | 完全一致 | 曖昧検索(LIKE / fuzzy) | ベクトル検索 |
|---|---|---|---|
| 見るもの | 文字が完全に同じか | 文字の形が部分一致 / 近いか | 意味が近いか |
| 条件の書き方 | WHERE = |
WHERE LIKE 等 |
ORDER BY 距離 LIMIT N |
| 強み | 正確・最速・説明容易 | 表記ゆれ・打ち間違い | 言い換え・同義語・文脈 |
| インデックス | B-tree など | B-tree / 専用 | HNSW / IVFFlat(近似最近傍) |
| 前処理 | 不要 | 不要 | AIモデルでベクトル化 |
変わらないこと
- テーブルに行を入れて、SQLで取り出すという基本構造
- 普通のカラム(メタデータ)と共存し、
WHEREで絞り込めること - RDBの設計・運用・インデックスの知識が土台になること
変わること
- 「一致」から「近さ」へ
- 検索対象が「文字列」から「意味(ベクトル)」へ
- 前処理としてAIモデル(ベクトル化)が入ってくること
おわりに
調べてみて思ったのは、ベクトル検索は「SQLの完全一致や曖昧検索をやめろ」という話ではないということでした。**「意味で探したいときの新しい道具が一つ増えた」**くらいの話なのかなと。曖昧検索が「文字のゆれ」に強いように、ベクトル検索は「意味のゆれ」に強い。そして、その新しい道具を支える「検索」と「テーブル設計」は、RDBエンジニアの得意分野そのものでした。
次の一歩としては、ローカルのPostgreSQLに pgvector を入れて、数件のデータで
... ORDER BY embedding <=> :q LIMIT N
を実際に叩いてみたら、一気に腹落ちするはずだと思います。
きっと…