{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/whatisshacl/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__6a8ebc0ad3fc9f00013c1f6f","title":"SHACLって結局何なの？実際にデータを検証してみて理解する","slug":"whatisshacl","featured":false,"feature_image":null,"excerpt":"\nオントロジー関連の記事を読んでいると、いきなり「SHACL」というものが当たり前のように登場します。\n\n「SPARQL」と違うの？なにそれ？って感じになったりしますよね。\n\nたとえば、\n\nsaas:WorkspaceShape a sh:NodeShape ;\n    sh:targetClass saas:Workspace ;\n    sh:property [\n        sh:path saas:workspaceBelongsToTenant ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:class saas:Tenant ;\n    ] .\n\n\nこういうコードが、説明なしにいきなり出てきます。\n\nしかし、「これは何をしているのか」「これはどうやって動かすのか」が分からないまま読み進めるのは、正直つらいと思います。ぼく自身も、最初にこれを見たとき、完全においていかれました。\n\nこの記事は、そのおいていかれた気持ちを解消するための記事です。SHACLだけに絞って、それが何であり、どうやって実際に手","custom_excerpt":null,"visibility":"public","created_at_pretty":"26 August, 2026","published_at_pretty":"26 August, 2026","updated_at_pretty":"26 August, 2026","created_at":"2026-08-26T19:12:26.000+09:00","published_at":"2026-08-26T19:18:01.000+09:00","updated_at":"2026-08-26T19:18:01.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":{"name":"Ontology","slug":"ontology","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"},"tags":[{"name":"Ontology","slug":"ontology","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"}],"plaintext":"\nオントロジー関連の記事を読んでいると、いきなり「SHACL」というものが当たり前のように登場します。\n\n「SPARQL」と違うの？なにそれ？って感じになったりしますよね。\n\nたとえば、\n\nsaas:WorkspaceShape a sh:NodeShape ;\n    sh:targetClass saas:Workspace ;\n    sh:property [\n        sh:path saas:workspaceBelongsToTenant ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:class saas:Tenant ;\n    ] .\n\n\nこういうコードが、説明なしにいきなり出てきます。\n\nしかし、「これは何をしているのか」「これはどうやって動かすのか」が分からないまま読み進めるのは、正直つらいと思います。ぼく自身も、最初にこれを見たとき、完全においていかれました。\n\nこの記事は、そのおいていかれた気持ちを解消するための記事です。SHACLだけに絞って、それが何であり、どうやって実際に手元で動かすのかまで説明します。\n\n※この記事は、RDF・RDFS・OWL・Turtleについて説明済みの前提で書きます。まだの方は、先に \nRDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門\n[https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/] \nを読んでおいてください。そこで使った「山田」「ABC社」「鈴木」の例を、この記事でもそのまま使います。\n\n\n--------------------------------------------------------------------------------\n\n1. SHACLとは何か\n一言でいうと、SHACLは次のようなものです。\n\nSHACL：RDFデータが期待する形になっているかを検証するための言語\n\n\n正式名称はShapes Constraint Languageです。読み方は「シャクル」です。\n\n別のブログで、次のように書きました。\n\nOWLは強力だが、\nデータベースの入力チェックとは考え方が異なる。\n\nOWLは一般にオープンワールド仮定を使うため、\n「情報が書かれていない」ことと\n「その情報が存在しない」ことを区別できない。\n\n\nつまり、OWLだけでは、次のような検証ができません。\n\n * employeeIdは必須である\n * employeeIdは必ず1つだけである\n * 年齢は0以上である\n\nこれらを検証するために使われるのが、SHACLです。\n\n役割を並べると、次のようになります。\n\nRDF\n    情報をトリプルとして表す\n\nRDFS・OWL\n    データが何を意味するかを定義する\n\nSHACL\n    データが期待する形になっているかを検証する\n\n\nRDFS・OWLが「意味」を定義するのに対して、SHACLは「形」を検証します。\n\n\n--------------------------------------------------------------------------------\n\n2. SHACLも、実はRDFで書かれている\nこれは、最初に知っておくと理解が早くなる点です。\n\nSHACLの制約(これをShapeと呼びます)自体も、RDFのトリプルとして表現されます。つまり、Turtleで書けます。\n\n@prefix sh:   <http://www.w3.org/ns/shacl#> .\n@prefix ex:   <https://example.com/company#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\n\nsh:という接頭辞は、SHACLの語彙を指す名前空間です。ex:と、扱いは何も変わりません。\n\nsh:NodeShape\nsh:targetClass\nsh:property\n\n\nこれらは、SHACLという名前空間に定義された、クラスやプロパティです。RDFの外側にある特別な魔法の構文ではありません。\n\n\n--------------------------------------------------------------------------------\n\n3. 最初のShapeを書く\n「Employeeには社員番号(employeeId)が必ず1つ必要」という制約を、Shapeとして書いてみます。\n\n@prefix sh:   <http://www.w3.org/ns/shacl#> .\n@prefix ex:   <https://example.com/company#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] .\n\n\n1行ずつ見ていきます。\n\nex:EmployeeShape a sh:NodeShape ;\n\n\nこれは、「ex:EmployeeShapeという名前のShape(検証ルールのまとまり)を1つ作る」という宣言です。\n\nsh:targetClass ex:Employee ;\n\n\nこれは、「このShapeは、ex:Employeeという型を持つリソースすべてに適用する」という指定です。\n\nsh:property [ ... ]\n\n\nこれは、「対象リソースが持つ、あるプロパティについての制約」です。角括弧[ ]の中に、具体的な制約を書きます。\n\nsh:path ex:employeeId ;\n\n\nチェック対象のプロパティはex:employeeIdです。\n\nsh:minCount 1 ;\nsh:maxCount 1 ;\n\n\nex:employeeIdは、最低1個、最大1個、つまり必ず1個だけ必要です。\n\nsh:datatype xsd:string ;\n\n\n値の型は、文字列(xsd:string)でなければなりません。\n\nsh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n\n\nこの制約に違反したときに表示するメッセージです。\n\nここまでで、sh:propertyの基本的な語彙は出そろいました。\n\nsh:path       ：どのプロパティを見るか\nsh:minCount   ：最低いくつ必要か\nsh:maxCount   ：最大いくつまでか\nsh:datatype   ：値の型は何か\nsh:message    ：違反したときに何を表示するか\n\n\n\n--------------------------------------------------------------------------------\n\n4. 値ではなくリソースを要求する場合\nemployeeIdは文字列でしたが、「Employeeは必ずOrganizationにworksFor\nしている」という制約も書いてみます。今回の目的語は、文字列ではなく、Organizationという型を持つ別のリソースです。\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] ;\n\n    sh:property [\n        sh:path ex:worksFor ;\n        sh:minCount 1 ;\n        sh:class ex:Organization ;\n        sh:message \"Employeeは必ずOrganizationに勤務している必要があります。\"@ja ;\n    ] .\n\n\nここで使っているのはsh:datatypeではなくsh:classです。\n\nsh:datatype ex:Organization   ← 誤り。datatypeはリテラルの型を指定するもの\nsh:class    ex:Organization   ← 正しい。classはリソースの型を指定するもの\n\n\n値がリテラル(文字列・数値など)ならsh:datatype、値が別のリソース(IRIで識別されるもの)ならsh:class\nを使います。目的語がリテラルかリソースかという区別は、rdf-turtle.mdの10節で説明したrdf:resourceの話とまったく同じ区別です。\n\n同じsh:propertyが、;でつながって2つ並んでいます。これはTurtleの記法で、「ex:EmployeeShapeは、この2つのsh:property\nを持つ」という意味です。\n\n\n--------------------------------------------------------------------------------\n\n5. 実際に検証してみる\nここまでは、Shapeの書き方の説明でした。ここからは、実際にこのShapeを使って、データを検証してみます。\n\n5.1 準備\nPythonの実行環境があれば、次のコマンドだけで準備できます。\n\npip install pyshacl\n\n\npyshacl\nは、SHACLをPythonで実装した、コマンドラインから使えるツールです。RDF/SPARQLの実装で知られるRDFLibプロジェクトの一部として公開されています。\n\nインストールが終わると、pyshaclというコマンドが使えるようになります。\n\n5.2 Shapeファイルを保存する\n先ほどのex:EmployeeShapeを、shapes.ttlというファイルに保存します。\n\n# shapes.ttl\n@prefix sh:   <http://www.w3.org/ns/shacl#> .\n@prefix ex:   <https://example.com/company#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] ;\n\n    sh:property [\n        sh:path ex:worksFor ;\n        sh:minCount 1 ;\n        sh:class ex:Organization ;\n        sh:message \"Employeeは必ずOrganizationに勤務している必要があります。\"@ja ;\n    ] .\n\n\n5.3 検証したいデータを保存する\nテストデータとして、山田のデータを、data.ttlというファイルに保存します。\n\n# data.ttl\n@prefix ex: <https://example.com/company#> .\n\nex:yamada a ex:Employee ;\n    ex:employeeId \"E001\" ;\n    ex:worksFor ex:abc .\n\nex:abc a ex:Organization .\n\n\n山田は、社員番号(E001)を持ち、ABC社(ex:abc、Organization型)に勤務しています。2つの制約を、どちらも満たしているはずです。\n\n5.4 検証を実行する\n同じフォルダにshapes.ttlとdata.ttlを置いた状態で、次のコマンドを実行します。\n\npyshacl -s shapes.ttl data.ttl\n\n\n-sのあとにShapeファイル、そのあとに検証したいデータファイルを指定します。\n\n結果は、次のように表示されます。\n\nValidation Report\nConforms: True\n\n\nConforms: True\nは、「このデータは、すべてのShapeを満たしている」という意味です。実際に手元で動かして、この結果が出ることを確認してみてください。\n\n\n--------------------------------------------------------------------------------\n\n6. 制約に違反するデータを検証してみる\n次は、あえて制約に違反するデータを用意します。data.ttlの内容を、次のように書き換えます。\n\n# data.ttl\n@prefix ex: <https://example.com/company#> .\n\nex:suzuki a ex:Employee ;\n    ex:employeeId \"E002\" .\n\n\n鈴木は、社員番号(E002)は持っていますが、ex:worksFor\nが書かれていません。「Employeeは必ずOrganizationに勤務している」という制約に違反しているはずです。\n\n同じコマンドを、もう一度実行します。\n\npyshacl -s shapes.ttl data.ttl\n\n\n今度は、次のように表示されます。\n\nValidation Report\nConforms: False\nResults (1):\nConstraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):\n\tSeverity: sh:Violation\n\tSource Shape: [ sh:class ex:Organization ; sh:message Literal(\"Employeeは必ずOrganizationに勤務している必要があります。\", lang=ja) ; sh:minCount Literal(\"1\", datatype=xsd:integer) ; sh:path ex:worksFor ]\n\tFocus Node: ex:suzuki\n\tResult Path: ex:worksFor\n\tMessage: Employeeは必ずOrganizationに勤務している必要があります。\n\n\nConforms: Falseになり、違反の内容が具体的に表示されました。\n\n\n--------------------------------------------------------------------------------\n\n7. 違反レポートの読み方\n出力が長く見えますが、押さえるべき点は3つだけです。\n\nFocus Node    ：どのリソースが違反したか\nResult Path   ：どのプロパティについての違反か\nMessage       ：なぜ違反なのか\n\n\n今回の場合は、次のように読めます。\n\nFocus Node  : ex:suzuki\nResult Path : ex:worksFor\nMessage     : Employeeは必ずOrganizationに勤務している必要があります。\n\n\nつまり、「ex:suzukiが、ex:worksForという点で、この理由により違反している」ということです。\n\nConstraint Violation in MinCountConstraintComponent\nという行は、「どの種類のSHACL制約(今回は個数の制約)に違反したか」を表しています。これは内部的な分類名なので、最初は無視してかまいません。まずFocus\nNode・Result Path・Messageの3つだけ読めれば十分です。\n\nSeverity: sh:Violationという行にも触れておきます。SHACLの制約には、重大度(severity)を指定できます。既定では\nsh:Violation(違反、ConformsをFalseにする)ですが、sh:Warningやsh:Infoを指定すると、「レポートには表示されるが、\nConformsはFalseにならない」という、もう少し緩い検証を書けます。\n\n\n--------------------------------------------------------------------------------\n\n8. もう少し複雑な検証について\nここまでで説明したsh:propertyは、「1つのリソースが持つ、1つのプロパティ」についての制約でした。\n\nしかし、場合によっては次のような制約も出てきます。\n\nTeamがアクセスするWorkspaceは、\nTeamと同じTenantに所属していなければならない\n\n\nこれは、「1つのプロパティの値」だけでは判定できません。TeamのTenantと、WorkspaceのTenantという、2つの値を比較する必要があります。\n\nこうした複雑な条件を書くために、SHACLにはsh:sparql\nという仕組みがあります。名前のとおり、SPARQL(RDFに対する検索言語)を使って、制約を自由に書けます。\n\n「SPARQL」ってなんなのよ！という方は、\n\n * SPARQLって結局何なの？実際にRDFデータに問い合わせしてみて勉強しよう！\n   [https://tech.anti-pattern.co.jp/whatisspaeql/]\n\nまずこちらをざっと眺めてみてください！\n\nsaas:TeamWorkspaceTenantConsistencyShape\n    a sh:NodeShape ;\n    sh:targetClass saas:Team ;\n    sh:sparql [\n        a sh:SPARQLConstraint ;\n        sh:select \"\"\"\n            SELECT $this\n            WHERE {\n                $this saas:canAccessWorkspace ?workspace .\n                ...\n                FILTER (?teamTenant != ?workspaceTenant)\n            }\n        \"\"\"\n    ] .\n\n\nここで大事なのは、次の1点だけです。\n\nsh:property   ：1つのプロパティについての、単純な制約\nsh:sparql     ：複数の値を比較するような、複雑な制約\n\n\nsh:propertyで書けることには限界がありますが、その限界を超えたいときにsh:sparql\nがある、ということだけ知っておけば、「ああ、複雑な条件を書くための機能を使っているんだな」となんとなく読めるはずです。\n\n\n--------------------------------------------------------------------------------\n\nまとめ\nSHACLとは\n    RDFデータが期待する形になっているかを検証する言語\n\nsh:NodeShape / sh:targetClass\n    どのクラスに制約をかけるか\n\nsh:property / sh:path\n    どのプロパティに、どんな制約をかけるか\n\nsh:minCount / sh:maxCount\n    個数の制約\n\nsh:datatype / sh:class\n    値がリテラルかリソースかによって使い分ける型の制約\n\nsh:message\n    違反したときに表示する説明文\n\nsh:sparql\n    sh:propertyでは書けない、複雑な制約を書くための仕組み\n\n\nそして、実際に検証を動かす手順は、次の3ステップです。\n\n1. pip install pyshacl\n2. Shapeを shapes.ttl に、データを data.ttl に保存する\n3. pyshacl -s shapes.ttl data.ttl を実行する\n\n\nConforms: Trueならデータは制約を満たしており、Conforms: Falseなら、Focus Node・Result Path・Message\nを読めば、何が問題なのかが分かります。\n\nここまで読めば、突然出てくるSHACLの例も、もう初見のコードではなくなっているはずです。ぜひ戻って読んでみてください。\n\nこちらからは以上です。","html":"<h1></h1><p>オントロジー関連の記事を読んでいると、いきなり「SHACL」というものが当たり前のように登場します。</p><p>「SPARQL」と違うの？なにそれ？って感じになったりしますよね。</p><p>たとえば、</p><pre><code class=\"language-turtle\">saas:WorkspaceShape a sh:NodeShape ;\n    sh:targetClass saas:Workspace ;\n    sh:property [\n        sh:path saas:workspaceBelongsToTenant ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:class saas:Tenant ;\n    ] .\n</code></pre><p>こういうコードが、説明なしにいきなり出てきます。</p><p>しかし、「これは何をしているのか」「これはどうやって動かすのか」が分からないまま読み進めるのは、正直つらいと思います。ぼく自身も、最初にこれを見たとき、完全においていかれました。</p><p>この記事は、そのおいていかれた気持ちを解消するための記事です。SHACLだけに絞って、それが何であり、どうやって実際に手元で動かすのかまで説明します。</p><p>※この記事は、RDF・RDFS・OWL・Turtleについて説明済みの前提で書きます。まだの方は、先に <a href=\"https://tech.anti-pattern.co.jp/rdfrdfsowlturtlenowei-iwozheng-li-site-huairudeli-jie-suruontoroziru-men/\">RDF・RDFS・OWL・Turtleの違いを整理して、ファイルで理解するオントロジー入門</a> を読んでおいてください。そこで使った「山田」「ABC社」「鈴木」の例を、この記事でもそのまま使います。</p><hr><h2 id=\"1-shacl%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B\">1. SHACLとは何か</h2><p>一言でいうと、SHACLは次のようなものです。</p><pre><code class=\"language-text\">SHACL：RDFデータが期待する形になっているかを検証するための言語\n</code></pre><p>正式名称はShapes Constraint Languageです。読み方は「シャクル」です。</p><p>別のブログで、次のように書きました。</p><pre><code class=\"language-text\">OWLは強力だが、\nデータベースの入力チェックとは考え方が異なる。\n\nOWLは一般にオープンワールド仮定を使うため、\n「情報が書かれていない」ことと\n「その情報が存在しない」ことを区別できない。\n</code></pre><p>つまり、OWLだけでは、次のような検証ができません。</p><ul><li><code>employeeId</code>は必須である</li><li><code>employeeId</code>は必ず1つだけである</li><li>年齢は0以上である</li></ul><p>これらを検証するために使われるのが、SHACLです。</p><p>役割を並べると、次のようになります。</p><pre><code class=\"language-text\">RDF\n    情報をトリプルとして表す\n\nRDFS・OWL\n    データが何を意味するかを定義する\n\nSHACL\n    データが期待する形になっているかを検証する\n</code></pre><p>RDFS・OWLが「意味」を定義するのに対して、SHACLは「形」を検証します。</p><hr><h2 id=\"2-shacl%E3%82%82%E3%80%81%E5%AE%9F%E3%81%AFrdf%E3%81%A7%E6%9B%B8%E3%81%8B%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B\">2. SHACLも、実はRDFで書かれている</h2><p>これは、最初に知っておくと理解が早くなる点です。</p><p>SHACLの制約(これをShapeと呼びます)自体も、RDFのトリプルとして表現されます。つまり、Turtleで書けます。</p><pre><code class=\"language-turtle\">@prefix sh:   &lt;http://www.w3.org/ns/shacl#&gt; .\n@prefix ex:   &lt;https://example.com/company#&gt; .\n@prefix xsd:  &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n</code></pre><p><code>sh:</code>という接頭辞は、SHACLの語彙を指す名前空間です。<code>ex:</code>と、扱いは何も変わりません。</p><pre><code class=\"language-text\">sh:NodeShape\nsh:targetClass\nsh:property\n</code></pre><p>これらは、SHACLという名前空間に定義された、クラスやプロパティです。RDFの外側にある特別な魔法の構文ではありません。</p><hr><h2 id=\"3-%E6%9C%80%E5%88%9D%E3%81%AEshape%E3%82%92%E6%9B%B8%E3%81%8F\">3. 最初のShapeを書く</h2><p>「Employeeには社員番号(<code>employeeId</code>)が必ず1つ必要」という制約を、Shapeとして書いてみます。</p><pre><code class=\"language-turtle\">@prefix sh:   &lt;http://www.w3.org/ns/shacl#&gt; .\n@prefix ex:   &lt;https://example.com/company#&gt; .\n@prefix xsd:  &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] .\n</code></pre><!--kg-card-begin: html-->  <script type=\"text/turtle\" class=\"ontologyviewer\" data-height=\"600px\" data-layout=\"dagre\">\n@prefix sh:   <http://www.w3.org/ns/shacl#> .\n@prefix ex:   <https://example.com/company#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] .\n\n</script><!--kg-card-end: html--><p>1行ずつ見ていきます。</p><pre><code class=\"language-turtle\">ex:EmployeeShape a sh:NodeShape ;\n</code></pre><p>これは、「<code>ex:EmployeeShape</code>という名前のShape(検証ルールのまとまり)を1つ作る」という宣言です。</p><pre><code class=\"language-turtle\">sh:targetClass ex:Employee ;\n</code></pre><p>これは、「このShapeは、<code>ex:Employee</code>という型を持つリソースすべてに適用する」という指定です。</p><pre><code class=\"language-turtle\">sh:property [ ... ]\n</code></pre><p>これは、「対象リソースが持つ、あるプロパティについての制約」です。角括弧<code>[ ]</code>の中に、具体的な制約を書きます。</p><pre><code class=\"language-turtle\">sh:path ex:employeeId ;\n</code></pre><p>チェック対象のプロパティは<code>ex:employeeId</code>です。</p><pre><code class=\"language-turtle\">sh:minCount 1 ;\nsh:maxCount 1 ;\n</code></pre><p><code>ex:employeeId</code>は、最低1個、最大1個、つまり必ず1個だけ必要です。</p><pre><code class=\"language-turtle\">sh:datatype xsd:string ;\n</code></pre><p>値の型は、文字列(<code>xsd:string</code>)でなければなりません。</p><pre><code class=\"language-turtle\">sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n</code></pre><p>この制約に違反したときに表示するメッセージです。</p><p>ここまでで、<code>sh:property</code>の基本的な語彙は出そろいました。</p><pre><code class=\"language-text\">sh:path       ：どのプロパティを見るか\nsh:minCount   ：最低いくつ必要か\nsh:maxCount   ：最大いくつまでか\nsh:datatype   ：値の型は何か\nsh:message    ：違反したときに何を表示するか\n</code></pre><hr><h2 id=\"4-%E5%80%A4%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%92%E8%A6%81%E6%B1%82%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88\">4. 値ではなくリソースを要求する場合</h2><p><code>employeeId</code>は文字列でしたが、「Employeeは必ずOrganizationに<code>worksFor</code>している」という制約も書いてみます。今回の目的語は、文字列ではなく、Organizationという型を持つ別のリソースです。</p><pre><code class=\"language-turtle\">ex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] ;\n\n    sh:property [\n        sh:path ex:worksFor ;\n        sh:minCount 1 ;\n        sh:class ex:Organization ;\n        sh:message \"Employeeは必ずOrganizationに勤務している必要があります。\"@ja ;\n    ] .\n</code></pre><!--kg-card-begin: html-->  <script type=\"text/turtle\" class=\"ontologyviewer\" data-height=\"600px\" data-layout=\"dagre\">\n@prefix sh:   <http://www.w3.org/ns/shacl#> .\n@prefix ex:   <https://example.com/company#> .\n@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] ;\n\n    sh:property [\n        sh:path ex:worksFor ;\n        sh:minCount 1 ;\n        sh:class ex:Organization ;\n        sh:message \"Employeeは必ずOrganizationに勤務している必要があります。\"@ja ;\n    ] .\n\n\n</script><!--kg-card-end: html--><p>ここで使っているのは<code>sh:datatype</code>ではなく<code>sh:class</code>です。</p><pre><code class=\"language-text\">sh:datatype ex:Organization   ← 誤り。datatypeはリテラルの型を指定するもの\nsh:class    ex:Organization   ← 正しい。classはリソースの型を指定するもの\n</code></pre><p>値がリテラル(文字列・数値など)なら<code>sh:datatype</code>、値が別のリソース(IRIで識別されるもの)なら<code>sh:class</code>を使います。目的語がリテラルかリソースかという区別は、<code>rdf-turtle.md</code>の10節で説明した<code>rdf:resource</code>の話とまったく同じ区別です。</p><p>同じ<code>sh:property</code>が、<code>;</code>でつながって2つ並んでいます。これはTurtleの記法で、「<code>ex:EmployeeShape</code>は、この2つの<code>sh:property</code>を持つ」という意味です。</p><hr><h2 id=\"5-%E5%AE%9F%E9%9A%9B%E3%81%AB%E6%A4%9C%E8%A8%BC%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B\">5. 実際に検証してみる</h2><p>ここまでは、Shapeの書き方の説明でした。ここからは、実際にこのShapeを使って、データを検証してみます。</p><h3 id=\"51-%E6%BA%96%E5%82%99\">5.1 準備</h3><p>Pythonの実行環境があれば、次のコマンドだけで準備できます。</p><pre><code class=\"language-bash\">pip install pyshacl\n</code></pre><p><code>pyshacl</code>は、SHACLをPythonで実装した、コマンドラインから使えるツールです。RDF/SPARQLの実装で知られるRDFLibプロジェクトの一部として公開されています。</p><p>インストールが終わると、<code>pyshacl</code>というコマンドが使えるようになります。</p><h3 id=\"52-shape%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\">5.2 Shapeファイルを保存する</h3><p>先ほどの<code>ex:EmployeeShape</code>を、<code>shapes.ttl</code>というファイルに保存します。</p><pre><code class=\"language-turtle\"># shapes.ttl\n@prefix sh:   &lt;http://www.w3.org/ns/shacl#&gt; .\n@prefix ex:   &lt;https://example.com/company#&gt; .\n@prefix xsd:  &lt;http://www.w3.org/2001/XMLSchema#&gt; .\n\nex:EmployeeShape a sh:NodeShape ;\n    sh:targetClass ex:Employee ;\n\n    sh:property [\n        sh:path ex:employeeId ;\n        sh:minCount 1 ;\n        sh:maxCount 1 ;\n        sh:datatype xsd:string ;\n        sh:message \"Employeeには社員番号が1つ必要です。\"@ja ;\n    ] ;\n\n    sh:property [\n        sh:path ex:worksFor ;\n        sh:minCount 1 ;\n        sh:class ex:Organization ;\n        sh:message \"Employeeは必ずOrganizationに勤務している必要があります。\"@ja ;\n    ] .\n</code></pre><h3 id=\"53-%E6%A4%9C%E8%A8%BC%E3%81%97%E3%81%9F%E3%81%84%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\">5.3 検証したいデータを保存する</h3><p>テストデータとして、山田のデータを、<code>data.ttl</code>というファイルに保存します。</p><pre><code class=\"language-turtle\"># data.ttl\n@prefix ex: &lt;https://example.com/company#&gt; .\n\nex:yamada a ex:Employee ;\n    ex:employeeId \"E001\" ;\n    ex:worksFor ex:abc .\n\nex:abc a ex:Organization .\n</code></pre><p>山田は、社員番号(<code>E001</code>)を持ち、ABC社(<code>ex:abc</code>、Organization型)に勤務しています。2つの制約を、どちらも満たしているはずです。</p><h3 id=\"54-%E6%A4%9C%E8%A8%BC%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B\">5.4 検証を実行する</h3><p>同じフォルダに<code>shapes.ttl</code>と<code>data.ttl</code>を置いた状態で、次のコマンドを実行します。</p><pre><code class=\"language-bash\">pyshacl -s shapes.ttl data.ttl\n</code></pre><p><code>-s</code>のあとにShapeファイル、そのあとに検証したいデータファイルを指定します。</p><p>結果は、次のように表示されます。</p><pre><code class=\"language-text\">Validation Report\nConforms: True\n</code></pre><p><code>Conforms: True</code>は、「このデータは、すべてのShapeを満たしている」という意味です。実際に手元で動かして、この結果が出ることを確認してみてください。</p><hr><h2 id=\"6-%E5%88%B6%E7%B4%84%E3%81%AB%E9%81%95%E5%8F%8D%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E6%A4%9C%E8%A8%BC%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B\">6. 制約に違反するデータを検証してみる</h2><p>次は、あえて制約に違反するデータを用意します。<code>data.ttl</code>の内容を、次のように書き換えます。</p><pre><code class=\"language-turtle\"># data.ttl\n@prefix ex: &lt;https://example.com/company#&gt; .\n\nex:suzuki a ex:Employee ;\n    ex:employeeId \"E002\" .\n</code></pre><p>鈴木は、社員番号(<code>E002</code>)は持っていますが、<code>ex:worksFor</code>が書かれていません。「Employeeは必ずOrganizationに勤務している」という制約に違反しているはずです。</p><p>同じコマンドを、もう一度実行します。</p><pre><code class=\"language-bash\">pyshacl -s shapes.ttl data.ttl\n</code></pre><p>今度は、次のように表示されます。</p><pre><code class=\"language-text\">Validation Report\nConforms: False\nResults (1):\nConstraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):\n\tSeverity: sh:Violation\n\tSource Shape: [ sh:class ex:Organization ; sh:message Literal(\"Employeeは必ずOrganizationに勤務している必要があります。\", lang=ja) ; sh:minCount Literal(\"1\", datatype=xsd:integer) ; sh:path ex:worksFor ]\n\tFocus Node: ex:suzuki\n\tResult Path: ex:worksFor\n\tMessage: Employeeは必ずOrganizationに勤務している必要があります。\n</code></pre><p><code>Conforms: False</code>になり、違反の内容が具体的に表示されました。</p><hr><h2 id=\"7-%E9%81%95%E5%8F%8D%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E8%AA%AD%E3%81%BF%E6%96%B9\">7. 違反レポートの読み方</h2><p>出力が長く見えますが、押さえるべき点は3つだけです。</p><pre><code class=\"language-text\">Focus Node    ：どのリソースが違反したか\nResult Path   ：どのプロパティについての違反か\nMessage       ：なぜ違反なのか\n</code></pre><p>今回の場合は、次のように読めます。</p><pre><code class=\"language-text\">Focus Node  : ex:suzuki\nResult Path : ex:worksFor\nMessage     : Employeeは必ずOrganizationに勤務している必要があります。\n</code></pre><p>つまり、「<code>ex:suzuki</code>が、<code>ex:worksFor</code>という点で、この理由により違反している」ということです。</p><p><code>Constraint Violation in MinCountConstraintComponent</code>という行は、「どの種類のSHACL制約(今回は個数の制約)に違反したか」を表しています。これは内部的な分類名なので、最初は無視してかまいません。まず<code>Focus Node</code>・<code>Result Path</code>・<code>Message</code>の3つだけ読めれば十分です。</p><p><code>Severity: sh:Violation</code>という行にも触れておきます。SHACLの制約には、重大度(severity)を指定できます。既定では<code>sh:Violation</code>(違反、<code>Conforms</code>をFalseにする)ですが、<code>sh:Warning</code>や<code>sh:Info</code>を指定すると、「レポートには表示されるが、<code>Conforms</code>はFalseにならない」という、もう少し緩い検証を書けます。</p><hr><h2 id=\"8-%E3%82%82%E3%81%86%E5%B0%91%E3%81%97%E8%A4%87%E9%9B%91%E3%81%AA%E6%A4%9C%E8%A8%BC%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\">8. もう少し複雑な検証について</h2><p>ここまでで説明した<code>sh:property</code>は、「1つのリソースが持つ、1つのプロパティ」についての制約でした。</p><p>しかし、場合によっては次のような制約も出てきます。</p><pre><code class=\"language-text\">TeamがアクセスするWorkspaceは、\nTeamと同じTenantに所属していなければならない\n</code></pre><p>これは、「1つのプロパティの値」だけでは判定できません。TeamのTenantと、WorkspaceのTenantという、2つの値を比較する必要があります。</p><p>こうした複雑な条件を書くために、SHACLには<code>sh:sparql</code>という仕組みがあります。名前のとおり、SPARQL(RDFに対する検索言語)を使って、制約を自由に書けます。</p><p>「SPARQL」ってなんなのよ！という方は、</p><ul><li><a href=\"https://tech.anti-pattern.co.jp/whatisspaeql/\">SPARQLって結局何なの？実際にRDFデータに問い合わせしてみて勉強しよう！</a></li></ul><p>まずこちらをざっと眺めてみてください！</p><pre><code class=\"language-turtle\">saas:TeamWorkspaceTenantConsistencyShape\n    a sh:NodeShape ;\n    sh:targetClass saas:Team ;\n    sh:sparql [\n        a sh:SPARQLConstraint ;\n        sh:select \"\"\"\n            SELECT $this\n            WHERE {\n                $this saas:canAccessWorkspace ?workspace .\n                ...\n                FILTER (?teamTenant != ?workspaceTenant)\n            }\n        \"\"\"\n    ] .\n</code></pre><p>ここで大事なのは、次の1点だけです。</p><pre><code class=\"language-text\">sh:property   ：1つのプロパティについての、単純な制約\nsh:sparql     ：複数の値を比較するような、複雑な制約\n</code></pre><p><code>sh:property</code>で書けることには限界がありますが、その限界を超えたいときに<code>sh:sparql</code>がある、ということだけ知っておけば、「ああ、複雑な条件を書くための機能を使っているんだな」となんとなく読めるはずです。</p><hr><h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\">まとめ</h2><pre><code class=\"language-text\">SHACLとは\n    RDFデータが期待する形になっているかを検証する言語\n\nsh:NodeShape / sh:targetClass\n    どのクラスに制約をかけるか\n\nsh:property / sh:path\n    どのプロパティに、どんな制約をかけるか\n\nsh:minCount / sh:maxCount\n    個数の制約\n\nsh:datatype / sh:class\n    値がリテラルかリソースかによって使い分ける型の制約\n\nsh:message\n    違反したときに表示する説明文\n\nsh:sparql\n    sh:propertyでは書けない、複雑な制約を書くための仕組み\n</code></pre><p>そして、実際に検証を動かす手順は、次の3ステップです。</p><pre><code class=\"language-text\">1. pip install pyshacl\n2. Shapeを shapes.ttl に、データを data.ttl に保存する\n3. pyshacl -s shapes.ttl data.ttl を実行する\n</code></pre><p><code>Conforms: True</code>ならデータは制約を満たしており、<code>Conforms: False</code>なら、<code>Focus Node</code>・<code>Result Path</code>・<code>Message</code>を読めば、何が問題なのかが分かります。</p><p>ここまで読めば、突然出てくるSHACLの例も、もう初見のコードではなくなっているはずです。ぜひ戻って読んでみてください。</p><p>こちらからは以上です。</p>","url":"https://ghost.tech.anti-pattern.co.jp/whatisshacl/","canonical_url":null,"uuid":"f37a8ea6-7dba-4f9d-9066-7f6befbf5b73","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"6a8ebc0ad3fc9f00013c1f6f","reading_time":7}},"pageContext":{"slug":"whatisshacl"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}