Skip to content

Relations

topofocus edited this page Apr 19, 2019 · 1 revision

OrientDB provides multiple solutions to achieve a RDBMS-like relations

  1. Create two classes and link them directly (unidirectional Link)
  2. Create a vertex-class and embed another class through a :embedded_list property
  3. Create two vertex-classes and connect them via an edge-class (bidirectional Link)

1:N Unidirectional Linking

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 >"] 

1:n:m

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>"] 

1:N Bidirectional Links

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}>] 

1:n:m

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

Basic querying

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