sqlc icon indicating copy to clipboard operation
sqlc copied to clipboard

Support dynamic order by clause

Open go-aegian opened this issue 2 years ago • 7 comments

the order by clause in the :many definition query should be set dynamically

-- name: ListOrders :many
  select * from order where status = $1 order by @orderBy::text;

Generated

const listOrders = "select * from order where status = $1 order by @orderBy::text"

type ListOrdersParams struct {
      Status string
      OrderBy string
}
func (q *Queries) ListOrders(ctx context.Context, arg ListOrdersParams) ([]*ListOrdersRow, error) {
        orderBy:=arg.OrderBy
        if orderBy=="" {
           orderBy= "0"
        }
        listOrdersWithOrderBy = strings.Replace(listOrders, "@orderBy::text", orderBy, -1)

	rows, err := q.db.Query(ctx, listOrdersWithOrderBy, arg.Status)

...

What database engines need to be changed?

PostgreSQL, MySQL

What programming language backends need to be changed?

Go, Python, Kotlin

go-aegian avatar Feb 11 '23 18:02 go-aegian

I have implemented this in #2343

jwc-clinnection avatar Jun 20 '23 17:06 jwc-clinnection

I understand the desire to prevent sqlc turning into a templating language, as that defeats the "it's just sql" pitch. Perhaps there's a middle ground using existing sqlc patterns to cover the most common ORDER BY use case.

I'm imagining a new macro sqlc.orderBy() that takes inspiration from sqlc.slice(). Sqlc could use the AST to figure out what columns are available to sort and present those as typesafe args in the generated code.

SELECT id, name FROM authors ORDER BY sqlc.orderBy(myOrderBy);

-- use it as many times as you want
SELECT name
FROM (
  SELECT id, name FROM authors ORDER BY sqlc.orderBy(myInnerOrderBy) LIMIT 10
)
ORDER BY sqlc.orderBy(myOuterOrderBy)

Generating code like

type ListAuthorsOrderByFoo string

const (
    ListAuthorsSortIdAsc     ListAuthorsOrderByFoo = "id_asc"
    ListAuthorsSortIdDesc    ListAuthorsOrderByFoo = "id_desc"
    ListAuthorsSortNameAsc   ListAuthorsOrderByFoo = "name_asc"
    ListAuthorsSortNameDesc  ListAuthorsOrderByFoo = "name_desc"
)

const listAuthors = `-- name: ListAuthors :many
SELECT id, name FROM authors
ORDER BY /*ORDERBY:foo*/` // <- similiar to how sqlc handles slices for mysql

func (q *Queries) ListAuthors(ctx context.Context, foo []ListAuthorsFooOrderBy) ([]Author, error) {
  // ... map orderBy args to the query
}

A solution like that might move the needle enough to solve a lot of the order-by blockers. It doesn't support dynamic expressions, but at least it handles the most common use case of wanting to control the sort order of the columns present in your projection.

seanlaff avatar Jan 10 '24 13:01 seanlaff

Any update on this?

danielbraun89 avatar May 24 '24 12:05 danielbraun89

I stumbled into this too, as it's quite common to have some kind of sorting that needs to be flexible. Think of showing data to users that they can sort by a number of different columns.

Is there a workaround for this besides having multiple queries defined with just the order by being different? Maybe with COALESCE or so? 🤔

marcomayer avatar May 28 '24 14:05 marcomayer

At now for having configurable ORDER BY clause you can do it like this:

ORDER BY
CASE WHEN @order_by::varchar = 'id_asc' THEN authors.id END ASC,
CASE WHEN @order_by = 'id_desc' THEN authors.id END DESC,
CASE WHEN @order_by = 'birth_year_asc' THEN authors.birth_year END ASC,
CASE WHEN @order_by = 'birth_year_desc' THEN authors.birth_year END DESC

But it will be a neat to have some enumerated type instead of just a string.

shagohead avatar Jul 10 '24 09:07 shagohead