= Manual Indexes

== Data Used

The below examples use https://github.com/nicolewhite/neo4j-flights[flight data].

Here is a sample subset of the data that can be load to try the procedures:

[source,cypher]
----
CREATE (slc:Airport {abbr:'SLC', id:14869, name:'SALT LAKE CITY INTERNATIONAL'})
CREATE (oak:Airport {abbr:'OAK', id:13796, name:'METROPOLITAN OAKLAND INTERNATIONAL'})
CREATE (bur:Airport {abbr:'BUR', id:10800, name:'BOB HOPE'})
CREATE (f2:Flight {flight_num:6147, day:2, month:1, weekday:6, year:2016})
CREATE (f9:Flight {flight_num:6147, day:9, month:1, weekday:6, year:2016})
CREATE (f16:Flight {flight_num:6147, day:16, month:1, weekday:6, year:2016})
CREATE (f23:Flight {flight_num:6147, day:23, month:1, weekday:6, year:2016})
CREATE (f30:Flight {flight_num:6147, day:30, month:1, weekday:6, year:2016})
CREATE (f2)-[:DESTINATION {arr_delay:-13, taxi_time:9}]->(oak)
CREATE (f9)-[:DESTINATION {arr_delay:-8, taxi_time:4}]->(bur)
CREATE (f16)-[:DESTINATION {arr_delay:-30, taxi_time:4}]->(slc)
CREATE (f23)-[:DESTINATION {arr_delay:-21, taxi_time:3}]->(slc)
CREATE (f30)-[:DESTINATION]->(slc)
----

== Using Manual Index on Node Properties


In order to create manual index on a node property, you call `apoc.index.addNode` with the node, providing the properties to be indexed.

[source,cypher]
----
MATCH (a:Airport)
CALL apoc.index.addNode(a,['name'])
RETURN count(*)
----

The statement will create the node index with the *same name as the Label name(s) of the node* in this case `Airport` and add the node by their properties to the index.

Once this has been added check if the node index exists using `apoc.index.list`.

[source,cypher]
----
CALL apoc.index.list()
----

Usually `apoc.index.addNode` would be used as part of node-creation, e.g. during LOAD CSV.
There is also `apoc.index.addNodes` for adding a list of multiple nodes at once.

Once the node index is created we can start using it.

Here are some examples:

The `apoc.index.nodes` finds nodes in a manual index using the given lucene query.

NOTE: That makes only sense if you combine multiple properties in one lookup or use case insensitive or fuzzy matching full-text queries.
In all other cases the built in schema indexes should be used.

[source,cypher]
----
CALL apoc.index.nodes('Airport','name:inter*') YIELD node AS airport, weight
RETURN airport.name, weight 
LIMIT 10
----

// todo example for index search and weight

NOTE: Apoc index queries not only return nodes and relationships but also a weight, which is the score returned from the underlying Lucene index.
The results are also sorted by that score. 
That's especially helpful for partial and fuzzy text searches.

To remove the node index `Airport` created, use:

[source,cypher]
----
CALL apoc.index.remove('Airport')
----

== Using Manual Index on Relationship Properties

The procedure `apoc.index.addRelationship` is used to create a manual index on relationship properties.

As there are no schema indexes for relationships, these manual indexes can be quite useful.

[source,cypher]
----
MATCH (:Flight)-[r:DESTINATION]->(:Airport)
CALL apoc.index.addRelationship(r,['taxi_time'])
RETURN count(*)
----

The statement will create the relationship index with the *same name as relationship-type*, in this case `DESTINATION` and add the relationship by its properties to the index.

Using `apoc.index.relationships`, we can find the relationship of type `DESTINATION` with the property `taxi_time` of 11 minutes.
We can chose to also return the start and end-node.

[source,cypher]
----
CALL apoc.index.relationships('DESTINATION','taxi_time:11') YIELD rel, start AS flight, end AS airport
RETURN flight_num.flight_num, airport.name;
----

NOTE: Manual relationship indexed do not only store the relationship by its properties but also the start- and end-node.

That's why we can use that information to subselect relationships not only by property but also by those nodes, which is quite powerful.

With `apoc.index.in` we can pin the node with incoming relationships (end-node) to get the start nodes for all the `DESTINATION` relationships.
For instance to find all flights arriving in 'SALT LAKE CITY INTERNATIONAL' with a taxi_time of 7 minutes we'd use:

[source,cypher]
----
MATCH (a:Airport {name:'SALT LAKE CITY INTERNATIONAL'}) 
CALL apoc.index.in(a,'DESTINATION','taxi_time:7') YIELD node AS flight
RETURN flight
----

The opposite is `apoc.index.out`, which takes and binds end-nodes and returns start-nodes of relationships.

Really useful to quickly find a subset of relationships between nodes with many relationships (tens of thousands to millions) is `apoc.index.between`.
Here you bind both the start and end-node and provide (or not) properties of the relationships.

[source,cypher]
----
MATCH (f:Flight {flight_num:6147}) 
MATCH (a:Airport {name:'SALT LAKE CITY INTERNATIONAL'})
CALL apoc.index.between(f,'DESTINATION',a,'taxi_time:7') YIELD rel, weight
RETURN *
----

To remove the relationship index `DESTINATION` that was created, use. 

[source,cypher]
----
CALL apoc.index.remove('DESTINATION')
----

