For further background, which you may or may not need, but which might help someone else…
Both Old and New have a db.sql method. As of this morning’s backwards compatibility update, both Old and New also have a db.query method. (In New, it’s only available if the notebook is set to use the 2018 standard library.) They behave similarly for the simplest queries, but the differences become important with complexity.
With query you query the database like this:
db.query("SELECT 1")
With sql you query the database like this:
db.sql`SELECT 1`
So far, those two queries will behave identically.
With query, you are passing a plain old string to the database as the query, and it would behave the same however you constructed the string, e.g. with backticks:
db.query(`SELECT 1`)
And it’d behave the same if you built the string by interpolating something into it, like this:
db.query(`SELECT ${number}`)
That would still just run the query SELECT 1 (assuming number is 1).
But with sql, if you do this:
db.sql`SELECT ${number}`
It is actually constructing a parameterized query string, SELECT ?, and then separately passing the parameter 1 directly to the database driver, which substitutes the 1 for the ? to produce SELECT 1, and you get the same results.
The new way is safer, because if the value of your variable number somehow became something like 1; DROP TABLE table_name;, the injection attack wouldn’t work.
And the new way can be more convenient, because you don’t have to put quotes around your strings or format your arrays to pass them in.
The problem is, you can’t put those question-mark parameters everywhere you might build up a string. They can only represent complete values.
With query, you could do something “crazy” like this:
db.query(`SELECT ${Math.random() > 0.5 ? "GREATEST" : "LEAST"}(1, 2)`)
Or even this:
db.query(`SEL${"ECT"} 1`)
But with sql, those are syntax errors. You can’t put ?s there! It ends up being like ?(1, 2) or SEL? 1, which indeed look like syntax errors. The question mark can only stand for a value.
There may be other database-specific limitations. For example, on Snowflake, it seems an INTERVAL cannot be dynamically constructed by a parameter. You certainly can’t do INTERVAL '${hours} hours', because that string would become '? hours', and that ? would not be replaced, it would just be nonsense, an invalid interval, a syntax error. But it looks like you can’t even do INTERVAL ${hoursString}, where hoursString is '6 hours', for whatever reason. Hence, the suggestion above to use DATEADD instead.
One “escape hatch” is that you can “cheat” and interpolate another sql fragment you’ve constructed by passing a raw string, like this:
db.sql`SELECT CURRENT_TIMESTAMP() - INTERVAL ${sql([`'${hours} hours'`])}`
That works because, behind the scenes, tagged template literals are just regular JavaScript functions with particular parameters, and nested sql fragments get flattened by the sql method. But that’s a hacky ugly confusing workaround that we wouldn’t really recommend. But maybe it helps elucidate what’s happening.