Relations
OrientDB provides multiple solutions to achieve a RDBMS-like relations
- Create two classes and link them directly (unidirectional Link)
- Create a vertex-class and embed another class through a
:embedded_list
property - Create two vertex-classes and connect them via an edge-class (bidirectional Link)
Instead of joining database classes (the classic RDMS approach) Orientdb provides a schema based direct linking feature.
First create a proper schema
ORD.create_vertex_class :base, :first_list
Base.create_property :first_list, type: :link_list, linked_class: FirstList
Base.create_property :label, index: :unique
Then fill it with data
( 0..9 ).each do | b |
base_record= Base.create label: b, first_list: []
( 0..9 ).each {|c| base_record.first_list << FirstList.create( label: c ) }
end
The record can inspected and accessed in the usual manner
> Base.first
=> #<Base:0x0000000002bf0570 @metadata={:type=>"d", :class=>"base", :version=>11, :fieldTypes=>"first_list=z", :cluster=>25, :record=>0},
@attributes={:first_list=>["#33:0", "#34:0", "#35:0", "#36:0", "#37:0", "#38:0", "#39:0", "#40:0", "#33:1", "#34:1"], :label=>0, :created_at=>Sat, 06 Apr 2019 06:10:01 +0000}>
> pp Base.first.first_list.to_human
["<FirstList: label : 0 >",
"<FirstList: label : 1 >",
"<FirstList: label : 2 >",
"<FirstList: label : 3 >",
(...)
"<FirstList: label : 9 >"]
> Base.first.first_list.where( label: 6).to_human
INFO->select from ( select expand( first_list) from #25:0 ) where label = 6
=> ["<FirstList: label : 6 >"]
> Base.first.first_list.where( 'label < 6 and label >4').to_human
INFO->select from ( select expand( first_list) from #25:0 ) where label < 6 and label >4
=> ["<FirstList: label : 5 >"]
This approach can easily extended. One can add a field second_list to FirstList
and assign a LinkList property to SecondList
. This is a blueprint to create such a relation
ORD.create_vertex_class :base, :first_list, :second_list, :log
Base.create_property :first_list, type: :link_list, linked_class: FirstList
Base.create_property :label, index: :unique
FirstList.create_property :second_list , type: :link_list, linked_class: SecondList
(0 .. 9).each do | b |
base_record = Base.create( label: b, first_list: [])
(0..9 ).each do | c |
list_record = FirstList.create( label: c , second_list: [])
list_record.second_list << (0..9).map{|n| SecondList.new( label: n ) }
base_record.first_list << list_record
end
end
# to verify
> SecondList.count
=> 1000
> FirstList.count
=> 100
note 1: The constructor list_record.second_list << (0..9).map{|n| SecondList.new( label: n )
creates SecondList
-records in the process of appending the records to FirstList
. Its much more efficient then the two step approach in the first layer.
note2: By changing the property of SecondList
to type: :list
the link_list becomes an embedded List. No changes to the behavior, but SecondList
-records are embedded to FirstList.second_list
.
Some queries.
> Base.where(label: 7).first.first_list.where( label: 8).first.second_list.to_human
INFO->MATCH {class: base, as: bases, where:( label = 7)} RETURN bases
INFO->select from ( select expand( first_list) from #32:0 ) where label = 8
=> ["<SecondList: label : 0>", "<SecondList: label : 1>", "<SecondList: label : 2>", "<SecondList: label : 3>", "<SecondList: label : 4>", "<SecondList: label : 5>", "<SecondList: label : 6>", "<SecondList: label : 7>", "<SecondList: label : 8>", "<SecondList: label : 9>"]
> Base.where(label: 7).first.first_list.where( label: 8)
.first.second_list.where( 'label <6' )
.to_human
INFO->MATCH {class: base, as: bases, where:( label = 7)} RETURN bases
INFO->select from ( select expand( first_list) from #32:0 ) where label = 8
INFO->select from ( select expand( second_list) from #39:9 ) where label <6
=> ["<SecondList: label : 0>", "<SecondList: label : 1>", "<SecondList: label : 2>", "<SecondList: label : 3>", "<SecondList: label : 4>", "<SecondList: label : 5>"]
We need two Vertex
-Classes to store the data and an Edge
-Class representing the link.
``ruby
ORD.create_vertex_class :base, :node
ORD.create_edge_class :connects
Connects.uniq_index
`Edge-Class.unique_index` puts constrains to the `Edge`class and eliminates duplicate links.
The 1:n relation is build by
```ruby
b = Base.create( item: 'b' );
(1..10).map{|n| Connects.create from: b, to: Node.create( item: n) }
To query the base-record of a given Node
-record
> Node.where(item: 5).first.in_connects.out
INFO->MATCH {class: node, as: nodes, where:( item = 5)} RETURN nodes
=> [#<Base:0x0000000001fbec70 @metadata={:type=>"d", :class=>"base", :version=>11, :fieldTypes=>"out_connects=g", :cluster=>25, :record=>0,
:edges=>{:in=>[], :out=>["connects"]}},
@attributes={:item=>"b", :out_connects=>["#41:0", "#42:0", "#43:0", "#44:0", "#45:0", "#46:0", "#47:0", "#48:0", "#41:1", "#42:1"]>]
and reverse
> Base.where(item: 'b').first.out_connects.in
=> [#<Node:0x000000000225e890 @metadata={:type=>"d", :class=>"node", :version=>2, :fieldTypes=>"in_connects=g", :cluster=>33, :record=>0,
:edges=>{:in=>["connects"], :out=>[]}},
@attributes={:item=>1, :in_connects=>["#41:0"], :created_at=>Mon, 08 Apr 2019 21:32:34 +0000}>,
(...)
#<Node:0x0000000002872628 @metadata={:type=>"d", :class=>"node", :version=>2, :fieldTypes=>"in_connects=g", :cluster=>34, :record=>1,
:edges=>{:in=>["connects"], :out=>[]}},
@attributes={:item=>10, :in_connects=>["#42:1"], :created_at=>Mon, 08 Apr 2019 21:32:34 +0000}>]
As above, a blueprint to create a 2-Layer 1:n Relation
b = Base.create( item: 'b' )
(1..10).map do |n|
new_node = Node.create( item: n)
(1..10).map{|i| Connects.create from: new_node, to: ExtraNode.create( item: new_node.item**2), attributes:{ extra: true } }
Connects.create from: b, to: new_node, attributes: {basic: true}
end
end
Adjacent nodes are resolved directly
> Base.first.out.in.item
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# or
> Base.first.out_connected.in.where(item: 6).out_connected.in.item
Overview
Data Management
- Joining Tables, embedded Lists
- Links and Link Lists
- Relations
- Bidirectional Connections
- Working with Hashes
Public API
- Database
- Model CRUD
Misc