メインコンテンツまでスキップ

第 4 章: パターンを使いこなす

ビジネスの洞察を得るためのクエリ実践

Advanced Pattern Matching

基本的な CRUD 操作をマスターしたところで、次により実践的なビジネスインサイトを得るための、複雑なパターンマッチングのテクニックを学びます。これらの機能は、現実世界のデータの不完全さや複雑さに対応するために設計されており、Cypher が単なるデータ取得言語ではなく、データ「探索」言語であることを示しています。

4.1 可変長パターン:階層関係の探索

ビジネスシナリオ

従業員「鈴木」さんのマネジメントラインを最大 3 階層上まで遡って特定したい。彼の直属の上司、そのまた上司は誰でしょうか?

RDB でこのような階層関係を問い合わせるには、複数回の自己結合(self-join)や複雑な再帰クエリが必要となり、非常に煩雑です。一方、Cypher ではリレーションシップパターンにアスタリスク * を使うことで、可変長のパスを驚くほどシンプルに表現できます。

Variable Length Path

基本的な可変長パターン

// 鈴木さんから上方向にMANAGESリレーションシップを1〜3階層たどる
MATCH path = (emp:Employee {name: "鈴木"})<-[:MANAGES*1..3]-(mgr:Employee)
RETURN emp.name AS employee,
mgr.name AS manager,
length(path) AS hierarchyLevel,
[node IN nodes(path) | node.name] AS managementChain

より実践的な階層分析

// 組織全体のマネジメント階層を可視化
MATCH path = (leaf:Employee)<-[:MANAGES*]-(root:Employee)
WHERE NOT (root)<-[:MANAGES]-() // トップマネジメント(上司がいない人)
RETURN root.name AS topManager,
leaf.name AS subordinate,
length(path) AS depth,
[n IN nodes(path) | n.name] AS chain
ORDER BY depth DESC, topManager

パス情報の詳細分析

// 特定の従業員から到達可能な全ての部下と距離を分析
MATCH path = (mgr:Employee {name: "佐藤"})-[:MANAGES*1..]->(sub:Employee)
RETURN mgr.name AS manager,
sub.name AS subordinate,
length(path) AS distance,
relationships(path) AS managementHistory
ORDER BY distance, sub.name

4.2 複数パターンの組み合わせ:複雑な条件検索

ビジネスシナリオ

「A 社に勤務し、Python スキルを持ち、かつ現在進行中のプロジェクトに参加している従業員」という、複数の条件を同時に満たす人物を探したい。

Multi-Pattern Combination

Cypher では、カンマ , で区切ることで、複数のパターンを一つの MATCH 句に記述できます。これにより、複雑な検索条件を宣言的に、かつ可読性高く表現することが可能です。

基本的な複数パターン

MATCH
(e:Employee)-[:WORKS_FOR]->(c:Company {name: "A社"}),
(e)-[:HAS_SKILL]->(s:Skill {name: "Python"}),
(e)-[:PARTICIPATES_IN]->(p:Project {status: "進行中"})
RETURN e.name AS employeeName,
c.name AS company,
s.name AS skill,
p.name AS currentProject

より複雑な条件の組み合わせ

// 特定のスキルセットを持ち、経験豊富で、活発にプロジェクトに参加している人材を検索
MATCH
(e:Employee)-[:HAS_SKILL]->(s1:Skill {category: "AI/ML"}),
(e)-[:HAS_SKILL]->(s2:Skill {category: "プログラミング"}),
(e)-[:PARTICIPATES_IN]->(p:Project)
WHERE e.experience >= 5
AND p.status IN ["進行中", "計画中"]
WITH e, count(DISTINCT p) AS projectCount, collect(DISTINCT s1.name + ", " + s2.name) AS skillCombinations
WHERE projectCount >= 2
RETURN e.name, e.experience, projectCount, skillCombinations
ORDER BY projectCount DESC, e.experience DESC

業界別スキル分析

