Graph databases: Getting started

This tutorial will help you get started with the graph-oriented database Neo4j. In particular, you will learn constructs of the Cypher query language that you will need for solving the practical exercises. (In case you are wondering, the name Cypher has nothing to do with ciphers in cryptography. The language is named after a character in the movie The Matrix.)

Setting up Neo4j

As in the previous practical on relational databases, we have prepared a dataset of movies and people (actors, directors, etc.) based on IMDb. Like last time, there is a large and a small version of the dataset. We recommend that you start with the small database, but you can experiment with the large one if you like.

Download the small example database (graph-small.zip), and unpack the zip file, which should give you a subdirectory called graph-small. If you wish, there is also the very large database, graph-large.zip, which you could unpack to a subdirectory called graph-large.

For getting the database program code, there are two options:

  1. You can download graph-db.jar, which we have packaged up in the same way as the program code for the relational practical. In your terminal, change to the directory containing the downloaded JAR file, and run it as follows:

    java -jar graph-db.jar graph-small
    

    where graph-small is the directory where you unpacked the data files. When you run it, you will get a textual Cypher prompt in your terminal. (This method is used for marking exercises when you submit through Moodle.)

  2. If you want to try the graphical interface that was demonstrated in lectures (which can visualise the graph as circles connected by lines), you can download the free Neo4j Community Edition from the Neo4j download website. They provide installers for Windows, Mac and Linux.

    When you run this application it will allow you to select a directory containing Neo4j data files. You can choose the graph-small directory from unpacking the zip file in order to access the IMDb example database. You can also leave it as the default, which will give you an empty database in which you can run through a tutorial.

    To access the graphical interface, first start up the Neo4j database, and then point your web browser at localhost:7474. We suggest that you first do this with the default empty database, and follow some of the built-in tutorials available in the browser.

Both methods give you a Cypher prompt (either in a terminal, or in a web browser). You can run through the following tutorial in either mode. Beware though that if a query returns a large number of results, the web interface gets very slow. The terminal version can cope better with larger amounts of data.

Exploring the movie database

A Neo4j database contains nodes and directed binary relationships between nodes. Nodes can have multiple labels that act to classify nodes into different, perhaps overlapping, classes. Each node or relationship is associated with a set of properties.

Rather than giving schema documentation, as we did for the relational database, we will start by using Cypher to explore the contents of the database.

Here is a Cypher query the explores what kinds of node exist in our small graph database:

match (n)
return labels(n) as labels, keys(n) as keys, count(*) as total
order by total desc;

The keyword match is followed by the pattern (n) that matches any node and assigns it to the variable n. The function call labels(n) returns a list of all labels associated with the node n. In our case, each node has exactly one label, but in general nodes can have any number of labels. The function call keys(n) returns a list of all of the names associated with properties — these act much like column names in SQL. (The term key here simply means the name of a node property, not a key that uniquely identifies the node.) The rest of the query looks very much like SQL, except for the fact that a group by construct is implicit in Cypher: count(*) is an aggregate function, so the query implicitly groups by labels and keys. The query returns:

+---------------------------------------------------------------------------------+
| labels       | keys                                                     | total |
+---------------------------------------------------------------------------------+
| ["Keyword"]  | ["keyword_id","keyword"]                                 | 7954  |
| ["Person"]   | ["person_id","name","gender"]                            | 7711  |
| ["Location"] | ["location_id","location"]                               | 758   |
| ["Country"]  | ["country_id","country"]                                 | 114   |
| ["Movie"]    | ["movie_id","title","year","color_info","running_times"] | 100   |
| ["Language"] | ["language_id","language"]                               | 34    |
| ["Genre"]    | ["genre_id","genre"]                                     | 22    |
+---------------------------------------------------------------------------------+

You can see that there are 100 movies and 7,711 people in the database — the same data as in the relational case. But the list of node labels is not quite the same as the list of tables in the relational model. Let's explore further.

In a similar way, we can extract information about the relationships in the database:

match (m)-[r]->(n)
return labels(m), type(r), labels(n), count(*) as total
order by total desc;

The pattern (m)-[r]->(n) matches two nodes (m and n) and a relationship (r) from m to n (all relationships in Neo4j have a direction). You can read it like an arrow m --> n where the relationship r is enclosed in square brackets. The function type(r) returns the type of the relationship r. The query returns:

+---------------------------------------------------------------+
| labels(m)  | type(r)                   | labels(n)    | total |
+---------------------------------------------------------------+
| ["Movie"]  | "HAS_KEYWORD"             | ["Keyword"]  | 15572 |
| ["Person"] | "ACTS_IN"                 | ["Movie"]    | 6913  |
| ["Movie"]  | "RELEASED_IN"             | ["Country"]  | 5775  |
| ["Movie"]  | "CERTIFIED_IN"            | ["Country"]  | 2155  |
| ["Movie"]  | "HAS_LOCATION"            | ["Location"] | 916   |
| ["Person"] | "PRODUCED"                | ["Movie"]    | 905   |
| ["Movie"]  | "HAS_GENRE"               | ["Genre"]    | 273   |
| ["Person"] | "WROTE"                   | ["Movie"]    | 212   |
| ["Movie"]  | "HAS_LANGUAGE"            | ["Language"] | 177   |
| ["Person"] | "EDITED"                  | ["Movie"]    | 134   |
| ["Person"] | "DIRECTED"                | ["Movie"]    | 113   |
| ["Person"] | "CINEMATOGRAPHER_FOR"     | ["Movie"]    | 108   |
| ["Person"] | "COMPOSER_FOR"            | ["Movie"]    | 101   |
| ["Person"] | "PRODUCTION_DESIGNER_FOR" | ["Movie"]    | 83    |
| ["Person"] | "COSTUME_DESIGNER_FOR"    | ["Movie"]    | 72    |
+---------------------------------------------------------------+

This tells us, for example, that there are 6,913 relationships of type ACTS_IN between nodes with label Person and nodes of label Movie.

Each relationship between two nodes can also have a set of properties. The following query uses the pattern () that matches any node:

match ()-[r]->()
return type(r) as type, keys(r) as keys, count(*) as total
order by type;

The query returns:

+------------------------------------------------------------------------------------------+
| type                      | keys                                                 | total |
+------------------------------------------------------------------------------------------+
| "ACTS_IN"                 | ["position","note"]                                  | 8     |
| "ACTS_IN"                 | ["position"]                                         | 61    |
| "ACTS_IN"                 | ["role","position","note"]                           | 3186  |
| "ACTS_IN"                 | ["role","position"]                                  | 3658  |
| "CERTIFIED_IN"            | ["certificate","note"]                               | 431   |
| "CERTIFIED_IN"            | ["certificate"]                                      | 1724  |
| "CINEMATOGRAPHER_FOR"     | ["note"]                                             | 63    |
| "CINEMATOGRAPHER_FOR"     | []                                                   | 45    |
| "COMPOSER_FOR"            | ["note"]                                             | 8     |
| "COMPOSER_FOR"            | []                                                   | 93    |
| "COSTUME_DESIGNER_FOR"    | ["note"]                                             | 8     |
| "COSTUME_DESIGNER_FOR"    | []                                                   | 64    |
| "DIRECTED"                | ["note"]                                             | 7     |
| "DIRECTED"                | []                                                   | 106   |
| "EDITED"                  | ["note"]                                             | 20    |
| "EDITED"                  | []                                                   | 114   |
| "HAS_GENRE"               | []                                                   | 273   |
| "HAS_KEYWORD"             | []                                                   | 15572 |
| "HAS_LANGUAGE"            | []                                                   | 153   |
| "HAS_LANGUAGE"            | ["note"]                                             | 24    |
| "HAS_LOCATION"            | []                                                   | 526   |
| "HAS_LOCATION"            | ["note"]                                             | 390   |
| "PRODUCED"                | ["note"]                                             | 905   |
| "PRODUCTION_DESIGNER_FOR" | []                                                   | 80    |
| "PRODUCTION_DESIGNER_FOR" | ["note"]                                             | 3     |
| "RELEASED_IN"             | ["date","note"]                                      | 1831  |
| "RELEASED_IN"             | ["date"]                                             | 3944  |
| "WROTE"                   | ["note","line_order","group_order","subgroup_order"] | 201   |
| "WROTE"                   | ["line_order","group_order","subgroup_order"]        | 11    |
+------------------------------------------------------------------------------------------+

