Mengelola data tanpa skema dengan Spanner Graph

Halaman ini menjelaskan cara mengelola data tanpa skema dalam Spanner Graph. Artikel ini juga menjelaskan praktik terbaik dan tips pemecahan masalah. Sebaiknya Anda memahami skema dan kueri Spanner Graph.

Pengelolaan data tanpa skema memungkinkan Anda membuat definisi grafik yang fleksibel, dengan definisi jenis node dan edge dapat ditambahkan, diperbarui, atau dihapus tanpa perubahan skema. Pendekatan ini mendukung pengembangan iteratif dan lebih sedikit overhead pengelolaan skema, sekaligus mempertahankan pengalaman kueri grafik yang sudah dikenal.

Pengelolaan data tanpa skema sangat berguna untuk skenario berikut:

  • Anda mengelola grafik dengan perubahan yang sering terjadi, seperti pembaruan dan penambahan label dan properti elemen.
  • Grafik Anda memiliki banyak jenis node dan edge, sehingga pembuatan dan pengelolaan tabel input menjadi sulit.

Membuat model data tanpa skema

Spanner Graph memungkinkan Anda membuat grafik dari tabel dengan baris yang dipetakan ke node dan edge. Daripada menggunakan tabel terpisah untuk setiap jenis elemen, pemodelan data tanpa skema biasanya menggunakan satu tabel node dan satu tabel tepi dengan kolom STRING untuk label dan kolom JSON untuk properti.

Membuat tabel input

Anda dapat membuat satu tabel GraphNode dan satu tabel GraphEdge untuk menyimpan data tanpa skema, seperti yang ditunjukkan pada contoh berikut. Nama tabel hanya untuk ilustrasi, dan Anda dapat memilih nama sendiri.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

Contoh ini melakukan hal berikut:

  • Menyimpan semua node dalam satu tabel GraphNode, yang diidentifikasi secara unik oleh id.

  • Menyimpan semua edge dalam satu tabel GraphEdge, yang diidentifikasi secara unik oleh kombinasi sumber (id), tujuan (dest_id), dan ID sendiri (edge_id). edge_id disertakan sebagai bagian dari kunci utama untuk mengizinkan lebih dari satu edge dari pasangan id ke dest_id.

Tabel node dan edge memiliki kolom label dan properties masing-masing dari jenis STRING dan JSON.

Membuat grafik properti

Dengan pernyataan CREATE PROPERTY GRAPH, tabel input di bagian sebelumnya dipetakan sebagai node dan tepi. Anda perlu menggunakan klausa berikut untuk menentukan label dan properti untuk data tanpa skema:

  • DYNAMIC LABEL: membuat label node atau tepi dari kolom STRING dari tabel input.
  • DYNAMIC PROPERTIES: membuat properti node atau tepi dari kolom JSON dari tabel input.

Contoh berikut menunjukkan cara membuat grafik menggunakan klausa tersebut:

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Label dinamis

Klausul DYNAMIC LABEL menetapkan kolom jenis data STRING untuk menyimpan nilai label.

Misalnya, dalam baris GraphNode, jika kolom label memiliki nilai person, kolom tersebut akan dipetakan ke node Person dalam grafik. Demikian pula, dalam baris GraphEdge, jika kolom label memiliki nilai owns, kolom tersebut akan dipetakan ke tepi Owns dalam grafik.

Memetakan label `GraphNode` ke label `GraphEdge`

Properti dinamis

Klausa DYNAMIC PROPERTIES menetapkan kolom jenis data JSON untuk menyimpan properti. Kunci JSON adalah nama properti dan nilai JSON adalah nilai properti.

Misalnya, jika kolom properties baris GraphNode memiliki nilai JSON '{"name": "David", "age": 43}', kolom tersebut akan dipetakan ke node dengan properti age dan name, dengan 43 dan "David" sebagai nilai properti.

Kapan sebaiknya Anda tidak menggunakan pengelolaan data tanpa skema

Anda mungkin tidak ingin menggunakan pengelolaan data tanpa skema dalam skenario berikut:

  • Jenis node dan edge untuk data grafik Anda ditentukan dengan baik atau label dan propertinya tidak perlu sering diperbarui.
  • Data Anda sudah disimpan di Spanner dan Anda lebih suka membuat grafik dari tabel yang ada, bukan memperkenalkan tabel node dan tepi khusus baru.
  • Batasan data tanpa skema mencegah adopsi Anda.

Selain itu, jika beban kerja Anda sangat sensitif terhadap performa operasi tulis, terutama jika properti sering diperbarui, menggunakan properti yang ditentukan skema dengan jenis data primitif seperti STRING atau INT64 lebih efektif daripada menggunakan properti dinamis dengan jenis JSON.

Untuk mengetahui informasi selengkapnya tentang cara menentukan skema grafik tanpa menggunakan label dan properti data dinamis, lihat Ringkasan skema Spanner Graph.

Membuat kueri data grafik tanpa skema

Anda dapat membuat kueri data grafik tanpa skema menggunakan Graph Query Language (GQL). Anda dapat menggunakan contoh kueri dalam ringkasan Kueri Grafik Spanner dan referensi GQL dengan modifikasi terbatas.

Mencocokkan node dan edge menggunakan label

Anda dapat mencocokkan node dan edge menggunakan ekspresi label di GQL.

Kueri berikut mencocokkan node dan tepi yang terhubung yang memiliki nilai account dan transfers di kolom labelnya.

GRAPH FinGraph
MATCH (a:Account {id: 1})-[t:Transfers]->(d:Account)
RETURN COUNT(*) AS result_count;

Mengakses properti

Kunci dan nilai tingkat teratas dari jenis data JSON dimodelkan sebagai properti, seperti age dan name dalam contoh berikut.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
   }
"name": "Tom"
"age": 34

Contoh berikut menunjukkan cara mengakses properti name dari node Person.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN person.name;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

JSON"Tom"

Mengonversi jenis data properti

Properti diperlakukan sebagai nilai jenis data JSON. Dalam beberapa kasus, seperti untuk perbandingan dengan jenis SQL, jenis tersebut harus dikonversi ke jenis SQL terlebih dahulu.

Dalam contoh berikut, kueri melakukan konversi jenis data berikut:

  • Mengonversi properti is_blocked menjadi jenis boolean untuk mengevaluasi ekspresi.
  • Mengonversi properti order_number_str menjadi jenis string dan membandingkannya dengan nilai literal "302290001255747".
  • Menggunakan fungsi LAX_INT64 untuk mengonversi order_number_str menjadi bilangan bulat sebagai jenis nilai yang ditampilkan dengan aman.
GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->()
WHERE BOOL(a.is_blocked) AND STRING(t.order_number_str) = "302290001255747"
RETURN LAX_INT64(t.order_number_str) AS order_number_as_int64;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------------------+
| order_number_as_int64 |
+-----------------------+
| 302290001255747       |
+-----------------------+

Dalam klausa seperti GROUP BY dan ORDER BY, Anda juga perlu mengonversi jenis data JSON. Contoh berikut mengonversi properti city menjadi jenis string, sehingga dapat digunakan untuk pengelompokan.

GRAPH FinGraph
MATCH (person:Person {country: "South Korea"})
RETURN STRING(person.city) as person_city, COUNT(*) as cnt
LIMIT 10

Tips untuk mengonversi jenis data JSON ke jenis data SQL:

  • Konverter ketat, seperti INT64, melakukan pemeriksaan jenis dan nilai yang ketat. Hal ini direkomendasikan jika jenis data JSON diketahui dan diterapkan, misalnya, menggunakan batasan skema untuk menerapkan jenis data properti.
  • Konverter fleksibel, seperti LAX_INT64, mengonversi nilai dengan aman, jika memungkinkan, dan menampilkan NULL saat konversi tidak memungkinkan. Hal ini direkomendasikan jika pemeriksaan yang ketat tidak diperlukan atau jenis sulit diterapkan.

Anda dapat membaca lebih lanjut konversi data di tips pemecahan masalah.

Memfilter berdasarkan nilai properti

Dalam filter properti, parameter filter diperlakukan sebagai nilai jenis data JSON. Misalnya, dalam kueri berikut, is_blocked diperlakukan sebagai boolean JSON dan order_number_str sebagai string JSON.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str:"302290001255747"}]->()
RETURN a.id AS account_id;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------------------+
| account_id            |
+-----------------------+
| 7                     |
+-----------------------+

