-
Notifications
You must be signed in to change notification settings - Fork 279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CASE...WHEN...THEN...END
Support
#214
Comments
I've been thinking about this recently. The urgency is there for sure. Its a difficult one to get right, as far as API goes.
Not sure if it should be bound to |
Yeah, it's a tricky one. As far as ExpressionBuilder, might make more sense to go in the other direction and look at expanding what fn.count (and other agg funcs) can use, allowing an ExpressionBuilder or some kind of subset of it there, rather than trying to treat CASE as a special case? No pun intended. Not sure about other engines and I'm not an SQL expert but for sqlite, the expr syntax https://www.sqlite.org/syntax/expr.html is reusable across almost everything (aggregate functions, select columns, order by clauses etc) - I figured this was roughly analogous to ExpressionBuilder. I'm a little confused about subqueries though, since you can use those in SELECT clauses but I don't think you can pass them to COUNT.
This is roughly what I was thinking. "then" could also just be a second argument to whenThen - since you'll never want multiple thens anyway. That would allow wb to be identical to the regular WhereBuilder. One random thing to keep in mind with types, if you don't specify an else, then the statement might return NULL. So by default, .whenThen would make the result nullable, but then calling .else() might remove that. I'm not sure if that will cause issues with types - what if the then type was already nullable? Then the result is nullable even after an .else() is chained. |
If we go down the road of adding builders for all kinds of expressions, we need to really think it through and create a cohesive expression builder instead of adding different things ad-hoc. I'm also not sure we should do it at all. The code quickly becomes unreadable and it's better just to write raw SQL and pay the small type-unsafety penalty. Let's think about the case builder for example. The syntax could indeed be something like this eb.case(maybeExpr)
.when(expr).then(expr)
.when(expr).then(expr)
.else(expr) The expressions can be anything but let's say they are simple comparison operations. We'd have two options:
Since we are on the road of full type-safety, we'd of course want to use the expression builder. We'd get something like this: eb.case()
.when(eb.bin('first_name', '=', 'foo')).then(eb.val(1))
.when(eb.bin('first_name', 'in', ['bar', 'baz'])).then(eb.val(2))
.else(eb.val(3)) In this example I used two non-existent expression builder methods sql<number>`
case
when first_name = 'foo' then 1
when first_name in ('bar', 'baz') then 2
else 3
end
` |
Just my two cents, I like the idea of an ExpressionBuilder that does pretty much everything, with type safety, and can be used anywhere. Could be useful in other places as well (orderBy, etc). But I'm very personally biased towards type safety everywhere, even at the cost of extra verbosity. But why not both that as a fallback, with an overload or two for the most common use? I figure CASE is similar to WHERE, in which the majority of use will be a simple IF X OP Y, THEN LITERAL. This is basically how orderBy/select already works.
Or just have the overloads be idential to qb.where - but then you lose some flexibility, unless falling back to raw. I feel like this could get a bit messy too. Do you need whenRef too? whenNotExists? Etc etc. Not the biggest fan for those reasons.
Ironically, if you did add to ExpressionBuilder, it that would make CASE no longer necessary for the use case that prompted my initial post on this (sorting a query by putting a set of items at the top based on an equality condition), because I could just use the operator in qb.orderBy using the EB and not have a case statement at all. I guess the downside is just that ExpressionBuilder may start to become pretty complicated, but a lot of that could go towards covering future use cases that no one is thinking of now, and for simple queries, you don't need to use it anyway. |
@midwesterntechnology |
@koskimas I think I get |
So we'd have select(eb => eb.bin('first_name', '=', 'Sami').as('is_sami')) |
This is ambitious, would help a lot creating db-exclusive helpers |
Note that with the |
Well since it will be on Typescript if the return is not a Promise probably it's okay |
Can we use const rows = await kysely
.updateTable("user")
.set((eb) => ({
first_name: eb
.case()
.when('first_name', '=', 'foo')
.then("found")
.when('first_name', '=', 'bar')
.then("found")
.else("not found")
// any maybe more …
.end()
})
).execute() more compact like const names = ["foo", "bar"]
const rows = await kysely
.updateTable("user")
.set((eb) => ({
first_name: (names.reduce((prev, curr) => prev.when("first_name", "=", curr).then("found"), eb.case())).else("not found").end()
})
).execute() but TypeScript sees the isolated eb.case() call as
I can get around it with an explicit cast import { DB, Member } from './db'
import { CaseWhenBuilder } from 'kysely'
const names = ["foo", "bar"]
const rows = await kysely
.updateTable("user")
.set((eb) => ({
first_name: (names.reduce((prev, curr) => prev.when("first_name", "=", curr).then("found"), eb.case() as unknown as CaseWhenBuilder<DB, 'UserTable', unknown, UserTable['first_name']>)).else("not found").end()
})
).execute() but I'd rather not. Arguably, that reduce call also isn't as readable as I'd like it to be, so I am open to approaching this differently. Appreciate any help and insight. 🙏 |
Doesnt'this solves it? const names = ["foo", "bar"] as const |
Nope. |
@twiddler native It won't work with the I think what @thelinuxlich suggested can work, if you explicitly write the return type. |
Ye, that's what I thought it probably would boil down to. Thanks. I do not know what you mean by "explicitely write the return type". I pasted this const names = ["foo", "bar"] as const
const rows = await kysely
.updateTable("user")
.set((eb) => ({
first_name: (names.reduce((prev, curr) => prev.when("first_name", "=", curr).then("found"), eb.case())).else("not found").end()
})
).execute() into https://kyse.link/ and it throws "No overload matches this call.". |
Would anybody have any tips to here for building a case statement via any other pattern so i can accomplish this without an any? for example... the update dictionary isn't constant, so i cant use the tip above const caseStatement = _.entries(someDataByUserEmail).reduce(
(
caseBuilder,
[userEmail, data],
) => {
return caseBuilder.when('email', '=', ).then(data);
},
// I have to cast the acc to any to avoid type errors.
kysely.case() as any,
); |
This is a bigger one, but, any plans on implementing CASE support?
https://modern-sql.com/feature/case
Looks like it is supported by: BigQuery, Db2, MariaDB, MySQL, Oracle DB, PostgreSQL, SQL Server, SQLite, so basically all major engines.
Syntax:
Probably would fit well as another method on ExpressionBuilder, ie:
The text was updated successfully, but these errors were encountered: