Lesson 07

Ordering, Pagination & Projections

Difficulty Intermediate Time

Learning objectives

After this lesson you will be able to:

  1. Sort result streams with multi-key ORDER BY, including ascending and descending directions.
  2. Page results with SKIP and LIMIT in the correct clause order.
  3. Design stable pagination using unique tie-breakers and know when keyset pagination is preferable.
  4. Shape API-friendly payloads with map projections and property selectors.
  5. Expand lists into rows with UNWIND and recombine them when needed.
  6. Use sub-projections inside map projections for nested relationship-aware payloads.
  7. Avoid expensive deep offsets and oversized node returns in application queries.

Core concepts

ORDER BY: controlling sequence

Graphs do not store an inherent “table order.” When you MATCH movies and people, the engine is free to return rows in any order that is correct for the pattern. User interfaces, however, need ranked lists: newest films first, highest review averages, alphabetical actor directories. ORDER BY sorts the current result rows by one or more expressions before later clauses such as SKIP, LIMIT, or another WITH.

Sort keys may be variables already in scope, property accesses, or computed expressions. Each key can be ASC (default) or DESC. Null ordering is version- and configuration-sensitive in edge cases; if nulls appear in production sorts, test the behavior on your Neo4j version and consider coalescing to a sentinel when business rules demand it.

After aggregation (Lesson 06), you typically order by the aggregate aliases—movie counts, average ratings—because those values only exist after the grouping boundary. You can also order inside a WITH pipeline so a later collect sees a deterministic sequence (for example, collect titles newest-first).

CYPHER
MATCH (m:Movie)
RETURN m.title AS title, m.released AS released
ORDER BY released DESC, title ASC
SKIP 0
LIMIT 10

SKIP, LIMIT, and stable pages

LIMIT n keeps at most n rows. SKIP k discards the first k rows of the ordered stream. Together they implement classic offset pagination: page 1 is SKIP 0 LIMIT 10, page 2 is SKIP 10 LIMIT 10, and so on. Clause order after RETURN is ORDER BY, then SKIP, then LIMIT.

Stability requires a total order. If two movies share the same released year and you order only by that year, page boundaries can shuffle between requests. Always add a unique tie-breaker such as title when unique in your domain, or elementId(m) when you need a guaranteed unique secondary key.

Deep offsets are expensive: the engine still produces and discards the skipped rows. For large catalogs, prefer keyset (seek) pagination: remember the last sort key from the previous page and filter WHERE released < $lastReleased OR (released = $lastReleased AND title > $lastTitle) with a small LIMIT. Offset pagination remains fine for admin UIs and small result sets.

Map projections, UNWIND, and sub-projections

Returning entire nodes ships every property—including ones clients never display—and can bloat payloads as models grow. Map projection syntax m { .title, .released } builds a map with only the selected properties. You can add aliases, literals, and computed entries: m { .title, year: m.released, source: 'movie-graph' }.

Sub-projections nest related data. A common pattern matches a movie and its actors, then returns m { .title, .released, actors: collect(p { .name, .born }) }. The result is a JSON-like document ideal for REST or GraphQL resolvers without over-fetching.

UNWIND is the inverse of collect: it turns a list into rows. Use it for parameter lists of ids, for post-processing collected values, or for generating ranges when combined with list functions. After UNWIND, you can MATCH from each element, filter, and re-aggregate.

Ordered movie result stream with SKIP discarding early rows and LIMIT taking a page window
ORDER BY defines sequence; SKIP moves the window; LIMIT sets page size.

Worked examples

Dataset note: Expected results use the classic Neo4j Movie graph (37 movies, 133 people) and can differ if the data changes.

Newest movies with stable ordering

Page through movies newest first. Secondary sort on title keeps ties deterministic across requests.

CYPHER
MATCH (m:Movie)
RETURN m.title AS title, m.released AS released
ORDER BY released DESC, title ASC
SKIP 0
LIMIT 5

Expected result

title released
Cloud Atlas2012
Ninja Assassin2009
Frost/Nixon2008
Speed Racer2008
Charlie Wilson's War2007

Map projection for a movie detail card

Build a compact document for “The Matrix” including a nested list of actor maps.

CYPHER
MATCH (m:Movie {title: 'The Matrix'})
OPTIONAL MATCH (m)<-[:ACTED_IN]-(p:Person)
WITH m, p
ORDER BY p.name
RETURN m {
  .title,
  .released,
  .tagline,
  actors: collect(p { .name, .born })
} AS movie

Expected result

movie
{title: "The Matrix", released: 1999, tagline: "Welcome to the Real World", actors: [{name: "Carrie-Anne Moss", born: 1967}, {name: "Emil Eifrem", born: 1978}, {name: "Hugo Weaving", born: 1960}, {name: "Keanu Reeves", born: 1964}, {name: "Laurence Fishburne", born: 1961}]}

UNWIND a title list to match movies

Applications often pass arrays of titles or ids. Unwind the parameter list, match each movie, and return a projected map sorted by release year.

CYPHER
// :param titles => ['The Matrix', 'Top Gun', 'Cloud Atlas']
UNWIND $titles AS title
MATCH (m:Movie {title: title})
RETURN m { .title, .released } AS movie
ORDER BY movie.released ASC

Expected result

movie
{title: "Top Gun", released: 1986}
{title: "The Matrix", released: 1999}
{title: "Cloud Atlas", released: 2012}

Second page of actors by name

Demonstrate offset pagination: page size 5, second page of people ordered by name.

CYPHER
MATCH (p:Person)
RETURN p.name AS name, p.born AS born
ORDER BY name ASC
SKIP 5
LIMIT 5

Expected result

name born
Audrey Tautou1976
Ben Miles1967
Bill Paxton1955
Bill Pullman1953
Billy Crystal1948

Pitfalls

Exercises

  1. 1. Alphabetical movie directory

    Return movie titles ordered A–Z with a page size of 8 starting at offset 8 (page 2).

    STARTER
    MATCH (m:Movie)
    RETURN m.title
    // ORDER BY, SKIP, LIMIT
  2. 2. Project person cards

    For people born after 1960, return a map projection with name and born, ordered by born descending, limited to 10.

    STARTER
    MATCH (p:Person)
    WHERE p.born > 1960
    RETURN p { /* .name, .born */ }
  3. 3. UNWIND roles parameter

    Assume $names is a list of actor names. Unwind it, match each person, and return name plus born.

    STARTER
    UNWIND $names AS name
    MATCH (p:Person {name: name})
    RETURN p.name, p.born
  4. 4. Nested director payload

    For “Lana Wachowski”, return a map with name and a films list of movie map projections (title, released) ordered by released.

    STARTER
    MATCH (d:Person {name: 'Lana Wachowski'})-[:DIRECTED]->(m:Movie)
    // ORDER BY m.released, collect movie maps, project director
    

Lesson quiz

Five questions. Submit once to see your score and the full answer key.

Loading quiz…

Resources

Video

Watch on YouTube (opens in a new tab).