Building SQL Expressions With Sequel
.jpg)
I’ve recently started working on a new project which uses Sequel, and it reminded me how much I love it. For those who don’t know, Sequel is a superb alternative to Active Record. I wrote a gentle introduction to Sequel a while back.
One of the prettiest parts of Sequel for me is the API for building SQL expressions, so in this article I would like to talk more about that. But before we start with Sequel, I first want to talk a little bit about Active Record.
Active Record
In Active Record, equality and inclusion expressions are typically built with simple ruby hashes:
Negation is also easy with where.not(...):
A nested hash can be used to qualify columns with a different table name (useful for joined datasets):
However, as soon as we need something that cannot be expressed by a hash, it’s common to just reach for raw SQL strings:
Personally, I find this less readable than the hash version, because we need to use placeholders for values that need to be escaped, and provide actual values after the SQL string. Using raw SQL strings is also not very consistent, because now you have some expressions built in Ruby and other are provided in raw SQL.
To address these disadvantages, many people have discovered Arel, Active Record’s internal query builder. It has an expression builder API that allows us to avoid SQL strings for many types of expressions.
We can chain method calls to create more complex expressions, and add the arel-helpers gem that aliases #arel_table to #[]:
However, whether this approach is better than using raw SQL strings is up for debate. On one hand, Arel allows you to inline the values that need to be escaped, so you don’t have to use the ? placeholders. It’s also database agnostic, since you’re building an abstract syntax tree that will be converted to the database-specific SQL by the underlying Active Record adapter.
On the other hand, Arel expressions can get difficult to read as they typically have much more parentheses than their raw SQL counterparts. Also, Arel is considered private API, so it’s not optimized for convenience and it’s subject to change. That’s probably the reason why I also found it very scarcely documented (you won’t find ActiveRecord::Base.arel_table in the API documentation).
Sequel
Sequel’s SQL expression builder API is similar to Arel in the sense that it also builds an abstract syntax tree. However, in Sequel this is public API, it’s optimized for convenience, it’s stable, and it’s very well documented.
We will now walk through various SQL expression types, starting from the most common ones, and show how Sequel always has us covered.
Operators
We’ve already seen that Arel uses methods for building expressions with SQL operators, which can create a lot of nested parentheses. Sequel, on the other hand, uses actual Ruby operators, which make the expressions naturally readable.
Equality Operators
Sequel can use simple Ruby hashes to do equality and inclusion, just like Active Record:
But what if you want to join conditions with OR instead of AND? Or you want to use inequality (>, >=, <=, <) or numeric (+, -, *, /) operators? Or LIKE conditions? Sure, you can always switch to raw SQL strings, but with Sequel we can do better.
Sequel gives you the option to break away from hashes by providing an API for building expressions. One of the simplest expression objects is a column identifier:
The basic operators defined on expression objects are =~ (meaning “equals”) and !~ (meaning “not equals”). The result of an operator is again an expression object.
We can go back and rewrite our hash example using Sequel expressions:
This is of course longer and more verbose than the hash version, it’s just to introduce the API, so bear with me.
Now, having to repeat Sequel[] whenever we want to create an identifier object can be cumbersome, so Sequel offers the virtual row block syntax that automatically creates identifier objects via method_missing. All you have to do is pass a block to .where and then you can omit the Sequel[]:
Active Record users might recognize this syntax if they have ever used the Squeel Active Record extension. Squeel was heavily inspired by Sequel’s virtual row blocks.
Boolean Operators
You may have noticed the & operators between the conditions above. As you might have guessed, Sequel will translate those into AND in SQL.
Up until now there was no benefit of using the expression API over hashes, because we’ve only dealt with equality conditions joined with AND. If we wanted to use an OR condition instead, in Active Record we would have to switch to raw SQL. But with Sequel we can just use the | operator:
Note that we have to put parentheses around the clauses, because & and | operators have higher predence than =~ and !~.
You might be wondering why we can’t just use && and || instead of & and |. The reason is that && and || are keywords built into the Ruby interpreter, therefore it’s not possible to change their meaning. & and |, on the other hand, are just methods which can be overriden like many other operators.
The third boolean operator is negation (NOT). With Sequel we can negate expressions using the ~ unary operator:
If you prefer and are able to use plain hashes, you can still use the &, |, and ~ operators defined on the Sequel module:
Inequality Operators
In addition to the equality operators (= and !=), Sequel also defines inequality operators (>, >=, <=, <) on the expression objects.
We can then chain them with boolean operators in the same way:
And use virtual row blocks:
Numeric and String Operators
The expression objects also define some numeric operators we’d expect:
On string expressions + is defined as concatenation:
Qualifying
Sequel doesn’t automatically qualify column identifiers, because SQL doesn’t either.
To qualify a column identifier with a table name, you can call another square brackets on the identifier:
You can also qualify whole expressions with Sequel.deep_qualify:
To just qualify all column identifiers in a dataset, use Sequel::Dataset#qualify:
Active Record does have the more convenient hash syntax for qualifying #where conditions. However, I don’t find it ideal to have to nest conditions that have the same level of importance.
Ordering
Ordered expressions can be created with Sequel.asc and Sequel.desc:
These can then be passed to #order:
When I first came to Sequel after having used Active Record for many years, I was wondering if there was something like order(rating: :desc) in Sequel. Jeremy explained that this syntax would violate the general rule in Sequel that hashes are used for equality expressions. I also came to realize how flexible is that #asc and #desc are methods.
For example, that allows Sequel to support NULLS FIRST and NULLS LAST via the :nulls option:
You can also order by any complex expressions:
Since ordered expressions are expressions, Sequel will recognize them anywhere, even for example in parameters for a window function:
Like
Sequel supports LIKE and ILIKE expressions via #like and #ilike methods:
They can then be used in #where:
Here is an example of creating filter expression that matches a search term onto multiple columns:
Functions
Some SQL functions have their dedicated API in Sequel, but in general Sequel supports calling any SQL function. Sequel.function can be used to create a function expression; the first argument is the function name and the rest are function arguments.
They can then be used in places like #select:
To create count(*), you can use the * unary operator:
Sequel’s virtual row blocks make calling SQL functions more convenient via method_missing. An undefined method called with arguments will automatically be converted into a function, whereas to create a function without arguments you’ll need to add a call to #function.
This means you can easily convert even complex SQL queries into Sequel:
In Arel you have the Arel::Nodes::NamedFunction counterpart, but using it is much more verbose:
Aliasing
Sequel provides support for aliasing via the #as method. You can alias anything from column identifiers:
to expressions:
and even whole datasets:
Arel also has #as for aliasing, but for some reason it’s not available for all types of expressions:
Case
You can build CASE statements with Sequel.case, where first argument is the hash of WHERE/THEN conditions, second argument is the ELSE default value, and the third optional argument is the CASE expression.
A few years ago I made a pull request to RubyGems.org, which added a Rake task that syncs cached download counts from Redis into the Postgres database. Since the rubygems table has many rows, I didn’t want to execute an UPDATE query for each row, so I built a CASE statement in SQL:
With Sequel this whole expression can be created in Ruby:
Arel also has its own Arel::Nodes::Case, which is built incrementally. However, Active Record’s #update_all method doesn’t seem to accept Arel expressions, because this case expression gets converted to NULL.
Array & Json Operations
The last type of expressions we’ll cover are operations with Postgres Array and JSON types. These operations include operators and function names that can be difficult to remember, and Sequel provides a convenience API via the pg_array_ops and pg_json_ops extensions (see this document for more on Sequel extensions).
We first need to create an array/json expression by calling #pg_array and #pg_jsonb, and then we can call operation methods on that object.
If you want to avoid repeating #pg_array or #pg_jsonb in your queries, you can save the array/json expression object into a variable or a constant.
Other uses
Other than for building queries, Sequel supports the expression API in many other areas where SQL expressions are needed.
For instance, you can use them when creating an index:
Another example is the USING statement when changing column type:
Yet another area where expressions are supported is in Sequel’s UPSERT method:
Conclusion
Sequel’s API for building SQL expressions allows you to consistently stay in Ruby even for very advanced use cases. This has numerous advantages:
- it’s more readable, with different styles to choose from
- it’s more hackable and reusable
- doesn’t require you to look up the correct SQL syntax every time
- eliminates the possibility of SQL injections
We’re much more motivated to use the various features our database has to offer when our database library encourages us to.
I’ve recently started working on a new project which uses Sequel, and it reminded me how much I love it. For those who don’t know, Sequel is a superb alternative to Active Record. I wrote a gentle introduction to Sequel a while back.
One of the prettiest parts of Sequel for me is the API for building SQL expressions, so in this article I would like to talk more about that. But before we start with Sequel, I first want to talk a little bit about Active Record.
Active Record
In Active Record, equality and inclusion expressions are typically built with simple ruby hashes:
Negation is also easy with where.not(...):
A nested hash can be used to qualify columns with a different table name (useful for joined datasets):
However, as soon as we need something that cannot be expressed by a hash, it’s common to just reach for raw SQL strings:
Personally, I find this less readable than the hash version, because we need to use placeholders for values that need to be escaped, and provide actual values after the SQL string. Using raw SQL strings is also not very consistent, because now you have some expressions built in Ruby and other are provided in raw SQL.
To address these disadvantages, many people have discovered Arel, Active Record’s internal query builder. It has an expression builder API that allows us to avoid SQL strings for many types of expressions.
We can chain method calls to create more complex expressions, and add the arel-helpers gem that aliases #arel_table to #[]:
However, whether this approach is better than using raw SQL strings is up for debate. On one hand, Arel allows you to inline the values that need to be escaped, so you don’t have to use the ? placeholders. It’s also database agnostic, since you’re building an abstract syntax tree that will be converted to the database-specific SQL by the underlying Active Record adapter.
On the other hand, Arel expressions can get difficult to read as they typically have much more parentheses than their raw SQL counterparts. Also, Arel is considered private API, so it’s not optimized for convenience and it’s subject to change. That’s probably the reason why I also found it very scarcely documented (you won’t find ActiveRecord::Base.arel_table in the API documentation).
Sequel
Sequel’s SQL expression builder API is similar to Arel in the sense that it also builds an abstract syntax tree. However, in Sequel this is public API, it’s optimized for convenience, it’s stable, and it’s very well documented.
We will now walk through various SQL expression types, starting from the most common ones, and show how Sequel always has us covered.
Operators
We’ve already seen that Arel uses methods for building expressions with SQL operators, which can create a lot of nested parentheses. Sequel, on the other hand, uses actual Ruby operators, which make the expressions naturally readable.
Equality Operators
Sequel can use simple Ruby hashes to do equality and inclusion, just like Active Record:
But what if you want to join conditions with OR instead of AND? Or you want to use inequality (>, >=, <=, <) or numeric (+, -, *, /) operators? Or LIKE conditions? Sure, you can always switch to raw SQL strings, but with Sequel we can do better.
Sequel gives you the option to break away from hashes by providing an API for building expressions. One of the simplest expression objects is a column identifier:
The basic operators defined on expression objects are =~ (meaning “equals”) and !~ (meaning “not equals”). The result of an operator is again an expression object.
We can go back and rewrite our hash example using Sequel expressions:
This is of course longer and more verbose than the hash version, it’s just to introduce the API, so bear with me.
Now, having to repeat Sequel[] whenever we want to create an identifier object can be cumbersome, so Sequel offers the virtual row block syntax that automatically creates identifier objects via method_missing. All you have to do is pass a block to .where and then you can omit the Sequel[]:
Active Record users might recognize this syntax if they have ever used the Squeel Active Record extension. Squeel was heavily inspired by Sequel’s virtual row blocks.
Boolean Operators
You may have noticed the & operators between the conditions above. As you might have guessed, Sequel will translate those into AND in SQL.
Up until now there was no benefit of using the expression API over hashes, because we’ve only dealt with equality conditions joined with AND. If we wanted to use an OR condition instead, in Active Record we would have to switch to raw SQL. But with Sequel we can just use the | operator:
Note that we have to put parentheses around the clauses, because & and | operators have higher predence than =~ and !~.
You might be wondering why we can’t just use && and || instead of & and |. The reason is that && and || are keywords built into the Ruby interpreter, therefore it’s not possible to change their meaning. & and |, on the other hand, are just methods which can be overriden like many other operators.
The third boolean operator is negation (NOT). With Sequel we can negate expressions using the ~ unary operator:
If you prefer and are able to use plain hashes, you can still use the &, |, and ~ operators defined on the Sequel module:
Inequality Operators
In addition to the equality operators (= and !=), Sequel also defines inequality operators (>, >=, <=, <) on the expression objects.
We can then chain them with boolean operators in the same way:
And use virtual row blocks:
Numeric and String Operators
The expression objects also define some numeric operators we’d expect:
On string expressions + is defined as concatenation:
Qualifying
Sequel doesn’t automatically qualify column identifiers, because SQL doesn’t either.
To qualify a column identifier with a table name, you can call another square brackets on the identifier:
You can also qualify whole expressions with Sequel.deep_qualify:
To just qualify all column identifiers in a dataset, use Sequel::Dataset#qualify:
Active Record does have the more convenient hash syntax for qualifying #where conditions. However, I don’t find it ideal to have to nest conditions that have the same level of importance.
Ordering
Ordered expressions can be created with Sequel.asc and Sequel.desc:
These can then be passed to #order:
When I first came to Sequel after having used Active Record for many years, I was wondering if there was something like order(rating: :desc) in Sequel. Jeremy explained that this syntax would violate the general rule in Sequel that hashes are used for equality expressions. I also came to realize how flexible is that #asc and #desc are methods.
For example, that allows Sequel to support NULLS FIRST and NULLS LAST via the :nulls option:
You can also order by any complex expressions:
Since ordered expressions are expressions, Sequel will recognize them anywhere, even for example in parameters for a window function:
Like
Sequel supports LIKE and ILIKE expressions via #like and #ilike methods:
They can then be used in #where:
Here is an example of creating filter expression that matches a search term onto multiple columns:
Functions
Some SQL functions have their dedicated API in Sequel, but in general Sequel supports calling any SQL function. Sequel.function can be used to create a function expression; the first argument is the function name and the rest are function arguments.
They can then be used in places like #select:
To create count(*), you can use the * unary operator:
Sequel’s virtual row blocks make calling SQL functions more convenient via method_missing. An undefined method called with arguments will automatically be converted into a function, whereas to create a function without arguments you’ll need to add a call to #function.
This means you can easily convert even complex SQL queries into Sequel:
In Arel you have the Arel::Nodes::NamedFunction counterpart, but using it is much more verbose:
Aliasing
Sequel provides support for aliasing via the #as method. You can alias anything from column identifiers:
to expressions:
and even whole datasets:
Arel also has #as for aliasing, but for some reason it’s not available for all types of expressions:
Case
You can build CASE statements with Sequel.case, where first argument is the hash of WHERE/THEN conditions, second argument is the ELSE default value, and the third optional argument is the CASE expression.
A few years ago I made a pull request to RubyGems.org, which added a Rake task that syncs cached download counts from Redis into the Postgres database. Since the rubygems table has many rows, I didn’t want to execute an UPDATE query for each row, so I built a CASE statement in SQL:
With Sequel this whole expression can be created in Ruby:
Arel also has its own Arel::Nodes::Case, which is built incrementally. However, Active Record’s #update_all method doesn’t seem to accept Arel expressions, because this case expression gets converted to NULL.
Array & Json Operations
The last type of expressions we’ll cover are operations with Postgres Array and JSON types. These operations include operators and function names that can be difficult to remember, and Sequel provides a convenience API via the pg_array_ops and pg_json_ops extensions (see this document for more on Sequel extensions).
We first need to create an array/json expression by calling #pg_array and #pg_jsonb, and then we can call operation methods on that object.
If you want to avoid repeating #pg_array or #pg_jsonb in your queries, you can save the array/json expression object into a variable or a constant.
Other uses
Other than for building queries, Sequel supports the expression API in many other areas where SQL expressions are needed.
For instance, you can use them when creating an index:
Another example is the USING statement when changing column type:
Yet another area where expressions are supported is in Sequel’s UPSERT method:
Conclusion
Sequel’s API for building SQL expressions allows you to consistently stay in Ruby even for very advanced use cases. This has numerous advantages:
- it’s more readable, with different styles to choose from
- it’s more hackable and reusable
- doesn’t require you to look up the correct SQL syntax every time
- eliminates the possibility of SQL injections
We’re much more motivated to use the various features our database has to offer when our database library encourages us to.
How can we
help you innovate?
Roku Channel: How to Make One, How Much it Will Cost, & How to Monetize

Roku Channel: How to Make One, How Much it Will Cost, & How to Monetize

Roku Channel: How to Make One, How Much it Will Cost, & How to Monetize

Roku Channel: How to Make One, How Much it Will Cost, & How to Monetize

Roku Channel: How to Make One, How Much it Will Cost, & How to Monetize

Building AT&T a platform to power healthcare data

Building AT&T a platform to power healthcare data

Building AT&T a platform to power healthcare data

Building AT&T a platform to power healthcare data

Building AT&T a platform to power healthcare data