Parameter filter harus cocok dengan jenis dan nilai properti. Misalnya, jika parameter filter order_number_str adalah bilangan bulat, tidak ada kecocokan yang ditemukan karena properti adalah string JSON.

GRAPH FinGraph
MATCH (a:Account {is_blocked: false})-[t:Transfers {order_number_str: 302290001255747}]->()
RETURN t.order_number_str;

Mengakses properti JSON bertingkat

Kunci dan nilai JSON bertingkat tidak dimodelkan sebagai properti. Pada contoh berikut, kunci JSON city, state, dan country tidak dimodelkan sebagai properti karena disusun bertingkat di bawah location. Namun, Anda dapat mengaksesnya dengan operator akses kolom JSON atau operator subskrip JSON.

JSON document Properties

   {
     "name": "Tom",
     "age": 43,
     "location": {
       "city": "New York",
       "state": "NY",
       "country": "USA",
     }
   }
"name": "Tom"
"age": 34
"location": {
  "city": "New York",
  "state": "NY",
  "country": "USA",
}

Contoh berikut menunjukkan cara mengakses properti bertingkat dengan operator akses kolom JSON.

GRAPH FinGraph
MATCH (person:Person {id: 1})
RETURN STRING(person.location.city);

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

"New York"

Mengubah data tanpa skema

Spanner Graph memetakan data dari tabel ke node dan tepi grafik. Saat Anda mengubah data tabel input, tindakan ini akan langsung menyebabkan mutasi pada data grafik yang sesuai. Untuk informasi selengkapnya tentang mutasi data grafik, lihat Menyisipkan, memperbarui, atau menghapus data Grafik Spanner.

Contoh

Bagian ini memberikan contoh cara membuat, memperbarui, dan menghapus data grafik.

Menyisipkan data grafik

Contoh berikut menyisipkan node person. Nama label dan properti harus menggunakan huruf kecil.

INSERT INTO GraphNode (id, label, properties)
VALUES (4, "person", JSON'{"name": "David", "age": 43}');

Memperbarui data grafik

Contoh berikut memperbarui node Account dan menggunakan fungsi JSON_SET untuk menetapkan properti is_blocked-nya.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked', false
)
WHERE label = "account" AND id = 16;

Contoh berikut memperbarui node person dengan kumpulan properti baru.

UPDATE GraphNode
SET properties = JSON'{"name": "David", "age": 43}'
WHERE label = "person" AND id = 4;

Contoh berikut menggunakan fungsi JSON_REMOVE untuk menghapus properti is_blocked dari node Account. Setelah eksekusi, semua properti lain yang ada tetap tidak berubah.

UPDATE GraphNode
SET properties = JSON_REMOVE(
  properties,
  '$.is_blocked'
)
WHERE label = "account" AND id = 16;

Menghapus data grafik

Contoh berikut menghapus tepi Transfers di node Account yang telah ditransfer ke akun yang diblokir.

DELETE FROM GraphEdge
WHERE label = "transfers" and id IN {
  GRAPH FinGraph
  MATCH (a:Account)-[:Transfers]->{1,2}(:Account {is_blocked: TRUE})
  RETURN a.id
}

Batasan

Bagian ini mencantumkan batasan penggunaan pengelolaan data tanpa skema.

Persyaratan satu tabel untuk label dinamis

Anda hanya dapat memiliki satu tabel node jika label dinamis digunakan dalam definisinya. Batasan ini juga berlaku untuk tabel tepi. Hal berikut tidak diizinkan:

  • Menentukan tabel node dengan label dinamis bersama tabel node lainnya.
  • Menentukan tabel tepi dengan label dinamis bersama tabel tepi lainnya.
  • Menentukan beberapa tabel node atau beberapa tabel tepi yang masing-masing menggunakan label dinamis.

Misalnya, kode berikut gagal saat mencoba membuat beberapa node grafik dengan label dinamis.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNodeOne
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    GraphNodeTwo
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties),
    Account
      LABEL Account PROPERTIES(create_time)
  )
  EDGE TABLES (
    ...
  );

Nama label harus berupa huruf kecil

