Gremlin query to order vertices with some locked to specific positions

I'm working with a product catalog in a graph database using Gremlin. The graph structure includes: 1. Product vertices 2. Category vertices 3. belongsTo edges connecting products to categories. The edge can have an optional lockedPosition property as integer. I need to create a query that returns products in a specific order, where: 1. Some products are "locked" to specific positions (stored as a lockedPosition property on the belongsTo edge) 2. Other "unlocked" products should fill in around these fixed positions For example, if I have 20 products in a category, and product A is locked to position 3 and product B to position 7, the result should return these products in those exact positions, with other products filling the remaining slots with whatever sorting order. (this "product pinning/locking" functionality is used by merchandising team to manually re-arrange products on a product listing page). Can anyone suggest a Gremlin query to achieve this? Also, maybe my data model is wrong for this use case, so I am also keen to accept other suggestions to represent this use case. Thank you!
6 Replies
kelvinl2816
kelvinl28163w ago
Are you able to share a small test graph (a few addV and addE steps) ? Having that makes writing tested answers a lot easier.
Max
Max3w ago
Here is the sample graph:
g.addV('Category').
property('name', 'Shoes').as('shoes').
addV('Product').
property('name', 'Product1').as('p1').
addE('belongsTo').from('p1').to('shoes').
addV('Product').
property('name', 'Product2').as('p2').
addE('belongsTo').from('p2').to('shoes').
addV('Product').
property('name', 'Product3').as('p3').
addE('belongsTo').from('p3').to('shoes').
addV('Product').
property('name', 'Product4').as('p4').
addE('belongsTo').from('p4').to('shoes').
addV('Product').
property('name', 'Product5').as('p5').
addE('belongsTo').from('p5').to('shoes').
addV('Product').
property('name', 'Product6').as('p6').
addE('belongsTo').from('p6').to('shoes').
addV('Product').
property('name', 'Product7').as('p7').
addE('belongsTo').from('p7').to('shoes').
addV('Product').
property('name', 'Product8').as('p8').
addE('belongsTo').from('p8').to('shoes').
addV('Product').
property('name', 'Product9').as('p9').
addE('belongsTo').from('p9').to('shoes').
property('lockedPosition', 7).
addV('Product').
property('name', 'Product10').as('p10').
addE('belongsTo').from('p10').to('shoes').
property('lockedPosition', 3).
iterate()
g.addV('Category').
property('name', 'Shoes').as('shoes').
addV('Product').
property('name', 'Product1').as('p1').
addE('belongsTo').from('p1').to('shoes').
addV('Product').
property('name', 'Product2').as('p2').
addE('belongsTo').from('p2').to('shoes').
addV('Product').
property('name', 'Product3').as('p3').
addE('belongsTo').from('p3').to('shoes').
addV('Product').
property('name', 'Product4').as('p4').
addE('belongsTo').from('p4').to('shoes').
addV('Product').
property('name', 'Product5').as('p5').
addE('belongsTo').from('p5').to('shoes').
addV('Product').
property('name', 'Product6').as('p6').
addE('belongsTo').from('p6').to('shoes').
addV('Product').
property('name', 'Product7').as('p7').
addE('belongsTo').from('p7').to('shoes').
addV('Product').
property('name', 'Product8').as('p8').
addE('belongsTo').from('p8').to('shoes').
addV('Product').
property('name', 'Product9').as('p9').
addE('belongsTo').from('p9').to('shoes').
property('lockedPosition', 7).
addV('Product').
property('name', 'Product10').as('p10').
addE('belongsTo').from('p10').to('shoes').
property('lockedPosition', 3).
iterate()
Note, the edge Product9 -belongsTo-> shoes has property lockedPosition: 7. The edge Product10 -belongsTo-> shoes has property lockedPosition: 3 . A traversal like g.V().hasLabel("Category").in("belongsTo").values("name") returns products with no specific order. My goal is to create a traversal such that getting products in shoes category would always result in a list, where 3rd and 7th positions would be always occupied by Product10 and Product9 respectively. Alternatively, I am okay with accommodating a different data model for this use case, if it makes for simpler or more efficient traversals.
Kennh
Kennh2w ago
I don't think there is that sort of fine-grained control over traversal iteration order or collection order. Do you have any ideas @spmallette ?
spmallette
spmallette2w ago
i missed this one for some reason. another interesting question from @Max wonder if this could be done..... i sense it's going to end up being one those things where it makes more sense to just handle this logic on the application side because the Gremlin to do it will be impenetrable to read. cant quite get it... i thought i could use index() to help with the ordering, but i guess as you go to "insert" a lockedPosition value in the right place technically disturbs the index. as a result, i sense this way will only work if you had one "lockedPosition" set, not multiple. anyway, for fun:
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").
......2> union(has('lockedPosition').
......3> map(union(outV().values('name'), values('lockedPosition')).fold()).fold(),
......4> hasNot('lockedPosition').outV().values('name').fold().index()).
......5> fold().
......6> project('a','b').
......7> by(limit(local,1)).
......8> by(tail(local)).as('m').
......9> select('b').unfold().as('l2').
.....10> map(union(select('m').select('a').unfold().as('l1').
.....11> where('l1', eq('l2')).by(tail(local)),
.....12> identity()).fold()).
.....13> unfold()
==>[Product5,0]
==>[Product6,1]
==>[Product1,2]
==>[Product10,3]
==>[Product7,3]
==>[Product2,4]
==>[Product8,5]
==>[Product3,6]
==>[Product9,7]
==>[Product4,7]
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").
......2> union(has('lockedPosition').
......3> map(union(outV().values('name'), values('lockedPosition')).fold()).fold(),
......4> hasNot('lockedPosition').outV().values('name').fold().index()).
......5> fold().
......6> project('a','b').
......7> by(limit(local,1)).
......8> by(tail(local)).as('m').
......9> select('b').unfold().as('l2').
.....10> map(union(select('m').select('a').unfold().as('l1').
.....11> where('l1', eq('l2')).by(tail(local)),
.....12> identity()).fold()).
.....13> unfold()
==>[Product5,0]
==>[Product6,1]
==>[Product1,2]
==>[Product10,3]
==>[Product7,3]
==>[Product2,4]
==>[Product8,5]
==>[Product3,6]
==>[Product9,7]
==>[Product4,7]
that bit of Gremlin might be a bit overdone, but since it's not working quite right i see no reason to clean up. still might be possible to figure out though for academic purposes.
kelvinl2816
kelvinl28162w ago
I wonder if the solution to a similar situation I posted in the tinkerpop-tips area could be used here as well? https://discord.com/channels/838910279550238720/1156920912206110834/1254910222837874759 This is not quite there - but it is close
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().order().by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local)))
==>[[0,Product5],0]
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[0,Product7],3]
==>[[3,Product10],9]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().order().by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local)))
==>[[0,Product5],0]
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[0,Product7],3]
==>[[3,Product10],9]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
I do think considering data model changes, or just doing this in code within the application (if possible) would be simpler. This is almost there, other than being zero based on the index and the 7 still being one row off
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().
......9> order().
.....10> by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local))).
.....11> by(limit(local,1),desc)
==>[[0,Product5],0]
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[3,Product10],9]
==>[[0,Product7],3]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().
......9> order().
.....10> by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local))).
.....11> by(limit(local,1),desc)
==>[[0,Product5],0]
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[3,Product10],9]
==>[[0,Product7],3]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
you can play tricks like this to move the 0 index to last place, but that still leaves the 7 one row off
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().
......9> order().
.....10> by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local))).
.....11> by(limit(local,1),desc).
.....12> fold().
.....13> union(range(local,1,-1).unfold(),limit(local,1)).fold().unfold()
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[3,Product10],9]
==>[[0,Product7],3]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
==>[[0,Product5],0]
gremlin> g.V().hasLabel("Category").
......1> inE("belongsTo").as('a').outV().
......2> path().
......3> from('a').
......4> by(coalesce(values('lockedPosition'),constant(0))).
......5> by('name').
......6> fold().
......7> index().
......8> unfold().
......9> order().
.....10> by(choose(limit(local,1).limit(local,1).is(neq(0)),limit(local,1).limit(local,1),tail(local))).
.....11> by(limit(local,1),desc).
.....12> fold().
.....13> union(range(local,1,-1).unfold(),limit(local,1)).fold().unfold()
==>[[0,Product6],1]
==>[[0,Product1],2]
==>[[3,Product10],9]
==>[[0,Product7],3]
==>[[0,Product2],4]
==>[[0,Product8],5]
==>[[0,Product3],6]
==>[[7,Product9],7]
==>[[0,Product4],8]
==>[[0,Product5],0]
Max
Max3d ago
Thank you! It was useful, I didn't know you could use by modulator for order like this.
Want results from more Discord servers?
Add your server