Consider the lists of properties of ACTS_IN relationships. We have modeled the ACTS_IN relationship as having properties role, position, and note. However, note that not all instances of this relationship type have all of these properties. This is how Neo4j deals with the absence of data: whereas SQL has a special NULL value that indicates there is no value, Neo4j simply leaves out a property that has no associated value, so it doesn't appear in keys().

SQL-like queries

Cypher has been designed so that many simple SQL queries can be translated easily into Cypher. Here we give translation examples for some of the queries from our SQL tutorial.

The SQL query:

SELECT title, year
FROM movies
WHERE id = 3470465;

can be written in Cypher as:

match (m:Movie)
where m.movie_id = '3470465'
return m.title as title, m.year as year;

The expression (m:Movie) matches a node that has the label Movie, and assigns it to a variable called m. You can then refer to m in other parts of the query, such as the where and return clauses. The query returns:

+------------------------------------------------------------+
| title                                               | year |
+------------------------------------------------------------+
| "Star Wars: Episode VII - The Force Awakens (2015)" | 2015 |
+------------------------------------------------------------+

SQL aggregation, such as

SELECT year, count(*) AS total
FROM movies
GROUP BY year
ORDER BY year DESC;

can be expressed directly in Cypher:

match (m:Movie)
return m.year as year, count(*) as total
order by year desc;

Note that in Cypher the GROUP BY construct is implicit when you use an aggregate function such as count(*). The query returns:

+--------------+
| year | total |
+--------------+
| 2015 | 8     |
| 2014 | 10    |
| 2013 | 11    |
| 2012 | 10    |
| 2011 | 10    |
| 2010 | 9     |
| 2009 | 10    |
| 2008 | 12    |
| 2007 | 10    |
| 2006 | 9     |
| 2004 | 1     |
+--------------+

Joins are easier to write in Cypher because we can use patterns. For example, the SQL query

SELECT title, year, count(*) AS language_count
FROM movies
JOIN languages ON movies.id = languages.movie_id
GROUP BY title, year
ORDER BY language_count desc
LIMIT 20 ;

can be written in Cypher as:

match (m:Movie)-[:HAS_LANGUAGE]->(l:Language)
return m.title as title, m.year as year, count(*) as language_count
order by language_count desc
limit 20;

The pattern (m:Movie)-[:HAS_LANGUAGE]->(l:Language) matches only relationships of type HAS_LANGUAGE. The variables m and l are bound such that l is a node of label Language, and l is a language that is spoken in movie m. The query returns:

+-----------------------------------------------------------------------+
| title                                         | year | language_count |
+-----------------------------------------------------------------------+
| "Children of Men (2006)"                      | 2006 | 9              |
| "District 9 (2009)"                           | 2009 | 6              |
| "Snowpiercer (2013)"                          | 2013 | 5              |
| "The Bourne Ultimatum (2007)"                 | 2007 | 5              |
| "Mission: Impossible - Ghost Protocol (2011)" | 2011 | 5              |
| "Iron Man (2008)"                             | 2008 | 5              |
| "Persepolis (2007)"                           | 2007 | 4              |
| "Drag Me to Hell (2009)"                      | 2009 | 4              |
| "Bikur Ha-Tizmoret (2007)"                    | 2007 | 3              |
| "Afghan Star (2009)"                          | 2009 | 3              |
| "Slumdog Millionaire (2008)"                  | 2008 | 3              |
| "Before Midnight (2013)"                      | 2013 | 3              |
| "Un prophète (2009)"                          | 2009 | 3              |
| "Blindsight (2006/I)"                         | 2006 | 3              |
| "The Queen (2006)"                            | 2006 | 3              |
| "The Artist (2011/I)"                         | 2011 | 3              |
| "Waste Land (2010)"                           | 2010 | 2              |
| "The Martian (2015)"                          | 2015 | 2              |
| "Argo (2012)"                                 | 2012 | 2              |
| "Boyhood (2014/I)"                            | 2014 | 2              |
+-----------------------------------------------------------------------+

