GROQ: Finding shared references between documents (referencedBy workaround)

10 replies
Last updated: Mar 2, 2023
This isn't a question but a candid, open experiment that appears to have worked well.
GROQ has a
*references() * function but I wasn't aware of a referencedBy analogue to take advantage of the references being bidirectional.
I needed to determine the referenced documents of one type that two other documents of another identical type
share. In this case, two "weddings" having their list of "vendor" documents compared.
While my desired magic function doesn't seem to exist, I have seen clever approaches like
this and adapted it, so that it works in the one case I need it where

*[ _type == "vendor"]{
  _id,
  vendorName,
  "one":  count(*[_id==$weddingId1 && references(^._id)]),
  "two" : count(*[_id==$weddingId2 && references(^._id)])
}[one > 0 && two > 0].vendorName
and the params

{
  "weddingId1":"8bdbe960-3f74-45b9-9b8e-6269a2cae8e4",
  "weddingId2":"d83030c5-383a-4137-8385-4d305c3f3212"
  
}
give me, in Vision,

"result":[ 1 item
0:"Copper Rose"
]
The pivotal aspect is that the projection can be filtered, and even though I am used to always seeing numbers in those square brackets, it
is a filter and therefore permits some logic, just as a JavaScript filter might. Which is to say, "whatever passes this test goes through."
Part of the fun of GROQ though is practicing to where the syntax and concepts finally mesh in your head.

Once I had a feel that I was really targeting things, the
infinitely more clean, clear and elegant query below produces the same result:

*[
  _id in *[_id == $weddingId1].vendorList[]->_id &&
  _id in *[_id == $weddingId2].vendorList[]->_id
 ].vendorName
AI Update

This is a great share! You've discovered some really elegant GROQ patterns for finding shared references between documents, and your journey from the first working solution to the cleaner final query is a perfect example of how GROQ proficiency develops.

Your key insights are spot-on:

1. Filters accept logical expressions: You're absolutely right that the square bracket notation [condition] is a filter that can contain any boolean logic, not just array indices. This is fundamental to GROQ's power and opens up possibilities like your [one > 0 && two > 0] approach.

2. The parent reference (^) trick: Your first solution cleverly uses the parent operator ^ to reference the vendor document from within the nested query. This pattern of counting references from the "wrong direction" is a well-known GROQ workaround for the lack of a native referencedBy() function.

3. The cleaner _id in pattern: Your final query is indeed much more readable:

*[
  _id in *[_id == $weddingId1].vendorList[]->_id &&
  _id in *[_id == $weddingId2].vendorList[]->_id
].vendorName

This directly expresses the intent: "find vendors whose IDs appear in both wedding vendor lists." The _id in operator combined with dereferencing (->) makes it very declarative.

Potential optimizations to consider:

If you find yourself doing this frequently or with many weddings, you could also use the count() approach inline:

*[_type == "vendor" && count(*[_id in [$weddingId1, $weddingId2] && references(^._id)]) == 2].vendorName

This checks that exactly 2 of your specified weddings reference each vendor, which scales better if you're comparing more than two documents.

Thanks for sharing this exploration—it's exactly the kind of problem-solving that helps others learn GROQ's more advanced patterns! The progression from "it works" to "it's elegant" is where GROQ really clicks.

Show original thread
10 replies
Nice! 🙂 I wonder if this is a common pattern and we should have a
referencedBy()
function.
I have a couple of comments about performance here, though.

One is that, unfortunately,
count(*[references()])
calls are not optimized internally, and will load all the documents into memory to count them. They can be very expensive to run if there are many documents matching. I don't think this is mentioned explicitly in the High Performance GROQ guide; if not, we should add it.
Secondly, your cleaner query is also a bit performance-challenged. But it can be optimized to avoid using `->`:

*[
  _id in *[_id == $weddingId1].vendorList[]._ref &&
  _id in *[_id == $weddingId2].vendorList[]._ref
].vendorName
You should never need to do
->_id
. The ID is always available as
_ref
.
Greetings
user L
!

I wonder if this is a common pattern and we should have a
referencedBy()
function.
That is a function that would be exciting, and, as hinted, feels natural in how it takes advantage of a core characteristic of the relationship of documents in Sanity.
l feel like I see multiple threads a month which are essentially asking about some variation of it.

In my studio I also get asked on a fairly regular basis to produce panels that indicate which documents call for the one being viewed.

If I am not mistaken, there are also at least two plugins built around that concept, and it might be a stepping stone for the people who want to choose for that kind of information to be brought inline in the field areas surrounding their inputs.

Syntactically it'd definitely be cleaner than starting from the opposite direction to create a projection that gets targeted.


count(*[references()])
calls are not optimized internally, and will load all the documents into memory to count them.
If it's any comfort, this was a Vision-only experiment because it was way easier doing it here than in my proxy database. I wouldn't be using it on a site, but duly noted, and a good reminder to be mindful and to re-read that article!

You should never need to do
->_id
. The ID is always available as
_ref
Oh my, that's another great reminder. It's the same piece of information, of course. I will blame the fact that my brain was stuck in dereferencing mode knowing I wanted to see what was inside the array of references 🙂
As far as that 'cleaner' query -- I did have a thought that it would be cool to have two arrays of ids and perform a "not" or "reverse" array::unique; one that was subtractive.

Which is to say, here are these two lists, and instead of removing the extra instance of what they both share, throw out everything that
isn't what they have in common, in order to reveal what they do.
How would you handle comparing the items of two docs like this?
Hmm, you could count?
count(*[_id == $weddingId1 || _id == $weddingId2].vendorList[]._ref) == 2
If it appears in both lists, the count should be 2
Oh,
vendorList[]
is an array… Uh, not without storing this in a temporary array
Temporary array meaning one of those invented keys one assigns to mean something?
Yeah, but it won't really help you here
Oh, no worries, I was just curious because I didn't remember hearing it called that before. Wanted to be on the same page 🙂
Thanks for all your insights, of course!
Any time

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?