Coder Social home page Coder Social logo

Comments (8)

danielrearden avatar danielrearden commented on May 18, 2024

That query fetches all customers (in any order) and for each one, fetches the invoices. The invoices themselves are sorted by the customer's last name (which in this case wouldn't do anything since they're already constrained to a single customer). If you want to order the customers by last name:

{
  customers(orderBy: { lastName: ASC }) {
    lastName
    invoices {
      total
    }
  }
}

Ordering the customers by last name and each customer's invoices by the total:

{
  customers(orderBy: { lastName: ASC }) {
    lastName
    invoices (orderBy: { total: DESC }){
      total
    }
  }
}

from sqlmancer.

JeffML avatar JeffML commented on May 18, 2024

Alright, I'm going to be argumentative here, and push for reopening this, possibly under a different title. Here are two queries, one with a nonworking orderBy, the other with:

query no1 {
  customers {
    lastName
    invoices (orderBy: {customer:{lastName:ASC}}){
      createdOn
    }
  }
}

query no2 {
  invoices (orderBy: {customer:{lastName:ASC}}){
    customer {
      lastName
    }
    total
  }
}

The no2 orderBy clause is identical to the nonworking no1 clause. The problem as I see it is the clause in no2 works, whereas the one in no1 is a null operation.

I view the ability to specify non-functioning operations as bug, not a feature. I can't think of a reason (there may be one) why the results of the relational join operation between customer and invoice can't be ordered independently of the hierarchical relation that is presented by GraphQL. I would expect something like the following to be generated:

select `c1`.`CustomerId` as `id`, `c1`.`FirstName` as `firstName`, `c1`.`LastName` as `lastName`, `c1`.`Email` as `email`, (select json_group_array(json(`i1`.`o`)) from (select json_object(\'id\', `i2`.`InvoiceId`, \'total\', `i2`.`Total`, \'createdOn\', `i2`.`InvoiceId`) as `o` from `invoices` as `i2` where `c1`.`CustomerId` = `i2`.`CustomerId` orderby (select `c2`.`LastName` from `customers` as `c2` where `i2`.`CustomerId` = `c2`.`CustomerId`) asc) as `i1`) as `invoices` from `customers` as `c1`
order by 'lastName';

The only difference I added was the orderBy clause at the end. The order by is on the result set, which is the product of the join (c1, i1), with the column alias 'lastName'. I'm not that familiar with Postgres' JSON query support, but I don't think that matters here*, though maybe there is a SqlLite3 restriction of some kind on order by syntax.

In any case, if for some reason an orderBy parameter can't be respected, an error or warning should be thrown. That's more work, I know, but I don't think it's unreasonable.

* Observations:

  1. why is Email in the SELECT statement? It wasn't asked for. For many-columned tables, and those with BLOBs, it would be really inefficient to fetch more fields than requested.
  2. I see invoices is stored as JSON and aliased to i2 in the inline join in the select clause, then outer alias as i1 (I think). I can't grok why JSON is used here, though. Maybe I need to finish reading the docs.
  3. Why the use of relational expressions in WHERE instead of JOIN statements? I'm sure there are reasons, just asking.

from sqlmancer.

danielrearden avatar danielrearden commented on May 18, 2024

Thanks for your comments. Maybe I'm not fully groking the point you're trying to raise, so bear with me if I'm misunderstanding. In our example, we have a graph of data representing a list of customers and each customer's invoices:

query no1 {
  customers {
    lastName
    invoices {
      createdOn
    }
  }
}

We can provide an orderBy to both the customers field and the invoices field. In the former case, we're saying "get all customers and sort them by these criteria". In the latter case, we're saying "for each customer, get their invoices and sort that set of invoices by these criteria".

In query no1, we don't provide any criteria for sorting customers so their order will not be guaranteed. Query no1 does provide a sort criteria for invoices. So for each customer, we return the invoices sorted by the provided criteria. However, the criteria is {customer:{lastName:ASC}}. That does not mean "sort the customers by last name". It means "sort the invoices according to each invoice's associated customer's last name". Looking at the generated query, this is exactly what it does:

select `c1`.`CustomerId`                                                           as `id`,
       `c1`.`FirstName`                                                            as `firstName`,
       `c1`.`LastName`                                                             as `lastName`,
       `c1`.`Email`                                                                as `email`,
       (select json_group_array(json(`i1`.`o`))
        from (select json_object('id', `i2`.`InvoiceId`, 'total', `i2`.`Total`, 'createdOn', `i2`.`InvoiceId`) as `o`
              from `invoices` as `i2`
              where `c1`.`CustomerId` = `i2`.`CustomerId`
              order by (select `c2`.`LastName`
                        from `customers` as `c2`
                        where `i2`.`CustomerId` = `c2`.`CustomerId`) asc) as `i1`) as `invoices`
from `customers` as `c1`

Again, it's not that this query doesn't work -- it's just that the value we're sorting by (invoice.customer.lastName) happens to be the same for all the records we're sorting. It's no different than if the invoices all had the same total and we sorted by that -- that query also "wouldn't work" in the same sense.

from sqlmancer.

danielrearden avatar danielrearden commented on May 18, 2024

The extraneous columns are actually a bug so thanks for catching that! Looks like a regression introduced by a recent change. I opened #61 to track it. Will fix ASAP.

from sqlmancer.

danielrearden avatar danielrearden commented on May 18, 2024

Utilizing JSON aggregation and correlated subqueries allows us to easily apply ORDER BY, WHERE, LIMIT and OFFSET clauses to each set of related records independently. Just using joins isn't really viable unless we used lateral joins but those are only supported by Postgres.

JSON aggregation also ensures that the driver returns the data in the shape that we need, without needing to do additional processing after the fact. This is in contrast with the approach taken by many other libraries, like Sequelize, where the database returns something like

SELECT "User"."id",
       "User"."createdAt",
       "User"."updatedAt",
       "Addresses"."id"                                                                       
       "Addresses"."createdAt"                                                                
       "Addresses"."updatedAt"                                                                
       "Addresses->UserAddressWithExtraNamePaddingHelloWorldWithExtraNamePadding"."createdAt",
       "Addresses->UserAddressWithExtraNamePaddingHelloWorldWithExtraNamePadding"."updatedAt",
       "Addresses->UserAddressWithExtraNamePaddingHelloWorldWithExtraNamePadding"."UserId",
       "Addresses->UserAddressWithExtraNamePaddingHelloWorldWithExtraNamePadding"."AddressId"

and the library has to transform it into the appropriate JSON structure after the fact.

from sqlmancer.

JeffML avatar JeffML commented on May 18, 2024

from sqlmancer.

JeffML avatar JeffML commented on May 18, 2024

from sqlmancer.

danielrearden avatar danielrearden commented on May 18, 2024

I understand what you are saying. Any nested orderBy operates within the constraints of the parent context. You could achieve something similar by doing a correlated subquery in SQL. And yes, your right: the orderBy statement on the Invoice in no1 "works" but there's nothing really to order by; there's only one lastName (one customer) for each invoice. In a heavily nested query, this is a kind of thing that could easily be missed, though. It seems possible to throw an error if the orderBy references a field in a ancestor...maybe? I'd have to think about it. Okay, my head is a little clearer now. Thanks for detailed clarification.

This is also a good example of where you might start with the @orderBy directive initially and then drop it and just provide your own modified type(s). This way you can get a MVP schema rolled out quickly, but then come back and tweak the input types and filter out options that don't really make sense in the context of your business logic.

from sqlmancer.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.