このトピックでは、公開されている保険データセットを使用して、PolarDB を使用してグラフクエリを実行し、保険金請求シナリオにおける異常な請求記録と不正なグループを特定する方法を紹介します。たとえば、請求者に関するポリシーを照会したり、保険契約者の社会的関係を分析して不正行為の可能性を探ることができます。PolarDB は、リレーショナルデータベースをグラフ分析機能で強化し、企業における集中型のデータ管理と分析をサポートします。
グラフデータベースエンジンについて
グラフ分析は、データサイエンスの重要な分野です。グラフ構造を使用してデータを表し、さまざまな計算および分析タスクを実行します。グラフ構造は、ノード(または頂点)とエッジで構成され、ノードはエンティティを表し、エッジはエンティティ間の関係を表します。グラフコンピューティングは、ソーシャルネットワーク分析、レコメンデーションシステム、知識グラフ、パス最適化などの分野で一般的に使用されます。
PolarDB for PostgreSQL は Apache AGE と高度に互換性があり、知識グラフの保存とクエリをサポートしています。同じデータベースクラスタ内のデータを照会するために、標準 ANSI SQL と openCypher グラフクエリ言語の両方を使用できます。
PolarDB for PostgreSQL と完全な互換性
AGE は PolarDB for PostgreSQL の拡張機能です。データベースの再構築を必要とせずに、既存の PolarDB データベースで使用できます。 AGE は、トランザクションサポート、同時実行制御、さまざまなインデックス作成とパフォーマンス最適化機能など、PolarDB が提供するすべての機能を活用します。
統合されたグラフとリレーショナルクエリ
AGE は、リレーショナルデータとグラフデータの同時処理を可能にします。単一のクエリで SQL とグラフクエリ言語の両方を使用できるため、複雑なデータモデルの処理が簡素化され、操作効率が向上します。
Cypher クエリ言語のサポート
AGE は、グラフデータベース向けに調整された Cypher クエリ言語をサポートしています。構文はシンプルで柔軟性があり、グラフデータのクエリと操作に対する直感的なアプローチを提供します。
高性能
PolarDB が提供する最適化機能とグラフデータ向けに調整されたインデックスを組み合わせることで、AGE は大規模なグラフデータと複雑なグラフクエリを効率的に管理できます。
結論として、AGE が提供する機能に基づいて、PolarDB はグラフクエリを簡単かつ効率的に処理できます。
シナリオ
説明
保険金請求詐欺には、通常、保険会社が保有する患者、病気、請求に関するデータが関係します。請求申請、病気、その他の関連エンティティ間の関連性を分析することにより、異常な請求記録を特定し、潜在的な不正グループを明らかにすることができます。
データとモデル
データは、公開されている保険データセットに基づいています。データセットはこちらにあります。保険業界の基本的な要素が含まれています。データモデルは、次のグラフに抽象化できます。
頂点:保険契約者、担当者、請求、患者、病気。
エッジ:has_disease、policyholder_of_claim、incharge_of_claim、insured_of_claim、similar_claim、policyholder_connection。
プロパティ:名前、高リスク、リスクスコア、病名、類似性スコア、レベル、請求日、料金など。
ベストプラクティス
データベースの準備
グラフエンジン拡張機能は、リビジョンバージョンが 2.0.14.12.24.0 以降の PolarDB for PostgreSQL 14 2.0 でのみサポートされています。詳細については、「はじめに」をご参照ください。
拡張機能を作成します。
CREATE EXTENSION age;
データベースまたは検索パスに拡張機能を追加し、アカウントのライブラリをプリロードします。
説明データ管理 (DMS) を使用して
search_path
を構成すると、互換性の問題が発生する可能性があります。そのような場合は、PolarDB-Tools を使用して関連する文を実行できます。ALTER DATABASE <dbname> SET search_path = public,ag_catalog; ALTER USER <username> SET search_path = public,ag_catalog; ALTER DATABASE <dbname> SET session_preload_libraries TO 'age'; ALTER USER <username> SET session_preload_libraries TO 'age';
データインポート
create_graph
名前空間のag_catalog
関数を使用してグラフを作成します。SELECT create_graph('graph');
頂点とエッジを挿入します。ダウンロードしたデータファイルは CSV 形式であり、必要な ID 情報が含まれていません。データをインポートする前にファイルを変換してください。付録には、データを PolarDB でサポートされている頂点とエッジに変換するための Python スクリプトが用意されています。次の変換結果をご覧ください。
保険契約者
SELECT create_vlabel('graph','policyholder'); SELECT * FROM cypher('graph', $$ CREATE (:policyholder {policyholder_id:'PH3068',fname:'ADAM',lname:'OCHSENBEIN',risk_score:'88',high_risk:'1'}) $$ ) as (n agtype); SELECT * FROM cypher('graph', $$ CREATE (:policyholder {policyholder_id:'PH3069',fname:'MALINDA',lname:'MEHSERLE',risk_score:'42',high_risk:'0'}) $$ ) as (n agtype); SELECT * FROM cypher('graph', $$ CREATE (:policyholder {policyholder_id:'PH3070',fname:'SANDRA',lname:'KUHTA',risk_score:'20',high_risk:'0'}) $$ ) as (n agtype); ...
請求
- vlabel を作成する SELECT create_vlabel('graph','claim'); SELECT * FROM cypher('graph', $$ CREATE (:claim {claim_id:'C3571',charge:'6517.53',claim_date:'2013-08-11 00:00:00',duration:'13',insured_id:'28523',diagnosis:'no exception',person_incharge_id:'PI23070',type:'services',policyholder_id:'PH9507'}) $$ ) as (n agtype); SELECT * FROM cypher('graph', $$ CREATE (:claim {claim_id:'C3572',charge:'49273.65',claim_date:'2017-02-10 00:00:00',duration:'3',insured_id:'1220',diagnosis:'no exception',person_incharge_id:'PI21197',type:'services',policyholder_id:'PH406'}) $$ ) as (n agtype); SELECT * FROM cypher('graph', $$ CREATE (:claim {claim_id:'C3573',charge:'52005.98',claim_date:'2014-06-29 00:00:00',duration:'27',insured_id:'23735',diagnosis:'no exception',person_incharge_id:'PI22361',type:'services',policyholder_id:'PH7911'}) $$ ) as (n agtype); ...
保険契約者と請求の間の関連付け
SELECT * FROM cypher('graph', $$ MATCH (a:claim), (b:policyholder) WHERE a.claim_id = 'C1528' AND b.policyholder_id = 'PH2963' CREATE (a)-[e:RELTYPE ]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('graph', $$ MATCH (a:claim), (b:policyholder) WHERE a.claim_id = 'C1529' AND b.policyholder_id = 'PH1353' CREATE (a)-[e:RELTYPE ]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('graph', $$ MATCH (a:claim), (b:policyholder) WHERE a.claim_id = 'C1530' AND b.policyholder_id = 'PH1071' CREATE (a)-[e:RELTYPE ]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('graph', $$ MATCH (a:claim), (b:policyholder) WHERE a.claim_id = 'C1531' AND b.policyholder_id = 'PH8102' CREATE (a)-[e:RELTYPE ]->(b) RETURN e$$) as (e agtype); SELECT * FROM cypher('graph', $$ MATCH (a:claim), (b:policyholder) WHERE a.claim_id = 'C1532' AND b.policyholder_id = 'PH4768' CREATE (a)-[e:RELTYPE ]->(b) RETURN e$$) as (e agtype); ...
変換された結果を SQL ファイルとして保存し、PostgreSQL クライアントなどのクライアントツールを使用してデータをインポートします。
例
シンプルなクエリ
統計
さまざまなタイプのノードの数を照会します。
SELECT count(*) FROM cypher('graph', $$ MATCH (v) RETURN v $$) as (v agtype);
次の結果が返されます。
count -------- 120567
請求の数を照会します
SELECT count(*) FROM cypher('graph', $$ MATCH (v:claim) RETURN v $$) as (v agtype);
次の結果が返されます。
count -------- 100001
保険契約者の数を照会します
SELECT count(*) FROM cypher('graph', $$ MATCH (v:policyholder) RETURN v $$) as (v agtype);
次の結果が返されます。
count ------- 10006
担当者の数を照会します
SELECT count(*) FROM cypher('graph', $$ MATCH (v:incharge) RETURN v $$) as (v agtype);
次の結果が返されます。
count ------- 10001
病気の数を照会します
SELECT count(*) FROM cypher('graph', $$ MATCH (v:disease) RETURN v $$) as (v agtype);
次の結果が返されます。
count ------- 393
患者の数を照会します
SELECT count(*) FROM cypher('graph', $$ MATCH (v:patient) RETURN v $$) as (v agtype);
次の結果が返されます。
count ------- 166
フィルターとソートクエリ
請求 C4377 の保険契約者、担当者、および患者のステータスを照会します。
SELECT 'policyholder_id' as type, policyholder_id FROM cypher('graph', $$
MATCH (:claim {claim_id: 'C4377'})-[]->(policyholder:policyholder)
RETURN policyholder.policyholder_id
$$) AS (policyholder_id agtype)
UNION
SELECT 'incharge_id', incharge_id FROM cypher('graph', $$
MATCH (:claim {claim_id: 'C4377'})-[]->(v:incharge)
RETURN v.incharge_id
$$) AS (incharge_id agtype)
UNION
SELECT 'patient_id', patient_id FROM cypher('graph', $$
MATCH (:claim {claim_id: 'C4377'})-[]->(v:patient)
RETURN v.patient_id
$$) AS (patient_id agtype);
次の結果が返されます。
type | policyholder_id
-----------------+-----------------
patient_id | "11279"
policyholder_id | "PH3759"
incharge_id | "PI26607"
一般的なシナリオ
K 近傍法
不正な請求 C4377 と同じ被保険患者を持つ請求を照会して、請求者が保険詐欺に関与しているかどうかを判断します。
SELECT 'claim_id', claim_id FROM cypher('graph', $$ MATCH (:claim {claim_id: 'C4377'})-[]->(p:patient)<-[]-(c:claim) RETURN c.claim_id $$) AS (claim_id agtype);
次の結果が返されます。
?column? | claim_id ----------+---------- claim_id | "C28963" claim_id | "C3679" claim_id | "C96545" claim_id | "C26586" claim_id | "C26754" claim_id | "C87278" claim_id | "C87603" claim_id | "C69395" claim_id | "C67594" claim_id | "C96155" claim_id | "C10160"
不正な請求 C4377 の保険契約者の社会的関係を照会して、請求に関する早期警告を提供します。
SELECT 'policyholder_id', policyholder_id FROM cypher('graph', $$ MATCH (:claim {claim_id: 'C4377'})-[]->(a:policyholder)-[r*1..3]->(p:policyholder) RETURN p.policyholder_id $$) AS (policyholder_id agtype);
次の結果が返されます。
?column? | policyholder_id -----------------+----------------- policyholder_id | "PH52532" policyholder_id | "PH11283" policyholder_id | "PH11328" policyholder_id | "PH1" policyholder_id | "PH5" policyholder_id | "PH512" policyholder_id | "PH1569" policyholder_id | "PH4722" policyholder_id | "PH4731"
パス取得
保険契約者 PH3759 と保険契約者 PH4722 の間のパスを照会して、それらの関連性を確認します。
SELECT *
FROM cypher('graph', $$
MATCH path = (:policyholder {policyholder_id: 'PH3759'})-[r*1..3]->(:policyholder {policyholder_id: 'PH4722'})
RETURN path
$$) AS (v agtype);
次の結果が返されます。
-------
[{"id": 844424930136988, "label": "policyholder", "properties": {"fname": "KURTIS", "lname": "ALKEMA", "high_risk": "1", "risk_score": "78", "policyholder_id": "PH3759"}}::vertex, {"id": 2251799813685487, "label": "RELTYPE", "end_id": 844424930133473, "start_id": 844424930136988, "properties": {"level": "65"}}::edge, {"id": 844424930133473, "label": "policyholder", "properties": {"fname": "TERRA", "lname": "SWARB", "high_risk": "0", "risk_score": "25", "policyholder_id": "PH512"}}::vertex, {"id": 2251799813685546, "label": "RELTYPE", "end_id": 844424930138502, "start_id": 844424930133473, "properties": {"level": "62"}}::edge, {"id": 844424930138502, "label": "policyholder", "properties": {"fname": "VETA", "lname": "SEDLACK", "high_risk": "0", "risk_score": "31", "policyholder_id": "PH1569"}}::vertex, {"id": 2251799813685594, "label": "RELTYPE", "end_id": 844424930136281, "start_id": 844424930138502, "properties": {"level": "92"}}::edge, {"id": 844424930136281, "label": "policyholder", "properties": {"fname": "DEANNA", "lname": "BALSER", "high_risk": "0", "risk_score": "36", "policyholder_id": "PH4722"}}::vertex]::path
共通の近傍
請求 C4377 と請求 C67594 の共通の近傍を照会して、共有の保険契約者を特定します。
SELECT 'policyholder_id', policyholder_id FROM cypher('graph', $$
MATCH (:claim {claim_id: 'C4377'})-[]->(p:policyholder)<-[]-(:claim {claim_id: 'C67594'})
RETURN p.policyholder_id
$$) AS (policyholder_id agtype);
次の結果が返されます。
?column? | policyholder_id
-----------------+-----------------
policyholder_id | "PH3759"
協調フィルタリング
不正な請求 C4377 と同じ保険契約者を共有するポリシーを照会して、他の潜在的な不正なポリシーを特定します。
SELECT 'claim_id', claim_id FROM cypher('graph', $$ MATCH (:claim {claim_id: 'C4377'})-[]->(p:policyholder)<-[]-(c:claim) RETURN c.claim_id $$) AS (claim_id agtype);
次の結果が返されます。
?column? | claim_id ----------+---------- claim_id | "C28963" claim_id | "C96545" claim_id | "C3679" claim_id | "C87603" claim_id | "C26754" claim_id | "C26586" claim_id | "C87278" claim_id | "C69395" claim_id | "C67594" claim_id | "C96155" claim_id | "C10160"
不正な請求 C4377 に最も類似している上位 20 件のポリシーを照会します。
WITH t AS ( SELECT claim_id, replace(trim(both '"' from to_jsonb(properties)::text), '\"', '"') AS similarity FROM cypher('graph', $$ MATCH (:claim {claim_id: 'C4377'})-[e]->(c:claim) RETURN properties(e), c.claim_id $$) AS (properties agtype, claim_id agtype) ) SELECT claim_id, replace((similarity::jsonb->'similarity_score')::text, '"','')::integer AS s FROM t ORDER BY s DESC LIMIT 20;
次の結果が返されます。
claim_id | s ----------+---- "C67594" | 13 "C69395" | 13 "C10160" | 13 "C87603" | 13 "C28963" | 13 "C3679" | 13 "C26754" | 13 "C96155" | 13 "C26586" | 13 "C87278" | 13 "C96545" | 13 "C20113" | 8 "C70759" | 8 "C28785" | 8 "C12793" | 8 "C59736" | 8 "C38059" | 8 "C34068" | 8 "C71827" | 8 "C15760" | 8
結論
PolarDB for PostgreSQL と AGE 拡張機能が提供するグラフ分析機能を組み合わせて、グラフデータ分析を実行できます。PolarDB は、Cypher クエリ言語のサポートや効率的なグラフデータクエリ処理など、グラフデータの計算と分析機能を提供し、企業における集中型のデータ管理と分析を促進します。
無料トライアル
無料トライアルと特別オファー ページにアクセスし、PolarDB オプションを選択して、PolarDB for PostgreSQL が提供する GanosBase のグラフコンピューティング機能をお試しください。
付録
CSV データを変換するためのデータ変換スクリプト。
import csv
import os
def convert_vertex_csv(file_path, graph):
file_name = os.path.splitext(os.path.basename(file_path))[0].lower()
# vlabel を作成する
print("------------------------------------------------")
print("-- vlabel を作成する")
print("SELECT create_vlabel('{}','{}');".format(graph, file_name))
with open(file_path, 'r') as csvfile:
reader = csv.reader(csvfile, delimiter=',')
header = next(reader)
for row in reader:
p = ""
for h in header:
if p != "":
p += ","
else:
p += "{"
p += "{}:'{}'".format(h.lower(), row[header.index(h)].strip())
if p != "":
p += "}"
print("SELECT * FROM cypher('{}', $$ CREATE (:{} {}) $$ ) as (n agtype);".format(graph, file_name, p))
def convert_edge_csv(file_path, graph, from_type, to_type):
file_name = os.path.splitext(os.path.basename(file_path))[0].lower()
with open(file_path, 'r') as csvfile:
reader = csv.reader(csvfile, delimiter=',')
header = next(reader)
for row in reader:
p = ""
for h in header:
if (h.endswith("ID")):
continue;
if p != "":
p += ","
else:
p += "{"
p += "{}:'{}'".format(h.lower(), row[header.index(h)].strip())
if p != "":
p += "}"
print("SELECT * FROM cypher('{0}', $$ MATCH (a:{1}), (b:{2}) WHERE a.{1}_id = '{3}' AND "
"b.{2}_id = '{4}' CREATE (a)-[e:RELTYPE {5} ]->(b) RETURN e$$) as (e agtype);".format(graph, from_type, to_type, row[0].strip(), row[1].strip(), p))
def generate_graph_csv(directory, graph):
print("------------------------------------------------")
print("-- グラフを作成する")
print("SELECT create_graph('{}');".format(graph))
print("------------------------------------------------")
print("-- 頂点を作成する")
convert_vertex_csv(directory + "/POLICYHOLDER.csv", graph)
convert_vertex_csv(directory + "/INCHARGE.csv", graph)
convert_vertex_csv(directory + "/PATIENT.csv", graph)
convert_vertex_csv(directory + "/CLAIM.csv", graph)
convert_vertex_csv(directory + "/DISEASE.csv", graph)
print("------------------------------------------------")
print("-- エッジを作成する")
convert_edge_csv(directory + "/POLICYHOLDER_CONNECTION.csv", graph, 'policyholder','policyholder')
convert_edge_csv(directory + "/INCHARGE_OF_CLAIM.csv", graph,'claim', 'incharge')
convert_edge_csv(directory + "/CLAIM_SIMILARITY.csv", graph, 'claim','claim')
convert_edge_csv(directory + "/POLICYHOLDER_OF_CLAIM.csv", graph,'claim', 'policyholder')
convert_edge_csv(directory + "/INSURED_OF_CLAIM.csv", graph, 'claim','patient')
convert_edge_csv(directory + "/HAS_DISEASE.csv", graph, 'patient','disease')
generate_graph_csv("analyzing-insurance-claims-using-ibm-db2-graph-master/data", "graph")