Nilai string label harus disimpan dalam huruf kecil agar dapat dicocokkan. Sebaiknya terapkan aturan ini di kode aplikasi atau menggunakan batasan skema.

Meskipun nilai string label harus disimpan dalam huruf kecil, nilai tersebut tidak peka huruf besar/kecil saat dirujuk dalam kueri.

Contoh berikut menunjukkan cara menyisipkan label dalam nilai huruf kecil:

INSERT INTO GraphNode (id, label) VALUES (1, "account");
INSERT INTO GraphNode (id, label) VALUES (2, "account");

Anda dapat menggunakan label yang tidak peka huruf besar/kecil untuk mencocokkan GraphNode atau GraphEdge.

GRAPH FinGraph
MATCH (accnt:Account {id: 1})-[:Transfers]->(dest_accnt:Account)
RETURN dest_accnt.id;

Nama properti harus berupa huruf kecil

Nama properti harus disimpan dalam huruf kecil. Sebaiknya terapkan aturan ini dalam kode aplikasi atau gunakan batasan skema.

Meskipun nama properti harus disimpan dalam huruf kecil, nama tersebut tidak peka huruf besar/kecil saat Anda mereferensikannya dalam kueri.

Contoh berikut menyisipkan properti name dan age menggunakan huruf kecil.

INSERT INTO GraphNode (id, label, properties)
VALUES (25, "person", JSON '{"name": "Kim", "age": 27}');

Dalam teks kueri, nama properti tidak peka huruf besar/kecil. Misalnya, Anda dapat menggunakan Age atau age untuk mengakses properti.

GRAPH FinGraph
MATCH (n:Person {Age: 27})
RETURN n.id;

Batasan lainnya

  • Hanya kunci tingkat atas dari jenis data JSON yang dimodelkan sebagai properti.
  • Jenis data properti harus sesuai dengan spesifikasi jenis JSON Spanner.

Praktik terbaik

Bagian ini menjelaskan praktik terbaik untuk membuat model data tanpa skema.

Definisi kunci utama untuk node dan edge

Kunci node harus unik di semua node grafik. Misalnya, sebagai kolom INT64 atau string UUID.

Jika memiliki beberapa tepi di antara dua node, Anda harus memasukkan ID unik untuk tepi tersebut. Contoh skema menggunakan kolom INT64 edge_id logika aplikasi.

Saat membuat skema untuk tabel node dan edge, Anda dapat menyertakan kolom label sebagai kolom kunci utama secara opsional, jika nilainya tidak dapat diubah. Jika Anda melakukannya, kunci komposit yang dibentuk oleh semua kolom kunci harus unik di semua node atau tepi. Teknik ini meningkatkan performa untuk kueri yang hanya difilter berdasarkan label.

Untuk informasi selengkapnya tentang pilihan kunci utama, lihat Memilih kunci utama.

Indeks sekunder untuk properti yang sering diakses

Untuk meningkatkan performa kueri untuk properti yang sering digunakan dalam filter, Anda dapat membuat indeks sekunder terhadap kolom properti yang dihasilkan, lalu menggunakannya dalam skema dan kueri grafik.

Contoh berikut menambahkan kolom age yang dihasilkan ke tabel GraphNode untuk node person. Nilainya adalah NULL untuk node tanpa label person.

ALTER TABLE GraphNode
ADD COLUMN person_age INT64 AS
(IF (label = "person", LAX_INT64(properties.age), NULL));

Kemudian, NULL FILTERED INDEX akan dibuat untuk person_age dan disisipkan ke dalam tabel GraphNode untuk akses lokal.

CREATE NULL_FILTERED INDEX IdxPersonAge
ON GraphNode(id, label, person_age), INTERLEAVE IN GraphNode;

Tabel GraphNode kini menyertakan kolom baru yang tersedia sebagai properti node grafik. Untuk mencerminkan hal ini dalam definisi grafik properti Anda, gunakan pernyataan CREATE OR REPLACE PROPERTY GRAPH. Tindakan ini akan mengompilasi ulang definisi dan menyertakan kolom person_age baru sebagai properti.

