Filtering and ordering¶
For the examples in this section, we will be using the following model:
class SupplierRel(StructuredRel):
since = DateTimeProperty(default=datetime.now)
class Supplier(StructuredNode):
name = StringProperty()
delivery_cost = IntegerProperty()
class Coffee(StructuredNode):
name = StringProperty(unique_index=True)
price = IntegerProperty()
suppliers = RelationshipFrom(Supplier, 'SUPPLIES', model=SupplierRel)
Filtering¶
neomodel allows filtering on nodes’ and relationships’ properties. Filters can be combined using Django’s Q syntax. It also allows multi-hop relationship traversals to filter on “remote” elements.
Filter methods¶
The .nodes
property of a class returns all nodes of that type from the database.
This set (called NodeSet) can be iterated over and filtered on, using the .filter method:
# nodes with label Coffee whose price is greater than 2
high_end_coffees = Coffee.nodes.filter(price__gt=2)
try:
java = Coffee.nodes.get(name='Java')
except DoesNotExist:
# .filter will not throw an exception if no results are found
# but .get will
print("Couldn't find coffee 'Java'")
The filter method borrows the same Django filter format with double underscore prefixed operators:
lt - less than
gt - greater than
lte - less than or equal to
gte - greater than or equal to
ne - not equal
in - item in list
isnull - True IS NULL, False IS NOT NULL
exact - string equals
iexact - string equals, case insensitive
contains - contains string value
icontains - contains string value, case insensitive
startswith - starts with string value
istartswith - starts with string value, case insensitive
endswith - ends with string value
iendswith - ends with string value, case insensitive
regex - matches a regex expression
iregex - matches a regex expression, case insensitive
These operators work with both .get and .filter methods.
Combining filters¶
The filter method allows you to combine multiple filters:
cheap_arabicas = Coffee.nodes.filter(price__lt=5, name__icontains='arabica')
These filters are combined using the logical AND operator. To execute more complex logic (for example, queries with OR statements), Q objects <neomodel.Q> can be used. This is borrowed from Django.
Q
objects can be combined using the &
and |
operators. Statements of arbitrary complexity can be composed by combining Q
objects
with the &
and |
operators and use parenthetical grouping. Also, Q
objects can be negated using the ~
operator, allowing for combined lookups
that combine both a normal query and a negated (NOT
) query:
Q(name__icontains='arabica') | ~Q(name__endswith='blend')
Chaining Q
objects will join them as an AND clause:
not_middle_priced_arabicas = Coffee.nodes.filter(
Q(name__icontains='arabica'),
Q(price__lt=5) | Q(price__gt=10)
)
Traversals and filtering¶
Sometimes you need to filter nodes based on other nodes they are connected to. This can be done by including a traversal in the filter method.
# Find all suppliers of coffee 'Java' who have been supplying since 2007
# But whose prices are greater than 5
since_date = datetime(2007, 1, 1)
java_old_timers = Coffee.nodes.filter(
name='Java',
suppliers__delivery_cost__gt=5,
**{"suppliers|since__lt": since_date}
)
In the example above, note the following syntax elements:
The name of relationships as defined in the StructuredNode class is used to traverse relationships. suppliers in this example.
Double underscore __ is used to target a property of a node. delivery_cost in this example.
A pipe | is used to separate the relationship traversal from the property filter. The filter also has to included in a **kwargs dictionary, because the pipe character would break the syntax. This is a special syntax to indicate that the filter is on the relationship itself, not on the node at the end of the relationship.
The filter operators like lt, gt, etc. can be used on the filtered property.
Traversals can be of any length, with each relationships separated by a double underscore __, for example:
# country is here a relationship between Supplier and Country
Coffee.nodes.filter(suppliers__country__name='Brazil')
Enforcing relationship/path existence¶
The has method checks for existence of (one or more) relationships, in this case it returns a set of Coffee nodes which have a supplier:
Coffee.nodes.has(suppliers=True)
This can be negated by setting suppliers=False, to find Coffee nodes without suppliers.
You can also filter on the existence of more complex traversals by using the traverse_relations method. See Path traversal.
Ordering¶
neomodel allows ordering by nodes’ and relationships’ properties. Order can be ascending or descending. Is also allows multi-hop relationship traversals to order on “remote” elements. Finally, you can inject raw Cypher clauses to have full control over ordering when necessary.
order_by¶
Ordering results by a particular property is done via the order_by method:
# Ascending sort
for coffee in Coffee.nodes.order_by('price'):
print(coffee, coffee.price)
# Descending sort
for supplier in Supplier.nodes.order_by('-delivery_cost'):
print(supplier, supplier.delivery_cost)
Removing the ordering from a previously defined query, is done by passing None to order_by:
# Sort in descending order
suppliers = Supplier.nodes.order_by('-delivery_cost')
# Don't order; yield nodes in the order neo4j returns them
suppliers = suppliers.order_by(None)
For random ordering simply pass ‘?’ to the order_by method:
Coffee.nodes.order_by('?')
Traversals and ordering¶
Sometimes you need to order results based on properties situated on different nodes or relationships. This can be done by including a traversal in the order_by method.
# Find the most expensive coffee to deliver
# Then order by the date the supplier started supplying
Coffee.nodes.order_by(
'-suppliers__delivery_cost',
'suppliers|since',
)
In the example above, note the following syntax elements:
The name of relationships as defined in the StructuredNode class is used to traverse relationships. suppliers in this example.
Double underscore __ is used to target a property of a node. delivery_cost in this example.
A pipe | is used to separate the relationship traversal from the property filter. This is a special syntax to indicate that the filter is on the relationship itself, not on the node at the end of the relationship.
Traversals can be of any length, with each relationships separated by a double underscore __, for example:
# country is here a relationship between Supplier and Country
Coffee.nodes.order_by('suppliers__country__latitude')
RawCypher¶
When you need more advanced ordering capabilities, for example to apply order to a transformed property, you can use the RawCypher method, like so:
from neomodel.sync_.match import RawCypher
class SoftwareDependency(AsyncStructuredNode):
name = StringProperty()
version = StringProperty()
SoftwareDependency(name="Package2", version="1.4.0").save()
SoftwareDependency(name="Package3", version="2.5.5").save()
latest_dep = SoftwareDependency.nodes.order_by(
RawCypher("toInteger(split($n.version, '.')[0]) DESC"),
)
In the example above, note the $n placeholder in the RawCypher clause. This is a placeholder for the node being ordered (SoftwareDependency in this case).