Control ordering of nodes created in subquery

I am creating a node with sub nodes using a nested create. In my case I have a Recipe object which then has Ingredients and Steps.
When I create this, I had (naievly?) expected that the order in which I created the sub-nodes would be matched in a query that then returns that recipe (so what I added as the first ingredient would come first, etc). But, what I am observing is that the order is changed into apparently random order. I assume some operations happen in parallel as looking at the logged cypher, it appears to all be in sequential order (recipe_0_ingredient_0.. etc)

So - my question:

  1. Should I expect that passing in "Ingredient 1", "Ingredient 2", etc should return in that order? Or is there a way to flag that this is the behaviour I want?
  2. If not, is the best approach to just add the index I want the ingredient / step to be in as a relationship property? And can I then provide a way that by default, this property would be used to order the results?

Hope that all makes sense, and thanks in advance for any advice!

If you want things in a specific order, you have to specify that order. I can think of 2 ways to model this: one way is to add a property to each ingredient specifying the order. Another is to have a "first_ingredient" relationship from the recipe to the first ingredient, then have "next_ingredient" relationships from each ingredient to the next one.

Ok - makes sense. And thanks for the quick reply! as an aside, be interested to understand under the hood what is going on here - are the relations just created in parallel and so that is why I'm seeing consistent order in the results (just not the order in which I supplied the variables)?

But - back to the question at hand, I think that specifying a property to control the order is going to fit my model best. And dont need to be too concerned about the cost of reordering - will be at most an infrequent operation, and, (relatively) low total number of steps or ingredients - so needing to update all ingredients/steps in a recipe to alter the order is not going to be a performance concern.

The closest I have seen on "how" to do this was here Control ordering of nodes created in subquery - Drivers & Stacks / GraphQL & GRANDstack - Neo4j Online Community

but assuming I wanted to add an index to the RECIPE_INGREDIENTS relationship, are there any examples out there of how to then ensure that this order will be used when I retrieve a recipe? Ideally by default. If not I will mock up a quick sandbox and post back with more specific follow ups if I cant work it out from Relationship Properties and docs for sorting in queries

Remember that (usually), the order of results returned by a query (SQL, Cypher, etc) is not guaranteed without some type of clause (e.g. ORDER BY) that forces an order. Even in something like a relational database, just having an index isn't enough to reliably enforce an order.

yes - true, and in my case, I do care about order (esp for steps) and so, need to tell the data layer what I would like to happen vs hope it guesses right (though - I would say that most relational systems, there is a known and repeatable ordering that will be implicitly used, and from limited testing, it does seem that there is consistent behaviour in the order nodes via a relationship are returned vs randomness - but again - appreciate the point that relying on underlying system implementation is not the right approach here!)

TL;DR: I was being a bit lazy and sort of knew it :slight_smile:

That "known and repeatable" order isn't really repeatable. In Oracle, for example, maybe your query plan uses an index, and it just happens to return in sorted order. But, as soon as the query plan changes (parallelism, hash join, etc) - that order is now different. In short - if you want the results ordered, then you must specify it in the query. If you get lucky, and get the order you want without specifying it - you can be unlucky one day.

While you do need to address this in your data (by providing some property to order by) I want to also let you know that there is sorting available in GraphQL, which you can use to specify the order of results.

1 Like

Yes - my plan was to add an Index property for both Recipe Step and for Ingredients, as a relationship property (will give that a go tonight)
I assume I can then use the Relationship property in the same way any other node property is used for sorting? And then add this to my query by default. If there are any examples of this, let me know otherwise I will post back once I have given it a go. Thanks again for the help!

Instead of relationship properties, which you can't sort on, you would need to use a node property and sort on that. Something like this:

    type Recipe {
        title: String
        ingredients: [Ingredient!]! @relationship(direction: IN, type: "INGREDIENT_IN")
    }

    type Ingredient {
        order: Int
        name: String
        recipes: [Recipe!]! @relationship(direction: OUT, type: "INGREDIENT_IN")
    }

Where you add them like this:

mutation Mutation {
  createRecipes(
    input: [
      {
        title: "Cake"
        ingredients: {
          create: [
            { node: { order: 1, name: "Flour" } }
            { node: { order: 2, name: "Eggs" } }
          ]
        }
      }
    ]
  ) {
    recipes {
      title
      ingredients {
        order
        name
      }
    }
  }
}

And the result of this mutation shows they were added in a non-deterministic order:

{
  "data": {
    "createRecipes": {
      "recipes": [
        {
          "title": "Cake",
          "ingredients": [
            {
              "order": 2,
              "name": "Eggs"
            },
            {
              "order": 1,
              "name": "Flour"
            }
          ]
        }
      ]
    }
  }
}

Perfect, thank you!

Especially as you prevented me from going down a rabbit hole on using Relationship Properties!

As a side note, would it be worth stating that relationship properties cannot be used to sort on in the docs? Though I get that this could just have been me not properly understanding the purpose of relationship properties! Thanks again for the help

That's valuable feedback for the docs, thank you. I'll make sure it's mentioned.

I would like to amend my previous message. You cannot sort on results using recipes in this example, but you can do it if you were to use the connection API (i.e. recipesConnection):

Type definitions:

type Recipe {
    title: String
    ingredients: [Ingredient!]! @relationship(direction: IN, type: "INGREDIENT_IN", properties: "IngredientIn")
}

type IngredientIn @relationshipProperties {
    order: Int!
}

type Ingredient {
    name: String
    recipes: [Recipe!]! @relationship(direction: OUT, type: "INGREDIENT_IN", properties: "IngredientIn")
}
mutation CreateRecipes {
  createRecipes(
    input: [
      {
        title: "Cake"
        ingredients: {
          create: [
            { node: { name: "Flour" }, edge: { order: 0 } }
            { node: { name: "Eggs" }, edge: { order: 1 } }
          ]
        }
      }
    ]
  ) {
    recipes {
      title
      ingredientsConnection(sort: [{ edge: { order: "ASC" } }]) {
        edges {
          node {
            name
          }
          properties {
            order
          }
        }
      }
    }
  }
}
{
  "data": {
    "createRecipes": {
      "recipes": [
        {
          "title": "Cake",
          "ingredientsConnection": {
            "edges": [
              {
                "node": {
                  "name": "Flour"
                },
                "properties": {
                  "order": 0
                }
              },
              {
                "node": {
                  "name": "Eggs"
                },
                "properties": {
                  "order": 1
                }
              }
            ]
          }
        }
      ]
    }
  }
}

Ah - interesting!
And thanks so much for the follow up, really appreciate it

1 Like