// 業界ごとに求められるスキルの傾向を分析
MATCH
(e:Employee)-[:WORKS_FOR]->(c:Company),
(e)-[:HAS_SKILL]->(s:Skill),
(e)-[:PARTICIPATES_IN]->(p:Project {status: "進行中"})
RETURN c.industry AS industry,
s.category AS skillCategory,
count(DISTINCT e) AS employeeCount,
collect(DISTINCT s.name) AS specificSkills
ORDER BY industry, employeeCount DESC

4.3 OPTIONAL MATCH:欠損データに対応する柔軟性

ビジネスシナリオ

全ての従業員と、その人が管理しているプロジェクトを一覧表示したい。この時、プロジェクトを一つも管理していない従業員もリストに含める必要があります。

Optional Match Concept

通常の MATCH 句は、SQL の INNER JOIN のように動作します。つまり、指定したパターンのいずれかの部分が見つからない場合、その行全体が結果から除外されます。しかし、現実のデータは不完全であることが多く、このままでは分析に偏りが生じます。

OPTIONAL MATCH 句は SQL の LEFT JOIN に相当し、パターンのマッチを試みますが、もし見つからなくても行を破棄しません。その代わり、見つからなかった部分に対応する変数を null として返します。

基本的な OPTIONAL MATCH

MATCH (e:Employee)
OPTIONAL MATCH (e)-[:MANAGES]->(subordinate:Employee)
RETURN e.name AS employeeName,
e.title AS title,
count(subordinate) AS directReports,
collect(subordinate.name) AS subordinateList
ORDER BY directReports DESC

複数の OPTIONAL MATCH の組み合わせ

// 従業員の包括的なプロファイル(スキル、プロジェクト、部下の情報)
MATCH (e:Employee)
OPTIONAL MATCH (e)-[:HAS_SKILL]->(skill:Skill)
OPTIONAL MATCH (e)-[:PARTICIPATES_IN]->(project:Project)
OPTIONAL MATCH (e)-[:MANAGES]->(subordinate:Employee)
RETURN e.name AS name,
e.title AS title,
e.experience AS experience,
collect(DISTINCT skill.name) AS skills,
collect(DISTINCT project.name) AS projects,
collect(DISTINCT subordinate.name) AS directReports
ORDER BY e.experience DESC

条件付き OPTIONAL MATCH

// 進行中のプロジェクトを持つ従業員と、そうでない従業員を区別して分析
MATCH (e:Employee)
OPTIONAL MATCH (e)-[:PARTICIPATES_IN]->(activeProject:Project {status: "進行中"})
RETURN e.name,
CASE
WHEN activeProject IS NOT NULL THEN "アクティブ"
ELSE "アサイン待ち"
END AS status,
count(activeProject) AS activeProjectCount
ORDER BY activeProjectCount DESC, e.name

4.4 WITH:クエリのパイプライン化で複雑な分析を実現

ビジネスシナリオ

まず「A 社」の全従業員をリストアップします。次に、その従業員たちがそれぞれ保有するスキルの数を数え、最終的にスキルを 2 つ以上持っている従業員だけを名前とスキル数と共に返します。

Query Pipeline Concept

WITH 句は、クエリを複数の論理的なステップに分割し、あるステップの結果を次のステップに引き渡すための強力なパイプライン機能です。

基本的な WITH パイプライン

// ステップ1: A社の従業員を見つける
MATCH (e:Employee)-[:WORKS_FOR]->(c:Company {name: "A社"})
// ステップ2: 見つけた従業員(e)を次のステップに渡す
WITH e
// ステップ3: 各従業員について、保有スキルを見つけて数を数える
MATCH (e)-[:HAS_SKILL]->(s:Skill)
WITH e, count(s) AS skillCount
// ステップ4: 集計結果(skillCount)に対してフィルタリングを行う
WHERE skillCount >= 2
// ステップ5: 最終結果を返す
RETURN e.name, skillCount
ORDER BY skillCount DESC

