GraphGists

The universe of cocktails is huge. Cocktails are tasty. Let’s learn about them, and help build out your own bar!

Our basic domain model is very simple. Nodes only have a single property: name. Any node can have one or more of the following labels: Cocktail, Spirit, Ingredient, or Garnish. In this example, I’ve only used a single label per ingredient. Our only relationship type, :CONTAINS, has quantity and unit properties, which we’ll use to define how much of a certain ingredient goes into a cocktail. Part of a manhattan would look something like this:

model

Let’s get some data in:

Here’s an example of what a cocktail might look like:

MATCH (manhattan:Cocktail {name: "manhattan"})-[r:CONTAINS]->(ingredient)
RETURN manhattan, ingredient

What’s in Sazerac again?

Sometimes you just want to know what’s in a drink. Here’s a quick query to pull up all the ingredients by quantity and name, using a sazerac as an example.

MATCH (sazerac:Cocktail {name: "sazerac"})-[r:CONTAINS]->(ingredient)
RETURN collect(r.quantity + ' ' + r.unit + ' ' + ingredient.name)

What drinks have vodka in them?

If you’re trying to explore the types of drinks a certain ingredient can contribute to, you’ll want to list all the cocktails that have that ingredient.

MATCH (cocktails:Cocktail)-[r:CONTAINS]->(ingredient {name: "vodka"}),
  (cocktails)-[:CONTAINS]->(otherIngredient)
WHERE otherIngredient <> ingredient
RETURN cocktails, collect(otherIngredient.name) as otherIngredients

How about multiple ingredients?

Sometimes it’s interesting to see which drinks pairs of ingredients show up in, say, bourbon and angostura bitters. We can use the UNWIND operator to group all of our desired ingredients at the top of the query and use them in our MATCH pattern.

UNWIND ["bourbon", "angostura"] as ingredientName
MATCH (cocktail:Cocktail)-[:CONTAINS]->(ingredient {name: ingredientName})
RETURN cocktail, collect(ingredient.name) as otherIngredients

Most/Least useful

In the given dataset, what are the 5 most useful ingredients to have?

MATCH (cocktail:Cocktail)-[:CONTAINS]->(ingredient)
RETURN ingredient, count(*) AS numCocktails
ORDER BY numCocktails DESC
LIMIT 5

How about the 5 least useful ingredients?

MATCH (cocktail:Cocktail)-[:CONTAINS]->(ingredient)
RETURN ingredient, count(*) AS numCocktails
ORDER BY numCocktails
LIMIT 5

What’s most similar to a manhattan

This is a twist on the typical friend of a friend query, where cocktails are "friends" with the ingredients they contain. We’ll order the results by the number of ingredients that are similar.

MATCH (cocktail:Cocktail {name: 'manhattan'})-[:CONTAINS]->(ingredient),
(ingredient)<-[:CONTAINS]-(otherCocktail:Cocktail)
WHERE cocktail <> otherCocktail
RETURN otherCocktail, collect(ingredient) AS commonIngredients
ORDER BY size(commonIngredients) DESC

What should I get next?

When you’re building your own cantina, it’s hard to know what’s the next bottle you should pick up. Given a list of ingredients you currently have, this query will recommend the single ingredients that will enable you to mix the most new cocktails, in order of impact. (I’m using a fairly trivial set of input ingredients since the cocktail list is relatively small.)

MATCH (cocktail:Cocktail)-[:CONTAINS]->(ingredient)
WHERE NOT ingredient.name IN ["vermouth", "angostura", "simple syrup", "bitters"]
WITH cocktail, collect(ingredient) AS ingredients
WHERE size(ingredients) = 1
WITH cocktail, head(ingredients) AS missingIngredient
RETURN missingIngredient, count(*) AS numDrinks, collect(cocktail.name) AS cocktails
ORDER BY numDrinks DESC

What’s a drink I should try?

MATCH (cocktail:Cocktail)-[:CONTAINS]->(ingredient)
RETURN cocktail, ingredient, ':p'

Thirsty?

With graphs, it’s easy to add more dimensions of information on existing data. We could easily add time period and location associated with the origins of certain cocktails. Then, we could start exploring cocktails across those time periods, or find cocktails that were created near our current location. Or we could add functions to our ingredients corresponding to their flavors: Alcohol, Bitter, Sweet, Sour, Salty. Then we can look at a particular drink recipe in a more generic form. A margarita might be represented as 7 parts alchohol, 4 parts sweet, 3 parts sour, .25 part salty. Using those ratios, you could substitute (or write a cypher query to substitute) alternative recipes that fit some abstract build of a drink.

In any case, a graph is a great way to explore the world of drinks, where relationships between ingredients are key.