{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/saaswoti-cai-nisitaontoroziwozuo-tutemiyousirizu-3-donoji-neng-gashi-erunoka/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__6ad5b842d3fc9f00013c2337","title":"SaaSを題材にしたオントロジーを作ってみようシリーズ #3.5 番外編  セマンティックレイヤー","slug":"saaswoti-cai-nisitaontoroziwozuo-tutemiyousirizu-3-donoji-neng-gashi-erunoka","featured":false,"feature_image":null,"excerpt":"\n作ったオントロジーは、結局どこで使うのか？\n\n\nセマンティックレイヤーという考え方を、手を動かして理解する\n\n\n今回は番外編です。新しいクラスは1つも追加しません。\n\n前回は、FeatureとEntitlementを導入しました。あわせて、Tenantから見たhasEntitlementと、Entitlementから見た\nentitledTenantという、逆プロパティのペアも定義しました。\n\nここでいったんクラスを増やす手を止めて、そこまでに作った語彙が実際どこで役に立つのか\nを確かめます。というのも、ここまで3回クラスを増やしてきて、そろそろこう思っている方がいるはずだからです。\n\nこれ、作ったはいいけど、結局どこでどう使うの？\n\n\nたとえば、実際の業務では次のような問いが出てきます。\n\n株式会社ABCというテナントは、SaaSの機能としての販売管理機能を使えるのか？\n\n\nこの質問に答えるために、あなたは今、どうしますか？\n\n・請求管理システムの契約画面を開いて確認する？\n・社内の管理DBにSQLを投げる？\n・Slackでカスタマーサクセスに聞く？\n\n\nどの方法でも答えは出ます。","custom_excerpt":null,"visibility":"public","created_at_pretty":"19 October, 2026","published_at_pretty":"19 October, 2026","updated_at_pretty":"19 October, 2026","created_at":"2026-10-19T15:27:14.000+09:00","published_at":"2026-10-20T01:55:51.000+09:00","updated_at":"2026-10-20T01:55:51.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null},"primary_tag":null,"tags":[],"plaintext":"\n作ったオントロジーは、結局どこで使うのか？\n\n\nセマンティックレイヤーという考え方を、手を動かして理解する\n\n\n今回は番外編です。新しいクラスは1つも追加しません。\n\n前回は、FeatureとEntitlementを導入しました。あわせて、Tenantから見たhasEntitlementと、Entitlementから見た\nentitledTenantという、逆プロパティのペアも定義しました。\n\nここでいったんクラスを増やす手を止めて、そこまでに作った語彙が実際どこで役に立つのか\nを確かめます。というのも、ここまで3回クラスを増やしてきて、そろそろこう思っている方がいるはずだからです。\n\nこれ、作ったはいいけど、結局どこでどう使うの？\n\n\nたとえば、実際の業務では次のような問いが出てきます。\n\n株式会社ABCというテナントは、SaaSの機能としての販売管理機能を使えるのか？\n\n\nこの質問に答えるために、あなたは今、どうしますか？\n\n・請求管理システムの契約画面を開いて確認する？\n・社内の管理DBにSQLを投げる？\n・Slackでカスタマーサクセスに聞く？\n\n\nどの方法でも答えは出ます。しかし、問いの意味自体は、どの方法で調べても同じはずです。\n\nこのTenantに、\n販売管理Featureに対する有効なEntitlementが存在するか？\n\n\nこれが、問いの正体です。第3回でFeatureとEntitlementを定義したのは、まさにこの「問いの正体」を書けるようにするためでした。\n\nそして、この「意味の定義」を、複数のシステムから共通で使える形にしたものが、セマンティックレイヤーです。\n\n\n--------------------------------------------------------------------------------\n\n1. セマンティックレイヤーとは何か\n一言でいうと、\n\nセマンティックレイヤー：\n    データの意味を一箇所に定義し、\n    どのツール・どのシステムからでも\n    同じ意味で問い合わせられるようにする仕組み\n\n\nもう少し具体的にいきます。\n\nセマンティックレイヤーが無い世界:\n\n    請求システム  →  grantedFeature という項目で機能を持つ\n    CRM           →  enabledFeature という項目で機能を持つ\n    社内DB        →  entitlements テーブルの feature_id カラム\n\n    3つとも同じことを記録しているのに、\n    呼び名がバラバラで、横断して聞けない。\n\nセマンティックレイヤーがある世界:\n\n    共通語彙 saas:entitlesFeature\n    ├── 請求システムの grantedFeature\n    ├── CRMの enabledFeature\n    └── 社内DBの feature_id\n\n    どのシステムのデータでも、1つの問いで横断できる。\n\n\nセマンティックレイヤーの本質は、「どのカラムに何が入っているか」ではなく、「それが何を意味するか」を定義することです。\n\n「レイヤー（層）」と呼ぶのは、これが問い合わせる側とデータの間に挟まる層だからです。\n\n問い合わせる側（人、BIツール、AI）\n    │  共通の語彙で問う\n    ↓\nセマンティックレイヤー（意味の定義と、各システムとの対応）\n    │  各システムの書き方の違いを、ここで吸収する\n    ↓\n各システムのデータ（請求システム、CRM、社内DB…）\n\n\n間に層が1枚あるので、問い合わせる側は、下にあるデータがどう書かれているかを知らなくても済みます。\n\nそして今回は、この「バラバラの語彙を1つの問いで横断する」ところまで、実際に手元で動かします。\n\nところで、「セマンティックレイヤー」で検索すると、たぶん次のようなものが出てきます。\n\n・Snowflake の Semantic Views\n・Databricks の Metric Views（Unity Catalog semantics）\n・Power BI / Microsoft Fabric の Semantic Model\n・dbt Semantic Layer、Cube、Looker の LookML\n\n\nそして、そこに書かれているのは、だいたいこういう話です。\n\n「売上」「アクティブユーザー数」の計算式を1箇所で定義して、\nどのBIツールからでも同じ数字が出るようにしましょう。\n\n\n一方、このシリーズで作っているのは、Turtleで書いたクラスとプロパティです。計算式は1つも出てきません。\n\nあれ？\nこのシリーズの「セマンティックレイヤー」と、\n世の中の「セマンティックレイヤー」、\n同じものの話をしている？\n\n\n結論から書きます。\n\n同じ言葉で、2つの系統のものが呼ばれている。\n\n    メトリクス型：数字の「計算のしかた」を定義する\n    オントロジー型：世界の「何が何であるか」を定義する\n\nこのシリーズが作っているのは、後者。\n\n\nこの記事では、まずこの2つの違いを整理してから、オントロジー型のほうを実際に動かします。後半で手を動かしているときに「あれ？」とならないためです。\n\n> 製品の名称や提供状況は動きが速いため、本記事の内容は2026年10月時点の公式ドキュメントに基づいています。出典は末尾にまとめました。\n2つの系統とも、上で書いた「データの意味を一箇所に定義する」という点は同じです。違うのは、「意味」として何を定義するかです。\n\nメトリクス型が定義する「意味」:\n    「月間アクティブテナント数」とは、\n    subscriptions テーブルの status = 'active' の行を\n    tenant_id で重複排除して数えたもの\n\nオントロジー型が定義する「意味」:\n    「Tenant」とは、Subscription を持ち、\n    Entitlement を通じて Feature を利用できる単位であり、\n    hasEntitlement と entitledTenant は互いに逆向きの関係である\n\n\n前者は数字の作り方、後者はものごとの関係を定義しています。\n\nこの違いがどこから来たのかを見ていきます。\n\n\n--------------------------------------------------------------------------------\n\n2. メトリクス型セマンティックレイヤーは、なぜ生まれたか\n2.1 出発点は「数字が合わない」問題\nメトリクス型が解こうとしてきた問題は、とても具体的です。\n\n営業のダッシュボード:  今月のアクティブテナント  128社\nCSのダッシュボード:    今月のアクティブテナント  131社\n経営会議の資料:        今月のアクティブテナント  125社\n\n\n3つとも間違ってはいません。トライアル中を含めるか、解約予定を含めるか、月末時点か月中の最大か。計算式が、ダッシュボードごとに少しずつ違うのです。\n\nそこで、「計算式を1箇所に集めて、全員がそれを使う」という発想が生まれました。\n\n2.2 置き場所の変遷\nこの「計算式の置き場所」は、時代とともに移動してきました。\n\n第1世代: BIツールの中\n    Looker の LookML、Power BI のデータセット（現 Semantic Model）\n    → もともと、そのBIツールのために設計された。\n      いまは外部からも使えるが（Looker の Open SQL Interface、\n      Power BI の XMLA エンドポイントなど）、\n      ツール固有の言語や接続方式に合わせる必要がある。\n\n第2世代: BIツールから独立（ヘッドレス）\n    dbt Semantic Layer（MetricFlow）、Cube、AtScale\n    → どのBIツールからでも、APIやSQLで同じ定義を使える。\n      （dbt の API は dbt platform で提供される）\n\n第3世代: データ基盤の中\n    Snowflake Semantic Views、Databricks Metric Views\n    → データと同じ場所で、同じ権限管理の下に置かれる。\n\n\nMicrosoft Fabric の Semantic Model は、第1世代の Power BI の Semantic Model\nと同じものかと思います。ただ、Fabric 上では Direct Lake という方式で OneLake\nのデータを直接読めるので、第1世代と第3世代の中間にあたります。\n\nなお、この「世代」は説明のための筆者の整理で、業界やベンダーの公式な区分ではありません。新しい世代ほど優れている、という意味でもありません。いま「セマンティックレイヤー」として話題になっているのが、主に第3世代だということを表現したいため、この整理をしました。\n\n2.3 実際には、こう書く\n第1回で作ったTenant・Subscription・Planが、ふつうのテーブルとしてデータウェアハウスに入っているとして、具体的に見てみます。\n\ntenants       (tenant_id, name)\nsubscriptions (subscription_id, tenant_id, plan_id, status, started_at)\nplans         (plan_id, plan_name)\n\n\nここで、次の問いを考えます。\n\nQ1. プラン別に、毎月いくつ新しい契約が始まっているか？\n\n\n※Q2は9章で出てきます。\n\nSnowflakeのSemantic Viewでは、こう定義します。\n\n-- あくまでもイメージで、動作保証はないです\nCREATE SEMANTIC VIEW subscription_sv\n  TABLES (\n    subscriptions PRIMARY KEY (subscription_id),\n    plans         PRIMARY KEY (plan_id)\n  )\n  RELATIONSHIPS (\n    subscriptions (plan_id) REFERENCES plans\n  )\n  DIMENSIONS (\n    plans.plan_name           AS plans.plan_name,\n    subscriptions.start_month AS DATE_TRUNC('MONTH', subscriptions.started_at)\n  )\n  METRICS (\n    subscriptions.new_subscriptions AS COUNT(DISTINCT subscriptions.subscription_id)\n  )\n  COMMENT = 'プラン別・月別の新規契約';\n\n\n使う側は、計算式を知らなくても、こう問い合わせるだけです。\n\nSELECT plan_name, start_month, AGG(new_subscriptions)\nFROM subscription_sv\nGROUP BY plan_name, start_month;\n\n\nDatabricksのMetric Viewでは、YAMLで同じことを書きます。\n\n-- あくまでもイメージで、動作保証はないです\nCREATE VIEW main.saas.subscription_metrics\nWITH METRICS LANGUAGE YAML AS $$\nversion: 1.1\nsource: main.saas.subscriptions\njoins:\n  - name: plan\n    source: main.saas.plans\n    on: source.plan_id = plan.plan_id\nfields:\n  - name: plan_name\n    expr: plan.plan_name\n  - name: start_month\n    expr: DATE_TRUNC('MONTH', started_at)\nmeasures:\n  - name: new_subscriptions\n    expr: COUNT(DISTINCT subscription_id)\n$$;\n\nSELECT plan_name, start_month, MEASURE(new_subscriptions)\nFROM main.saas.subscription_metrics\nGROUP BY ALL;\n\n\n書き方は違いますが、やっていることは同じです。\n\n2.4 メトリクス型の「共通の形」\n製品は違っても、中身の構成要素はほぼ共通しています。\n\n論理テーブル     どのテーブルを使うか\nリレーション     テーブル同士をどう結合するか\nディメンション   どの切り口で分けるか（プラン別、月別…）\nメジャー         何をどう集計するか（COUNT, SUM, 比率…）\n同義語・説明     人間とAIのための補足\n\n\nそして、問い合わせ方も共通しています。\n\nSnowflake   AGG(new_subscriptions)\nDatabricks  MEASURE(new_subscriptions)\nCube        MEASURE(...)\nLooker      AGGREGATE(...)\n\n\nAGG()やMEASURE()は、「定義側に書いてある集計式（ここではCOUNT(DISTINCT …)）で集計して」という指示です。使う側は、COUNTや\nSUMを自分で書く必要がありません。\n\n集計のしかたは定義側が持ち、使う側は切り口を選ぶだけ。これがメトリクス型の設計の核です。\n\nこの「共通の形」を標準にする動きも進んでいます。2025年9月、Open Semantic\nInterchange（OSI）が立ち上がり、2026年1月にはYAML/JSONで記述する仕様の初版（v0.1）が公開されました。2026年7月にはApache\nIncubatorに入り、Apache Ossie（incubating）に改称されています。\n\n2026年1月の初版仕様はRDF/OWLに触れられていませんでしたが、2026年5月にはオントロジー仕様の草案が出ており、7月のApache移管の時点では『セマンティックレイヤーとオントロジーの両方のための仕様』と明示的に説明されています。\n\n\n--------------------------------------------------------------------------------\n\n3. オントロジー型セマンティックレイヤー\nもう1つの系統は、このシリーズで作っているものです。\n\nオントロジー型が定義するもの:\n\n    クラス         Tenant, Subscription, Plan, Feature, Entitlement\n    プロパティ     hasSubscription, basedOnPlan, hasEntitlement\n    関係の性質     hasEntitlement と entitledTenant は互いに逆向き\n    階層           ある関係は、別の関係の一種である（7章で実例を見ます）\n    制約           Entitlement には必ず Tenant と Feature が1つずつ必要\n\n\nルーツは、W3Cが標準化したSemantic\nWebの技術（RDF、OWL、SPARQL、SHACL）です。データベースのテーブルではなく、「主語・述語・目的語」の3つ組（トリプル）で世界を記述します。\n\nメトリクス型との一番の違いは、定義から新しい事実を導けること（推論） と、どのテーブルにも属さない「意味の対応」を書けること\nです。「意味の対応」とは、「請求システムのこの項目と、CRMのこの項目は同じ意味だ」という取り決めのことで、7章で実際に書きます。\n\nメトリクス型:\n    定義は「テーブルの上」に乗っている。\n    テーブルが先にあり、その上に計算式を載せる。\n\nオントロジー型:\n    定義は「テーブルから独立」している。\n    意味の体系が先にあり、各システムのデータをそこに接続する。\n\n\nこのシリーズで作っているのは、こちらです。\n\n「Tenantとは何か」\n「FeatureとEntitlementはどう違うか」\n「PlanとFeatureの関係は何か」\n\n\nこのあたりは、言葉で説明するより動かしたほうが早いので、このあと実際に手を動かして確かめます。\n\n\n--------------------------------------------------------------------------------\n\n4. T-BoxとA-Box\nオントロジーを実際に使うとき、記述は2つに分かれます。\n\nT-Box（Terminology Box）＝ 語彙の定義\n    「Tenantというクラスが存在する」\n    「hasEntitlementはTenantからEntitlementへの関係である」\n    「entitledTenantはhasEntitlementの逆方向である」\n\nA-Box（Assertion Box）＝ 事実の記述\n    「ex:tenant-abcはTenantである」\n    「ex:entitlement-abc-csv-exportの対象テナントはex:tenant-abcである」\n\n\nたとえで言うと、こうです。\n\nT-Box ＝ 言語のルールブック\nA-Box ＝ そのルールで書かれた文章\n\n\n第1回〜第3回で書いてきた「オントロジー定義のTurtle」がT-Box、「具体的なデータのTurtle」がA-Boxです。\n\nここで大事なのは、セマンティックレイヤーの本体はT-Boxのほう\nだという点です。A-Boxはデータなので日々変わりますが、T-Boxは「意味の取り決め」なので、そう頻繁には変わりません。\n\nでは、そのT-Boxは具体的に何の役に立つのでしょうか。ここからは、手を動かして確認します。\n\n\n--------------------------------------------------------------------------------\n\n5. 手を動かす準備\n5.1 インストール\npip install rdflib owlrl\n\n\nrdflibは、これまでの記事でも使ったRDF操作ライブラリです。owlrl\nは、そのrdflibの上でOWL/RDFSの推論を行うライブラリです。推論とは、T-Boxに書かれたルールを使って、データに書かれていない事実を導き出すことを指します。今回はこの推論が主役になります。\n\n5.2 T-Boxを保存する\nontology.ttlとして保存します。第1回〜第3回で定義した語彙のうち、今回使う分だけの抜粋です。\n\n# ontology.ttl — T-Box（語彙の定義）\n@prefix saas: <https://example.com/ontology/saas#> .\n@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix owl:  <http://www.w3.org/2002/07/owl#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\n#\n# Classes\n#\n\nsaas:Tenant a owl:Class ;\n    rdfs:label \"Tenant\"@en , \"テナント\"@ja .\n\nsaas:Plan a owl:Class ;\n    rdfs:label \"Plan\"@en , \"プラン\"@ja .\n\nsaas:Subscription a owl:Class ;\n    rdfs:label \"Subscription\"@en , \"契約\"@ja .\n\nsaas:Feature a owl:Class ;\n    rdfs:label \"Feature\"@en , \"機能\"@ja .\n\nsaas:Entitlement a owl:Class ;\n    rdfs:label \"Entitlement\"@en , \"機能利用資格\"@ja .\n\n#\n# Tenant and Subscription\n#\n\nsaas:hasSubscription a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Subscription ;\n    rdfs:label \"has subscription\"@en .\n\nsaas:basedOnPlan a owl:ObjectProperty ;\n    rdfs:domain saas:Subscription ;\n    rdfs:range saas:Plan ;\n    rdfs:label \"based on plan\"@en .\n\n#\n# Plan and Feature\n#\n\nsaas:includesFeature a owl:ObjectProperty ;\n    rdfs:domain saas:Plan ;\n    rdfs:range saas:Feature ;\n    rdfs:label \"includes feature\"@en .\n\n#\n# Tenant and Entitlement（第3回で定義した逆プロパティのペア）\n#\n\nsaas:hasEntitlement a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Entitlement ;\n    rdfs:label \"has entitlement\"@en ,\n               \"機能利用資格を持つ\"@ja .\n\nsaas:entitledTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:hasEntitlement ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range saas:Tenant ;\n    rdfs:label \"entitled tenant\"@en ,\n               \"利用資格の対象テナント\"@ja .\n\nsaas:entitlesFeature a owl:ObjectProperty ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range saas:Feature ;\n    rdfs:label \"entitles feature\"@en ,\n               \"利用可能にする機能\"@ja .\n\n#\n# Datatype Properties\n#\n\nsaas:name a owl:DatatypeProperty ;\n    rdfs:range xsd:string .\n\nsaas:featureCode a owl:DatatypeProperty ;\n    rdfs:domain saas:Feature ;\n    rdfs:range xsd:string .\n\nsaas:entitlementStatus a owl:DatatypeProperty ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range xsd:string .\n\n\n今回の主役は、この1行です。\n\nsaas:entitledTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:hasEntitlement ;\n\n\n「entitledTenantはhasEntitlementの逆プロパティである」という宣言です。第3回で定義したものを、そのまま持ってきています。\n\n5.3 A-Boxを保存する\ndata.ttlとして保存します。第3回のインスタンス例と同じく、Entitlement側からsaas:entitledTenant\nでTenantを指す書き方です。\n\n# data.ttl — A-Box（実データ）\n@prefix saas: <https://example.com/ontology/saas#> .\n@prefix ex:   <https://example.com/data/> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\n#\n# Tenant\n#\n\nex:tenant-abc a saas:Tenant ;\n    saas:name \"株式会社ABCテナント\" ;\n    saas:hasSubscription ex:subscription-abc-standard .\n\n#\n# Subscription\n#\n\nex:subscription-abc-standard a saas:Subscription ;\n    saas:basedOnPlan ex:plan-standard .\n\n#\n# Plans\n#\n\nex:plan-standard a saas:Plan ;\n    saas:name \"Standard Plan\" ;\n    saas:includesFeature\n        ex:feature-project-management ,\n        ex:feature-csv-export ,\n        ex:feature-api-access .\n\nex:plan-enterprise a saas:Plan ;\n    saas:name \"Enterprise Plan\" ;\n    saas:includesFeature\n        ex:feature-project-management ,\n        ex:feature-csv-export ,\n        ex:feature-api-access ,\n        ex:feature-saml-sso ,\n        ex:feature-audit-log ,\n        ex:feature-sales-management ,\n        ex:feature-inventory-management .\n\n#\n# Features\n#\n\nex:feature-project-management a saas:Feature ;\n    saas:name \"基本プロジェクト管理\" ;\n    saas:featureCode \"core.project-management\" .\n\nex:feature-csv-export a saas:Feature ;\n    saas:name \"CSVエクスポート\" ;\n    saas:featureCode \"data.csv-export\" .\n\nex:feature-api-access a saas:Feature ;\n    saas:name \"APIアクセス\" ;\n    saas:featureCode \"integration.api-access\" .\n\nex:feature-saml-sso a saas:Feature ;\n    saas:name \"SAML SSO\" ;\n    saas:featureCode \"security.saml-sso\" .\n\nex:feature-audit-log a saas:Feature ;\n    saas:name \"監査ログ\" ;\n    saas:featureCode \"security.audit-log\" .\n\nex:feature-sales-management a saas:Feature ;\n    saas:name \"販売管理\" ;\n    saas:featureCode \"sales.management\" .\n\nex:feature-inventory-management a saas:Feature ;\n    saas:name \"在庫管理\" ;\n    saas:featureCode \"inventory.management\" .\n\n#\n# Entitlements（Standard Plan由来。販売管理機能に対するEntitlementは存在しない）\n#\n\nex:entitlement-abc-project-management a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-project-management ;\n    saas:entitlementStatus \"active\" .\n\nex:entitlement-abc-csv-export a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-csv-export ;\n    saas:entitlementStatus \"active\" .\n\nex:entitlement-abc-api-access a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-api-access ;\n    saas:entitlementStatus \"active\" .\n\n\n5.4 実行スクリプトを保存する\nこれまでの記事ではsparqlquery\nコマンドを使いましたが、あのコマンドは推論をしてくれません。今回は推論の有無を切り替えたいので、短いスクリプトを用意します。query.py\nとして保存してください。\n\n\"\"\"query.py — Turtleファイルを読み込んでSPARQLを実行する。\n\n使い方:\n    python query.py <ttlファイル...> <クエリファイル.rq>\n    python query.py --inference <ttlファイル...> <クエリファイル.rq>\n\n--inference を付けると、T-Boxの定義（owl:inverseOf、rdfs:subPropertyOfなど）に\n基づく推論を行ったうえで問い合わせる。\n\"\"\"\n\nimport sys\n\nfrom rdflib import Graph\n\ndef main(argv: list[str]) -> int:\n    use_inference = \"--inference\" in argv\n    paths = [a for a in argv if a != \"--inference\"]\n\n    if len(paths) < 2:\n        print(__doc__)\n        return 1\n\n    *data_files, query_file = paths\n\n    graph = Graph()\n    for path in data_files:\n        graph.parse(path, format=\"turtle\")\n\n    triples_before = len(graph)\n\n    if use_inference:\n        import owlrl\n\n        owlrl.DeductiveClosure(owlrl.OWLRL_Semantics).expand(graph)\n\n    with open(query_file, encoding=\"utf-8\") as f:\n        query = f.read()\n\n    results = graph.query(query)\n\n    print(\",\".join(str(v) for v in results.vars))\n    for row in results:\n        print(\",\".join(\"\" if value is None else str(value) for value in row))\n\n    mode = \"推論あり\" if use_inference else \"推論なし\"\n    print(\n        f\"\\n-- {len(results)} 行 / {mode}\"\n        f\"（トリプル数 {triples_before} -> {len(graph)}）\"\n    )\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n\n\nやっていることは3つだけです。\n\n1. 指定されたTurtleファイルを全部1つのグラフに読み込む\n2. --inference が付いていたら推論して、導けるトリプルを追加する\n3. SPARQLを実行して結果を表示する\n\n\n推論にOWLRL_Semanticsを指定している点だけ、先に理由を書いておきます。owlrlには軽いRDFS_Semanticsもありますが、今回主役になる\nowl:inverseOfはOWLの語彙なので、RDFS推論では処理されません。この違いは8章でもう一度触れます。\n\n\n--------------------------------------------------------------------------------\n\n6. T-Boxは、本当に結果を変えるのか\n6.1 まず問い合わせてみる\n「株式会社ABCが使える機能は何か？」を聞きます。query-features.rqとして保存してください。\n\n# query-features.rq — 株式会社ABCが使える機能は何か？\nPREFIX saas: <https://example.com/ontology/saas#>\nPREFIX ex:   <https://example.com/data/>\n\nSELECT ?featureName ?featureCode\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?entitlement .\n    ?entitlement saas:entitlesFeature ?feature .\n    ?entitlement saas:entitlementStatus \"active\" .\n    ?feature saas:name ?featureName .\n    ?feature saas:featureCode ?featureCode .\n}\nORDER BY ?featureCode\n\n\n注目してほしいのは1行目のパターンです。\n\nex:tenant-abc saas:hasEntitlement ?entitlement .\n\n\nTenant側からsaas:hasEntitlementでたどっています。一方、data.ttlに書いてあるのは逆向きのsaas:entitledTenant\nだけでした。\n\n実行します。\n\npython query.py ontology.ttl data.ttl query-features.rq\n\n\n結果:\n\nfeatureName,featureCode\n\n-- 0 行 / 推論なし（トリプル数 103 -> 103）\n\n\n0行です。T-Boxを読み込んでいるにもかかわらず、何も返りません。\n\nこれは、多くの人が最初にハマるところです。\n\nT-Boxに owl:inverseOf と書いただけでは、\n逆方向のトリプルは自動的には存在しない。\n\n\nRDFのグラフには、書かれたトリプルしか入っていません。ex:entitlement-abc-csv-export saas:entitledTenant\nex:tenant-abcは存在しますが、ex:tenant-abc saas:hasEntitlement\nex:entitlement-abc-csv-exportは、まだどこにも書かれていないのです。\n\nここで、正直に書いておかなければならないことがあります。\n\n「逆方向をたどるには推論が必須」というわけではない。\n\n\n次の2つは、どちらも推論なしで3件返ります。\n\n# データが書かれている向きで、そのまま問う\n?entitlement saas:entitledTenant ex:tenant-abc .\n\n# SPARQLの逆パス演算子 ^ を使う\nex:tenant-abc ^saas:entitledTenant ?entitlement .\n\n\nつまり、推論の価値は「逆方向が取れること」そのものではありません。\n\n推論の本当の価値:\n\n    データがどちら向きに書かれていても、\n    問い合わせる側は saas:hasEntitlement 一本で書ける。\n\n    そして次の7章で見るように、\n    クエリを1文字も変えないまま、\n    複数のシステムのデータを束ねられる。\n\n\nデータがどちら向きに書かれているかという違いは、間に挟まる層が吸収します。だから問い合わせる側は、データの書かれ方を知らなくてよい。1章で説明した「レイヤー（層）」の価値は、ここにあります。\n\n6.2 推論を有効にする\n--inferenceを付けて、もう一度実行します。\n\npython query.py --inference ontology.ttl data.ttl query-features.rq\n\n\n結果:\n\nfeatureName,featureCode\n基本プロジェクト管理,core.project-management\nCSVエクスポート,data.csv-export\nAPIアクセス,integration.api-access\n\n-- 3 行 / 推論あり（トリプル数 103 -> 368）\n\n\n3行返ってきました。そして、トリプル数が103から368に増えています（この増え方の内訳は8章で見ます）。\n\n何が起きたのでしょうか。\n\nT-Boxにこう書いてあった:\n    saas:entitledTenant owl:inverseOf saas:hasEntitlement\n\nA-Boxにこう書いてあった:\n    ex:entitlement-abc-csv-export saas:entitledTenant ex:tenant-abc\n\n推論エンジンは、この2つから次を導き出した:\n    ex:tenant-abc saas:hasEntitlement ex:entitlement-abc-csv-export\n\n\nつまり、T-Boxに書いた1行（owl:inverseOf）が、問い合わせの結果を0行から3行に変えた\nのです。T-Boxは人間向けの説明書きではありません。推論エンジンが読んで、データに書かれていない事実を導くためのルールとして働きました。\n\nontology.ttlを読み込まずに推論しても、この結果は出ません。逆方向であるという情報が、どこにも無いからです。ぜひontology.ttl\nを外して試してみてください。\n\n6.3 販売管理機能について聞いてみる\nでは冒頭の問いに戻ります。query-sales.rqとして保存してください。\n\n# query-sales.rq — 株式会社ABCは、販売管理機能を使えるか？\nPREFIX saas: <https://example.com/ontology/saas#>\nPREFIX ex:   <https://example.com/data/>\n\nSELECT ?featureName ?status\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?entitlement .\n    ?entitlement saas:entitlesFeature ex:feature-sales-management .\n    ?entitlement saas:entitlementStatus ?status .\n\n    ex:feature-sales-management saas:name ?featureName .\n}\n\n\n実行します。\n\npython query.py --inference ontology.ttl data.ttl query-sales.rq\n\n\n結果:\n\nfeatureName,status\n\n-- 0 行 / 推論あり（トリプル数 103 -> 368）\n\n\n推論を有効にしても0行です。これは推論不足ではなく、グラフの中に該当するEntitlementが存在しないという答えです。\n\n厳密に言うと、OWLの推論は「書かれていない＝偽」とは考えません。これを開世界仮定と呼びます。反対に、書かれていないことを偽とみなす考え方を閉世界仮定と呼びます。ふつうのデータベースは、こちらの考え方です。\n\nそれでもここで「使えない」と言い切れるのは、「Entitlementはすべてこのグラフに記録されている」という業務上の前提\nを置いているからです。Entitlementの記録元を網羅しているからこそ、0行を「無い」と読めます。ただし、7章で見るとおり、実務ではこの前提こそが崩れやすいのです。\n\n株式会社ABCは、販売管理機能を使えない。\n販売管理機能を対象とした有効なEntitlementが、\nグラフの中に1つも存在しないから。\n\n\ndata.ttlを見ると、ABCが契約しているStandard Planにも、販売管理は含まれていません。クエリの結果とも矛盾しません。\n\n冒頭の問いに、事実に基づいて答えられました。\n\n\n--------------------------------------------------------------------------------\n\n7. 本番: バラバラの語彙を、1つの問いで横断する\nここまでは1つのシステムのデータだけでした。ここからが、セマンティックレイヤーが本当に役に立つ場面です。\n\n7.1 現実の状況\n実務では、こうなっています。\n\n株式会社ABCは、商談の結果、\nStandard Planに含まれないSAML SSOと監査ログを\n個別に付与されている。\n\nただし、その事実の記録場所はバラバラ:\n\n    請求システム : SAML SSO を課金対象として登録\n                  → billing:grantedFeature という項目名\n\n    CRM          : 監査ログ を有効化済みとして記録\n                  → crm:enabledFeature という項目名\n\n\n同じ「機能を利用可能にした」という意味なのに、項目名が違います。普通なら、どちらかに寄せる変換バッチを書くところです。\n\nセマンティックレイヤーでは、データを書き換えず、意味の対応だけを宣言します。\n\n7.2 マッピングを宣言する\nmapping.ttlとして保存してください。\n\n# mapping.ttl — 各ソースシステムの語彙を、共通語彙 saas: に接続する\n@prefix saas:    <https://example.com/ontology/saas#> .\n@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix billing: <https://example.com/source/billing#> .\n@prefix crm:     <https://example.com/source/crm#> .\n\nbilling:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .\nbilling:forCustomer    rdfs:subPropertyOf saas:entitledTenant .\n\ncrm:enabledFeature     rdfs:subPropertyOf saas:entitlesFeature .\ncrm:forAccount         rdfs:subPropertyOf saas:entitledTenant .\n\n\nrdfs:subPropertyOfは、「このプロパティは、あのプロパティの一種である」という宣言です。\n\nbilling:grantedFeature rdfs:subPropertyOf saas:entitlesFeature\n\n意味:\n    「請求システムのgrantedFeatureで結ばれているものは、\n      saas:entitlesFeatureでも結ばれていると見なしてよい」\n\n\nこれだけです。4行の宣言で、2つのシステムの語彙が共通語彙に接続されました。\n\n7.3 各システムのデータをそのまま置く\ndata-multi-source.ttlとして保存してください。各システムの語彙のまま、一切書き換えていません。\n\n# data-multi-source.ttl — 2つの別システムから取り込んだ、そのままの形のデータ\n@prefix saas:    <https://example.com/ontology/saas#> .\n@prefix ex:      <https://example.com/data/> .\n@prefix billing: <https://example.com/source/billing#> .\n@prefix crm:     <https://example.com/source/crm#> .\n\n#\n# 請求システム由来（billing: 語彙）\n#\n\nex:billing-entitlement-001\n    billing:forCustomer ex:tenant-abc ;\n    billing:grantedFeature ex:feature-saml-sso ;\n    saas:entitlementStatus \"active\" .\n\n#\n# CRM由来（crm: 語彙）\n#\n\nex:crm-entitlement-042\n    crm:forAccount ex:tenant-abc ;\n    crm:enabledFeature ex:feature-audit-log ;\n    saas:entitlementStatus \"active\" .\n\n\n7.4 同じクエリで、横断して問い合わせる\nなお、a saas:Entitlement\nというクラス型付けを書いていません。第3回でEntitlementをクラスとして定義したので気になるかと思いますが、今回の問いはプロパティの経路だけでたどれるため、省いています。型付けが必要になるのは、SHACLで検証する場合や、クラス単位で数え上げる場合です。\n\nここが大事なところです。クエリは6.1節のものを、1文字も変えません。\n\npython query.py --inference \\\n    ontology.ttl mapping.ttl data.ttl data-multi-source.ttl \\\n    query-features.rq\n\n\n結果:\n\nfeatureName,featureCode\n基本プロジェクト管理,core.project-management\nCSVエクスポート,data.csv-export\nAPIアクセス,integration.api-access\n監査ログ,security.audit-log\nSAML SSO,security.saml-sso\n\n-- 5 行 / 推論あり（トリプル数 113 -> 410）\n\n\n3行から5行に増えました。増えたのは、請求システム由来のSAML SSOと、CRM由来の監査ログです。\n\n改めて、何をやったか整理します。\n\nやったこと:\n    ・請求システムのデータを、billing: 語彙のまま置いた\n    ・CRMのデータを、crm: 語彙のまま置いた\n    ・「これらは saas: の何に相当するか」を4行宣言した\n\nやらなかったこと:\n    ・項目名をそろえるために、データを書き換える変換バッチやETL処理を書く\n    ・クエリを1文字でも変更する\n\n\nこれが、セマンティックレイヤーです。\n\nデータを1箇所に集めて形を揃えるのではなく、\n「意味の対応」を宣言することで、\nバラバラのままのデータを1つの語彙で問い合わせる。\n\n\n新しいシステムが増えたら、mapping.ttl\nに数行足すだけです。クエリを書く側は、各システムの項目名を知らなくてよい。違いは間の層がすべて引き受けます。これが「レイヤー」と呼ばれる理由です。\n\n\n--------------------------------------------------------------------------------\n\n8. SQLと比べて、何が違うのか\n「同じことをSQLでもできるのでは？」という疑問に、正直に答えます。\n\nまずSQLで書いてみます。\n\nSELECT f.name, f.code\nFROM entitlements e\nJOIN features f ON e.feature_id = f.id\nWHERE e.tenant_id = 'abc' AND e.status = 'active'\n\nUNION\n\nSELECT f.name, f.code\nFROM billing_entitlements be\nJOIN features f ON be.granted_feature_id = f.id\nWHERE be.customer_id = 'abc' AND be.status = 'active'\n\nUNION\n\nSELECT f.name, f.code\nFROM crm_entitlements ce\nJOIN features f ON ce.enabled_feature_id = f.id\nWHERE ce.account_id = 'abc' AND ce.status = 'active';\n\n\nできます。ちゃんと動きます。ただし、違いが2つあります。\n\n違い1: 増えたときに変わるのはどちらか\n\nSQL:\n    4つ目のシステムが増えたら、\n    このクエリ自体に UNION を1ブロック足す。\n    このクエリを使っている全ての箇所を直す。\n\nセマンティックレイヤー:\n    mapping.ttl に2行足す。\n    クエリは変更しない。\n\n\nただし公平に言えば、実務のSQLでは、このUNIONをあちこちに書いたりはしません。dbtなどの変換処理で1つのentitlements\nテーブル（またはビュー）にまとめておけば、直す場所は1箇所で済みます。2章で見たメトリクス型のセマンティックレイヤーも、この前提に立っています。\n\nそのため、本当の違いは次の「違い2」のほうです。\n\n違い2: 「同じ意味である」がどこに書かれているか\n\nSQLのUNION\nは、「この3つのテーブルは同じものとして扱う」という判断をクエリの中に埋め込んでいます。その判断の根拠は、クエリを書いた人の頭の中にしかありません。\n\n一方、rdfs:subPropertyOf\nは、その判断をクエリの外に、機械が読める宣言として書いています。だから推論エンジンが自動で適用できるし、他の人も他のツールも同じ判断を再利用できます。\n\nもう1点あります。よく「SPARQLならスキーマ変更に強い」と言われますが、それは半分だけ本当です。\n\nSPARQLも、述語のIRIが変わればクエリは壊れる。\nその意味では、カラム名が変わればSQLが壊れるのと同じ。\n\n本当の違いは、\n「変更に強い」ことではなく、\n「意味の対応関係を、クエリの外に、\n  機械可読な形で書き出せる」こと。\n\n\nそして、これにはコストもあります。\n\n推論のコスト:\n    今回は103トリプルが368に増えた（約3.5倍）。\n\n\nただし、この「3.5倍」を鵜呑みにしないでください。増えた265トリプルの内訳を数えると、こうなっています。\n\n増加 265トリプルの内訳:\n\n    262件  W3C語彙（owl:, rdfs: など）に関わるトリプル\n           ・大半（約230件）は、データの量と関係なく毎回追加されるもの\n             （例:「owl:Thing はクラスである」のような事実）\n           ・残りは、データ中のIRIや値ごとに機械的に付く自明なもの\n             （例:「X は X と同じものである（owl:sameAs）」）\n      3件  saas:hasEntitlement の導出（＝実際に欲しかったもの）\n\n\nつまり3.5倍の大半は、データ量に関係なく発生する固定オーバーヘッドです。A-Boxが小さいほど倍率は大きく見えます。データを増やすと、倍率そのものは下がっていきます。実際に\ndata.ttlと同じ形のデータを20倍に増やして試すと、推論後のトリプル数は推論前の約1.8倍まで下がりました。\n\nとはいえ、絶対的な計算量とメモリは確実に増えます。本番では次のような判断が必要になります。\n\n・推論結果を事前に計算して保存しておく（マテリアライズ。問い合わせのたびには推論しない）\n・推論を使わず、SPARQLの逆パス演算子（6.1節の ^）で書く\n・必要な導出だけを CONSTRUCT（クエリ結果から新しいトリプルを作る構文）で明示的に作る\n\n\nここで1つ注意点があります。「軽いRDFS推論に絞る」という対策をよく見かけますが、今回のハンズオンではそれをやると、うまく動かなくなります。\n\nowl:inverseOf は、RDFS推論では処理されない。\n\n実測:\n    RDFS_Semantics  → 0行\n    OWLRL_Semantics → 3行\n\n\nrdfs:subPropertyOfはRDFS語彙なのでRDFS推論でも処理されますが、owl:inverseOfはOWLの語彙です。この記事のquery.py\nがOWLRL_Semanticsを使っているのは、そのためです。\n\n魔法ではありません。トレードオフのある設計選択です。\n\n\n--------------------------------------------------------------------------------\n\n9. メトリクス型と比べて、何が違うのか\n8章ではSQLと比べました。ここでは、2章で見たメトリクス型のセマンティックレイヤーと比べます。\n\n違いが一番よくわかるのは、得意な問いが違うところです。2つの問いを用意しました。\n\nQ1. プラン別に、毎月いくつ新しい契約が始まっているか？\n    （集計の問い）\n\nQ2. 株式会社ABCというテナントは、販売管理機能を使えるか？\n    （関係をたどる問い。しかも記録が複数システムに散らばっている）\n\n\n9.1 Q1（集計）は、メトリクス型の得意分野\nQ1は、2.3節で見たとおり、メトリクス型ならきれいに書けます。\n\nオントロジー型でも、SPARQLで集計はできます。Subscriptionの開始日を表すsaas:subscriptionStart\nは後続の会で正式に定義する予定ですが、ここでは先取りして使います。\n\nSELECT ?planName ?month (COUNT(DISTINCT ?sub) AS ?newSubscriptions)\nWHERE {\n    ?sub a saas:Subscription ;\n         saas:basedOnPlan ?plan ;\n         saas:subscriptionStart ?started .\n    ?plan saas:name ?planName .\n    BIND(SUBSTR(STR(?started), 1, 7) AS ?month)\n}\nGROUP BY ?planName ?month\n\n\n書けます。ただし、次のような点でメトリクス型に分があります。\n\n・「new_subscriptions とはこの計算式である」という定義を、\n  再利用可能な部品として置く標準的な仕組みが、OWLにはない。\n  （OWLは「何が存在するか」の言語であって、「どう数えるか」の言語ではない。\n    統計などの多次元データを記述する W3C の RDF Data Cube 語彙はあるが、\n    計算式そのものを定義する、広く使われた標準はない）\n\n・時間の粒度（日・週・月）、足し算できない指標（期末残高など）の扱いを、\n  メトリクス型は定義側で面倒を見てくれる。SPARQLでは毎回書く。\n\n・数億行の集計は、列指向のデータウェアハウスのほうが圧倒的に速い。\n\n\n9.2 Q2（関係・横断）は、オントロジー型の得意分野\nQ2の答えは、「ABCに、販売管理機能を対象とした有効なEntitlementがあるか」です。しかも現実には、その記録が請求システムとCRMに、別々の項目名で散らばっています。\n\n請求システム: billing_entitlements.granted_feature_id\nCRM:          crm_entitlements.enabled_feature_id\n\n\nオントロジー型では、こう書くだけで2つを同じ意味として扱えます。\n\nbilling:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .\ncrm:enabledFeature     rdfs:subPropertyOf saas:entitlesFeature .\n\n\n問い合わせる側はsaas:entitlesFeature一本で書けばよく、データ自体は書き換えません。これは7章で実際に確かめたとおりです。\n\nでは、メトリクス型ではQ2はどうなるか。ここは公平に書いておきます。\n\n「メトリクス型は複数ソースを束ねられない」は、正確ではない。\n\nメトリクス型の世界では、\n複数ソースの統合は、セマンティックレイヤーの「下」で済ませておく。\ndbt などの変換処理で、請求とCRMのテーブルを\n1つの entitlements テーブルに揃えてから、その上に定義を載せる。\n\n\nつまり、違いは「束ねられるか」ではなく、束ねる判断をどこに書くかです。\n\nメトリクス型:\n    「grantedFeature と enabledFeature は同じ意味だ」という判断は、\n    変換処理（SQL）の中に書かれる。\n\nオントロジー型:\n    同じ判断を、変換処理の外に、\n    機械が読める「意味の宣言」として書く。\n\n\nそして、Q2はそもそも集計の問いではありません。メトリクスとディメンションという型に、自然には収まらない問いです。\n\n9.3 比較表\nここまでの比較を表にまとめます。\n\n観点メトリクス型オントロジー型定義する単位メジャー・ディメンション・結合クラス・プロパティ・関係答えやすい問いどれだけ？（集計）何が何と、どうつながっている？\n定義の置き場所テーブルの上（DWHのカタログ）テーブルから独立（Turtleなど）複数ソースの語彙合わせ下の変換層（dbt等）で済ませておく\nマッピングとして宣言する推論なし（集計の意味づけはある）あり（逆関係、上位プロパティ、クラス階層）書かれていない事実の扱い「無い」とみなす（閉世界仮定）\n推論では「わからない」とみなす（開世界仮定、6.3節）。ただしSPARQLの結果やSHACLの検証は、グラフにあるものだけで判断する制約の検証\n定義の整合性は作成時に検証される。データの制約はテスト（dbt testsなど）で確認するSHACLで宣言する集計性能強い（列指向エンジン）\n弱め（大規模集計は不得意）標準Apache Ossie（旧OSI）が整備中W3C標準（RDF/OWL/SPARQL/SHACL）人材・ツール豊富少ない\nAIとの組み合わせセマンティックモデルを手がかりに、自然文→SQL（Cortex Analyst、Genieなど）自然文→SPARQL、ナレッジグラフ9.4\nメトリクス型のPros / Cons\nPros:\n    ・「数字が合わない」問題に、直接対応する\n    ・データウェアハウスの中にあり、権限管理も性能もそのまま使える\n    ・SQLの延長なので、データ分析の人材がそのまま使える\n    ・BIツールやAIエージェントとの連携が、製品として整っている\n\nCons:\n    ・定義はテーブル構造に依存する。テーブルが先、意味が後\n    ・「AとBは同じ意味」という判断が、変換処理のSQLに埋もれやすい\n    ・推論がない。書いた計算式以上のことは出てこない\n    ・集計にならない問い（関係・資格・依存）は、型に収まりにくい\n\n\n9.5 オントロジー型のPros / Cons\nPros:\n    ・意味の体系がテーブルから独立しているので、ソースが増えても影響を受けにくい\n    ・意味の対応関係を、クエリの外に機械可読な形で書ける\n    ・推論で、書かれていない事実を導ける\n    ・W3C標準なので、特定ベンダーに閉じない\n\nCons:\n    ・集計・時間粒度・大規模データの扱いは、メトリクス型に劣る\n    ・RDF/SPARQLを扱える人が少ない\n    ・既存のRDBのデータをRDFとして扱う工程（変換か仮想化）が別途必要\n    ・推論には計算コストがかかり、設計判断が要る\n\n\nどちらも魔法ではありません。目的が違う道具です。\n\n\n--------------------------------------------------------------------------------\n\n10. AIがセマンティックレイヤーを使う場合\nもう1つ、いまの時代に重要な応用に触れておきます。\n\nLLM（大規模言語モデル）に「株式会社ABCは販売管理機能を使えますか？」と聞いたとき、何が起きるべきでしょうか。\n\nセマンティックレイヤーが無い場合:\n    LLMが学習データからそれっぽい答えを生成する\n    → ハルシネーション（幻覚）のリスク\n\nセマンティックレイヤーがある場合:\n    LLMが、問いをSPARQLに変換する\n    → セマンティックレイヤーに問い合わせる\n    → 返ってきた事実に基づいて答える\n\n\nセマンティックレイヤーがあれば、LLMは「答えを創作する」のではなく「問いを翻訳する」役割に徹することができます。\n\n人間の質問:\n    「株式会社ABCは販売管理機能を使える？」\n\nLLMが生成するSPARQL:\n\n\nPREFIX saas: <https://example.com/ontology/saas#>\nPREFIX ex:   <https://example.com/data/>\n\nSELECT ?featureName ?status\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?e .\n    ?e saas:entitlesFeature ?f .\n    ?f saas:name ?featureName .\n    FILTER(CONTAINS(?featureName, \"販売管理\"))\n    ?e saas:entitlementStatus ?status .\n}\n\n\nセマンティックレイヤーの応答:\n    （0行）\n\nLLMの最終回答:\n    「株式会社ABCは現在、販売管理機能を利用できません。\n      この機能に対する有効なEntitlementが\n      存在しないためです。」\n\n\nここでLLMが正しいクエリを組み立てられるのは、T-Boxに「Tenant」「Entitlement」「Feature」が何であり、どうつながっているかが書いてあるからです。T-Boxからは、クエリの形（どのプロパティをたどるか）が分かります。絞り込む値の「販売管理」は、LLMが質問の言葉から取り出しています。\n\nLLMにテーブル定義だけを渡した場合:\n    「entitlements.status って何の状態？」\n    「tenant_id と customer_id は同じもの？」\n    → 推測が入る\n\nLLMにT-Boxを渡した場合:\n    第3回で Entitlement の rdfs:comment に書いた\n    「特定のTenantに、特定のFeatureを提供する権利、資格または状態」\n    が読める\n    → 推測が減る\n\n\n第1回からrdfs:labelとrdfs:commentを丁寧に書いてきたのは、人間のためだけではなかったわけです。\n\nなお、今回のontology.ttlは、説明を短くするためにrdfs:comment\nを省いています。実際にLLMへ渡すなら、第3回までの定義をコメントごと渡します。\n\nこの方向性は、メトリクス型の製品にも共通しています。どの製品も、AIエージェント向けの機能を足しています。\n\nSnowflake   Semantic View に、AI向けの指示や検証済みクエリを書ける句がある\n            → Cortex Analyst などが参照する\nDatabricks  Metric View に synonyms（同義語）を書ける\n            → Genie が参照する\nLooker      LookML を、Gemini の会話型分析の根拠として使う\n\n\n目的は共通していて、「LLMに計算を推測させず、定義に従って決定的に答えさせる\n」ことです。これは、この章で見た「LLMを『答えを創作する役』から『問いを翻訳する役』にする」という話と、まったく同じ方向です。\n\n\n--------------------------------------------------------------------------------\n\n11. 2つの系統は近づいている\nここまで「2つの系統」として分けてきましたが、実は最近、両者の境界は少しずつ曖昧になっています。きっかけは、10章で見たとおり、どちらもAIに正確に答えさせる手段として注目されていることです。\n\n11.1 Microsoft Fabric IQのOntology\n一番わかりやすい合流の例が、MicrosoftがIgnite 2025で発表したFabric\nIQです。その中核に、Ontologyというアイテム（2026年10月時点でプレビュー）があります。\n\nFabric IQ の Ontology で定義するもの（公式ドキュメントより）:\n\n    entity type      （≒ クラス）\n    relationship     （≒ プロパティ）\n    継承・namespace\n    metric           ← Power BI の Semantic Model の DAX メジャーを参照する\n    rule             （自然言語で書く）\n\n\n面白いのは、metricをオントロジーの中で「参照」している点です。計算式そのもの（DAX）は元のSemantic\nModelが持ち続け、オントロジーはそれを「このエンティティに関する指標」として結びつけます。\n\nオントロジー   : 「Tenant とは何か」「何とつながっているか」\nメトリクス層   : 「Tenant ごとの数字を、どう計算するか」\n\n→ 置き換えるのではなく、重ねている\n\n\nまた、FabricのOntologyはRDF / Turtle / OWLのサブセットの取り込みと、RDF /\nTurtleでの書き出しをドキュメントに載せています（取り込み先は空のOntologyアイテムで、取り込み結果は、そのまま保たれた要素・自動で調整された要素・対応していない要素に分けて報告されます）。データ・分析基盤の大手が、W3Cのオントロジー形式との行き来を公式にうたった例です。\n\nさらに、FabricのOntologyは基本的には自分でデータを持たない「仮想的な意味のレイヤー」で、実際の問い合わせは接続先のデータソースで実行されます（多段の関係をたどる場合などは、FabricのGraphにデータを任意で実体化することもできます）。これは、RDFの世界で以前からあるR2RML\n/ Ontop（SPARQLをSQLに書き換えて、元のDBで実行する）とよく似た発想です。\n\n11.2 Apache Ossie も、オントロジーを対象に加えた\n2章で触れたOSI（現 Apache Ossie）も、同じ方向に動いています。2026年7月にApache\nIncubatorへ移ったとき、Ossieは自らを「セマンティックレイヤーとオントロジーの両方のための仕様」と位置づけ直しました。\n\nさらに、草案段階（v0.2.0.dev0）のオントロジー仕様では、概念や関係にiri\nフィールドを持たせ、既存のRDF/OWL語彙のクラスやプロパティと対応づけられるようになっています。メトリクス型から始まった標準化が、オントロジー、さらにW3Cの語彙との接続にまで広がってきたわけです。\n\n11.3 「オントロジー」という言葉の広がり\nちなみに、Palantir\nFoundryのOntologyは、オブジェクト型・リンク型・アクション型という独自のモデルで業務の世界を表現するもので、RDF/OWLをデータモデルの基盤にはしていません（Palantir自身の資料では、RDF/OWL形式との入出力もうたわれています）。MicrosoftのGraphRAGも、LLMでテキストからナレッジグラフを作るもので、RDFではありません。\n\n「オントロジー」「ナレッジグラフ」と名乗っていても、\nW3C標準の RDF/OWL とは限らない。\n\n\nここは混乱しやすいので、覚えておくと便利です。\n\n\n--------------------------------------------------------------------------------\n\n12. レイヤー構成を整理する\n最後に、全体像を整理します。\n\n問い合わせる側（BIツール、AI、スクリプト、人間）\n    │\n    │  共通語彙 saas: で問う\n    ↓\nセマンティックレイヤー\n    ├── ontology.ttl ＝ T-Box（意味の定義）\n    └── mapping.ttl  ＝ 各ソース語彙との意味の対応\n    │\n    │  推論して、書かれていない事実も導く\n    ↓\nRDFとして表現されたデータ（A-Box）\n    │\n    │  ここに、元のデータをRDFに変換する（取り込む）工程が必要\n    ↓\n実際のデータソース\n    ├── Postgres\n    ├── SaaSのAPI\n    └── CSV など\n\n\nここで、正直に書いておかなければならないことがあります。\n\n今回の記事では、data.ttl を手で書いた。\n\nしかし実務では、データはPostgresやSaaSのAPIの中にある。\nそれをA-Box（RDF）にする工程は、\n誰かが作らなければならない。\n\n\nこの工程は、この記事では扱っていません。実際には、次のような選択肢があります。\n\n・定期的にRDFへ書き出す（マテリアライズ）\n・問い合わせ時にSQLへ変換する（R2RML / Ontop などの仮想化。データはRDBに置いたまま、SPARQLをSQLに書き換えて実行する）\n・アプリケーション側でRDFとして書き出す\n\n\n「セマンティックレイヤーがあれば変換が不要になる」わけではありません。7章で不要になったのは、項目名をそろえるための変換でした（mapping.ttl\nが語彙どうしの対応を引き受けたからです）。PostgresなどのデータをRDFの形にする変換は、依然として必要です\n。セマンティックレイヤーが不要にするのは、「システムごとにバラバラな意味の解釈を、問い合わせる側が毎回やり直すこと」です。\n\nメトリクス型のセマンティックレイヤーも含めて描き直すと、こうなります。\n\n問い合わせる側（BIツール、AIエージェント、人間）\n    │\n    ├──「どれだけ？」────→ メトリクス型セマンティックレイヤー\n    │                      （Snowflake / Databricks / Fabric / dbt）\n    │                         │\n    │                         │ 「Tenant」「Plan」の意味は\n    │                         │ オントロジーに従う\n    │                         ↓\n    └──「何が何と、どう？」→ オントロジー型セマンティックレイヤー\n                              （このシリーズで作っている saas: 語彙）\n                                 │\n                                 ↓\n                            各システムのデータ\n                            （SaaS管理基盤、請求、CRM、サポート…）\n\n\nオントロジー   = 意味の基準\n    「Tenantとは何か」\n    「EntitlementとSubscriptionはどう違うか」\n    「請求システムの customer と CRM の account は同じものか」\n\nメトリクス層   = 計算の基準\n    「Tenantごとの月間利用量を、どう集計するか」\n    「プラン別の新規契約数を、どう数えるか」\n\n\nメトリクス型で「アクティブテナント数」を定義するとき、その前提には「そもそもTenantとは何か」「どの状態をアクティブと呼ぶか」という取り決めがあるはずです。その取り決めを、テーブルやSQLの中に埋もれさせずに書き出したものが、オントロジーです。\n\n2つは競合ではなく、積み重なるものだと考えています。そして、このシリーズが作っているのは下の層、つまり意味の基準のほうです。\n\nここまで見てきた「意味の定義」は、このシリーズの第1回から第3回で作ってきたもの、そのものです。\n\n第1回：Tenant, User, Membership, Role, Permission, Subscription, Plan, Resource\n第2回：Organization, Workspace, Team, RoleAssignment\n第3回：Feature, Entitlement\n\n\nつまり、オントロジーを作ること＝セマンティックレイヤーの語彙を構築すること\nでした。第4回以降で語彙が増えるたびに、このセマンティックレイヤーが答えられる問いも増えていきます。\n\n\n--------------------------------------------------------------------------------\n\nまとめ\n「セマンティックレイヤー」には、2つの系統がある:\n\n    メトリクス型（Snowflake / Databricks / Fabric / dbt / Cube / Looker）\n        数字の計算のしかたを、テーブルの上に定義する\n        → 「どれだけ？」に強い。数字のズレをなくす\n\n    オントロジー型（RDF / OWL / SPARQL / SHACL）\n        ものごとの関係と意味を、テーブルから独立して定義する\n        → 「何が何と、どうつながっている？」に強い。推論と横断ができる\n\n    このシリーズが作っているのは、オントロジー型\n\nセマンティックレイヤーとは:\n    データの意味を一箇所に定義し、\n    どのツールからでも同じ意味で問い合わせられるようにする仕組み\n\nオントロジーとの関係:\n    T-Box（クラスとプロパティの定義）がセマンティックレイヤーの本体\n    A-Box（実データ）はT-Boxの語彙で記述される\n\nT-Boxは飾りではない:\n    owl:inverseOf を書いておくと、推論で逆方向をたどれる\n    → 推論なし 0行、推論あり 3行\n\n意味の対応を宣言すれば、語彙の違いを吸収できる:\n    rdfs:subPropertyOf で各システムの語彙を共通語彙に接続\n    → データもクエリも変更せず、3行から5行に\n\n正直な限界:\n    ・逆方向は、推論なしでも（逆パス ^ などで）取れる。\n      推論の価値は「問い合わせる側が共通語彙一本で書けること」\n    ・owl:inverseOf はRDFS推論では処理されない（OWLの推論が必要）\n    ・推論の増加分の大半は固定オーバーヘッド（265件中、約230件）\n    ・SPARQLも述語IRIが変われば壊れる\n    ・PostgresなどからA-Boxへの変換工程は別途必要\n\n\n手を動かした手順は、次の4ステップでした。\n\n1. pip install rdflib owlrl\n2. ontology.ttl（T-Box）と data.ttl（A-Box）を保存する\n3. python query.py ontology.ttl data.ttl query-features.rq\n   → 0行（hasEntitlement 方向のトリプルがまだ存在しない）\n4. python query.py --inference ontology.ttl data.ttl query-features.rq\n   → 3行（T-Boxの owl:inverseOf から、逆向きの事実が導かれた）\n\n\nここまでで、このシリーズで作っているオントロジーがどう使われるかが、なんとなく伝わったかと思います。具体的なユースケースでどう使うの？というのは、さらに別の会でもっと具体的に説明したいと思います。\n\n\n\n次回（第4回）からは、語彙の構築に戻ります。テーマは「使える。でも、どこまで？」です。Featureが使えるとして、その上限と消費量をどう表現するか、という話に入っていこうと思います！\n\nこちらからは以上です。\n\n\n--------------------------------------------------------------------------------\n\n参考資料（2026年10月時点）\n * Snowflake — Semantic views: \n   https://docs.snowflake.com/en/user-guide/views-semantic/querying / CREATE\n   SEMANTIC VIEW: \n   https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view\n * Snowflake — Open Semantic Interchange: \n   https://www.snowflake.com/en/blog/open-semantic-interchange-ai-standard/ /\n   仕様公開: \n   https://www.snowflake.com/en/blog/open-semantic-interchanges-specs-finalized/\n * Apache Ossie (incubating): \n   https://ossie.apache.org/updates/ossie-enters-apache-incubator/ /\n   オントロジー仕様（草案）: https://github.com/apache/ossie/blob/main/ontology/ontology.md\n * dbt v1.12（Ossie形式の読み込み）: \n   https://docs.getdbt.com/docs/dbt-versions/dbt-upgrade/upgrading-to-v1.12\n * Databricks — Metric views: https://docs.databricks.com/aws/en/metric-views/ /\n   Unity Catalog semantics: https://docs.databricks.com/aws/en/uc-semantics/\n * Microsoft — Fabric IQ: https://learn.microsoft.com/en-us/fabric/iq/overview /\n   Ontology: https://learn.microsoft.com/en-us/fabric/iq/ontology/overview\n * Microsoft — Power BI semantic model（旧 dataset）: \n   https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename \n   / XMLA エンドポイント: \n   https://learn.microsoft.com/en-us/fabric/enterprise/powerbi/service-premium-connect-tools \n   / Direct Lake: \n   https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-overview\n * dbt — MetricFlow の Apache 2.0 化: \n   https://www.getdbt.com/blog/open-source-metricflow-governed-metrics\n * Cube — SQL API: https://docs.cube.dev/reference/core-data-apis/sql-api\n * Looker — Open SQL Interface: \n   https://docs.cloud.google.com/looker/docs/sql-interface\n * Palantir Foundry Ontology: \n   https://www.palantir.com/docs/foundry/ontology/core-concepts\n * Microsoft GraphRAG: https://microsoft.github.io/graphrag/\n * Ontop（R2RML / 仮想ナレッジグラフ）: https://ontop-vkg.org/guide/","html":"<h1></h1><h1 id=\"%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E3%81%AF%E3%80%81%E7%B5%90%E5%B1%80%E3%81%A9%E3%81%93%E3%81%A7%E4%BD%BF%E3%81%86%E3%81%AE%E3%81%8B%EF%BC%9F\">作ったオントロジーは、結局どこで使うのか？</h1><p></p><h2 id=\"%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%A8%E3%81%84%E3%81%86%E8%80%83%E3%81%88%E6%96%B9%E3%82%92%E3%80%81%E6%89%8B%E3%82%92%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B\">セマンティックレイヤーという考え方を、手を動かして理解する</h2><p></p><p>今回は番外編です。<strong>新しいクラスは1つも追加しません。</strong></p><p>前回は、FeatureとEntitlementを導入しました。あわせて、Tenantから見た<code>hasEntitlement</code>と、Entitlementから見た<code>entitledTenant</code>という、逆プロパティのペアも定義しました。</p><p>ここでいったんクラスを増やす手を止めて、<strong>そこまでに作った語彙が実際どこで役に立つのか</strong>を確かめます。というのも、ここまで3回クラスを増やしてきて、そろそろこう思っている方がいるはずだからです。</p><pre><code class=\"language-text\">これ、作ったはいいけど、結局どこでどう使うの？\n</code></pre><p>たとえば、実際の業務では次のような問いが出てきます。</p><pre><code class=\"language-text\">株式会社ABCというテナントは、SaaSの機能としての販売管理機能を使えるのか？\n</code></pre><p>この質問に答えるために、あなたは今、どうしますか？</p><pre><code class=\"language-text\">・請求管理システムの契約画面を開いて確認する？\n・社内の管理DBにSQLを投げる？\n・Slackでカスタマーサクセスに聞く？\n</code></pre><p>どの方法でも答えは出ます。しかし、<strong>問いの意味自体</strong>は、どの方法で調べても同じはずです。</p><pre><code class=\"language-text\">このTenantに、\n販売管理Featureに対する有効なEntitlementが存在するか？\n</code></pre><p>これが、問いの正体です。第3回でFeatureとEntitlementを定義したのは、まさにこの「問いの正体」を書けるようにするためでした。</p><p>そして、この「意味の定義」を、複数のシステムから共通で使える形にしたものが、<strong>セマンティックレイヤー</strong>です。</p><hr><h2 id=\"1-%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">1. セマンティックレイヤーとは何か</h2><p>一言でいうと、</p><pre><code class=\"language-text\">セマンティックレイヤー：\n    データの意味を一箇所に定義し、\n    どのツール・どのシステムからでも\n    同じ意味で問い合わせられるようにする仕組み\n</code></pre><p>もう少し具体的にいきます。</p><pre><code class=\"language-text\">セマンティックレイヤーが無い世界:\n\n    請求システム  →  grantedFeature という項目で機能を持つ\n    CRM           →  enabledFeature という項目で機能を持つ\n    社内DB        →  entitlements テーブルの feature_id カラム\n\n    3つとも同じことを記録しているのに、\n    呼び名がバラバラで、横断して聞けない。\n\nセマンティックレイヤーがある世界:\n\n    共通語彙 saas:entitlesFeature\n    ├── 請求システムの grantedFeature\n    ├── CRMの enabledFeature\n    └── 社内DBの feature_id\n\n    どのシステムのデータでも、1つの問いで横断できる。\n</code></pre><p>セマンティックレイヤーの本質は、<strong>「どのカラムに何が入っているか」ではなく、「それが何を意味するか」を定義すること</strong>です。</p><p>「レイヤー（層）」と呼ぶのは、これが問い合わせる側とデータの間に挟まる層だからです。</p><pre><code class=\"language-text\">問い合わせる側（人、BIツール、AI）\n    │  共通の語彙で問う\n    ↓\nセマンティックレイヤー（意味の定義と、各システムとの対応）\n    │  各システムの書き方の違いを、ここで吸収する\n    ↓\n各システムのデータ（請求システム、CRM、社内DB…）\n</code></pre><p>間に層が1枚あるので、問い合わせる側は、下にあるデータがどう書かれているかを知らなくても済みます。</p><p>そして今回は、この「バラバラの語彙を1つの問いで横断する」ところまで、実際に手元で動かします。</p><p>ところで、「セマンティックレイヤー」で検索すると、たぶん次のようなものが出てきます。</p><pre><code class=\"language-text\">・Snowflake の Semantic Views\n・Databricks の Metric Views（Unity Catalog semantics）\n・Power BI / Microsoft Fabric の Semantic Model\n・dbt Semantic Layer、Cube、Looker の LookML\n</code></pre><p>そして、そこに書かれているのは、だいたいこういう話です。</p><pre><code class=\"language-text\">「売上」「アクティブユーザー数」の計算式を1箇所で定義して、\nどのBIツールからでも同じ数字が出るようにしましょう。\n</code></pre><p>一方、このシリーズで作っているのは、Turtleで書いたクラスとプロパティです。計算式は1つも出てきません。</p><pre><code class=\"language-text\">あれ？\nこのシリーズの「セマンティックレイヤー」と、\n世の中の「セマンティックレイヤー」、\n同じものの話をしている？\n</code></pre><p>結論から書きます。</p><pre><code class=\"language-text\">同じ言葉で、2つの系統のものが呼ばれている。\n\n    メトリクス型：数字の「計算のしかた」を定義する\n    オントロジー型：世界の「何が何であるか」を定義する\n\nこのシリーズが作っているのは、後者。\n</code></pre><p>この記事では、まずこの2つの違いを整理してから、オントロジー型のほうを実際に動かします。後半で手を動かしているときに「あれ？」とならないためです。</p><blockquote>製品の名称や提供状況は動きが速いため、本記事の内容は2026年10月時点の公式ドキュメントに基づいています。出典は末尾にまとめました。</blockquote><p>2つの系統とも、上で書いた「データの意味を一箇所に定義する」という点は同じです。違うのは、<strong>「意味」として何を定義するか</strong>です。</p><pre><code class=\"language-text\">メトリクス型が定義する「意味」:\n    「月間アクティブテナント数」とは、\n    subscriptions テーブルの status = 'active' の行を\n    tenant_id で重複排除して数えたもの\n\nオントロジー型が定義する「意味」:\n    「Tenant」とは、Subscription を持ち、\n    Entitlement を通じて Feature を利用できる単位であり、\n    hasEntitlement と entitledTenant は互いに逆向きの関係である\n</code></pre><p>前者は数字の作り方、後者はものごとの関係を定義しています。</p><p>この違いがどこから来たのかを見ていきます。</p><hr><h2 id=\"2-%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E5%9E%8B%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%AF%E3%80%81%E3%81%AA%E3%81%9C%E7%94%9F%E3%81%BE%E3%82%8C%E3%81%9F%E3%81%8B\">2. メトリクス型セマンティックレイヤーは、なぜ生まれたか</h2><h3 id=\"21-%E5%87%BA%E7%99%BA%E7%82%B9%E3%81%AF%E3%80%8C%E6%95%B0%E5%AD%97%E3%81%8C%E5%90%88%E3%82%8F%E3%81%AA%E3%81%84%E3%80%8D%E5%95%8F%E9%A1%8C\">2.1 出発点は「数字が合わない」問題</h3><p>メトリクス型が解こうとしてきた問題は、とても具体的です。</p><pre><code class=\"language-text\">営業のダッシュボード:  今月のアクティブテナント  128社\nCSのダッシュボード:    今月のアクティブテナント  131社\n経営会議の資料:        今月のアクティブテナント  125社\n</code></pre><p>3つとも間違ってはいません。トライアル中を含めるか、解約予定を含めるか、月末時点か月中の最大か。<strong>計算式が、ダッシュボードごとに少しずつ違う</strong>のです。</p><p>そこで、「計算式を1箇所に集めて、全員がそれを使う」という発想が生まれました。</p><h3 id=\"22-%E7%BD%AE%E3%81%8D%E5%A0%B4%E6%89%80%E3%81%AE%E5%A4%89%E9%81%B7\">2.2 置き場所の変遷</h3><p>この「計算式の置き場所」は、時代とともに移動してきました。</p><pre><code class=\"language-text\">第1世代: BIツールの中\n    Looker の LookML、Power BI のデータセット（現 Semantic Model）\n    → もともと、そのBIツールのために設計された。\n      いまは外部からも使えるが（Looker の Open SQL Interface、\n      Power BI の XMLA エンドポイントなど）、\n      ツール固有の言語や接続方式に合わせる必要がある。\n\n第2世代: BIツールから独立（ヘッドレス）\n    dbt Semantic Layer（MetricFlow）、Cube、AtScale\n    → どのBIツールからでも、APIやSQLで同じ定義を使える。\n      （dbt の API は dbt platform で提供される）\n\n第3世代: データ基盤の中\n    Snowflake Semantic Views、Databricks Metric Views\n    → データと同じ場所で、同じ権限管理の下に置かれる。\n</code></pre><p>Microsoft Fabric の Semantic Model は、第1世代の Power BI の Semantic Model と同じものかと思います。ただ、Fabric 上では Direct Lake という方式で OneLake のデータを直接読めるので、第1世代と第3世代の中間にあたります。</p><p>なお、この「世代」は説明のための筆者の整理で、業界やベンダーの公式な区分ではありません。新しい世代ほど優れている、という意味でもありません。いま「セマンティックレイヤー」として話題になっているのが、主に第3世代だということを表現したいため、この整理をしました。</p><h3 id=\"23-%E5%AE%9F%E9%9A%9B%E3%81%AB%E3%81%AF%E3%80%81%E3%81%93%E3%81%86%E6%9B%B8%E3%81%8F\">2.3 実際には、こう書く</h3><p>第1回で作ったTenant・Subscription・Planが、ふつうのテーブルとしてデータウェアハウスに入っているとして、具体的に見てみます。</p><pre><code class=\"language-text\">tenants       (tenant_id, name)\nsubscriptions (subscription_id, tenant_id, plan_id, status, started_at)\nplans         (plan_id, plan_name)\n</code></pre><p>ここで、次の問いを考えます。</p><pre><code class=\"language-text\">Q1. プラン別に、毎月いくつ新しい契約が始まっているか？\n</code></pre><p>※Q2は9章で出てきます。</p><p>SnowflakeのSemantic Viewでは、こう定義します。</p><pre><code class=\"language-sql\">-- あくまでもイメージで、動作保証はないです\nCREATE SEMANTIC VIEW subscription_sv\n  TABLES (\n    subscriptions PRIMARY KEY (subscription_id),\n    plans         PRIMARY KEY (plan_id)\n  )\n  RELATIONSHIPS (\n    subscriptions (plan_id) REFERENCES plans\n  )\n  DIMENSIONS (\n    plans.plan_name           AS plans.plan_name,\n    subscriptions.start_month AS DATE_TRUNC('MONTH', subscriptions.started_at)\n  )\n  METRICS (\n    subscriptions.new_subscriptions AS COUNT(DISTINCT subscriptions.subscription_id)\n  )\n  COMMENT = 'プラン別・月別の新規契約';\n</code></pre><p>使う側は、計算式を知らなくても、こう問い合わせるだけです。</p><pre><code class=\"language-sql\">SELECT plan_name, start_month, AGG(new_subscriptions)\nFROM subscription_sv\nGROUP BY plan_name, start_month;\n</code></pre><p>DatabricksのMetric Viewでは、YAMLで同じことを書きます。</p><pre><code class=\"language-sql\">-- あくまでもイメージで、動作保証はないです\nCREATE VIEW main.saas.subscription_metrics\nWITH METRICS LANGUAGE YAML AS $$\nversion: 1.1\nsource: main.saas.subscriptions\njoins:\n  - name: plan\n    source: main.saas.plans\n    on: source.plan_id = plan.plan_id\nfields:\n  - name: plan_name\n    expr: plan.plan_name\n  - name: start_month\n    expr: DATE_TRUNC('MONTH', started_at)\nmeasures:\n  - name: new_subscriptions\n    expr: COUNT(DISTINCT subscription_id)\n$$;\n\nSELECT plan_name, start_month, MEASURE(new_subscriptions)\nFROM main.saas.subscription_metrics\nGROUP BY ALL;\n</code></pre><p>書き方は違いますが、やっていることは同じです。</p><h3 id=\"24-%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E5%9E%8B%E3%81%AE%E3%80%8C%E5%85%B1%E9%80%9A%E3%81%AE%E5%BD%A2%E3%80%8D\">2.4 メトリクス型の「共通の形」</h3><p>製品は違っても、中身の構成要素はほぼ共通しています。</p><pre><code class=\"language-text\">論理テーブル     どのテーブルを使うか\nリレーション     テーブル同士をどう結合するか\nディメンション   どの切り口で分けるか（プラン別、月別…）\nメジャー         何をどう集計するか（COUNT, SUM, 比率…）\n同義語・説明     人間とAIのための補足\n</code></pre><p>そして、問い合わせ方も共通しています。</p><pre><code class=\"language-text\">Snowflake   AGG(new_subscriptions)\nDatabricks  MEASURE(new_subscriptions)\nCube        MEASURE(...)\nLooker      AGGREGATE(...)\n</code></pre><p><code>AGG()</code>や<code>MEASURE()</code>は、「定義側に書いてある集計式（ここでは<code>COUNT(DISTINCT …)</code>）で集計して」という指示です。使う側は、<code>COUNT</code>や<code>SUM</code>を自分で書く必要がありません。</p><p><strong>集計のしかたは定義側が持ち、使う側は切り口を選ぶだけ</strong>。これがメトリクス型の設計の核です。</p><p>この「共通の形」を標準にする動きも進んでいます。2025年9月、Open Semantic Interchange（OSI）が立ち上がり、2026年1月にはYAML/JSONで記述する仕様の初版（v0.1）が公開されました。2026年7月にはApache Incubatorに入り、Apache Ossie（incubating）に改称されています。</p><p>2026年1月の初版仕様はRDF/OWLに触れられていませんでしたが、2026年5月にはオントロジー仕様の草案が出ており、7月のApache移管の時点では『セマンティックレイヤーとオントロジーの両方のための仕様』と明示的に説明されています。</p><hr><h2 id=\"3-%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E5%9E%8B%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC\">3. オントロジー型セマンティックレイヤー</h2><p>もう1つの系統は、このシリーズで作っているものです。</p><pre><code class=\"language-text\">オントロジー型が定義するもの:\n\n    クラス         Tenant, Subscription, Plan, Feature, Entitlement\n    プロパティ     hasSubscription, basedOnPlan, hasEntitlement\n    関係の性質     hasEntitlement と entitledTenant は互いに逆向き\n    階層           ある関係は、別の関係の一種である（7章で実例を見ます）\n    制約           Entitlement には必ず Tenant と Feature が1つずつ必要\n</code></pre><p>ルーツは、W3Cが標準化したSemantic Webの技術（RDF、OWL、SPARQL、SHACL）です。データベースのテーブルではなく、「主語・述語・目的語」の3つ組（トリプル）で世界を記述します。</p><p>メトリクス型との一番の違いは、<strong>定義から新しい事実を導けること（推論）</strong> と、<strong>どのテーブルにも属さない「意味の対応」を書けること</strong>です。「意味の対応」とは、「請求システムのこの項目と、CRMのこの項目は同じ意味だ」という取り決めのことで、7章で実際に書きます。</p><pre><code class=\"language-text\">メトリクス型:\n    定義は「テーブルの上」に乗っている。\n    テーブルが先にあり、その上に計算式を載せる。\n\nオントロジー型:\n    定義は「テーブルから独立」している。\n    意味の体系が先にあり、各システムのデータをそこに接続する。\n</code></pre><p><strong>このシリーズで作っているのは、こちらです。</strong></p><pre><code class=\"language-text\">「Tenantとは何か」\n「FeatureとEntitlementはどう違うか」\n「PlanとFeatureの関係は何か」\n</code></pre><p>このあたりは、言葉で説明するより動かしたほうが早いので、このあと実際に手を動かして確かめます。</p><hr><h2 id=\"4-t-box%E3%81%A8a-box\">4. T-BoxとA-Box</h2><p>オントロジーを実際に使うとき、記述は2つに分かれます。</p><pre><code class=\"language-text\">T-Box（Terminology Box）＝ 語彙の定義\n    「Tenantというクラスが存在する」\n    「hasEntitlementはTenantからEntitlementへの関係である」\n    「entitledTenantはhasEntitlementの逆方向である」\n\nA-Box（Assertion Box）＝ 事実の記述\n    「ex:tenant-abcはTenantである」\n    「ex:entitlement-abc-csv-exportの対象テナントはex:tenant-abcである」\n</code></pre><p>たとえで言うと、こうです。</p><pre><code class=\"language-text\">T-Box ＝ 言語のルールブック\nA-Box ＝ そのルールで書かれた文章\n</code></pre><p>第1回〜第3回で書いてきた「オントロジー定義のTurtle」がT-Box、「具体的なデータのTurtle」がA-Boxです。</p><p>ここで大事なのは、<strong>セマンティックレイヤーの本体はT-Boxのほう</strong>だという点です。A-Boxはデータなので日々変わりますが、T-Boxは「意味の取り決め」なので、そう頻繁には変わりません。</p><p>では、そのT-Boxは具体的に何の役に立つのでしょうか。ここからは、手を動かして確認します。</p><hr><h2 id=\"5-%E6%89%8B%E3%82%92%E5%8B%95%E3%81%8B%E3%81%99%E6%BA%96%E5%82%99\">5. 手を動かす準備</h2><h3 id=\"51-%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB\">5.1 インストール</h3><pre><code class=\"language-bash\">pip install rdflib owlrl\n</code></pre><p><code>rdflib</code>は、これまでの記事でも使ったRDF操作ライブラリです。<code>owlrl</code>は、そのrdflibの上でOWL/RDFSの推論を行うライブラリです。推論とは、T-Boxに書かれたルールを使って、データに書かれていない事実を導き出すことを指します。今回はこの推論が主役になります。</p><h3 id=\"52-t-box%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\">5.2 T-Boxを保存する</h3><p><code>ontology.ttl</code>として保存します。第1回〜第3回で定義した語彙のうち、今回使う分だけの抜粋です。</p><pre><code class=\"language-turtle\"># ontology.ttl — T-Box（語彙の定義）\n@prefix saas: &lt;https://example.com/ontology/saas#&gt; .\n@prefix rdf:  &lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&gt; .\n@prefix rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt; .\n@prefix owl:  &lt;http://www.w3.org/2002/07/owl#&gt; .\n@prefix xsd:  &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n\n#\n# Classes\n#\n\nsaas:Tenant a owl:Class ;\n    rdfs:label \"Tenant\"@en , \"テナント\"@ja .\n\nsaas:Plan a owl:Class ;\n    rdfs:label \"Plan\"@en , \"プラン\"@ja .\n\nsaas:Subscription a owl:Class ;\n    rdfs:label \"Subscription\"@en , \"契約\"@ja .\n\nsaas:Feature a owl:Class ;\n    rdfs:label \"Feature\"@en , \"機能\"@ja .\n\nsaas:Entitlement a owl:Class ;\n    rdfs:label \"Entitlement\"@en , \"機能利用資格\"@ja .\n\n#\n# Tenant and Subscription\n#\n\nsaas:hasSubscription a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Subscription ;\n    rdfs:label \"has subscription\"@en .\n\nsaas:basedOnPlan a owl:ObjectProperty ;\n    rdfs:domain saas:Subscription ;\n    rdfs:range saas:Plan ;\n    rdfs:label \"based on plan\"@en .\n\n#\n# Plan and Feature\n#\n\nsaas:includesFeature a owl:ObjectProperty ;\n    rdfs:domain saas:Plan ;\n    rdfs:range saas:Feature ;\n    rdfs:label \"includes feature\"@en .\n\n#\n# Tenant and Entitlement（第3回で定義した逆プロパティのペア）\n#\n\nsaas:hasEntitlement a owl:ObjectProperty ;\n    rdfs:domain saas:Tenant ;\n    rdfs:range saas:Entitlement ;\n    rdfs:label \"has entitlement\"@en ,\n               \"機能利用資格を持つ\"@ja .\n\nsaas:entitledTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:hasEntitlement ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range saas:Tenant ;\n    rdfs:label \"entitled tenant\"@en ,\n               \"利用資格の対象テナント\"@ja .\n\nsaas:entitlesFeature a owl:ObjectProperty ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range saas:Feature ;\n    rdfs:label \"entitles feature\"@en ,\n               \"利用可能にする機能\"@ja .\n\n#\n# Datatype Properties\n#\n\nsaas:name a owl:DatatypeProperty ;\n    rdfs:range xsd:string .\n\nsaas:featureCode a owl:DatatypeProperty ;\n    rdfs:domain saas:Feature ;\n    rdfs:range xsd:string .\n\nsaas:entitlementStatus a owl:DatatypeProperty ;\n    rdfs:domain saas:Entitlement ;\n    rdfs:range xsd:string .\n</code></pre><p>今回の主役は、この1行です。</p><pre><code class=\"language-turtle\">saas:entitledTenant a owl:ObjectProperty ;\n    owl:inverseOf saas:hasEntitlement ;\n</code></pre><p>「<code>entitledTenant</code>は<code>hasEntitlement</code>の逆プロパティである」という宣言です。第3回で定義したものを、そのまま持ってきています。</p><h3 id=\"53-a-box%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\">5.3 A-Boxを保存する</h3><p><code>data.ttl</code>として保存します。第3回のインスタンス例と同じく、Entitlement側から<code>saas:entitledTenant</code>でTenantを指す書き方です。</p><pre><code class=\"language-turtle\"># data.ttl — A-Box（実データ）\n@prefix saas: &lt;https://example.com/ontology/saas#&gt; .\n@prefix ex:   &lt;https://example.com/data/&gt; .\n@prefix xsd:  &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n\n#\n# Tenant\n#\n\nex:tenant-abc a saas:Tenant ;\n    saas:name \"株式会社ABCテナント\" ;\n    saas:hasSubscription ex:subscription-abc-standard .\n\n#\n# Subscription\n#\n\nex:subscription-abc-standard a saas:Subscription ;\n    saas:basedOnPlan ex:plan-standard .\n\n#\n# Plans\n#\n\nex:plan-standard a saas:Plan ;\n    saas:name \"Standard Plan\" ;\n    saas:includesFeature\n        ex:feature-project-management ,\n        ex:feature-csv-export ,\n        ex:feature-api-access .\n\nex:plan-enterprise a saas:Plan ;\n    saas:name \"Enterprise Plan\" ;\n    saas:includesFeature\n        ex:feature-project-management ,\n        ex:feature-csv-export ,\n        ex:feature-api-access ,\n        ex:feature-saml-sso ,\n        ex:feature-audit-log ,\n        ex:feature-sales-management ,\n        ex:feature-inventory-management .\n\n#\n# Features\n#\n\nex:feature-project-management a saas:Feature ;\n    saas:name \"基本プロジェクト管理\" ;\n    saas:featureCode \"core.project-management\" .\n\nex:feature-csv-export a saas:Feature ;\n    saas:name \"CSVエクスポート\" ;\n    saas:featureCode \"data.csv-export\" .\n\nex:feature-api-access a saas:Feature ;\n    saas:name \"APIアクセス\" ;\n    saas:featureCode \"integration.api-access\" .\n\nex:feature-saml-sso a saas:Feature ;\n    saas:name \"SAML SSO\" ;\n    saas:featureCode \"security.saml-sso\" .\n\nex:feature-audit-log a saas:Feature ;\n    saas:name \"監査ログ\" ;\n    saas:featureCode \"security.audit-log\" .\n\nex:feature-sales-management a saas:Feature ;\n    saas:name \"販売管理\" ;\n    saas:featureCode \"sales.management\" .\n\nex:feature-inventory-management a saas:Feature ;\n    saas:name \"在庫管理\" ;\n    saas:featureCode \"inventory.management\" .\n\n#\n# Entitlements（Standard Plan由来。販売管理機能に対するEntitlementは存在しない）\n#\n\nex:entitlement-abc-project-management a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-project-management ;\n    saas:entitlementStatus \"active\" .\n\nex:entitlement-abc-csv-export a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-csv-export ;\n    saas:entitlementStatus \"active\" .\n\nex:entitlement-abc-api-access a saas:Entitlement ;\n    saas:entitledTenant ex:tenant-abc ;\n    saas:entitlesFeature ex:feature-api-access ;\n    saas:entitlementStatus \"active\" .\n</code></pre><h3 id=\"54-%E5%AE%9F%E8%A1%8C%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\">5.4 実行スクリプトを保存する</h3><p>これまでの記事では<code>sparqlquery</code>コマンドを使いましたが、あのコマンドは推論をしてくれません。今回は推論の有無を切り替えたいので、短いスクリプトを用意します。<code>query.py</code>として保存してください。</p><pre><code class=\"language-python\">\"\"\"query.py — Turtleファイルを読み込んでSPARQLを実行する。\n\n使い方:\n    python query.py &lt;ttlファイル...&gt; &lt;クエリファイル.rq&gt;\n    python query.py --inference &lt;ttlファイル...&gt; &lt;クエリファイル.rq&gt;\n\n--inference を付けると、T-Boxの定義（owl:inverseOf、rdfs:subPropertyOfなど）に\n基づく推論を行ったうえで問い合わせる。\n\"\"\"\n\nimport sys\n\nfrom rdflib import Graph\n\ndef main(argv: list[str]) -&gt; int:\n    use_inference = \"--inference\" in argv\n    paths = [a for a in argv if a != \"--inference\"]\n\n    if len(paths) &lt; 2:\n        print(__doc__)\n        return 1\n\n    *data_files, query_file = paths\n\n    graph = Graph()\n    for path in data_files:\n        graph.parse(path, format=\"turtle\")\n\n    triples_before = len(graph)\n\n    if use_inference:\n        import owlrl\n\n        owlrl.DeductiveClosure(owlrl.OWLRL_Semantics).expand(graph)\n\n    with open(query_file, encoding=\"utf-8\") as f:\n        query = f.read()\n\n    results = graph.query(query)\n\n    print(\",\".join(str(v) for v in results.vars))\n    for row in results:\n        print(\",\".join(\"\" if value is None else str(value) for value in row))\n\n    mode = \"推論あり\" if use_inference else \"推論なし\"\n    print(\n        f\"\\n-- {len(results)} 行 / {mode}\"\n        f\"（トリプル数 {triples_before} -&gt; {len(graph)}）\"\n    )\n    return 0\n\nif __name__ == \"__main__\":\n    sys.exit(main(sys.argv[1:]))\n</code></pre><p>やっていることは3つだけです。</p><pre><code class=\"language-text\">1. 指定されたTurtleファイルを全部1つのグラフに読み込む\n2. --inference が付いていたら推論して、導けるトリプルを追加する\n3. SPARQLを実行して結果を表示する\n</code></pre><p>推論に<code>OWLRL_Semantics</code>を指定している点だけ、先に理由を書いておきます。owlrlには軽い<code>RDFS_Semantics</code>もありますが、今回主役になる<code>owl:inverseOf</code>はOWLの語彙なので、RDFS推論では処理されません。この違いは8章でもう一度触れます。</p><hr><h2 id=\"6-t-box%E3%81%AF%E3%80%81%E6%9C%AC%E5%BD%93%E3%81%AB%E7%B5%90%E6%9E%9C%E3%82%92%E5%A4%89%E3%81%88%E3%82%8B%E3%81%AE%E3%81%8B\">6. T-Boxは、本当に結果を変えるのか</h2><h3 id=\"61-%E3%81%BE%E3%81%9A%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E3%81%A6%E3%81%BF%E3%82%8B\">6.1 まず問い合わせてみる</h3><p>「株式会社ABCが使える機能は何か？」を聞きます。<code>query-features.rq</code>として保存してください。</p><pre><code class=\"language-sparql\"># query-features.rq — 株式会社ABCが使える機能は何か？\nPREFIX saas: &lt;https://example.com/ontology/saas#&gt;\nPREFIX ex:   &lt;https://example.com/data/&gt;\n\nSELECT ?featureName ?featureCode\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?entitlement .\n    ?entitlement saas:entitlesFeature ?feature .\n    ?entitlement saas:entitlementStatus \"active\" .\n    ?feature saas:name ?featureName .\n    ?feature saas:featureCode ?featureCode .\n}\nORDER BY ?featureCode\n</code></pre><p>注目してほしいのは1行目のパターンです。</p><pre><code class=\"language-sparql\">ex:tenant-abc saas:hasEntitlement ?entitlement .\n</code></pre><p><strong>Tenant側から<code>saas:hasEntitlement</code>でたどっています</strong>。一方、<code>data.ttl</code>に書いてあるのは逆向きの<code>saas:entitledTenant</code>だけでした。</p><p>実行します。</p><pre><code class=\"language-bash\">python query.py ontology.ttl data.ttl query-features.rq\n</code></pre><p>結果:</p><pre><code class=\"language-text\">featureName,featureCode\n\n-- 0 行 / 推論なし（トリプル数 103 -&gt; 103）\n</code></pre><p><strong>0行です</strong>。T-Boxを読み込んでいるにもかかわらず、何も返りません。</p><p>これは、多くの人が最初にハマるところです。</p><pre><code class=\"language-text\">T-Boxに owl:inverseOf と書いただけでは、\n逆方向のトリプルは自動的には存在しない。\n</code></pre><p>RDFのグラフには、書かれたトリプルしか入っていません。<code>ex:entitlement-abc-csv-export saas:entitledTenant ex:tenant-abc</code>は存在しますが、<code>ex:tenant-abc saas:hasEntitlement ex:entitlement-abc-csv-export</code>は、まだどこにも書かれていないのです。</p><p>ここで、正直に書いておかなければならないことがあります。</p><pre><code class=\"language-text\">「逆方向をたどるには推論が必須」というわけではない。\n</code></pre><p>次の2つは、どちらも推論なしで3件返ります。</p><pre><code class=\"language-sparql\"># データが書かれている向きで、そのまま問う\n?entitlement saas:entitledTenant ex:tenant-abc .\n\n# SPARQLの逆パス演算子 ^ を使う\nex:tenant-abc ^saas:entitledTenant ?entitlement .\n</code></pre><p>つまり、推論の価値は「逆方向が取れること」そのものではありません。</p><pre><code class=\"language-text\">推論の本当の価値:\n\n    データがどちら向きに書かれていても、\n    問い合わせる側は saas:hasEntitlement 一本で書ける。\n\n    そして次の7章で見るように、\n    クエリを1文字も変えないまま、\n    複数のシステムのデータを束ねられる。\n</code></pre><p>データがどちら向きに書かれているかという違いは、間に挟まる層が吸収します。だから問い合わせる側は、データの書かれ方を知らなくてよい。1章で説明した「レイヤー（層）」の価値は、ここにあります。</p><h3 id=\"62-%E6%8E%A8%E8%AB%96%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B\">6.2 推論を有効にする</h3><p><code>--inference</code>を付けて、もう一度実行します。</p><pre><code class=\"language-bash\">python query.py --inference ontology.ttl data.ttl query-features.rq\n</code></pre><p>結果:</p><pre><code class=\"language-text\">featureName,featureCode\n基本プロジェクト管理,core.project-management\nCSVエクスポート,data.csv-export\nAPIアクセス,integration.api-access\n\n-- 3 行 / 推論あり（トリプル数 103 -&gt; 368）\n</code></pre><p><strong>3行返ってきました</strong>。そして、トリプル数が103から368に増えています（この増え方の内訳は8章で見ます）。</p><p>何が起きたのでしょうか。</p><pre><code class=\"language-text\">T-Boxにこう書いてあった:\n    saas:entitledTenant owl:inverseOf saas:hasEntitlement\n\nA-Boxにこう書いてあった:\n    ex:entitlement-abc-csv-export saas:entitledTenant ex:tenant-abc\n\n推論エンジンは、この2つから次を導き出した:\n    ex:tenant-abc saas:hasEntitlement ex:entitlement-abc-csv-export\n</code></pre><p>つまり、<strong>T-Boxに書いた1行（<code>owl:inverseOf</code>）が、問い合わせの結果を0行から3行に変えた</strong>のです。T-Boxは人間向けの説明書きではありません。推論エンジンが読んで、データに書かれていない事実を導くためのルールとして働きました。</p><p><code>ontology.ttl</code>を読み込まずに推論しても、この結果は出ません。逆方向であるという情報が、どこにも無いからです。ぜひ<code>ontology.ttl</code>を外して試してみてください。</p><h3 id=\"63-%E8%B2%A9%E5%A3%B2%E7%AE%A1%E7%90%86%E6%A9%9F%E8%83%BD%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E8%81%9E%E3%81%84%E3%81%A6%E3%81%BF%E3%82%8B\">6.3 販売管理機能について聞いてみる</h3><p>では冒頭の問いに戻ります。<code>query-sales.rq</code>として保存してください。</p><pre><code class=\"language-sparql\"># query-sales.rq — 株式会社ABCは、販売管理機能を使えるか？\nPREFIX saas: &lt;https://example.com/ontology/saas#&gt;\nPREFIX ex:   &lt;https://example.com/data/&gt;\n\nSELECT ?featureName ?status\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?entitlement .\n    ?entitlement saas:entitlesFeature ex:feature-sales-management .\n    ?entitlement saas:entitlementStatus ?status .\n\n    ex:feature-sales-management saas:name ?featureName .\n}\n</code></pre><p>実行します。</p><pre><code class=\"language-bash\">python query.py --inference ontology.ttl data.ttl query-sales.rq\n</code></pre><p>結果:</p><pre><code class=\"language-text\">featureName,status\n\n-- 0 行 / 推論あり（トリプル数 103 -&gt; 368）\n</code></pre><p>推論を有効にしても0行です。これは推論不足ではなく、グラフの中に該当するEntitlementが存在しないという答えです。</p><p>厳密に言うと、OWLの推論は「書かれていない＝偽」とは考えません。これを開世界仮定と呼びます。反対に、書かれていないことを偽とみなす考え方を閉世界仮定と呼びます。ふつうのデータベースは、こちらの考え方です。</p><p>それでもここで「使えない」と言い切れるのは、<strong>「Entitlementはすべてこのグラフに記録されている」という業務上の前提</strong>を置いているからです。Entitlementの記録元を網羅しているからこそ、0行を「無い」と読めます。ただし、7章で見るとおり、実務ではこの前提こそが崩れやすいのです。</p><pre><code class=\"language-text\">株式会社ABCは、販売管理機能を使えない。\n販売管理機能を対象とした有効なEntitlementが、\nグラフの中に1つも存在しないから。\n</code></pre><p><code>data.ttl</code>を見ると、ABCが契約しているStandard Planにも、販売管理は含まれていません。クエリの結果とも矛盾しません。</p><p>冒頭の問いに、事実に基づいて答えられました。</p><hr><h2 id=\"7-%E6%9C%AC%E7%95%AA-%E3%83%90%E3%83%A9%E3%83%90%E3%83%A9%E3%81%AE%E8%AA%9E%E5%BD%99%E3%82%92%E3%80%811%E3%81%A4%E3%81%AE%E5%95%8F%E3%81%84%E3%81%A7%E6%A8%AA%E6%96%AD%E3%81%99%E3%82%8B\">7. 本番: バラバラの語彙を、1つの問いで横断する</h2><p>ここまでは1つのシステムのデータだけでした。ここからが、セマンティックレイヤーが本当に役に立つ場面です。</p><h3 id=\"71-%E7%8F%BE%E5%AE%9F%E3%81%AE%E7%8A%B6%E6%B3%81\">7.1 現実の状況</h3><p>実務では、こうなっています。</p><pre><code class=\"language-text\">株式会社ABCは、商談の結果、\nStandard Planに含まれないSAML SSOと監査ログを\n個別に付与されている。\n\nただし、その事実の記録場所はバラバラ:\n\n    請求システム : SAML SSO を課金対象として登録\n                  → billing:grantedFeature という項目名\n\n    CRM          : 監査ログ を有効化済みとして記録\n                  → crm:enabledFeature という項目名\n</code></pre><p>同じ「機能を利用可能にした」という意味なのに、項目名が違います。普通なら、どちらかに寄せる変換バッチを書くところです。</p><p>セマンティックレイヤーでは、<strong>データを書き換えず、意味の対応だけを宣言します。</strong></p><h3 id=\"72-%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%82%92%E5%AE%A3%E8%A8%80%E3%81%99%E3%82%8B\">7.2 マッピングを宣言する</h3><p><code>mapping.ttl</code>として保存してください。</p><pre><code class=\"language-turtle\"># mapping.ttl — 各ソースシステムの語彙を、共通語彙 saas: に接続する\n@prefix saas:    &lt;https://example.com/ontology/saas#&gt; .\n@prefix rdfs:    &lt;http://www.w3.org/2000/01/rdf-schema#&gt; .\n@prefix billing: &lt;https://example.com/source/billing#&gt; .\n@prefix crm:     &lt;https://example.com/source/crm#&gt; .\n\nbilling:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .\nbilling:forCustomer    rdfs:subPropertyOf saas:entitledTenant .\n\ncrm:enabledFeature     rdfs:subPropertyOf saas:entitlesFeature .\ncrm:forAccount         rdfs:subPropertyOf saas:entitledTenant .\n</code></pre><p><code>rdfs:subPropertyOf</code>は、「このプロパティは、あのプロパティの一種である」という宣言です。</p><pre><code class=\"language-text\">billing:grantedFeature rdfs:subPropertyOf saas:entitlesFeature\n\n意味:\n    「請求システムのgrantedFeatureで結ばれているものは、\n      saas:entitlesFeatureでも結ばれていると見なしてよい」\n</code></pre><p>これだけです。4行の宣言で、2つのシステムの語彙が共通語彙に接続されました。</p><h3 id=\"73-%E5%90%84%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E3%81%9D%E3%81%AE%E3%81%BE%E3%81%BE%E7%BD%AE%E3%81%8F\">7.3 各システムのデータをそのまま置く</h3><p><code>data-multi-source.ttl</code>として保存してください。各システムの語彙のまま、一切書き換えていません。</p><pre><code class=\"language-turtle\"># data-multi-source.ttl — 2つの別システムから取り込んだ、そのままの形のデータ\n@prefix saas:    &lt;https://example.com/ontology/saas#&gt; .\n@prefix ex:      &lt;https://example.com/data/&gt; .\n@prefix billing: &lt;https://example.com/source/billing#&gt; .\n@prefix crm:     &lt;https://example.com/source/crm#&gt; .\n\n#\n# 請求システム由来（billing: 語彙）\n#\n\nex:billing-entitlement-001\n    billing:forCustomer ex:tenant-abc ;\n    billing:grantedFeature ex:feature-saml-sso ;\n    saas:entitlementStatus \"active\" .\n\n#\n# CRM由来（crm: 語彙）\n#\n\nex:crm-entitlement-042\n    crm:forAccount ex:tenant-abc ;\n    crm:enabledFeature ex:feature-audit-log ;\n    saas:entitlementStatus \"active\" .\n</code></pre><h3 id=\"74-%E5%90%8C%E3%81%98%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%A7%E3%80%81%E6%A8%AA%E6%96%AD%E3%81%97%E3%81%A6%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B%E3%82%8B\">7.4 同じクエリで、横断して問い合わせる</h3><p>なお、<code>a saas:Entitlement</code>というクラス型付けを書いていません。第3回でEntitlementをクラスとして定義したので気になるかと思いますが、今回の問いはプロパティの経路だけでたどれるため、省いています。型付けが必要になるのは、SHACLで検証する場合や、クラス単位で数え上げる場合です。</p><p>ここが大事なところです。<strong>クエリは6.1節のものを、1文字も変えません</strong>。</p><pre><code class=\"language-bash\">python query.py --inference \\\n    ontology.ttl mapping.ttl data.ttl data-multi-source.ttl \\\n    query-features.rq\n</code></pre><p>結果:</p><pre><code class=\"language-text\">featureName,featureCode\n基本プロジェクト管理,core.project-management\nCSVエクスポート,data.csv-export\nAPIアクセス,integration.api-access\n監査ログ,security.audit-log\nSAML SSO,security.saml-sso\n\n-- 5 行 / 推論あり（トリプル数 113 -&gt; 410）\n</code></pre><p><strong>3行から5行に増えました</strong>。増えたのは、請求システム由来のSAML SSOと、CRM由来の監査ログです。</p><p>改めて、何をやったか整理します。</p><pre><code class=\"language-text\">やったこと:\n    ・請求システムのデータを、billing: 語彙のまま置いた\n    ・CRMのデータを、crm: 語彙のまま置いた\n    ・「これらは saas: の何に相当するか」を4行宣言した\n\nやらなかったこと:\n    ・項目名をそろえるために、データを書き換える変換バッチやETL処理を書く\n    ・クエリを1文字でも変更する\n</code></pre><p>これが、セマンティックレイヤーです。</p><pre><code class=\"language-text\">データを1箇所に集めて形を揃えるのではなく、\n「意味の対応」を宣言することで、\nバラバラのままのデータを1つの語彙で問い合わせる。\n</code></pre><p>新しいシステムが増えたら、<code>mapping.ttl</code>に数行足すだけです。クエリを書く側は、各システムの項目名を知らなくてよい。違いは間の層がすべて引き受けます。これが「レイヤー」と呼ばれる理由です。</p><hr><h2 id=\"8-sql%E3%81%A8%E6%AF%94%E3%81%B9%E3%81%A6%E3%80%81%E4%BD%95%E3%81%8C%E9%81%95%E3%81%86%E3%81%AE%E3%81%8B\">8. SQLと比べて、何が違うのか</h2><p>「同じことをSQLでもできるのでは？」という疑問に、正直に答えます。</p><p>まずSQLで書いてみます。</p><pre><code class=\"language-sql\">SELECT f.name, f.code\nFROM entitlements e\nJOIN features f ON e.feature_id = f.id\nWHERE e.tenant_id = 'abc' AND e.status = 'active'\n\nUNION\n\nSELECT f.name, f.code\nFROM billing_entitlements be\nJOIN features f ON be.granted_feature_id = f.id\nWHERE be.customer_id = 'abc' AND be.status = 'active'\n\nUNION\n\nSELECT f.name, f.code\nFROM crm_entitlements ce\nJOIN features f ON ce.enabled_feature_id = f.id\nWHERE ce.account_id = 'abc' AND ce.status = 'active';\n</code></pre><p>できます。ちゃんと動きます。ただし、違いが2つあります。</p><p><strong>違い1: 増えたときに変わるのはどちらか</strong></p><pre><code class=\"language-text\">SQL:\n    4つ目のシステムが増えたら、\n    このクエリ自体に UNION を1ブロック足す。\n    このクエリを使っている全ての箇所を直す。\n\nセマンティックレイヤー:\n    mapping.ttl に2行足す。\n    クエリは変更しない。\n</code></pre><p>ただし公平に言えば、実務のSQLでは、このUNIONをあちこちに書いたりはしません。dbtなどの変換処理で1つの<code>entitlements</code>テーブル（またはビュー）にまとめておけば、直す場所は1箇所で済みます。2章で見たメトリクス型のセマンティックレイヤーも、この前提に立っています。</p><p>そのため、本当の違いは次の「違い2」のほうです。</p><p><strong>違い2: 「同じ意味である」がどこに書かれているか</strong></p><p>SQLの<code>UNION</code>は、「この3つのテーブルは同じものとして扱う」という判断をクエリの中に埋め込んでいます。その判断の根拠は、クエリを書いた人の頭の中にしかありません。</p><p>一方、<code>rdfs:subPropertyOf</code>は、その判断をクエリの外に、機械が読める宣言として書いています。だから推論エンジンが自動で適用できるし、他の人も他のツールも同じ判断を再利用できます。</p><p>もう1点あります。よく「SPARQLならスキーマ変更に強い」と言われますが、<strong>それは半分だけ本当です。</strong></p><pre><code class=\"language-text\">SPARQLも、述語のIRIが変わればクエリは壊れる。\nその意味では、カラム名が変わればSQLが壊れるのと同じ。\n\n本当の違いは、\n「変更に強い」ことではなく、\n「意味の対応関係を、クエリの外に、\n  機械可読な形で書き出せる」こと。\n</code></pre><p>そして、これにはコストもあります。</p><pre><code class=\"language-text\">推論のコスト:\n    今回は103トリプルが368に増えた（約3.5倍）。\n</code></pre><p>ただし、この「3.5倍」を鵜呑みにしないでください。増えた265トリプルの内訳を数えると、こうなっています。</p><pre><code class=\"language-text\">増加 265トリプルの内訳:\n\n    262件  W3C語彙（owl:, rdfs: など）に関わるトリプル\n           ・大半（約230件）は、データの量と関係なく毎回追加されるもの\n             （例:「owl:Thing はクラスである」のような事実）\n           ・残りは、データ中のIRIや値ごとに機械的に付く自明なもの\n             （例:「X は X と同じものである（owl:sameAs）」）\n      3件  saas:hasEntitlement の導出（＝実際に欲しかったもの）\n</code></pre><p>つまり3.5倍の大半は、データ量に関係なく発生する固定オーバーヘッドです。A-Boxが小さいほど倍率は大きく見えます。データを増やすと、倍率そのものは下がっていきます。実際に<code>data.ttl</code>と同じ形のデータを20倍に増やして試すと、推論後のトリプル数は推論前の約1.8倍まで下がりました。</p><p>とはいえ、絶対的な計算量とメモリは確実に増えます。本番では次のような判断が必要になります。</p><pre><code class=\"language-text\">・推論結果を事前に計算して保存しておく（マテリアライズ。問い合わせのたびには推論しない）\n・推論を使わず、SPARQLの逆パス演算子（6.1節の ^）で書く\n・必要な導出だけを CONSTRUCT（クエリ結果から新しいトリプルを作る構文）で明示的に作る\n</code></pre><p>ここで1つ注意点があります。「軽いRDFS推論に絞る」という対策をよく見かけますが、<strong>今回のハンズオンではそれをやると、うまく動かなくなります。</strong></p><pre><code class=\"language-text\">owl:inverseOf は、RDFS推論では処理されない。\n\n実測:\n    RDFS_Semantics  → 0行\n    OWLRL_Semantics → 3行\n</code></pre><p><code>rdfs:subPropertyOf</code>はRDFS語彙なのでRDFS推論でも処理されますが、<code>owl:inverseOf</code>はOWLの語彙です。この記事の<code>query.py</code>が<code>OWLRL_Semantics</code>を使っているのは、そのためです。</p><p>魔法ではありません。トレードオフのある設計選択です。</p><hr><h2 id=\"9-%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E5%9E%8B%E3%81%A8%E6%AF%94%E3%81%B9%E3%81%A6%E3%80%81%E4%BD%95%E3%81%8C%E9%81%95%E3%81%86%E3%81%AE%E3%81%8B\">9. メトリクス型と比べて、何が違うのか</h2><p>8章ではSQLと比べました。ここでは、2章で見たメトリクス型のセマンティックレイヤーと比べます。</p><p>違いが一番よくわかるのは、<strong>得意な問いが違う</strong>ところです。2つの問いを用意しました。</p><pre><code class=\"language-text\">Q1. プラン別に、毎月いくつ新しい契約が始まっているか？\n    （集計の問い）\n\nQ2. 株式会社ABCというテナントは、販売管理機能を使えるか？\n    （関係をたどる問い。しかも記録が複数システムに散らばっている）\n</code></pre><h3 id=\"91-q1%EF%BC%88%E9%9B%86%E8%A8%88%EF%BC%89%E3%81%AF%E3%80%81%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E5%9E%8B%E3%81%AE%E5%BE%97%E6%84%8F%E5%88%86%E9%87%8E\">9.1 Q1（集計）は、メトリクス型の得意分野</h3><p>Q1は、2.3節で見たとおり、メトリクス型ならきれいに書けます。</p><p>オントロジー型でも、SPARQLで集計はできます。Subscriptionの開始日を表す<code>saas:subscriptionStart</code>は後続の会で正式に定義する予定ですが、ここでは先取りして使います。</p><pre><code class=\"language-sparql\">SELECT ?planName ?month (COUNT(DISTINCT ?sub) AS ?newSubscriptions)\nWHERE {\n    ?sub a saas:Subscription ;\n         saas:basedOnPlan ?plan ;\n         saas:subscriptionStart ?started .\n    ?plan saas:name ?planName .\n    BIND(SUBSTR(STR(?started), 1, 7) AS ?month)\n}\nGROUP BY ?planName ?month\n</code></pre><p>書けます。ただし、次のような点でメトリクス型に分があります。</p><pre><code class=\"language-text\">・「new_subscriptions とはこの計算式である」という定義を、\n  再利用可能な部品として置く標準的な仕組みが、OWLにはない。\n  （OWLは「何が存在するか」の言語であって、「どう数えるか」の言語ではない。\n    統計などの多次元データを記述する W3C の RDF Data Cube 語彙はあるが、\n    計算式そのものを定義する、広く使われた標準はない）\n\n・時間の粒度（日・週・月）、足し算できない指標（期末残高など）の扱いを、\n  メトリクス型は定義側で面倒を見てくれる。SPARQLでは毎回書く。\n\n・数億行の集計は、列指向のデータウェアハウスのほうが圧倒的に速い。\n</code></pre><h3 id=\"92-q2%EF%BC%88%E9%96%A2%E4%BF%82%E3%83%BB%E6%A8%AA%E6%96%AD%EF%BC%89%E3%81%AF%E3%80%81%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E5%9E%8B%E3%81%AE%E5%BE%97%E6%84%8F%E5%88%86%E9%87%8E\">9.2 Q2（関係・横断）は、オントロジー型の得意分野</h3><p>Q2の答えは、「ABCに、販売管理機能を対象とした有効なEntitlementがあるか」です。しかも現実には、その記録が請求システムとCRMに、別々の項目名で散らばっています。</p><pre><code class=\"language-text\">請求システム: billing_entitlements.granted_feature_id\nCRM:          crm_entitlements.enabled_feature_id\n</code></pre><p>オントロジー型では、こう書くだけで2つを同じ意味として扱えます。</p><pre><code class=\"language-turtle\">billing:grantedFeature rdfs:subPropertyOf saas:entitlesFeature .\ncrm:enabledFeature     rdfs:subPropertyOf saas:entitlesFeature .\n</code></pre><p>問い合わせる側は<code>saas:entitlesFeature</code>一本で書けばよく、データ自体は書き換えません。これは7章で実際に確かめたとおりです。</p><p>では、メトリクス型ではQ2はどうなるか。ここは公平に書いておきます。</p><pre><code class=\"language-text\">「メトリクス型は複数ソースを束ねられない」は、正確ではない。\n\nメトリクス型の世界では、\n複数ソースの統合は、セマンティックレイヤーの「下」で済ませておく。\ndbt などの変換処理で、請求とCRMのテーブルを\n1つの entitlements テーブルに揃えてから、その上に定義を載せる。\n</code></pre><p>つまり、違いは「束ねられるか」ではなく、<strong>束ねる判断をどこに書くか</strong>です。</p><pre><code class=\"language-text\">メトリクス型:\n    「grantedFeature と enabledFeature は同じ意味だ」という判断は、\n    変換処理（SQL）の中に書かれる。\n\nオントロジー型:\n    同じ判断を、変換処理の外に、\n    機械が読める「意味の宣言」として書く。\n</code></pre><p>そして、Q2はそもそも集計の問いではありません。メトリクスとディメンションという型に、自然には収まらない問いです。</p><h3 id=\"93-%E6%AF%94%E8%BC%83%E8%A1%A8\">9.3 比較表</h3><p>ここまでの比較を表にまとめます。</p><!--kg-card-begin: html--><table data-line=\"1174\" class=\"code-line\" dir=\"auto\" style=\"border-collapse: collapse; margin-bottom: 0.7em; color: rgb(187, 190, 191); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe WPC&quot;, &quot;Segoe UI&quot;, system-ui, Ubuntu, &quot;Droid Sans&quot;, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;\"><thead data-line=\"1174\" class=\"code-line\" dir=\"auto\"><tr data-line=\"1174\" class=\"code-line\" dir=\"auto\"><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">観点</th><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">メトリクス型</th><th style=\"text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.69); padding: 5px 10px; border-top-color: rgba(255, 255, 255, 0.69); border-right-color: rgba(255, 255, 255, 0.69); border-left-color: rgba(255, 255, 255, 0.69);\">オントロジー型</th></tr></thead><tbody data-line=\"1176\" class=\"code-line\" dir=\"auto\"><tr data-line=\"1176\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">定義する単位</td><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">メジャー・ディメンション・結合</td><td style=\"padding: 5px 10px; border-color: rgba(255, 255, 255, 0.18);\">クラス・プロパティ・関係</td></tr><tr data-line=\"1177\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">答えやすい問い</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">どれだけ？（集計）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">何が何と、どうつながっている？</td></tr><tr data-line=\"1178\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">定義の置き場所</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">テーブルの上（DWHのカタログ）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">テーブルから独立（Turtleなど）</td></tr><tr data-line=\"1179\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">複数ソースの語彙合わせ</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">下の変換層（dbt等）で済ませておく</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">マッピングとして宣言する</td></tr><tr data-line=\"1180\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">推論</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">なし（集計の意味づけはある）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">あり（逆関係、上位プロパティ、クラス階層）</td></tr><tr data-line=\"1181\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">書かれていない事実の扱い</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">「無い」とみなす（閉世界仮定）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">推論では「わからない」とみなす（開世界仮定、6.3節）。ただしSPARQLの結果やSHACLの検証は、グラフにあるものだけで判断する</td></tr><tr data-line=\"1182\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">制約の検証</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">定義の整合性は作成時に検証される。データの制約はテスト（dbt testsなど）で確認する</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">SHACLで宣言する</td></tr><tr data-line=\"1183\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">集計性能</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">強い（列指向エンジン）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">弱め（大規模集計は不得意）</td></tr><tr data-line=\"1184\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">標準</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">Apache Ossie（旧OSI）が整備中</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">W3C標準（RDF/OWL/SPARQL/SHACL）</td></tr><tr data-line=\"1185\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">人材・ツール</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">豊富</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">少ない</td></tr><tr data-line=\"1186\" class=\"code-line\" dir=\"auto\"><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">AIとの組み合わせ</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">セマンティックモデルを手がかりに、自然文→SQL（Cortex Analyst、Genieなど）</td><td style=\"padding: 5px 10px; border-top: 1px solid rgba(255, 255, 255, 0.18); border-right-color: rgba(255, 255, 255, 0.18); border-bottom-color: rgba(255, 255, 255, 0.18); border-left-color: rgba(255, 255, 255, 0.18);\">自然文→SPARQL、ナレッジグラフ</td></tr></tbody></table><!--kg-card-end: html--><h3 id=\"94-%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E5%9E%8B%E3%81%AEpros-cons\">9.4 メトリクス型のPros / Cons</h3><pre><code class=\"language-text\">Pros:\n    ・「数字が合わない」問題に、直接対応する\n    ・データウェアハウスの中にあり、権限管理も性能もそのまま使える\n    ・SQLの延長なので、データ分析の人材がそのまま使える\n    ・BIツールやAIエージェントとの連携が、製品として整っている\n\nCons:\n    ・定義はテーブル構造に依存する。テーブルが先、意味が後\n    ・「AとBは同じ意味」という判断が、変換処理のSQLに埋もれやすい\n    ・推論がない。書いた計算式以上のことは出てこない\n    ・集計にならない問い（関係・資格・依存）は、型に収まりにくい\n</code></pre><h3 id=\"95-%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E5%9E%8B%E3%81%AEpros-cons\">9.5 オントロジー型のPros / Cons</h3><pre><code class=\"language-text\">Pros:\n    ・意味の体系がテーブルから独立しているので、ソースが増えても影響を受けにくい\n    ・意味の対応関係を、クエリの外に機械可読な形で書ける\n    ・推論で、書かれていない事実を導ける\n    ・W3C標準なので、特定ベンダーに閉じない\n\nCons:\n    ・集計・時間粒度・大規模データの扱いは、メトリクス型に劣る\n    ・RDF/SPARQLを扱える人が少ない\n    ・既存のRDBのデータをRDFとして扱う工程（変換か仮想化）が別途必要\n    ・推論には計算コストがかかり、設計判断が要る\n</code></pre><p>どちらも魔法ではありません。<strong>目的が違う道具</strong>です。</p><hr><h2 id=\"10-ai%E3%81%8C%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%83%83%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%92%E4%BD%BF%E3%81%86%E5%A0%B4%E5%90%88\">10. AIがセマンティックレイヤーを使う場合</h2><p>もう1つ、いまの時代に重要な応用に触れておきます。</p><p>LLM（大規模言語モデル）に「株式会社ABCは販売管理機能を使えますか？」と聞いたとき、何が起きるべきでしょうか。</p><pre><code class=\"language-text\">セマンティックレイヤーが無い場合:\n    LLMが学習データからそれっぽい答えを生成する\n    → ハルシネーション（幻覚）のリスク\n\nセマンティックレイヤーがある場合:\n    LLMが、問いをSPARQLに変換する\n    → セマンティックレイヤーに問い合わせる\n    → 返ってきた事実に基づいて答える\n</code></pre><p>セマンティックレイヤーがあれば、LLMは「答えを創作する」のではなく「問いを翻訳する」役割に徹することができます。</p><pre><code class=\"language-text\">人間の質問:\n    「株式会社ABCは販売管理機能を使える？」\n\nLLMが生成するSPARQL:\n</code></pre><pre><code class=\"language-sparql\">PREFIX saas: &lt;https://example.com/ontology/saas#&gt;\nPREFIX ex:   &lt;https://example.com/data/&gt;\n\nSELECT ?featureName ?status\nWHERE {\n    ex:tenant-abc saas:hasEntitlement ?e .\n    ?e saas:entitlesFeature ?f .\n    ?f saas:name ?featureName .\n    FILTER(CONTAINS(?featureName, \"販売管理\"))\n    ?e saas:entitlementStatus ?status .\n}\n</code></pre><pre><code class=\"language-text\">セマンティックレイヤーの応答:\n    （0行）\n\nLLMの最終回答:\n    「株式会社ABCは現在、販売管理機能を利用できません。\n      この機能に対する有効なEntitlementが\n      存在しないためです。」\n</code></pre><p>ここでLLMが正しいクエリを組み立てられるのは、T-Boxに「Tenant」「Entitlement」「Feature」が何であり、どうつながっているかが書いてあるからです。T-Boxからは、クエリの形（どのプロパティをたどるか）が分かります。絞り込む値の「販売管理」は、LLMが質問の言葉から取り出しています。</p><pre><code class=\"language-text\">LLMにテーブル定義だけを渡した場合:\n    「entitlements.status って何の状態？」\n    「tenant_id と customer_id は同じもの？」\n    → 推測が入る\n\nLLMにT-Boxを渡した場合:\n    第3回で Entitlement の rdfs:comment に書いた\n    「特定のTenantに、特定のFeatureを提供する権利、資格または状態」\n    が読める\n    → 推測が減る\n</code></pre><p>第1回から<code>rdfs:label</code>と<code>rdfs:comment</code>を丁寧に書いてきたのは、人間のためだけではなかったわけです。</p><p>なお、今回の<code>ontology.ttl</code>は、説明を短くするために<code>rdfs:comment</code>を省いています。実際にLLMへ渡すなら、第3回までの定義をコメントごと渡します。</p><p>この方向性は、メトリクス型の製品にも共通しています。どの製品も、AIエージェント向けの機能を足しています。</p><pre><code class=\"language-text\">Snowflake   Semantic View に、AI向けの指示や検証済みクエリを書ける句がある\n            → Cortex Analyst などが参照する\nDatabricks  Metric View に synonyms（同義語）を書ける\n            → Genie が参照する\nLooker      LookML を、Gemini の会話型分析の根拠として使う\n</code></pre><p>目的は共通していて、「<strong>LLMに計算を推測させず、定義に従って決定的に答えさせる</strong>」ことです。これは、この章で見た「LLMを『答えを創作する役』から『問いを翻訳する役』にする」という話と、まったく同じ方向です。</p><hr><h2 id=\"11-2%E3%81%A4%E3%81%AE%E7%B3%BB%E7%B5%B1%E3%81%AF%E8%BF%91%E3%81%A5%E3%81%84%E3%81%A6%E3%81%84%E3%82%8B\">11. 2つの系統は近づいている</h2><p>ここまで「2つの系統」として分けてきましたが、実は最近、両者の境界は少しずつ曖昧になっています。きっかけは、10章で見たとおり、どちらもAIに正確に答えさせる手段として注目されていることです。</p><h3 id=\"111-microsoft-fabric-iq%E3%81%AEontology\">11.1 Microsoft Fabric IQのOntology</h3><p>一番わかりやすい合流の例が、MicrosoftがIgnite 2025で発表したFabric IQです。その中核に、Ontologyというアイテム（2026年10月時点でプレビュー）があります。</p><pre><code class=\"language-text\">Fabric IQ の Ontology で定義するもの（公式ドキュメントより）:\n\n    entity type      （≒ クラス）\n    relationship     （≒ プロパティ）\n    継承・namespace\n    metric           ← Power BI の Semantic Model の DAX メジャーを参照する\n    rule             （自然言語で書く）\n</code></pre><p>面白いのは、<strong>metricをオントロジーの中で「参照」している</strong>点です。計算式そのもの（DAX）は元のSemantic Modelが持ち続け、オントロジーはそれを「このエンティティに関する指標」として結びつけます。</p><pre><code class=\"language-text\">オントロジー   : 「Tenant とは何か」「何とつながっているか」\nメトリクス層   : 「Tenant ごとの数字を、どう計算するか」\n\n→ 置き換えるのではなく、重ねている\n</code></pre><p>また、FabricのOntologyはRDF / Turtle / OWLのサブセットの取り込みと、RDF / Turtleでの書き出しをドキュメントに載せています（取り込み先は空のOntologyアイテムで、取り込み結果は、そのまま保たれた要素・自動で調整された要素・対応していない要素に分けて報告されます）。データ・分析基盤の大手が、W3Cのオントロジー形式との行き来を公式にうたった例です。</p><p>さらに、FabricのOntologyは基本的には自分でデータを持たない「仮想的な意味のレイヤー」で、実際の問い合わせは接続先のデータソースで実行されます（多段の関係をたどる場合などは、FabricのGraphにデータを任意で実体化することもできます）。これは、RDFの世界で以前からあるR2RML / Ontop（SPARQLをSQLに書き換えて、元のDBで実行する）とよく似た発想です。</p><h3 id=\"112-apache-ossie-%E3%82%82%E3%80%81%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E3%82%92%E5%AF%BE%E8%B1%A1%E3%81%AB%E5%8A%A0%E3%81%88%E3%81%9F\">11.2 Apache Ossie も、オントロジーを対象に加えた</h3><p>2章で触れたOSI（現 Apache Ossie）も、同じ方向に動いています。2026年7月にApache Incubatorへ移ったとき、Ossieは自らを「セマンティックレイヤーとオントロジーの両方のための仕様」と位置づけ直しました。</p><p>さらに、草案段階（v0.2.0.dev0）のオントロジー仕様では、概念や関係に<code>iri</code>フィールドを持たせ、既存のRDF/OWL語彙のクラスやプロパティと対応づけられるようになっています。メトリクス型から始まった標準化が、オントロジー、さらにW3Cの語彙との接続にまで広がってきたわけです。</p><h3 id=\"113-%E3%80%8C%E3%82%AA%E3%83%B3%E3%83%88%E3%83%AD%E3%82%B8%E3%83%BC%E3%80%8D%E3%81%A8%E3%81%84%E3%81%86%E8%A8%80%E8%91%89%E3%81%AE%E5%BA%83%E3%81%8C%E3%82%8A\">11.3 「オントロジー」という言葉の広がり</h3><p>ちなみに、Palantir FoundryのOntologyは、オブジェクト型・リンク型・アクション型という独自のモデルで業務の世界を表現するもので、RDF/OWLをデータモデルの基盤にはしていません（Palantir自身の資料では、RDF/OWL形式との入出力もうたわれています）。MicrosoftのGraphRAGも、LLMでテキストからナレッジグラフを作るもので、RDFではありません。</p><pre><code class=\"language-text\">「オントロジー」「ナレッジグラフ」と名乗っていても、\nW3C標準の RDF/OWL とは限らない。\n</code></pre><p>ここは混乱しやすいので、覚えておくと便利です。</p><hr><h2 id=\"12-%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E6%A7%8B%E6%88%90%E3%82%92%E6%95%B4%E7%90%86%E3%81%99%E3%82%8B\">12. レイヤー構成を整理する</h2><p>最後に、全体像を整理します。</p><pre><code class=\"language-text\">問い合わせる側（BIツール、AI、スクリプト、人間）\n    │\n    │  共通語彙 saas: で問う\n    ↓\nセマンティックレイヤー\n    ├── ontology.ttl ＝ T-Box（意味の定義）\n    └── mapping.ttl  ＝ 各ソース語彙との意味の対応\n    │\n    │  推論して、書かれていない事実も導く\n    ↓\nRDFとして表現されたデータ（A-Box）\n    │\n    │  ここに、元のデータをRDFに変換する（取り込む）工程が必要\n    ↓\n実際のデータソース\n    ├── Postgres\n    ├── SaaSのAPI\n    └── CSV など\n</code></pre><p>ここで、正直に書いておかなければならないことがあります。</p><pre><code class=\"language-text\">今回の記事では、data.ttl を手で書いた。\n\nしかし実務では、データはPostgresやSaaSのAPIの中にある。\nそれをA-Box（RDF）にする工程は、\n誰かが作らなければならない。\n</code></pre><p>この工程は、この記事では扱っていません。実際には、次のような選択肢があります。</p><pre><code class=\"language-text\">・定期的にRDFへ書き出す（マテリアライズ）\n・問い合わせ時にSQLへ変換する（R2RML / Ontop などの仮想化。データはRDBに置いたまま、SPARQLをSQLに書き換えて実行する）\n・アプリケーション側でRDFとして書き出す\n</code></pre><p>「セマンティックレイヤーがあれば変換が不要になる」わけではありません。7章で不要になったのは、項目名をそろえるための変換でした（<code>mapping.ttl</code>が語彙どうしの対応を引き受けたからです）。PostgresなどのデータをRDFの形にする変換は、<strong>依然として必要です</strong>。セマンティックレイヤーが不要にするのは、「システムごとにバラバラな意味の解釈を、問い合わせる側が毎回やり直すこと」です。</p><p>メトリクス型のセマンティックレイヤーも含めて描き直すと、こうなります。</p><pre><code class=\"language-text\">問い合わせる側（BIツール、AIエージェント、人間）\n    │\n    ├──「どれだけ？」────→ メトリクス型セマンティックレイヤー\n    │                      （Snowflake / Databricks / Fabric / dbt）\n    │                         │\n    │                         │ 「Tenant」「Plan」の意味は\n    │                         │ オントロジーに従う\n    │                         ↓\n    └──「何が何と、どう？」→ オントロジー型セマンティックレイヤー\n                              （このシリーズで作っている saas: 語彙）\n                                 │\n                                 ↓\n                            各システムのデータ\n                            （SaaS管理基盤、請求、CRM、サポート…）\n</code></pre><pre><code class=\"language-text\">オントロジー   = 意味の基準\n    「Tenantとは何か」\n    「EntitlementとSubscriptionはどう違うか」\n    「請求システムの customer と CRM の account は同じものか」\n\nメトリクス層   = 計算の基準\n    「Tenantごとの月間利用量を、どう集計するか」\n    「プラン別の新規契約数を、どう数えるか」\n</code></pre><p>メトリクス型で「アクティブテナント数」を定義するとき、その前提には「そもそもTenantとは何か」「どの状態をアクティブと呼ぶか」という取り決めがあるはずです。その取り決めを、テーブルやSQLの中に埋もれさせずに書き出したものが、オントロジーです。</p><p><strong>2つは競合ではなく、積み重なるもの</strong>だと考えています。そして、このシリーズが作っているのは下の層、つまり意味の基準のほうです。</p><p>ここまで見てきた「意味の定義」は、このシリーズの第1回から第3回で作ってきたもの、そのものです。</p><pre><code class=\"language-text\">第1回：Tenant, User, Membership, Role, Permission, Subscription, Plan, Resource\n第2回：Organization, Workspace, Team, RoleAssignment\n第3回：Feature, Entitlement\n</code></pre><p>つまり、<strong>オントロジーを作ること＝セマンティックレイヤーの語彙を構築すること</strong>でした。第4回以降で語彙が増えるたびに、このセマンティックレイヤーが答えられる問いも増えていきます。</p><hr><h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\">まとめ</h2><pre><code class=\"language-text\">「セマンティックレイヤー」には、2つの系統がある:\n\n    メトリクス型（Snowflake / Databricks / Fabric / dbt / Cube / Looker）\n        数字の計算のしかたを、テーブルの上に定義する\n        → 「どれだけ？」に強い。数字のズレをなくす\n\n    オントロジー型（RDF / OWL / SPARQL / SHACL）\n        ものごとの関係と意味を、テーブルから独立して定義する\n        → 「何が何と、どうつながっている？」に強い。推論と横断ができる\n\n    このシリーズが作っているのは、オントロジー型\n\nセマンティックレイヤーとは:\n    データの意味を一箇所に定義し、\n    どのツールからでも同じ意味で問い合わせられるようにする仕組み\n\nオントロジーとの関係:\n    T-Box（クラスとプロパティの定義）がセマンティックレイヤーの本体\n    A-Box（実データ）はT-Boxの語彙で記述される\n\nT-Boxは飾りではない:\n    owl:inverseOf を書いておくと、推論で逆方向をたどれる\n    → 推論なし 0行、推論あり 3行\n\n意味の対応を宣言すれば、語彙の違いを吸収できる:\n    rdfs:subPropertyOf で各システムの語彙を共通語彙に接続\n    → データもクエリも変更せず、3行から5行に\n\n正直な限界:\n    ・逆方向は、推論なしでも（逆パス ^ などで）取れる。\n      推論の価値は「問い合わせる側が共通語彙一本で書けること」\n    ・owl:inverseOf はRDFS推論では処理されない（OWLの推論が必要）\n    ・推論の増加分の大半は固定オーバーヘッド（265件中、約230件）\n    ・SPARQLも述語IRIが変われば壊れる\n    ・PostgresなどからA-Boxへの変換工程は別途必要\n</code></pre><p>手を動かした手順は、次の4ステップでした。</p><pre><code class=\"language-text\">1. pip install rdflib owlrl\n2. ontology.ttl（T-Box）と data.ttl（A-Box）を保存する\n3. python query.py ontology.ttl data.ttl query-features.rq\n   → 0行（hasEntitlement 方向のトリプルがまだ存在しない）\n4. python query.py --inference ontology.ttl data.ttl query-features.rq\n   → 3行（T-Boxの owl:inverseOf から、逆向きの事実が導かれた）\n</code></pre><p>ここまでで、このシリーズで作っているオントロジーがどう使われるかが、なんとなく伝わったかと思います。具体的なユースケースでどう使うの？というのは、さらに別の会でもっと具体的に説明したいと思います。</p><p></p><p>次回（第4回）からは、語彙の構築に戻ります。テーマは「使える。でも、どこまで？」です。Featureが使えるとして、その上限と消費量をどう表現するか、という話に入っていこうと思います！</p><p>こちらからは以上です。</p><hr><h2 id=\"%E5%8F%82%E8%80%83%E8%B3%87%E6%96%99%EF%BC%882026%E5%B9%B410%E6%9C%88%E6%99%82%E7%82%B9%EF%BC%89\">参考資料（2026年10月時点）</h2><ul><li>Snowflake — Semantic views: <a href=\"https://docs.snowflake.com/en/user-guide/views-semantic/querying\">https://docs.snowflake.com/en/user-guide/views-semantic/querying</a> / CREATE SEMANTIC VIEW: <a href=\"https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view\">https://docs.snowflake.com/en/sql-reference/sql/create-semantic-view</a></li><li>Snowflake — Open Semantic Interchange: <a href=\"https://www.snowflake.com/en/blog/open-semantic-interchange-ai-standard/\">https://www.snowflake.com/en/blog/open-semantic-interchange-ai-standard/</a> / 仕様公開: <a href=\"https://www.snowflake.com/en/blog/open-semantic-interchanges-specs-finalized/\">https://www.snowflake.com/en/blog/open-semantic-interchanges-specs-finalized/</a></li><li>Apache Ossie (incubating): <a href=\"https://ossie.apache.org/updates/ossie-enters-apache-incubator/\">https://ossie.apache.org/updates/ossie-enters-apache-incubator/</a> / オントロジー仕様（草案）: <a href=\"https://github.com/apache/ossie/blob/main/ontology/ontology.md\">https://github.com/apache/ossie/blob/main/ontology/ontology.md</a></li><li>dbt v1.12（Ossie形式の読み込み）: <a href=\"https://docs.getdbt.com/docs/dbt-versions/dbt-upgrade/upgrading-to-v1.12\">https://docs.getdbt.com/docs/dbt-versions/dbt-upgrade/upgrading-to-v1.12</a></li><li>Databricks — Metric views: <a href=\"https://docs.databricks.com/aws/en/metric-views/\">https://docs.databricks.com/aws/en/metric-views/</a> / Unity Catalog semantics: <a href=\"https://docs.databricks.com/aws/en/uc-semantics/\">https://docs.databricks.com/aws/en/uc-semantics/</a></li><li>Microsoft — Fabric IQ: <a href=\"https://learn.microsoft.com/en-us/fabric/iq/overview\">https://learn.microsoft.com/en-us/fabric/iq/overview</a> / Ontology: <a href=\"https://learn.microsoft.com/en-us/fabric/iq/ontology/overview\">https://learn.microsoft.com/en-us/fabric/iq/ontology/overview</a></li><li>Microsoft — Power BI semantic model（旧 dataset）: <a href=\"https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename\">https://learn.microsoft.com/en-us/power-bi/connect-data/service-datasets-rename</a> / XMLA エンドポイント: <a href=\"https://learn.microsoft.com/en-us/fabric/enterprise/powerbi/service-premium-connect-tools\">https://learn.microsoft.com/en-us/fabric/enterprise/powerbi/service-premium-connect-tools</a> / Direct Lake: <a href=\"https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-overview\">https://learn.microsoft.com/en-us/fabric/fundamentals/direct-lake-overview</a></li><li>dbt — MetricFlow の Apache 2.0 化: <a href=\"https://www.getdbt.com/blog/open-source-metricflow-governed-metrics\">https://www.getdbt.com/blog/open-source-metricflow-governed-metrics</a></li><li>Cube — SQL API: <a href=\"https://docs.cube.dev/reference/core-data-apis/sql-api\">https://docs.cube.dev/reference/core-data-apis/sql-api</a></li><li>Looker — Open SQL Interface: <a href=\"https://docs.cloud.google.com/looker/docs/sql-interface\">https://docs.cloud.google.com/looker/docs/sql-interface</a></li><li>Palantir Foundry Ontology: <a href=\"https://www.palantir.com/docs/foundry/ontology/core-concepts\">https://www.palantir.com/docs/foundry/ontology/core-concepts</a></li><li>Microsoft GraphRAG: <a href=\"https://microsoft.github.io/graphrag/\">https://microsoft.github.io/graphrag/</a></li><li>Ontop（R2RML / 仮想ナレッジグラフ）: <a href=\"https://ontop-vkg.org/guide/\">https://ontop-vkg.org/guide/</a></li></ul><p></p>","url":"https://ghost.tech.anti-pattern.co.jp/saaswoti-cai-nisitaontoroziwozuo-tutemiyousirizu-3-donoji-neng-gashi-erunoka/","canonical_url":null,"uuid":"a4602a42-50eb-4713-a38a-30cf7ea57972","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"6ad5b842d3fc9f00013c2337","reading_time":30}},"pageContext":{"slug":"saaswoti-cai-nisitaontoroziwozuo-tutemiyousirizu-3-donoji-neng-gashi-erunoka"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}