Two Kinds of SQL Query Builders

26 points by pushcx


lalitm

I read through the post and found myself nodding along. But I was quite surprised to reach the end without any discussion of Bigquery's pipe syntax [1]. While not a silver bullet, IMO, databases adding support for pipe sql would take a big step forward towards resolving a lot of the criticisms levelled against SQL, both by this article and in general.

[1] https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax

noteflakes

Something that struck me reading the article is in fact how similar the different examples of query builders are to plain SQL. Which begs the question, why use them in the first place? I mean, on the one hand those DSLs actually require us to be knowledgeable in SQL. On the other hand they prevent using more advanced features of SQL such as CTEs, window functions, RETURNING clauses, etc. Those abstractions are quite leaky. The way those WHERE clauses are put together in some of the examples. Sure, SQL has some warts, but finally what is the value of all those DSLs, apart from letting you put the SELECT clause at the end?

I wrote more of my thoughts on the subject here: https://noteflakes.com/articles/2026-09-05-beyond-orms

noirscape

I'm not entirely convinced any of those query builders do anything you couldn't express in a cleaner way with SQL. Generally speaking, I'd say the Django style of query building is the best one out there in that it actually feels like it takes advantage of using a programming language to express your SQL queries; to express the same sample query in Django, it'd be something like this:

Person.objects.filter(gender_concept_id=8507).order_by('year_of_birth').values_list('person_id', flat=True)[:100]

The main advantages I find this has are that filter is just expressed with keyword arguments (making it really easy to create dynamic queries without needing to violate DRY in annoying ways) rather than a messy OR/AND system to express the where clause, that the select equivalent (values_list) is technically optional (it's usually more of a specific optimization to use it, as you just get a model without it) and that to limit the list, you can use python slices rather than more function stacking.

Not to say Django is perfect though; there's definitely some warts on it's ORM. Double underscores for processing filter components are an acquired taste for example. Similarly, annotating queries that are more complex than Sum/Count/Min/Max quickly becomes a headache. But at that point you might as well drop down to SQL anyways, as every query builder and ORM I've seen just absolutely shits the bed on clean code for that subject. Same with tricks like CTEs. For almost every CRUD operation (which are tbf most operations on a database), it works fine though.

Oh one final pain points about the Django ORM is how stacking filter/exclude clauses work vs. adding keyword arguments to a single call. Stacking filter clauses works more or less the way you expect; every filter clause filters the query down further, and usually the difference between keyword arguments and more function calls is meaningless. For exclude calls, combining keyword arguments instead creates an AND clause on every exclude keyword argument, so usually you end up needing to stack the function calls. It's a bit weird but you get used to it.

veqq
Comment removed by author