Pernyataan berikut mengompilasi ulang definisi dan menyertakan kolom person_age baru sebagai properti.

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode (id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode (id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Contoh berikut menjalankan kueri dengan properti yang diindeks.

GRAPH FinGraph
MATCH (person:Person {person_age: 43})
RETURN person.id, person.name;

Atau, Anda dapat menjalankan perintah ANALYZE setelah pembuatan indeks sehingga pengoptimal kueri diperbarui dengan statistik database terbaru.

Memeriksa batasan untuk integritas data

Spanner mendukung objek skema seperti batasan pemeriksaan untuk menerapkan integritas data label dan properti. Bagian ini mencantumkan rekomendasi untuk memeriksa batasan yang dapat Anda gunakan dengan data tanpa skema.

Menerapkan nilai label yang valid

Sebaiknya gunakan NOT NULL dalam definisi kolom label untuk menghindari nilai label yang tidak ditentukan.

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON,
) PRIMARY KEY (id);

Menerapkan nilai label dan nama properti dalam huruf kecil

Karena nama label dan properti harus disimpan sebagai nilai huruf kecil, sebaiknya Anda melakukan salah satu hal berikut:

Pada waktu kueri, nama label dan properti tidak peka huruf besar/kecil.

Contoh berikut menambahkan batasan label node ke tabel GraphNode untuk memastikan label dalam huruf kecil.

ALTER TABLE GraphNode ADD CONSTRAINT NodeLabelLowerCaseCheck
CHECK(LOWER(label) = label);

Contoh berikut menambahkan batasan pemeriksaan ke nama properti tepi. Pemeriksaan menggunakan JSON_KEYS untuk mengakses kunci tingkat atas. COALESCE mengonversi output menjadi array kosong jika JSON_KEYS menampilkan NULL, lalu memeriksa apakah setiap kunci menggunakan huruf kecil.

ALTER TABLE GraphEdge ADD CONSTRAINT EdgePropertiesLowerCaseCheck
CHECK(NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

Menerapkan keberadaan properti

Anda dapat membuat batasan yang memeriksa apakah properti ada untuk label.

Dalam contoh berikut, batasan memeriksa apakah node person memiliki properti name.

ALTER TABLE GraphNode
ADD CONSTRAINT NameMustExistForPersonConstraint
CHECK (IF(label = 'person', properties.name IS NOT NULL, TRUE));

Menerapkan keunikan properti

Anda dapat membuat batasan berbasis properti yang memeriksa apakah properti node atau tepi unik di seluruh node atau tepi dengan label yang sama. Untuk melakukannya, gunakan UNIQUE INDEX terhadap kolom yang dihasilkan properti.

Pada contoh berikut, indeks unik memeriksa apakah properti name dan country yang digabungkan bersifat unik untuk node person apa pun.

  1. Tambahkan kolom yang dihasilkan untuk PersonName.

    ALTER TABLE GraphNode
    ADD COLUMN person_name STRING(MAX)
    AS (IF(label = 'person', STRING(properties.name), NULL)) Hidden;
    
  2. Tambahkan kolom yang dihasilkan untuk PersonCountry.

    ALTER TABLE GraphNode
    ADD COLUMN person_country STRING(MAX)
    AS (IF(label = 'person', STRING(properties.country), NULL)) Hidden;
    
  3. Buat indeks unik NULL_FILTERED terhadap properti PersonName dan PersonCountry.

    CREATE UNIQUE NULL_FILTERED INDEX NameAndCountryMustBeUniqueForPerson
    ON GraphNode (person_name, person_country);
    

Menerapkan jenis data properti

Anda dapat menerapkan jenis data properti menggunakan batasan jenis data pada nilai properti untuk label, seperti yang ditunjukkan pada contoh berikut. Contoh ini menggunakan fungsi JSON_TYPE untuk memeriksa apakah properti name dari label person menggunakan jenis STRING.

ALTER TABLE GraphNode
ADD CONSTRAINT PersonNameMustBeStringTypeConstraint
CHECK (IF(label = 'person', JSON_TYPE(properties.name) = 'string', TRUE));

Menggabungkan label yang ditentukan dan dinamis

Spanner memungkinkan node dalam grafik properti Anda memiliki label yang ditentukan (ditentukan dalam skema) dan label dinamis (berasal dari data). Anda dapat menyesuaikan label untuk menggunakan fleksibilitas ini.

Pertimbangkan skema berikut yang menunjukkan pembuatan tabel GraphNode:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      LABEL Entity -- Defined label
      DYNAMIC LABEL (label) -- Dynamic label from data column 'label'
      DYNAMIC PROPERTIES (properties)
  );

Di sini, setiap node yang dibuat dari GraphNode memiliki label Entity yang ditentukan. Selain itu, setiap node memiliki label dinamis yang ditentukan oleh nilai dalam kolom labelnya.

Kemudian, Anda dapat menulis kueri yang cocok dengan node berdasarkan jenis label. Misalnya, kueri berikut menemukan node menggunakan label Entity yang ditentukan:

GRAPH FinGraph
MATCH (node:Entity {id: 1}) -- Querying by the defined label
RETURN node.name;

Meskipun kueri ini menggunakan label Entity yang ditentukan, ingat bahwa node yang cocok juga memiliki label dinamis berdasarkan datanya.

Contoh skema

Anda dapat menggunakan contoh skema di bagian ini sebagai template untuk membuat skema Anda sendiri. Komponen skema utama mencakup hal berikut:

  • Pembuatan tabel input grafik
  • Pembuatan grafik properti
  • Opsional: indeks traversal tepi terbalik untuk meningkatkan performa traversal terbalik
  • Opsional: indeks label untuk meningkatkan performa kueri menurut label
  • Opsional: batasan skema untuk menerapkan label huruf kecil dan nama properti

Contoh berikut menunjukkan cara membuat tabel input dan grafik properti:

CREATE TABLE GraphNode (
  id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id);

CREATE TABLE GraphEdge (
  id INT64 NOT NULL,
  dest_id INT64 NOT NULL,
  edge_id INT64 NOT NULL,
  label STRING(MAX) NOT NULL,
  properties JSON
) PRIMARY KEY (id, dest_id, edge_id),
  INTERLEAVE IN PARENT GraphNode;

CREATE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  )
  EDGE TABLES (
    GraphEdge
      SOURCE KEY (id) REFERENCES GraphNode(id)
      DESTINATION KEY (dest_id) REFERENCES GraphNode(id)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Contoh berikut menggunakan indeks untuk meningkatkan traversal tepi terbalik. Klausa STORING (properties) menyertakan salinan properti tepi, yang mempercepat kueri yang memfilter properti ini. Anda dapat menghapus klausa STORING (properties) jika kueri Anda tidak mendapatkan manfaat darinya.

CREATE INDEX R_EDGE ON GraphEdge (dest_id, id, edge_id) STORING (properties),
INTERLEAVE IN GraphNode;

Contoh berikut menggunakan indeks label untuk mempercepat pencocokan node berdasarkan label.

CREATE INDEX IDX_NODE_LABEL ON GraphNode (label);

Contoh berikut menambahkan batasan yang menerapkan label dan properti lowercase. Dua contoh terakhir menggunakan fungsi JSON_KEYS. Secara opsional, Anda dapat menerapkan pemeriksaan huruf kecil dalam logika aplikasi.

ALTER TABLE GraphNode ADD CONSTRAINT node_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphEdge ADD CONSTRAINT edge_label_lower_case
CHECK(LOWER(label) = label);

ALTER TABLE GraphNode ADD CONSTRAINT node_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

ALTER TABLE GraphEdge ADD CONSTRAINT edge_property_keys_lower_case
CHECK(
  NOT array_includes(COALESCE(JSON_KEYS(properties, 1), []), key->key<>LOWER(key)));

Mengoptimalkan update batch properti dinamis dengan DML

Mengubah properti dinamis menggunakan fungsi seperti JSON_SET dan JSON_REMOVE melibatkan operasi baca-ubah-tulis. Hal ini dapat menyebabkan biaya yang lebih tinggi dibandingkan dengan memperbarui properti jenis STRING atau INT64.

Jika beban kerja Anda melibatkan pembaruan batch ke properti dinamis menggunakan DML, pertimbangkan rekomendasi berikut untuk mencapai performa yang lebih baik:

  • Perbarui beberapa baris dalam satu pernyataan DML, bukan memproses baris satu per satu.

  • Saat memperbarui rentang kunci yang luas, kelompokkan dan urutkan baris yang terpengaruh berdasarkan kunci utamanya. Memperbarui rentang yang tidak tumpang-tindih dengan setiap DML akan mengurangi pertentangan kunci.

  • Gunakan parameter kueri dalam pernyataan DML, bukan hard code, untuk meningkatkan performa.

Berdasarkan saran ini, contoh berikut memperbarui properti is_blocked untuk 100 node dalam satu pernyataan DML. Parameter kueri mencakup:

  1. @node_ids: kunci baris GraphNode, yang disimpan dalam parameter ARRAY. Jika berlaku, pengelompokan dan pengurutan di seluruh DML akan memberikan performa yang lebih baik.

  2. @is_blocked_values: nilai yang sesuai untuk diperbarui, disimpan dalam parameter ARRAY.

UPDATE GraphNode
SET properties = JSON_SET(
  properties,
  '$.is_blocked',
  CASE id
    WHEN @node_ids[OFFSET(0)] THEN @is_blocked_values[OFFSET(0)]
    WHEN @node_ids[OFFSET(1)] THEN @is_blocked_values[OFFSET(1)]
    ...
    WHEN @node_ids[OFFSET(99)] THEN @is_blocked_values[OFFSET(99)]
  END,
  create_if_missing => TRUE)
WHERE id IN UNNEST(@node_ids)

Memecahkan masalah

Bagian ini menjelaskan cara memecahkan masalah terkait data tanpa skema.

Properti muncul lebih dari sekali dalam hasil TO_JSON

Masalah

Node berikut membuat model properti birthday dan name sebagai properti dinamis di kolom JSON-nya. Properti duplikat birthday dan name muncul dalam hasil JSON elemen grafik.

GRAPH FinGraph
MATCH (n: Person {id: 14})
RETURN SAFE_TO_JSON(n) AS n;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

{
  ,
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 14,
    "label": "person",
    "properties": {
      "birthday": "1991-12-21 00:00:00",
      "name": "Alex"
    }
  }
  
}