The SQL query:

SELECT movie_id, title, person_id, name, type, character
FROM movies
JOIN credits ON movies.id = movie_id
JOIN people  ON people.id = person_id
WHERE title = 'Gui tu lie che (2009)'
ORDER BY type ;

can be written in Cypher as:

match (m:Movie)<-[r]-(p:Person)
where m.title = 'Gui tu lie che (2009)'
return m.movie_id as movie_id,
       m.title as title,
       p.person_id as person_id,
       p.name as name,
       type(r) as type,
       r.role as character
order by type;

You can see from the exploration above that a node with label Person can have the properties person_id and name, and that a relationship of type ACTS_IN can have the property role. The query returns:

+------------------------------------------------------------------------------------------------------------+
| movie_id  | title                   | person_id | name                 | type                  | character |
+------------------------------------------------------------------------------------------------------------+
| "2949599" | "Gui tu lie che (2009)" | "2081387" | "Tang, Tingsui"      | "ACTS_IN"             | "Himself" |
| "2949599" | "Gui tu lie che (2009)" | "2352605" | "Zhang, Yang (VII)"  | "ACTS_IN"             | "Himself" |
| "2949599" | "Gui tu lie che (2009)" | "3655929" | "Zhang, Qin (I)"     | "ACTS_IN"             | "Herself" |
| "2949599" | "Gui tu lie che (2009)" | "2567414" | "Chen, Suqin"        | "ACTS_IN"             | "Herself" |
| "2949599" | "Gui tu lie che (2009)" | "2351887" | "Zhan, Changhua"     | "ACTS_IN"             | "Himself" |
| "2949599" | "Gui tu lie che (2009)" | "636899"  | "Fan, Lixin (II)"    | "CINEMATOGRAPHER_FOR" | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "3665606" | "Alary, Olivier"     | "COMPOSER_FOR"        | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "636899"  | "Fan, Lixin (II)"    | "DIRECTED"            | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "3479099" | "Stephen, Mary"      | "EDITED"              | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "636899"  | "Fan, Lixin (II)"    | "EDITED"              | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "359849"  | "Chang, Yung (I)"    | "EDITED"              | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "4195273" | "Zhao, Qi (VI)"      | "PRODUCED"            | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "1456830" | "Moore, Bob (IV)"    | "PRODUCED"            | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "3698467" | "Cross, Daniel (II)" | "PRODUCED"            | <null>    |
| "2949599" | "Gui tu lie che (2009)" | "3670895" | "Aung-Thwin, Mila"   | "PRODUCED"            | <null>    |
+------------------------------------------------------------------------------------------------------------+

Multiple patterns

Recall our SQL query for counting the number of movies associated with a pair of distinct genres:

SELECT G1.genre AS genre1, G2.genre AS genre2, count(*) AS total
FROM genres AS G1
JOIN genres AS G2 ON G1.movie_id = G2.movie_id
WHERE G1.genre < G2.genre
GROUP BY genre1, genre2
ORDER BY total desc
LIMIT 10;

This can be written in Cypher as:

match
  (m:Movie)-[:HAS_GENRE]->(g1:Genre),
  (m:Movie)-[:HAS_GENRE]->(g2:Genre)
where g1.genre < g2.genre
return g1.genre as genre1, g2.genre as genre2, count(*) as total
order by total desc
limit 10;

