GraphQL: Filter data in an array
Asked Answered
H

2

18

I'm sure it's a simple thing to do, but I couldn't find anything in either GraphQL's doc or Graphcool's.

Say I have an entity with this schema (new GraphQL user, sorry if I make mistake in the schema representation):

Book {
  name: String!
  author: String!
  categories: [String!]
}

How would I do a query for all books that are part of the "mystery" category? I know I can filter with allBooks(filter: {}), but categories_in: ["mystery"] and categories_contains: "mystery" didn't do the trick.

Hillhouse answered 6/1, 2017 at 22:15 Comment(3)
This is currently no supported on Graphcool. I have added a feature request to track this: github.com/graphcool/feature-requests/issues/60Fumble
Well, that's good to know. I'll go and +1 your issue. Let's hope we can get this soon!Hillhouse
A current workaround might be to introduce a new model Category with a many-to-many relation to Book. Then you can do this : allCategories(filter: {tag: "mystery"}) { books { id } }. I imagine having a Category model might be advantageous for future meta data or something like that anyway.Casias
C
13

Category model

Thinking a bit more about this situation, creating a Category model is definitely the way to go.

For example, imagine you want to allow readers to subscribe to their favorite categories later. Or, what if you want a list of all existing categories? Using string lists, you would need to query all books and somehow postprocess all obtained categories. Handling this on a model level rather than using string lists feels much more natural.

Instead, you can create a new Category model and add a many-to-many relation between Category and Book. In situations like this, I like to add a unique enum field tag and a string field text. (A unique string field tag alone would also be suitable, probably a matter of taste.)

With this setup, you can easily fulfill data requirements like

Which books are assigned to a given category?

query {
  # query books by unique category tag
  Category(tag: MYSTERY) {
    books {
      id
    }
  }
  # query books by specific category text
  Category(filter: {
    text: "mystery"
  }) {
    books {
      id
    }
  }
}

Which books are assigned to at least one category of a given list?

query {
  allCategories(filter: {
    OR: [{
      tag: MYSTERY
    }, {
      tag: MAGIC
    }]
  }) {
    books {
      id
    }
  }
}

Which books are assigned to all categories of a given list?

query {
  allCategories(filter: {
    AND: [{
      tag: MYSTERY
    }, {
      tag: MAGIC
    }]
  }) {
    books {
      id
    }
  }
}

Related filters

Even though the above queries fulfill the specified data requirements, books are grouped by Category in the response, meaning that we would have to flatten the groups on the client.

With so called related filters, we can turn that around to only obtain books based on conditions defined its related categories.

For example, to query books assigned to at least one category of a given list:

query {
  allBooks(filter: {
    OR: [{
      categories_some: {
        tag: MYSTERY
      },
      categories_some: {
        tag: MAGIC
      }
    }]
  }) {
    id
  }
}
Casias answered 10/1, 2017 at 17:10 Comment(1)
Unfortunately it doesn't really answer the question. I am trying to query shopify products and each product contains an array of tags, which are strings. I guess it is impossible to query for all products that contain a given tag?! Hmm. Solution: get all products and filter them on the client!Unbosom
P
1

If you are interested in using a hosted GraphQL service, scaphold.io has had this feature for a while now. All connection fields in your API come with a WhereArgs argument that exposes filters that let you really dig into your data. When you have a list of scalars like this, the WhereArgs include a contains & notContains field that allow you to filter results based off the values in your list. This allows you to make a query like this.

query MysteriousBooks($where:BookWhereArgs) {
  viewer {
    allBooks(where:$where) {
      edges { node { title, ... } }
    }
  }
}

# Variables
{
  "where": {
    "categories": {
      "contains": "mystery"
    }
  }
}

Just to be complete, you could also do a slight schema readjustment to make this work without having to filter on a scalar list. For example, you could make Category a node implementing type and then create a connection between Category and Book. Although a Book will likely not have many categories, this would allow you to issue a query like this:

query MysteriousBooks($where: CategoryWhereArgs) {
  viewer {
    allCategories(where: $where) {
      books {
        edges { node { title, ... } }
      }
    }
  }
}

# Variables
{
  "where": { 
    "name": { 
      "eq": "mystery" 
    } 
  }
}

If you structure your schema this way then you would also be able to do more filtering on the books in the category without having to loop through every book in your archive. E.G. you could efficiently ask for "all the mystery books written in the last year."

Full disclosure: I work at Scaphold and although I'd love you to try it out no hard feelings if you don't switch over. I'm excited to see people trying and loving GraphQL. If you're curious about how to implement this type of behavior on your own server let me know and I'd be happy to help there as well!

I hope this helps!

Phebe answered 9/1, 2017 at 10:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.