Filter on absence of an edge

Hi, I am at the v early stages of designing an event store for an ecommerce site, and wish to model as a graph. I’m not sure whether events should be nodes or edges, but my first iteration is to model as edges. Had some success with some basic use cases, but one interesting question I’m stumbling on is, what products our customers have added to cart but where not purchased.

I’m getting a bit stuck and would really appreciate some tips.

This is my initial graph:
Screenshot 2020-07-12 at 21.04.18

I want a query to return:

Danielle <add_to_cart> shag rug
Joe <add_to_cart> $10 gift card

Any tips on query (or schema) appreciated.

If any help, here are my mutations:

{
  set {

    _:p1 <product> "red toaster" .
    _:p2 <product> "shag rug" .
    _:p3 <product> "$10 gift card" .

     _:c1 <customer> "Danielle" .
    _:c2 <customer> "Joe" .

    _:c1 <purchased> _:p1  .
    _:c2 <purchased> _:p1  .
    _:c2 <purchased> _:p2  .

    _:c1 <add_to_cart> _:p1  .
    _:c1 <add_to_cart> _:p2  .
    _:c2 <add_to_cart> _:p1  .
    _:c2 <add_to_cart> _:p2  .
    _:c2 <add_to_cart> _:p3  .
  }
}

Hey @damienburke,

Thanks for reaching out to us!

Your schema design looks good to me. Events are generally modeled as edges, so having purchased an edge between customer and product is fine.

In order to query for products that were added to the cart but were not purchased can be done in the following manner:

{
  var(func: eq(customer, "Joe")){
    customer
    p as purchased
  }
  
  f(func: eq(customer, "Joe")){
    customer
    added_but_not_purchased: add_to_cart @filter(NOT uid(p)){
      product
    }
  }
}

The response would be the following:

"f": [
      {
        "customer": "Joe",
        "added_but_not_purchased": [
          {
            "product": "$10 gift card"
          }
        ]
      }
    ]
  },]

You can run the above query for all the customers or a better and efficient approach would be to fetch the products purchased as well as added by each customer and post-process the two sets (purchased and added) to find the set difference in the client side. I am trying to think of a single query which can do this.

2 Likes

Awesome!! Thanks @ahsan
I did have a similar half cooked var approach. (One thing I was mulling over when writing my var approach, was how such a query gets executed, e.g. is it 2 queries, one to get the var and the second to apply, and if its a good approach when parsing a lot of data, etc).

But a single query to run for all nodes would be fantastic, and to see how it can be done would really help me!

@damienburke

An another approach that would enable us to do this in a single query would be to use facets. We can have an edge called “interacted” with facets (purchased=False, add_to_cart=True). So a sample mutation would look like:

{
  set {

    _:p1 <product> "red toaster" .
    _:p2 <product> "shag rug" .

    _:c1 <customer> "Danielle" .
    _:c2 <customer> "Joe" .

    _:c1 <interacted> _:p1 (added=true, purchased=false) .
    _:c2 <interacted> _:p2 (added=true, purchased=true) .

  }
}

and the query would look like:

{
 q(func: has(interacted)) {
  	customer 
  	added_but_not_purchased: interacted @facets(eq(added, "true") AND eq(purchased, "false")){
      product
    }
	}
}

Thanks @ahsan, i dont think that will be extensible enough for what we need to achieve. We will have a lot of events and that will be hard to maintain in a facet. Plus for each event, i will prob need to capture a create date and other meta data about the event.

@damienburke

Yes right. It will become cumbersome after a point. we have an RFC to include loops in the query; Add foreach() function, which will probably make querying for each customers straight-forward.

3 Likes

Cool, thanks @ahsan. Do u have any other suggestions in mean-time? This is one of the core use cases we had hoped dgraph would work well with.

@damienburke,

I think you are good with the first iteration of your model, and you could probably go with the first solution or with the facets solution. We hope to get loops within query that would help us do this without facets.

Other than this, feel free to raise questions as you go ahead.

1 Like

Ok, thanks @ahsan, the facets is not a runner really, so looks like this would need to done in an app. Hopefully loops within query will arrive soon… Thanks again for your help

1 Like