Here, there are two matching patterns: (m)-->(g1) and (m)-->(g2). The query searches for nodes m, g1, g2 that satisfy both patterns. Since the same variable m appears in both patterns, the query requires that the two genres g1 and g2 are associated with the same movie (this is equivalent to the G1.movie_id = G2.movie_id condition in the SQL query).

The same query can be written more concisely in Cypher:

match (g1:Genre)<-[:HAS_GENRE]-(:Movie)-[:HAS_GENRE]->(g2:Genre)
where g1.genre < g2.genre
return g1.genre as genre1, g2.genre as genre2, count(*) as total
order by total desc
limit 10;

The two matching patterns (m)-->(g1) and (m)-->(g2) have been combined into a single pattern (g1)<--(m)-->(g2). We left out the variable name m because we no longer need it, so we can refer to the middle node as (:Movie), which means "any node labelled Movie".

The query returns:

+-----------------------------------+
| genre1      | genre2      | total |
+-----------------------------------+
| "Biography" | "Drama"     | 13    |
| "Drama"     | "Romance"   | 12    |
| "Drama"     | "Thriller"  | 10    |
| "Drama"     | "History"   | 10    |
| "Action"    | "Adventure" | 9     |
| "Crime"     | "Drama"     | 9     |
| "Biography" | "History"   | 8     |
| "Animation" | "Family"    | 8     |
| "Comedy"    | "Drama"     | 8     |
| "Adventure" | "Family"    | 8     |
+-----------------------------------+

The with construct

Suppose we want to modify this last Cypher query so that it only returns those rows where total is greater than 9. The problem is that total is defined in the return clause and we have no place to put a where total > 9 clause. The with construct solves this problem. We can replace any return by with and then use what were returned values in further computation. In this way we can treat the output of a sub-query as input to another sub-query. We can actually string together as many with constructs as we like. Here is the query we want (using only one with):

match (g1:Genre)<-[:HAS_GENRE]-(:Movie)-[:HAS_GENRE]->(g2:Genre)
where g1.genre < g2.genre
with  g1.genre as genre1, g2.genre as genre2, count(*) as total
where total > 9
return genre1, genre2, total
order by total desc ;

The query returns:

+----------------------------------+
| genre1      | genre2     | total |
+----------------------------------+
| "Biography" | "Drama"    | 13    |
| "Drama"     | "Romance"  | 12    |
| "Drama"     | "Thriller" | 10    |
| "Drama"     | "History"  | 10    |
+----------------------------------+

The power of path-oriented queries

The real strength of Neo4j and Cypher is with path-oriented queries.

Here is a query that counts the number of co-actors for Jennifer Lawrence:

match (p1:Person)-[:ACTS_IN]->(:Movie)<-[:ACTS_IN]-(p2:Person)
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name <> 'Lawrence, Jennifer (III)'
return count(*) as total ;

The query returns:

+-------+
| total |
+-------+
| 331   |
+-------+

Wait one second! Is this query correct? No! This query will count the same co-actors multiple times if they have acted in multiple movies with Jennifer Lawrence. How can with fix this? As follows:

match (p1:Person)-[:ACTS_IN]->(:Movie)<-[:ACTS_IN]-(p2:Person)
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name <> 'Lawrence, Jennifer (III)'
return count(distinct p2) as total ;

The query returns:

+-------+
| total |
+-------+
| 321   |
+-------+

We can achieve the same results with the following query:

match (p1:Person)-[:ACTS_IN*2]-(p2:Person)
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name <> 'Lawrence, Jennifer (III)'
return count(distinct p2) as total ;

Here the pattern (p1:Person)-[:ACTS_IN*2]-(p2:Person) matches any path containing two occurrences of the ACTS_IN relationship. Note that we have dropped the directional arrow (->) here since the direction alternates along such paths. A pattern such as (...)-[...]-(...) indicates we are not concerned with direction.

Cypher has a built-in function allshortestpaths that when applied to a path pattern returns all shortest instances of the path:

