SaaSを題材にしたオントロジーを作ってみようシリーズ #3.5 番外編 セマンティックレイヤー
作ったオントロジーは、結局どこで使うのか?
セマンティックレイヤーという考え方を、手を動かして理解する
今回は番外編です。新しいクラスは1つも追加しません。
前回は、FeatureとEntitlementを導入しました。あわせて、Tenantから見たhasEntitlementと、Entitlementから見たentitledTenantという、逆プロパティのペアも定義しました。
ここでいったんクラスを増やす手を止めて、そこまでに作った語彙が実際どこで役に立つのかを確かめます。というのも、ここまで3回クラスを増やしてきて、そろそろこう思っている方がいるはずだからです。
これ、作ったはいいけど、結局どこでどう使うの?
たとえば、実際の業務では次のような問いが出てきます。
株式会社ABCというテナントは、SaaSの機能としての販売管理機能を使えるのか?
この質問に答えるために、あなたは今、どうしますか?
・請求管理システムの契約画面を開いて確認する?
・社内の管理DBにSQLを投げる?
・Slackでカスタマーサクセスに聞く?
どの方法でも答えは出ます。しかし、問いの意味自体は、どの方法で調べても同じはずです。
このTenantに、
販売管理Featureに対する有効なEntitlementが存在するか?
これが、問いの正体です。第3回でFeatureとEntitlementを定義したのは、まさにこの「問いの正体」を書けるようにするためでした。
そして、この「意味の定義」を、複数のシステムから共通で使える形にしたものが、セマンティックレイヤーです。
1. セマンティックレイヤーとは何か
一言でいうと、
セマンティックレイヤー:
データの意味を一箇所に定義し、
どのツール・どのシステムからでも
同じ意味で問い合わせられるようにする仕組み
もう少し具体的にいきます。
セマンティックレイヤーが無い世界:
請求システム → grantedFeature という項目で機能を持つ
CRM → enabledFeature という項目で機能を持つ
社内DB → entitlements テーブルの feature_id カラム
3つとも同じことを記録しているのに、
呼び名がバラバラで、横断して聞けない。
セマンティックレイヤーがある世界:
共通語彙 saas:entitlesFeature
├── 請求システムの grantedFeature
├── CRMの enabledFeature
└── 社内DBの feature_id
どのシステムのデータでも、1つの問いで横断できる。
セマンティックレイヤーの本質は、「どのカラムに何が入っているか」ではなく、「それが何を意味するか」を定義することです。
「レイヤー(層)」と呼ぶのは、これが問い合わせる側とデータの間に挟まる層だからです。
問い合わせる側(人、BIツール、AI)
│ 共通の語彙で問う
↓
セマンティックレイヤー(意味の定義と、各システムとの対応)
│ 各システムの書き方の違いを、ここで吸収する
↓
各システムのデータ(請求システム、CRM、社内DB…)
間に層が1枚あるので、問い合わせる側は、下にあるデータがどう書かれているかを知らなくても済みます。
そして今回は、この「バラバラの語彙を1つの問いで横断する」ところまで、実際に手元で動かします。
ところで、「セマンティックレイヤー」で検索すると、たぶん次のようなものが出てきます。
・Snowflake の Semantic Views
・Databricks の Metric Views(Unity Catalog semantics)
・Power BI / Microsoft Fabric の Semantic Model
・dbt Semantic Layer、Cube、Looker の LookML
そして、そこに書かれているのは、だいたいこういう話です。
「売上」「アクティブユーザー数」の計算式を1箇所で定義して、
どのBIツールからでも同じ数字が出るようにしましょう。
一方、このシリーズで作っているのは、Turtleで書いたクラスとプロパティです。計算式は1つも出てきません。
あれ?
このシリーズの「セマンティックレイヤー」と、
世の中の「セマンティックレイヤー」、
同じものの話をしている?
結論から書きます。
同じ言葉で、2つの系統のものが呼ばれている。
メトリクス型:数字の「計算のしかた」を定義する
オントロジー型:世界の「何が何であるか」を定義する
このシリーズが作っているのは、後者。
この記事では、まずこの2つの違いを整理してから、オントロジー型のほうを実際に動かします。後半で手を動かしているときに「あれ?」とならないためです。
製品の名称や提供状況は動きが速いため、本記事の内容は2026年10月時点の公式ドキュメントに基づいています。出典は末尾にまとめました。
2つの系統とも、上で書いた「データの意味を一箇所に定義する」という点は同じです。違うのは、「意味」として何を定義するかです。
メトリクス型が定義する「意味」:
「月間アクティブテナント数」とは、
subscriptions テーブルの status = 'active' の行を
tenant_id で重複排除して数えたもの
オントロジー型が定義する「意味」:
「Tenant」とは、Subscription を持ち、
Entitlement を通じて Feature を利用できる単位であり、
hasEntitlement と entitledTenant は互いに逆向きの関係である
前者は数字の作り方、後者はものごとの関係を定義しています。
この違いがどこから来たのかを見ていきます。
2. メトリクス型セマンティックレイヤーは、なぜ生まれたか
2.1 出発点は「数字が合わない」問題
メトリクス型が解こうとしてきた問題は、とても具体的です。
営業のダッシュボード: 今月のアクティブテナント 128社
CSのダッシュボード: 今月のアクティブテナント 131社
経営会議の資料: 今月のアクティブテナント 125社
3つとも間違ってはいません。トライアル中を含めるか、解約予定を含めるか、月末時点か月中の最大か。計算式が、ダッシュボードごとに少しずつ違うのです。
そこで、「計算式を1箇所に集めて、全員がそれを使う」という発想が生まれました。
2.2 置き場所の変遷
この「計算式の置き場所」は、時代とともに移動してきました。
第1世代: BIツールの中
Looker の LookML、Power BI のデータセット(現 Semantic Model)
→ もともと、そのBIツールのために設計された。
いまは外部からも使えるが(Looker の Open SQL Interface、
Power BI の XMLA エンドポイントなど)、
ツール固有の言語や接続方式に合わせる必要がある。
第2世代: BIツールから独立(ヘッドレス)
dbt Semantic Layer(MetricFlow)、Cube、AtScale
→ どのBIツールからでも、APIやSQLで同じ定義を使える。
(dbt の API は dbt platform で提供される)
第3世代: データ基盤の中
Snowflake Semantic Views、Databricks Metric Views
→ データと同じ場所で、同じ権限管理の下に置かれる。
Microsoft Fabric の Semantic Model は、第1世代の Power BI の Semantic Model と同じものかと思います。ただ、Fabric 上では Direct Lake という方式で OneLake のデータを直接読めるので、第1世代と第3世代の中間にあたります。
なお、この「世代」は説明のための筆者の整理で、業界やベンダーの公式な区分ではありません。新しい世代ほど優れている、という意味でもありません。いま「セマンティックレイヤー」として話題になっているのが、主に第3世代だということを表現したいため、この整理をしました。
2.3 実際には、こう書く
第1回で作ったTenant・Subscription・Planが、ふつうのテーブルとしてデータウェアハウスに入っているとして、具体的に見てみます。
tenants (tenant_id, name)
subscriptions (subscription_id, tenant_id, plan_id, status, started_at)
plans (plan_id, plan_name)
ここで、次の問いを考えます。
Q1. プラン別に、毎月いくつ新しい契約が始まっているか?
※Q2は9章で出てきます。
SnowflakeのSemantic Viewでは、こう定義します。
-- あくまでもイメージで、動作保証はないです
CREATE SEMANTIC VIEW subscription_sv
TABLES (
subscriptions PRIMARY KEY (subscription_id),
plans PRIMARY KEY (plan_id)
)
RELATIONSHIPS (
subscriptions (plan_id) REFERENCES plans
)
DIMENSIONS (
plans.plan_name AS plans.plan_name,
subscriptions.start_month AS DATE_TRUNC('MONTH', subscriptions.started_at)
)
METRICS (
subscriptions.new_subscriptions AS COUNT(DISTINCT subscriptions.subscription_id)
)
COMMENT = 'プラン別・月別の新規契約';
使う側は、計算式を知らなくても、こう問い合わせるだけです。
SELECT plan_name, start_month, AGG(new_subscriptions)
FROM subscription_sv
GROUP BY plan_name, start_month;
DatabricksのMetric Viewでは、YAMLで同じことを書きます。
-- あくまでもイメージで、動作保証はないです
CREATE VIEW main.saas.subscription_metrics
WITH METRICS LANGUAGE YAML AS $$
version: 1.1
source: main.saas.subscriptions
joins:
- name: plan
source: main.saas.plans
on: source.plan_id = plan.plan_id
fields:
- name: plan_name
expr: plan.plan_name
- name: start_month
expr: DATE_TRUNC('MONTH', started_at)
measures:
- name: new_subscriptions
expr: COUNT(DISTINCT subscription_id)
$$;
SELECT plan_name, start_month, MEASURE(new_subscriptions)
FROM main.saas.subscription_metrics
GROUP BY ALL;
書き方は違いますが、やっていることは同じです。
2.4 メトリクス型の「共通の形」
製品は違っても、中身の構成要素はほぼ共通しています。
論理テーブル どのテーブルを使うか
リレーション テーブル同士をどう結合するか
ディメンション どの切り口で分けるか(プラン別、月別…)
メジャー 何をどう集計するか(COUNT, SUM, 比率…)
同義語・説明 人間とAIのための補足
そして、問い合わせ方も共通しています。
Snowflake AGG(new_subscriptions)
Databricks MEASURE(new_subscriptions)
Cube MEASURE(...)
Looker AGGREGATE(...)
AGG()やMEASURE()は、「定義側に書いてある集計式(ここではCOUNT(DISTINCT …))で集計して」という指示です。使う側は、COUNTやSUMを自分で書く必要がありません。
集計のしかたは定義側が持ち、使う側は切り口を選ぶだけ。これがメトリクス型の設計の核です。
この「共通の形」を標準にする動きも進んでいます。2025年9月、Open Semantic Interchange(OSI)が立ち上がり、2026年1月にはYAML/JSONで記述する仕様の初版(v0.1)が公開されました。2026年7月にはApache Incubatorに入り、Apache Ossie(incubating)に改称されています。
2026年1月の初版仕様はRDF/OWLに触れられていませんでしたが、2026年5月にはオントロジー仕様の草案が出ており、7月のApache移管の時点では『セマンティックレイヤーとオントロジーの両方のための仕様』と明示的に説明されています。
3. オントロジー型セマンティックレイヤー
もう1つの系統は、このシリーズで作っているものです。
オントロジー型が定義するもの:
クラス Tenant, Subscription, Plan, Feature, Entitlement
プロパティ hasSubscription, basedOnPlan, hasEntitlement
関係の性質 hasEntitlement と entitledTenant は互いに逆向き
階層 ある関係は、別の関係の一種である(7章で実例を見ます)
制約 Entitlement には必ず Tenant と Feature が1つずつ必要
ルーツは、W3Cが標準化したSemantic Webの技術(RDF、OWL、SPARQL、SHACL)です。データベースのテーブルではなく、「主語・述語・目的語」の3つ組(トリプル)で世界を記述します。
メトリクス型との一番の違いは、定義から新しい事実を導けること(推論) と、どのテーブルにも属さない「意味の対応」を書けることです。「意味の対応」とは、「請求システムのこの項目と、CRMのこの項目は同じ意味だ」という取り決めのことで、7章で実際に書きます。
メトリクス型:
定義は「テーブルの上」に乗っている。
テーブルが先にあり、その上に計算式を載せる。
オントロジー型:
定義は「テーブルから独立」している。
意味の体系が先にあり、各システムのデータをそこに接続する。
このシリーズで作っているのは、こちらです。
「Tenantとは何か」
「FeatureとEntitlementはどう違うか」
「PlanとFeatureの関係は何か」
このあたりは、言葉で説明するより動かしたほうが早いので、このあと実際に手を動かして確かめます。
4. T-BoxとA-Box
オントロジーを実際に使うとき、記述は2つに分かれます。
T-Box(Terminology Box)= 語彙の定義
「Tenantというクラスが存在する」
「hasEntitlementはTenantからEntitlementへの関係である」
「entitledTenantはhasEntitlementの逆方向である」
A-Box(Assertion Box)= 事実の記述
「ex:tenant-abcはTenantである」
「ex:entitlement-abc-csv-exportの対象テナントはex:tenant-abcである」
たとえで言うと、こうです。
T-Box = 言語のルールブック
A-Box = そのルールで書かれた文章
第1回〜第3回で書いてきた「オントロジー定義のTurtle」がT-Box、「具体的なデータのTurtle」がA-Boxです。
ここで大事なのは、セマンティックレイヤーの本体はT-Boxのほうだという点です。A-Boxはデータなので日々変わりますが、T-Boxは「意味の取り決め」なので、そう頻繁には変わりません。
では、そのT-Boxは具体的に何の役に立つのでしょうか。ここからは、手を動かして確認します。
5. 手を動かす準備
5.1 インストール
pip install rdflib owlrl
rdflibは、これまでの記事でも使ったRDF操作ライブラリです。owlrlは、そのrdflibの上でOWL/RDFSの推論を行うライブラリです。推論とは、T-Boxに書かれたルールを使って、データに書かれていない事実を導き出すことを指します。今回はこの推論が主役になります。
5.2 T-Boxを保存する
ontology.ttlとして保存します。第1回〜第3回で定義した語彙のうち、今回使う分だけの抜粋です。
# ontology.ttl — T-Box(語彙の定義)
@prefix saas: <https://example.com/ontology/saas#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#
# Classes
#
saas:Tenant a owl:Class ;
rdfs:label "Tenant"@en , "テナント"@ja .
saas:Plan a owl:Class ;
rdfs:label "Plan"@en , "プラン"@ja .
saas:Subscription a owl:Class ;
rdfs:label "Subscription"@en , "契約"@ja .
saas:Feature a owl:Class ;
rdfs:label "Feature"@en , "機能"@ja .
saas:Entitlement a owl:Class ;
rdfs:label "Entitlement"@en , "機能利用資格"@ja .
#
# Tenant and Subscription
#
saas:hasSubscription a owl:ObjectProperty ;
rdfs:domain saas:Tenant ;
rdfs:range saas:Subscription ;
rdfs:label "has subscription"@en .
saas:basedOnPlan a owl:ObjectProperty ;
rdfs:domain saas:Subscription ;
rdfs:range saas:Plan ;
rdfs:label "based on plan"@en .
#
# Plan and Feature
#
saas:includesFeature a owl:ObjectProperty ;
rdfs:domain saas:Plan ;
rdfs:range saas:Feature ;
rdfs:label "includes feature"@en .
#
# Tenant and Entitlement(第3回で定義した逆プロパティのペア)
#
saas:hasEntitlement a owl:ObjectProperty ;
rdfs:domain saas:Tenant ;
rdfs:range saas:Entitlement ;
rdfs:label "has entitlement"@en ,
"機能利用資格を持つ"@ja .
saas:entitledTenant a owl:ObjectProperty ;
owl:inverseOf saas:hasEntitlement ;
rdfs:domain saas:Entitlement ;
rdfs:range saas:Tenant ;
rdfs:label "entitled tenant"@en ,
"利用資格の対象テナント"@ja .
saas:entitlesFeature a owl:ObjectProperty ;
rdfs:domain saas:Entitlement ;
rdfs:range saas:Feature ;
rdfs:label "entitles feature"@en ,
"利用可能にする機能"@ja .
#
# Datatype Properties
#
saas:name a owl:DatatypeProperty ;
rdfs:range xsd:string .
saas:featureCode a owl:DatatypeProperty ;
rdfs:domain saas:Feature ;
rdfs:range xsd:string .
saas:entitlementStatus a owl:DatatypeProperty ;
rdfs:domain saas:Entitlement ;
rdfs:range xsd:string .
今回の主役は、この1行です。
saas:entitledTenant a owl:ObjectProperty ;
owl:inverseOf saas:hasEntitlement ;
「entitledTenantはhasEntitlementの逆プロパティである」という宣言です。第3回で定義したものを、そのまま持ってきています。
5.3 A-Boxを保存する
data.ttlとして保存します。第3回のインスタンス例と同じく、Entitlement側からsaas:entitledTenantでTenantを指す書き方です。
# data.ttl — A-Box(実データ)
@prefix saas: <https://example.com/ontology/saas#> .
@prefix ex: <https://example.com/data/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#
# Tenant
#
ex:tenant-abc a saas:Tenant ;
saas:name "株式会社ABCテナント" ;
saas:hasSubscription ex:subscription-abc-standard .
#
# Subscription
#
ex:subscription-abc-standard a saas:Subscription ;
saas:basedOnPlan ex:plan-standard .
#
# Plans
#
ex:plan-standard a saas:Plan ;
saas:name "Standard Plan" ;
saas:includesFeature
ex:feature-project-management ,
ex:feature-csv-export ,
ex:feature-api-access .
ex:plan-enterprise a saas:Plan ;
saas:name "Enterprise Plan" ;
saas:includesFeature
ex:feature-project-management ,
ex:feature-csv-export ,
ex:feature-api-access ,
ex:feature-saml-sso ,
ex:feature-audit-log ,
ex:feature-sales-management ,
ex:feature-inventory-management .
#
# Features
#
ex:feature-project-management a saas:Feature ;
saas:name "基本プロジェクト管理" ;
saas:featureCode "core.project-management" .
ex:feature-csv-export a saas:Feature ;
saas:name "CSVエクスポート" ;
saas:featureCode "data.csv-export" .
ex:feature-api-access a saas:Feature ;
saas:name "APIアクセス" ;
saas:featureCode "integration.api-access" .
ex:feature-saml-sso a saas:Feature ;
saas:name "SAML SSO" ;
saas:featureCode "security.saml-sso" .
ex:feature-audit-log a saas:Feature ;
saas:name "監査ログ" ;
saas:featureCode "security.audit-log" .
ex:feature-sales-management a saas:Feature ;
saas:name "販売管理" ;
saas:featureCode "sales.management" .
ex:feature-inventory-management a saas:Feature ;
saas:name "在庫管理" ;
saas:featureCode "inventory.management" .
#
# Entitlements(Standard Plan由来。販売管理機能に対するEntitlementは存在しない)
#
ex:entitlement-abc-project-management a saas:Entitlement ;
saas:entitledTenant ex:tenant-abc ;
saas:entitlesFeature ex:feature-project-management ;
saas:entitlementStatus "active" .
ex:entitlement-abc-csv-export a saas:Entitlement ;
saas:entitledTenant ex:tenant-abc ;
saas:entitlesFeature ex:feature-csv-export ;
saas:entitlementStatus "active" .
ex:entitlement-abc-api-access a saas:Entitlement ;
saas:entitledTenant ex:tenant-abc ;
saas:entitlesFeature ex:feature-api-access ;
saas:entitlementStatus "active" .
5.4 実行スクリプトを保存する
これまでの記事ではsparqlqueryコマンドを使いましたが、あのコマンドは推論をしてくれません。今回は推論の有無を切り替えたいので、短いスクリプトを用意します。query.pyとして保存してください。
"""query.py — Turtleファイルを読み込んでSPARQLを実行する。
使い方:
python query.py <ttlファイル...> <クエリファイル.rq>
python query.py --inference <ttlファイル...> <クエリファイル.rq>
--inference を付けると、T-Boxの定義(owl:inverseOf、rdfs:subPropertyOfなど)に
基づく推論を行ったうえで問い合わせる。
"""
import sys
from rdflib import Graph
def main(argv: list[str]) -> int:
use_inference = "--inference" in argv
paths = [a for a in argv if a != "--inference"]
if len(paths) < 2:
print(__doc__)
return 1
*data_files, query_file = paths
graph = Graph()
for path in data_files:
graph.parse(path, format="turtle")
triples_before = len(graph)
if use_inference:
import owlrl
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics).expand(graph)
with open(query_file, encoding="utf-8") as f:
query = f.read()
results = graph.query(query)
print(",".join(str(v) for v in results.vars))
for row in results:
print(",".join("" if value is None else str(value) for value in row))
mode = "推論あり" if use_inference else "推論なし"
print(
f"\n-- {len(results)} 行 / {mode}"
f"(トリプル数 {triples_before} -> {len(graph)})"
)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
やっていることは3つだけです。
1. 指定されたTurtleファイルを全部1つのグラフに読み込む
2. --inference が付いていたら推論して、導けるトリプルを追加する
3. SPARQLを実行して結果を表示する
推論にOWLRL_Semanticsを指定している点だけ、先に理由を書いておきます。owlrlには軽いRDFS_Semanticsもありますが、今回主役になるowl:inverseOfはOWLの語彙なので、RDFS推論では処理されません。この違いは8章でもう一度触れます。
6. T-Boxは、本当に結果を変えるのか
6.1 まず問い合わせてみる
「株式会社ABCが使える機能は何か?」を聞きます。query-features.rqとして保存してください。
# query-features.rq — 株式会社ABCが使える機能は何か?
PREFIX saas: <https://example.com/ontology/saas#>
PREFIX ex: <https://example.com/data/>
SELECT ?featureName ?featureCode
WHERE {
ex:tenant-abc saas:hasEntitlement ?entitlement .
?entitlement saas:entitlesFeature ?feature .
?entitlement saas:entitlementStatus "active" .
?feature saas:name ?featureName .
?feature saas:featureCode ?featureCode .
}
ORDER BY ?featureCode
注目してほしいのは1行目のパターンです。
ex:tenant-abc saas:hasEntitlement ?entitlement .
Tenant側からsaas:hasEntitlementでたどっています。一方、data.ttlに書いてあるのは逆向きのsaas:entitledTenantだけでした。
実行します。
python query.py ontology.ttl data.ttl query-features.rq
結果:
featureName,featureCode
-- 0 行 / 推論なし(トリプル数 103 -> 103)
0行です。T-Boxを読み込んでいるにもかかわらず、何も返りません。
これは、多くの人が最初にハマるところです。
T-Boxに owl:inverseOf と書いただけでは、
逆方向のトリプルは自動的には存在しない。
RDFのグラフには、書かれたトリプルしか入っていません。ex:entitlement-abc-csv-export saas:entitledTenant ex:tenant-abcは存在しますが、ex:tenant-abc saas:hasEntitlement ex:entitlement-abc-csv-exportは、まだどこにも書かれていないのです。
ここで、正直に書いておかなければならないことがあります。
「逆方向をたどるには推論が必須」というわけではない。
次の2つは、どちらも推論なしで3件返ります。
# データが書かれている向きで、そのまま問う
?entitlement saas:entitledTenant ex:tenant-abc .
# SPARQLの逆パス演算子 ^ を使う
ex:tenant-abc ^saas:entitledTenant ?entitlement .
つまり、推論の価値は「逆方向が取れること」そのものではありません。
推論の本当の価値:
データがどちら向きに書かれていても、
問い合わせる側は saas:hasEntitlement 一本で書ける。
そして次の7章で見るように、
クエリを1文字も変えないまま、
複数のシステムのデータを束ねられる。
データがどちら向きに書かれているかという違いは、間に挟まる層が吸収します。だから問い合わせる側は、データの書かれ方を知らなくてよい。1章で説明した「レイヤー(層)」の価値は、ここにあります。
6.2 推論を有効にする
--inferenceを付けて、もう一度実行します。
python query.py --inference ontology.ttl data.ttl query-features.rq
結果:
featureName,featureCode
基本プロジェクト管理,core.project-management
CSVエクスポート,data.csv-export
APIアクセス,integration.api-access
-- 3 行 / 推論あり(トリプル数 103 -> 368)
3行返ってきました。そして、トリプル数が103から368に増えています(この増え方の内訳は8章で見ます)。
何が起きたのでしょうか。
T-Boxにこう書いてあった:
saas:entitledTenant owl:inverseOf saas:hasEntitlement
A-Boxにこう書いてあった:
ex:entitlement-abc-csv-export saas:entitledTenant ex:tenant-abc
推論エンジンは、この2つから次を導き出した:
ex:tenant-abc saas:hasEntitlement ex:entitlement-abc-csv-export
つまり、T-Boxに書いた1行(owl:inverseOf)が、問い合わせの結果を0行から3行に変えたのです。T-Boxは人間向けの説明書きではありません。推論エンジンが読んで、データに書かれていない事実を導くためのルールとして働きました。
ontology.ttlを読み込まずに推論しても、この結果は出ません。逆方向であるという情報が、どこにも無いからです。ぜひontology.ttlを外して試してみてください。
6.3 販売管理機能について聞いてみる
では冒頭の問いに戻ります。query-sales.rqとして保存してください。
# query-sales.rq — 株式会社ABCは、販売管理機能を使えるか?
PREFIX saas: <https://example.com/ontology/saas#>
PREFIX ex: <https://example.com/data/>
SELECT ?featureName ?status
WHERE {
ex:tenant-abc saas:hasEntitlement ?entitlement .
?entitlement saas:entitlesFeature ex:feature-sales-management .
?entitlement saas:entitlementStatus ?status .
ex:feature-sales-management saas:name ?featureName .
}
実行します。
python query.py --inference ontology.ttl data.ttl query-sales.rq
結果:
featureName,status
-- 0 行 / 推論あり(トリプル数 103 -> 368)
推論を有効にしても0行です。これは推論不足ではなく、グラフの中に該当するEntitlementが存在しないという答えです。
厳密に言うと、OWLの推論は「書かれていない=偽」とは考えません。これを開世界仮定と呼びます。反対に、書かれていないことを偽とみなす考え方を閉世界仮定と呼びます。ふつうのデータベースは、こちらの考え方です。
それでもここで「使えない」と言い切れるのは、「Entitlementはすべてこのグラフに記録されている」という業務上の前提を置いているからです。Entitlementの記録元を網羅しているからこそ、0行を「無い」と読めます。ただし、7章で見るとおり、実務ではこの前提こそが崩れやすいのです。
株式会社ABCは、販売管理機能を使えない。
販売管理機能を対象とした有効なEntitlementが、
グラフの中に1つも存在しないから。
data.ttlを見ると、ABCが契約しているStandard Planにも、販売管理は含まれていません。クエリの結果とも矛盾しません。
冒頭の問いに、事実に基づいて答えられました。
7. 本番: バラバラの語彙を、1つの問いで横断する
ここまでは1つのシステムのデータだけでした。ここからが、セマンティックレイヤーが本当に役に立つ場面です。
7.1 現実の状況
実務では、こうなっています。
株式会社ABCは、商談の結果、
Standard Planに含まれないSAML SSOと監査ログを
個別に付与されている。
ただし、その事実の記録場所はバラバラ:
請求システム : SAML SSO を課金対象として登録
→ billing:grantedFeature という項目名
CRM : 監査ログ を有効化済みとして記録
→ crm:enabledFeature という項目名
同じ「機能を利用可能にした」という意味なのに、項目名が違います。普通なら、どちらかに寄せる変換バッチを書くところです。
セマンティックレイヤーでは、データを書き換えず、意味の対応だけを宣言します。
7.2 マッピングを宣言する
mapping.ttlとして保存してください。
# mapping.ttl — 各ソースシステムの語彙を、共通語彙 saas: に接続する
@prefix saas: <https://example.com/ontology/saas#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix billing: <https://example.com/source/billing#> .
@prefix crm: <https://example.com/source/crm#> .
billing:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .
billing:forCustomer rdfs:subPropertyOf saas:entitledTenant .
crm:enabledFeature rdfs:subPropertyOf saas:entitlesFeature .
crm:forAccount rdfs:subPropertyOf saas:entitledTenant .
rdfs:subPropertyOfは、「このプロパティは、あのプロパティの一種である」という宣言です。
billing:grantedFeature rdfs:subPropertyOf saas:entitlesFeature
意味:
「請求システムのgrantedFeatureで結ばれているものは、
saas:entitlesFeatureでも結ばれていると見なしてよい」
これだけです。4行の宣言で、2つのシステムの語彙が共通語彙に接続されました。
7.3 各システムのデータをそのまま置く
data-multi-source.ttlとして保存してください。各システムの語彙のまま、一切書き換えていません。
# data-multi-source.ttl — 2つの別システムから取り込んだ、そのままの形のデータ
@prefix saas: <https://example.com/ontology/saas#> .
@prefix ex: <https://example.com/data/> .
@prefix billing: <https://example.com/source/billing#> .
@prefix crm: <https://example.com/source/crm#> .
#
# 請求システム由来(billing: 語彙)
#
ex:billing-entitlement-001
billing:forCustomer ex:tenant-abc ;
billing:grantedFeature ex:feature-saml-sso ;
saas:entitlementStatus "active" .
#
# CRM由来(crm: 語彙)
#
ex:crm-entitlement-042
crm:forAccount ex:tenant-abc ;
crm:enabledFeature ex:feature-audit-log ;
saas:entitlementStatus "active" .
7.4 同じクエリで、横断して問い合わせる
なお、a saas:Entitlementというクラス型付けを書いていません。第3回でEntitlementをクラスとして定義したので気になるかと思いますが、今回の問いはプロパティの経路だけでたどれるため、省いています。型付けが必要になるのは、SHACLで検証する場合や、クラス単位で数え上げる場合です。
ここが大事なところです。クエリは6.1節のものを、1文字も変えません。
python query.py --inference \
ontology.ttl mapping.ttl data.ttl data-multi-source.ttl \
query-features.rq
結果:
featureName,featureCode
基本プロジェクト管理,core.project-management
CSVエクスポート,data.csv-export
APIアクセス,integration.api-access
監査ログ,security.audit-log
SAML SSO,security.saml-sso
-- 5 行 / 推論あり(トリプル数 113 -> 410)
3行から5行に増えました。増えたのは、請求システム由来のSAML SSOと、CRM由来の監査ログです。
改めて、何をやったか整理します。
やったこと:
・請求システムのデータを、billing: 語彙のまま置いた
・CRMのデータを、crm: 語彙のまま置いた
・「これらは saas: の何に相当するか」を4行宣言した
やらなかったこと:
・項目名をそろえるために、データを書き換える変換バッチやETL処理を書く
・クエリを1文字でも変更する
これが、セマンティックレイヤーです。
データを1箇所に集めて形を揃えるのではなく、
「意味の対応」を宣言することで、
バラバラのままのデータを1つの語彙で問い合わせる。
新しいシステムが増えたら、mapping.ttlに数行足すだけです。クエリを書く側は、各システムの項目名を知らなくてよい。違いは間の層がすべて引き受けます。これが「レイヤー」と呼ばれる理由です。
8. SQLと比べて、何が違うのか
「同じことをSQLでもできるのでは?」という疑問に、正直に答えます。
まずSQLで書いてみます。
SELECT f.name, f.code
FROM entitlements e
JOIN features f ON e.feature_id = f.id
WHERE e.tenant_id = 'abc' AND e.status = 'active'
UNION
SELECT f.name, f.code
FROM billing_entitlements be
JOIN features f ON be.granted_feature_id = f.id
WHERE be.customer_id = 'abc' AND be.status = 'active'
UNION
SELECT f.name, f.code
FROM crm_entitlements ce
JOIN features f ON ce.enabled_feature_id = f.id
WHERE ce.account_id = 'abc' AND ce.status = 'active';
できます。ちゃんと動きます。ただし、違いが2つあります。
違い1: 増えたときに変わるのはどちらか
SQL:
4つ目のシステムが増えたら、
このクエリ自体に UNION を1ブロック足す。
このクエリを使っている全ての箇所を直す。
セマンティックレイヤー:
mapping.ttl に2行足す。
クエリは変更しない。
ただし公平に言えば、実務のSQLでは、このUNIONをあちこちに書いたりはしません。dbtなどの変換処理で1つのentitlementsテーブル(またはビュー)にまとめておけば、直す場所は1箇所で済みます。2章で見たメトリクス型のセマンティックレイヤーも、この前提に立っています。
そのため、本当の違いは次の「違い2」のほうです。
違い2: 「同じ意味である」がどこに書かれているか
SQLのUNIONは、「この3つのテーブルは同じものとして扱う」という判断をクエリの中に埋め込んでいます。その判断の根拠は、クエリを書いた人の頭の中にしかありません。
一方、rdfs:subPropertyOfは、その判断をクエリの外に、機械が読める宣言として書いています。だから推論エンジンが自動で適用できるし、他の人も他のツールも同じ判断を再利用できます。
もう1点あります。よく「SPARQLならスキーマ変更に強い」と言われますが、それは半分だけ本当です。
SPARQLも、述語のIRIが変わればクエリは壊れる。
その意味では、カラム名が変わればSQLが壊れるのと同じ。
本当の違いは、
「変更に強い」ことではなく、
「意味の対応関係を、クエリの外に、
機械可読な形で書き出せる」こと。
そして、これにはコストもあります。
推論のコスト:
今回は103トリプルが368に増えた(約3.5倍)。
ただし、この「3.5倍」を鵜呑みにしないでください。増えた265トリプルの内訳を数えると、こうなっています。
増加 265トリプルの内訳:
262件 W3C語彙(owl:, rdfs: など)に関わるトリプル
・大半(約230件)は、データの量と関係なく毎回追加されるもの
(例:「owl:Thing はクラスである」のような事実)
・残りは、データ中のIRIや値ごとに機械的に付く自明なもの
(例:「X は X と同じものである(owl:sameAs)」)
3件 saas:hasEntitlement の導出(=実際に欲しかったもの)
つまり3.5倍の大半は、データ量に関係なく発生する固定オーバーヘッドです。A-Boxが小さいほど倍率は大きく見えます。データを増やすと、倍率そのものは下がっていきます。実際にdata.ttlと同じ形のデータを20倍に増やして試すと、推論後のトリプル数は推論前の約1.8倍まで下がりました。
とはいえ、絶対的な計算量とメモリは確実に増えます。本番では次のような判断が必要になります。
・推論結果を事前に計算して保存しておく(マテリアライズ。問い合わせのたびには推論しない)
・推論を使わず、SPARQLの逆パス演算子(6.1節の ^)で書く
・必要な導出だけを CONSTRUCT(クエリ結果から新しいトリプルを作る構文)で明示的に作る
ここで1つ注意点があります。「軽いRDFS推論に絞る」という対策をよく見かけますが、今回のハンズオンではそれをやると、うまく動かなくなります。
owl:inverseOf は、RDFS推論では処理されない。
実測:
RDFS_Semantics → 0行
OWLRL_Semantics → 3行
rdfs:subPropertyOfはRDFS語彙なのでRDFS推論でも処理されますが、owl:inverseOfはOWLの語彙です。この記事のquery.pyがOWLRL_Semanticsを使っているのは、そのためです。
魔法ではありません。トレードオフのある設計選択です。
9. メトリクス型と比べて、何が違うのか
8章ではSQLと比べました。ここでは、2章で見たメトリクス型のセマンティックレイヤーと比べます。
違いが一番よくわかるのは、得意な問いが違うところです。2つの問いを用意しました。
Q1. プラン別に、毎月いくつ新しい契約が始まっているか?
(集計の問い)
Q2. 株式会社ABCというテナントは、販売管理機能を使えるか?
(関係をたどる問い。しかも記録が複数システムに散らばっている)
9.1 Q1(集計)は、メトリクス型の得意分野
Q1は、2.3節で見たとおり、メトリクス型ならきれいに書けます。
オントロジー型でも、SPARQLで集計はできます。Subscriptionの開始日を表すsaas:subscriptionStartは後続の会で正式に定義する予定ですが、ここでは先取りして使います。
SELECT ?planName ?month (COUNT(DISTINCT ?sub) AS ?newSubscriptions)
WHERE {
?sub a saas:Subscription ;
saas:basedOnPlan ?plan ;
saas:subscriptionStart ?started .
?plan saas:name ?planName .
BIND(SUBSTR(STR(?started), 1, 7) AS ?month)
}
GROUP BY ?planName ?month
書けます。ただし、次のような点でメトリクス型に分があります。
・「new_subscriptions とはこの計算式である」という定義を、
再利用可能な部品として置く標準的な仕組みが、OWLにはない。
(OWLは「何が存在するか」の言語であって、「どう数えるか」の言語ではない。
統計などの多次元データを記述する W3C の RDF Data Cube 語彙はあるが、
計算式そのものを定義する、広く使われた標準はない)
・時間の粒度(日・週・月)、足し算できない指標(期末残高など)の扱いを、
メトリクス型は定義側で面倒を見てくれる。SPARQLでは毎回書く。
・数億行の集計は、列指向のデータウェアハウスのほうが圧倒的に速い。
9.2 Q2(関係・横断)は、オントロジー型の得意分野
Q2の答えは、「ABCに、販売管理機能を対象とした有効なEntitlementがあるか」です。しかも現実には、その記録が請求システムとCRMに、別々の項目名で散らばっています。
請求システム: billing_entitlements.granted_feature_id
CRM: crm_entitlements.enabled_feature_id
オントロジー型では、こう書くだけで2つを同じ意味として扱えます。
billing:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .
crm:enabledFeature rdfs:subPropertyOf saas:entitlesFeature .
問い合わせる側はsaas:entitlesFeature一本で書けばよく、データ自体は書き換えません。これは7章で実際に確かめたとおりです。
では、メトリクス型ではQ2はどうなるか。ここは公平に書いておきます。
「メトリクス型は複数ソースを束ねられない」は、正確ではない。
メトリクス型の世界では、
複数ソースの統合は、セマンティックレイヤーの「下」で済ませておく。
dbt などの変換処理で、請求とCRMのテーブルを
1つの entitlements テーブルに揃えてから、その上に定義を載せる。
つまり、違いは「束ねられるか」ではなく、束ねる判断をどこに書くかです。
メトリクス型:
「grantedFeature と enabledFeature は同じ意味だ」という判断は、
変換処理(SQL)の中に書かれる。
オントロジー型:
同じ判断を、変換処理の外に、
機械が読める「意味の宣言」として書く。
そして、Q2はそもそも集計の問いではありません。メトリクスとディメンションという型に、自然には収まらない問いです。
9.3 比較表
ここまでの比較を表にまとめます。
| 観点 | メトリクス型 | オントロジー型 |
|---|---|---|
| 定義する単位 | メジャー・ディメンション・結合 | クラス・プロパティ・関係 |
| 答えやすい問い | どれだけ?(集計) | 何が何と、どうつながっている? |
| 定義の置き場所 | テーブルの上(DWHのカタログ) | テーブルから独立(Turtleなど) |
| 複数ソースの語彙合わせ | 下の変換層(dbt等)で済ませておく | マッピングとして宣言する |
| 推論 | なし(集計の意味づけはある) | あり(逆関係、上位プロパティ、クラス階層) |
| 書かれていない事実の扱い | 「無い」とみなす(閉世界仮定) | 推論では「わからない」とみなす(開世界仮定、6.3節)。ただしSPARQLの結果やSHACLの検証は、グラフにあるものだけで判断する |
| 制約の検証 | 定義の整合性は作成時に検証される。データの制約はテスト(dbt testsなど)で確認する | SHACLで宣言する |
| 集計性能 | 強い(列指向エンジン) | 弱め(大規模集計は不得意) |
| 標準 | Apache Ossie(旧OSI)が整備中 | W3C標準(RDF/OWL/SPARQL/SHACL) |
| 人材・ツール | 豊富 | 少ない |
| AIとの組み合わせ | セマンティックモデルを手がかりに、自然文→SQL(Cortex Analyst、Genieなど) | 自然文→SPARQL、ナレッジグラフ |
9.4 メトリクス型のPros / Cons
Pros:
・「数字が合わない」問題に、直接対応する
・データウェアハウスの中にあり、権限管理も性能もそのまま使える
・SQLの延長なので、データ分析の人材がそのまま使える
・BIツールやAIエージェントとの連携が、製品として整っている
Cons:
・定義はテーブル構造に依存する。テーブルが先、意味が後
・「AとBは同じ意味」という判断が、変換処理のSQLに埋もれやすい
・推論がない。書いた計算式以上のことは出てこない
・集計にならない問い(関係・資格・依存)は、型に収まりにくい
9.5 オントロジー型のPros / Cons
Pros:
・意味の体系がテーブルから独立しているので、ソースが増えても影響を受けにくい
・意味の対応関係を、クエリの外に機械可読な形で書ける
・推論で、書かれていない事実を導ける
・W3C標準なので、特定ベンダーに閉じない
Cons:
・集計・時間粒度・大規模データの扱いは、メトリクス型に劣る
・RDF/SPARQLを扱える人が少ない
・既存のRDBのデータをRDFとして扱う工程(変換か仮想化)が別途必要
・推論には計算コストがかかり、設計判断が要る
どちらも魔法ではありません。目的が違う道具です。
10. AIがセマンティックレイヤーを使う場合
もう1つ、いまの時代に重要な応用に触れておきます。
LLM(大規模言語モデル)に「株式会社ABCは販売管理機能を使えますか?」と聞いたとき、何が起きるべきでしょうか。
セマンティックレイヤーが無い場合:
LLMが学習データからそれっぽい答えを生成する
→ ハルシネーション(幻覚)のリスク
セマンティックレイヤーがある場合:
LLMが、問いをSPARQLに変換する
→ セマンティックレイヤーに問い合わせる
→ 返ってきた事実に基づいて答える
セマンティックレイヤーがあれば、LLMは「答えを創作する」のではなく「問いを翻訳する」役割に徹することができます。
人間の質問:
「株式会社ABCは販売管理機能を使える?」
LLMが生成するSPARQL:
PREFIX saas: <https://example.com/ontology/saas#>
PREFIX ex: <https://example.com/data/>
SELECT ?featureName ?status
WHERE {
ex:tenant-abc saas:hasEntitlement ?e .
?e saas:entitlesFeature ?f .
?f saas:name ?featureName .
FILTER(CONTAINS(?featureName, "販売管理"))
?e saas:entitlementStatus ?status .
}
セマンティックレイヤーの応答:
(0行)
LLMの最終回答:
「株式会社ABCは現在、販売管理機能を利用できません。
この機能に対する有効なEntitlementが
存在しないためです。」
ここでLLMが正しいクエリを組み立てられるのは、T-Boxに「Tenant」「Entitlement」「Feature」が何であり、どうつながっているかが書いてあるからです。T-Boxからは、クエリの形(どのプロパティをたどるか)が分かります。絞り込む値の「販売管理」は、LLMが質問の言葉から取り出しています。
LLMにテーブル定義だけを渡した場合:
「entitlements.status って何の状態?」
「tenant_id と customer_id は同じもの?」
→ 推測が入る
LLMにT-Boxを渡した場合:
第3回で Entitlement の rdfs:comment に書いた
「特定のTenantに、特定のFeatureを提供する権利、資格または状態」
が読める
→ 推測が減る
第1回からrdfs:labelとrdfs:commentを丁寧に書いてきたのは、人間のためだけではなかったわけです。
なお、今回のontology.ttlは、説明を短くするためにrdfs:commentを省いています。実際にLLMへ渡すなら、第3回までの定義をコメントごと渡します。
この方向性は、メトリクス型の製品にも共通しています。どの製品も、AIエージェント向けの機能を足しています。
Snowflake Semantic View に、AI向けの指示や検証済みクエリを書ける句がある
→ Cortex Analyst などが参照する
Databricks Metric View に synonyms(同義語)を書ける
→ Genie が参照する
Looker LookML を、Gemini の会話型分析の根拠として使う
目的は共通していて、「LLMに計算を推測させず、定義に従って決定的に答えさせる」ことです。これは、この章で見た「LLMを『答えを創作する役』から『問いを翻訳する役』にする」という話と、まったく同じ方向です。
11. 2つの系統は近づいている
ここまで「2つの系統」として分けてきましたが、実は最近、両者の境界は少しずつ曖昧になっています。きっかけは、10章で見たとおり、どちらもAIに正確に答えさせる手段として注目されていることです。
11.1 Microsoft Fabric IQのOntology
一番わかりやすい合流の例が、MicrosoftがIgnite 2025で発表したFabric IQです。その中核に、Ontologyというアイテム(2026年10月時点でプレビュー)があります。
Fabric IQ の Ontology で定義するもの(公式ドキュメントより):
entity type (≒ クラス)
relationship (≒ プロパティ)
継承・namespace
metric ← Power BI の Semantic Model の DAX メジャーを参照する
rule (自然言語で書く)
面白いのは、metricをオントロジーの中で「参照」している点です。計算式そのもの(DAX)は元のSemantic Modelが持ち続け、オントロジーはそれを「このエンティティに関する指標」として結びつけます。
オントロジー : 「Tenant とは何か」「何とつながっているか」
メトリクス層 : 「Tenant ごとの数字を、どう計算するか」
→ 置き換えるのではなく、重ねている
また、FabricのOntologyはRDF / Turtle / OWLのサブセットの取り込みと、RDF / Turtleでの書き出しをドキュメントに載せています(取り込み先は空のOntologyアイテムで、取り込み結果は、そのまま保たれた要素・自動で調整された要素・対応していない要素に分けて報告されます)。データ・分析基盤の大手が、W3Cのオントロジー形式との行き来を公式にうたった例です。
さらに、FabricのOntologyは基本的には自分でデータを持たない「仮想的な意味のレイヤー」で、実際の問い合わせは接続先のデータソースで実行されます(多段の関係をたどる場合などは、FabricのGraphにデータを任意で実体化することもできます)。これは、RDFの世界で以前からあるR2RML / Ontop(SPARQLをSQLに書き換えて、元のDBで実行する)とよく似た発想です。
11.2 Apache Ossie も、オントロジーを対象に加えた
2章で触れたOSI(現 Apache Ossie)も、同じ方向に動いています。2026年7月にApache Incubatorへ移ったとき、Ossieは自らを「セマンティックレイヤーとオントロジーの両方のための仕様」と位置づけ直しました。
さらに、草案段階(v0.2.0.dev0)のオントロジー仕様では、概念や関係にiriフィールドを持たせ、既存のRDF/OWL語彙のクラスやプロパティと対応づけられるようになっています。メトリクス型から始まった標準化が、オントロジー、さらにW3Cの語彙との接続にまで広がってきたわけです。
11.3 「オントロジー」という言葉の広がり
ちなみに、Palantir FoundryのOntologyは、オブジェクト型・リンク型・アクション型という独自のモデルで業務の世界を表現するもので、RDF/OWLをデータモデルの基盤にはしていません(Palantir自身の資料では、RDF/OWL形式との入出力もうたわれています)。MicrosoftのGraphRAGも、LLMでテキストからナレッジグラフを作るもので、RDFではありません。
「オントロジー」「ナレッジグラフ」と名乗っていても、
W3C標準の RDF/OWL とは限らない。
ここは混乱しやすいので、覚えておくと便利です。
12. レイヤー構成を整理する
最後に、全体像を整理します。
問い合わせる側(BIツール、AI、スクリプト、人間)
│
│ 共通語彙 saas: で問う
↓
セマンティックレイヤー
├── ontology.ttl = T-Box(意味の定義)
└── mapping.ttl = 各ソース語彙との意味の対応
│
│ 推論して、書かれていない事実も導く
↓
RDFとして表現されたデータ(A-Box)
│
│ ここに、元のデータをRDFに変換する(取り込む)工程が必要
↓
実際のデータソース
├── Postgres
├── SaaSのAPI
└── CSV など
ここで、正直に書いておかなければならないことがあります。
今回の記事では、data.ttl を手で書いた。
しかし実務では、データはPostgresやSaaSのAPIの中にある。
それをA-Box(RDF)にする工程は、
誰かが作らなければならない。
この工程は、この記事では扱っていません。実際には、次のような選択肢があります。
・定期的にRDFへ書き出す(マテリアライズ)
・問い合わせ時にSQLへ変換する(R2RML / Ontop などの仮想化。データはRDBに置いたまま、SPARQLをSQLに書き換えて実行する)
・アプリケーション側でRDFとして書き出す
「セマンティックレイヤーがあれば変換が不要になる」わけではありません。7章で不要になったのは、項目名をそろえるための変換でした(mapping.ttlが語彙どうしの対応を引き受けたからです)。PostgresなどのデータをRDFの形にする変換は、依然として必要です。セマンティックレイヤーが不要にするのは、「システムごとにバラバラな意味の解釈を、問い合わせる側が毎回やり直すこと」です。
メトリクス型のセマンティックレイヤーも含めて描き直すと、こうなります。
問い合わせる側(BIツール、AIエージェント、人間)
│
├──「どれだけ?」────→ メトリクス型セマンティックレイヤー
│ (Snowflake / Databricks / Fabric / dbt)
│ │
│ │ 「Tenant」「Plan」の意味は
│ │ オントロジーに従う
│ ↓
└──「何が何と、どう?」→ オントロジー型セマンティックレイヤー
(このシリーズで作っている saas: 語彙)
│
↓
各システムのデータ
(SaaS管理基盤、請求、CRM、サポート…)
オントロジー = 意味の基準
「Tenantとは何か」
「EntitlementとSubscriptionはどう違うか」
「請求システムの customer と CRM の account は同じものか」
メトリクス層 = 計算の基準
「Tenantごとの月間利用量を、どう集計するか」
「プラン別の新規契約数を、どう数えるか」
メトリクス型で「アクティブテナント数」を定義するとき、その前提には「そもそもTenantとは何か」「どの状態をアクティブと呼ぶか」という取り決めがあるはずです。その取り決めを、テーブルやSQLの中に埋もれさせずに書き出したものが、オントロジーです。
2つは競合ではなく、積み重なるものだと考えています。そして、このシリーズが作っているのは下の層、つまり意味の基準のほうです。
ここまで見てきた「意味の定義」は、このシリーズの第1回から第3回で作ってきたもの、そのものです。
第1回:Tenant, User, Membership, Role, Permission, Subscription, Plan, Resource
第2回:Organization, Workspace, Team, RoleAssignment
第3回:Feature, Entitlement
つまり、オントロジーを作ること=セマンティックレイヤーの語彙を構築することでした。第4回以降で語彙が増えるたびに、このセマンティックレイヤーが答えられる問いも増えていきます。
まとめ
「セマンティックレイヤー」には、2つの系統がある:
メトリクス型(Snowflake / Databricks / Fabric / dbt / Cube / Looker)
数字の計算のしかたを、テーブルの上に定義する
→ 「どれだけ?」に強い。数字のズレをなくす
オントロジー型(RDF / OWL / SPARQL / SHACL)
ものごとの関係と意味を、テーブルから独立して定義する
→ 「何が何と、どうつながっている?」に強い。推論と横断ができる
このシリーズが作っているのは、オントロジー型
セマンティックレイヤーとは:
データの意味を一箇所に定義し、
どのツールからでも同じ意味で問い合わせられるようにする仕組み
オントロジーとの関係:
T-Box(クラスとプロパティの定義)がセマンティックレイヤーの本体
A-Box(実データ)はT-Boxの語彙で記述される
T-Boxは飾りではない:
owl:inverseOf を書いておくと、推論で逆方向をたどれる
→ 推論なし 0行、推論あり 3行
意味の対応を宣言すれば、語彙の違いを吸収できる:
rdfs:subPropertyOf で各システムの語彙を共通語彙に接続
→ データもクエリも変更せず、3行から5行に
正直な限界:
・逆方向は、推論なしでも(逆パス ^ などで)取れる。
推論の価値は「問い合わせる側が共通語彙一本で書けること」
・owl:inverseOf はRDFS推論では処理されない(OWLの推論が必要)
・推論の増加分の大半は固定オーバーヘッド(265件中、約230件)
・SPARQLも述語IRIが変われば壊れる
・PostgresなどからA-Boxへの変換工程は別途必要
手を動かした手順は、次の4ステップでした。
1. pip install rdflib owlrl
2. ontology.ttl(T-Box)と data.ttl(A-Box)を保存する
3. python query.py ontology.ttl data.ttl query-features.rq
→ 0行(hasEntitlement 方向のトリプルがまだ存在しない)
4. python query.py --inference ontology.ttl data.ttl query-features.rq
→ 3行(T-Boxの owl:inverseOf から、逆向きの事実が導かれた)
ここまでで、このシリーズで作っているオントロジーがどう使われるかが、なんとなく伝わったかと思います。具体的なユースケースでどう使うの?というのは、さらに別の会でもっと具体的に説明したいと思います。
次回(第4回)からは、語彙の構築に戻ります。テーマは「使える。でも、どこまで?」です。Featureが使えるとして、その上限と消費量をどう表現するか、という話に入っていこうと思います!
こちらからは以上です。
参考資料(2026年10月時点)
- Snowflake — Semantic views: https://docs.snowflake.com/en/user-guide/views-semantic/querying / CREATE SEMANTIC VIEW: https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view
- Snowflake — Open Semantic Interchange: https://www.snowflake.com/en/blog/open-semantic-interchange-ai-standard/ / 仕様公開: https://www.snowflake.com/en/blog/open-semantic-interchanges-specs-finalized/
- Apache Ossie (incubating): https://ossie.apache.org/updates/ossie-enters-apache-incubator/ / オントロジー仕様(草案): https://github.com/apache/ossie/blob/main/ontology/ontology.md
- dbt v1.12(Ossie形式の読み込み): https://docs.getdbt.com/docs/dbt-versions/dbt-upgrade/upgrading-to-v1.12
- Databricks — Metric views: https://docs.databricks.com/aws/en/metric-views/ / Unity Catalog semantics: https://docs.databricks.com/aws/en/uc-semantics/
- Microsoft — Fabric IQ: https://learn.microsoft.com/en-us/fabric/iq/overview / Ontology: https://learn.microsoft.com/en-us/fabric/iq/ontology/overview
- Microsoft — Power BI semantic model(旧 dataset): https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename / XMLA エンドポイント: https://learn.microsoft.com/en-us/fabric/enterprise/powerbi/service-premium-connect-tools / Direct Lake: https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-overview
- dbt — MetricFlow の Apache 2.0 化: https://www.getdbt.com/blog/open-source-metricflow-governed-metrics
- Cube — SQL API: https://docs.cube.dev/reference/core-data-apis/sql-api
- Looker — Open SQL Interface: https://docs.cloud.google.com/looker/docs/sql-interface
- Palantir Foundry Ontology: https://www.palantir.com/docs/foundry/ontology/core-concepts
- Microsoft GraphRAG: https://microsoft.github.io/graphrag/
- Ontop(R2RML / 仮想ナレッジグラフ): https://ontop-vkg.org/guide/