Kemungkinan penyebab

Secara default, semua kolom tabel dasar ditentukan sebagai properti. Menggunakan TO_JSON atau SAFE_TO_JSON untuk menampilkan elemen grafik akan menghasilkan properti duplikat. Hal ini karena kolom JSON (yaitu, properties) adalah properti yang ditentukan skema, sedangkan kunci tingkat pertama JSON dimodelkan sebagai properti dinamis.

Solusi yang direkomendasikan

Untuk menghindari perilaku ini, gunakan klausa PROPERTIES ALL COLUMNS EXCEPT untuk mengecualikan kolom properties saat Anda menentukan properti dalam skema, seperti yang ditunjukkan dalam contoh berikut:

CREATE OR REPLACE PROPERTY GRAPH FinGraph
  NODE TABLES (
    GraphNode
      PROPERTIES ALL COLUMNS EXCEPT (properties)
      DYNAMIC LABEL (label)
      DYNAMIC PROPERTIES (properties)
  );

Setelah skema berubah, elemen grafik yang ditampilkan dari jenis data JSON tidak memiliki duplikat.

GRAPH FinGraph
MATCH (n: Person {id: 1})
RETURN TO_JSON(n) AS n;

Kueri ini menampilkan hal berikut:

{
  
  "properties": {
    "birthday": "1991-12-21 00:00:00",
    "name": "Alex",
    "id": 1,
    "label": "person",
  }
}