複雑な多段階分析

// 段階1: プロジェクトごとのチーム規模を計算
MATCH (p:Project)<-[:PARTICIPATES_IN]-(e:Employee)
WITH p, count(e) AS teamSize, collect(e) AS teamMembers

// 段階2: 大規模チーム(5人以上)のプロジェクトに絞る
WHERE teamSize >= 5
WITH p, teamSize, teamMembers

// 段階3: チームメンバーのスキル多様性を分析
UNWIND teamMembers AS member
MATCH (member)-[:HAS_SKILL]->(skill:Skill)
WITH p, teamSize, count(DISTINCT skill.category) AS skillDiversity

// 段階4: 最終的な分析結果を返す
RETURN p.name AS projectName,
teamSize,
skillDiversity,
round(skillDiversity * 1.0 / teamSize, 2) AS diversityRatio
ORDER BY diversityRatio DESC

統計分析のパイプライン

// 従業員の経験年数分布とスキル習得パターンの分析
MATCH (e:Employee)
WITH e,
CASE
WHEN e.experience <= 2 THEN "新人"
WHEN e.experience <= 5 THEN "中堅"
WHEN e.experience <= 10 THEN "ベテラン"
ELSE "エキスパート"
END AS experienceLevel

// 経験レベルごとにグループ化
WITH experienceLevel, collect(e) AS employees

// 各グループの統計を計算
UNWIND employees AS emp
OPTIONAL MATCH (emp)-[:HAS_SKILL]->(skill:Skill)
WITH experienceLevel,
count(DISTINCT emp) AS employeeCount,
avg(emp.experience) AS avgExperience,
count(skill) AS totalSkills,
count(DISTINCT skill.category) AS skillCategories

RETURN experienceLevel,
employeeCount,
round(avgExperience, 1) AS averageExperience,
round(totalSkills * 1.0 / employeeCount, 1) AS avgSkillsPerPerson,
skillCategories AS uniqueSkillCategories
ORDER BY avgExperience

4.5 実践的なパターン組み合わせ例

人材マッチングアルゴリズム

// プロジェクト要求スキルと従業員スキルのマッチング分析
MATCH (p:Project {status: "計画中"})
// プロジェクトが求めるスキル(仮想的な関係として定義)
OPTIONAL MATCH (p)-[:REQUIRES_SKILL]->(requiredSkill:Skill)
WITH p, collect(requiredSkill.name) AS requiredSkills

// 利用可能な従業員のスキルを評価
MATCH (e:Employee)
WHERE NOT (e)-[:PARTICIPATES_IN]->(:Project {status: "進行中"}) // 現在フリーな従業員
OPTIONAL MATCH (e)-[:HAS_SKILL]->(skill:Skill)
WITH p, requiredSkills, e, collect(skill.name) AS employeeSkills

// スキルマッチ度を計算
WITH p, requiredSkills, e, employeeSkills,
[skill IN requiredSkills WHERE skill IN employeeSkills] AS matchingSkills
WHERE size(matchingSkills) > 0 // 何らかのスキルマッチがある従業員のみ

RETURN p.name AS project,
e.name AS candidate,
size(requiredSkills) AS requiredSkillCount,
size(matchingSkills) AS matchingSkillCount,
round(size(matchingSkills) * 100.0 / size(requiredSkills)) AS matchPercentage,
matchingSkills AS relevantSkills
ORDER BY p.name, matchPercentage DESC

Pattern Matching Mastery

これらの高度なパターンマッチング技術をマスターすることで、Cypher は単なるデータ取得ツールから、ビジネスインサイトを発見するための強力な探索エンジンへと変貌します。次の章では、これらのパターンに集計機能を組み合わせて、さらに深い分析を行う方法を学びます。


前へ: 第 3 章: 知識グラフを育てる | 次へ: 第 5 章: データから価値を掘り出す

関連記事

著者: hnish