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.traverse("suppliers").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.

  • The traversal is done explicitly before the ordering, so that the traversed relationship’s properties are available for ordering.

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).