Masalah umum saat nilai properti tidak dikonversi dengan benar

Perbaikan umum untuk masalah berikut adalah selalu menggunakan konversi nilai properti saat menggunakan properti di dalam ekspresi kueri.

Perbandingan nilai properti tanpa konversi

Masalah

No matching signature for operator = for argument types: JSON, STRING

Kemungkinan penyebab

Kueri tidak mengonversi nilai properti dengan benar. Misalnya, properti name tidak dikonversi ke jenis STRING dalam perbandingan:

GRAPH FinGraph
MATCH (p:Person)
WHERE p.name = "Alex"
RETURN p.id;

Solusi yang direkomendasikan

Untuk memperbaiki masalah ini, gunakan konversi nilai sebelum perbandingan.

GRAPH FinGraph
MATCH (p:Person)
WHERE STRING(p.name) = "Alex"
RETURN p.id;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+------+
| id   |
+------+
| 1    |
+------+

Atau, gunakan filter properti untuk menyederhanakan perbandingan kesetaraan saat konversi nilai dilakukan secara otomatis. Perhatikan bahwa jenis nilai ("Alex") harus sama persis dengan jenis STRING properti di JSON.

GRAPH FinGraph
MATCH (p:Person {name: 'Alex'})
RETURN p.id;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+------+
| id   |
+------+
| 1    |
+------+