match path = allshortestpaths( (p1:Person)-[:ACTS_IN*]-(p2:Person) )
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name = 'Damon, Matt'
return distinct length(path)/2 as length ;

Here the pattern (p1:Person)-[:ACTS_IN*]-(p2:Person) matches all paths between p1 and p2 and allshortestpaths returns a list of the shortest paths. The query returns:

+--------+
| length |
+--------+
| 2      |
+--------+

(Try the query without the keyword distinct --- can you understand the new result?)

Let's take a look at one of these paths:

match path = allshortestpaths( (p1:Person)-[:ACTS_IN*]-(p2:Person) )
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name = 'Damon, Matt'
return path
limit 1;

The query returns:

+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| path                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [Node[4072]{person_id:"3018535",name:"Lawrence, Jennifer (III)",gender:"female"},:ACTS_IN[15721]{role:"Rosalyn Rosenfeld",position:5},Node[3]{movie_id:"2607939",title:"American Hustle (2013)",year:2013,color_info:["Black and White:(surveillance footage)","Color"],running_times:["138","Taiwan:129"]},:ACTS_IN[15743]{role:"Suburban Businessman",position:27},Node[2577]{person_id:"754595",name:"Giannone, Jay",gender:"male"},:ACTS_IN[20866]{role:"Detective #4 Tailing Queenan",position:60},Node[76]{movie_id:"3541513",title:"The Departed (2006)",year:2006,color_info:["Color"],running_times:["151"]},:ACTS_IN[20809]{role:"Colin Sullivan",position:2},Node[1684]{person_id:"473946",name:"Damon, Matt",gender:"male"}] |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

The result contains every node along the path together with its property set.

In order to display paths in a more readable form we can process paths using the reduce construct:

match path = allshortestpaths( (p1:Person)-[:ACTS_IN*]-(p2:Person) )
where p1.name = 'Lawrence, Jennifer (III)'
  and p2.name = 'Damon, Matt'
return reduce(t = "", i in nodes(path) | t + (case when i:Movie then i.title else i.name end) + "---") as simple_path;

The reduce construct is much like a fold from functional programming (SML, OCaml, Haskell). In this case, for each path t is initialised to the empty string, and then i iterates through the list of nodes in the path. At each step we compute t + (case when i:Movie then i.title else i.name end) + "---" where here t represents the results of the previous iteration.

The query returns:

+----------------------------------------------------------------------------------------------------------------------------+
| simple_path                                                                                                                |
+----------------------------------------------------------------------------------------------------------------------------+
| "Lawrence, Jennifer (III)---American Hustle (2013)---Giannone, Jay---The Departed (2006)---Damon, Matt---"                 |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Oliveira, Joseph (III)---The Departed (2006)---Damon, Matt---"        |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Boston, David (IV)---The Departed (2006)---Damon, Matt---"            |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Boston, David (IV)---The Bourne Ultimatum (2007)---Damon, Matt---"    |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Flynn, Steve (III)---The Departed (2006)---Damon, Matt---"            |
| "Lawrence, Jennifer (III)---Silver Linings Playbook (2012)---Falvo, Mark---The Departed (2006)---Damon, Matt---"           |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Falvo, Mark---The Departed (2006)---Damon, Matt---"                   |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Ford, Jim (III)---The Departed (2006)---Damon, Matt---"               |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Corazzini, Jeffrey---The Departed (2006)---Damon, Matt---"            |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Cleary, Bo---The Departed (2006)---Damon, Matt---"                    |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Silvia, Billy---The Departed (2006)---Damon, Matt---"                 |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Garo, Armen---The Departed (2006)---Damon, Matt---"                   |
| "Lawrence, Jennifer (III)---Silver Linings Playbook (2012)---Stiles, Julia---The Bourne Ultimatum (2007)---Damon, Matt---" |
| "Lawrence, Jennifer (III)---American Hustle (2013)---Peña, Michael---The Martian (2015)---Damon, Matt---"                  |
+----------------------------------------------------------------------------------------------------------------------------+