Penggunaan nilai properti RETURN DISTINCT tanpa konversi

Masalah

Column order_number_str of type JSON cannot be used in `RETURN DISTINCT

Kemungkinan penyebab

Dalam contoh berikut, order_number_str belum dikonversi sebelum digunakan dalam pernyataan RETURN DISTINCT:

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT t.order_number_str AS order_number_str;

Solusi yang direkomendasikan

Untuk memperbaiki masalah ini, gunakan konversi nilai sebelum RETURN DISTINCT.

GRAPH FinGraph
MATCH -[t:Transfers]->
RETURN DISTINCT STRING(t.order_number_str) AS order_number_str;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------------+
| order_number_str|
+-----------------+
| 302290001255747 |
| 103650009791820 |
| 304330008004315 |
| 304120005529714 |
+-----------------+

Properti yang digunakan sebagai kunci pengelompokan tanpa konversi

Masalah

Grouping by expressions of type JSON is not allowed.

Kemungkinan penyebab

Dalam contoh berikut, t.order_number_str tidak dikonversi sebelum digunakan untuk mengelompokkan objek JSON:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN t.order_number_str, COUNT(*) AS total_transfers;

Solusi yang direkomendasikan

Untuk memperbaiki masalah ini, gunakan konversi nilai sebelum menggunakan properti sebagai kunci pengelompokan.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN STRING(t.order_number_str) AS order_number_str, COUNT(*) AS total_transfers;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------------+------------------+
| order_number_str | total_transfers |
+-----------------+------------------+
| 302290001255747 |                1 |
| 103650009791820 |                1 |
| 304330008004315 |                1 |
| 304120005529714 |                2 |
+-----------------+------------------+

Properti yang digunakan sebagai kunci pengurutan tanpa konversi

Masalah

ORDER BY does not support expressions of type JSON

Kemungkinan penyebab

Dalam contoh berikut, t.amount tidak dikonversi sebelum digunakan untuk mengurutkan hasil:

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY t.amount DESC
LIMIT 1;

Solusi yang direkomendasikan

Untuk memperbaiki masalah ini, lakukan konversi pada t.amount dalam klausa ORDER BY.

GRAPH FinGraph
MATCH (a:Account)-[t:Transfers]->(b:Account)
RETURN a.Id AS from_account, b.Id AS to_account, t.amount
ORDER BY DOUBLE(t.amount) DESC
LIMIT 1;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+--------------+------------+--------+
| from_account | to_account | amount |
+--------------+------------+--------+
|           20 |          7 | 500    |
+--------------+------------+--------+

Ketidakcocokan jenis selama konversi

Masalah

The provided JSON input is not an integer

Kemungkinan penyebab

Dalam contoh berikut, properti order_number_str disimpan sebagai jenis data STRING JSON. Jika Anda mencoba melakukan konversi ke INT64, konversi tersebut akan menampilkan error.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

Solusi yang direkomendasikan

Untuk memperbaiki masalah ini, gunakan pengonversi nilai yang sama persis dengan jenis nilai.

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE STRING(e.order_number_str) = "302290001255747"
RETURN e.amount;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

Atau, gunakan pengonversi fleksibel jika nilai dapat dikonversi ke jenis target, seperti yang ditunjukkan dalam contoh berikut:

GRAPH FinGraph
MATCH -[e:Transfers]->
WHERE LAX_INT64(e.order_number_str) = 302290001255747
RETURN e.amount;

Tindakan ini akan menampilkan hasil yang mirip dengan berikut ini:

+-----------+
| amount    |
+-----------+
| JSON"200" |
+-----------+

Langkah berikutnya