drizzle-orm icon indicating copy to clipboard operation
drizzle-orm copied to clipboard

[FEATURE]: Allow adding filters based on conditions

Open saurabh7481 opened this issue 1 year ago • 4 comments

Describe want to want

In real world, it is very rare to just fetch stuff from database in one go using pre defined filters and send it to client. There are many complex conditions which needs to be checked and based on them filters need to be added. I stumbled across this situation and I am not sure how to tackle it.

For reference, this is my base query:

let dataQuery = connection.db
            .select({
                account_id: paymentCustomers.accountId,
                charge_id: paymentCharges.chargeId
            })
            .from(paymentCharges)
            .innerJoin(
                paymentCustomers,
                eq(paymentCharges.customer, paymentCustomers.customerId)
            )
            .orderBy(paymentCharges.created)
            .groupBy(paymentCharges.chargeId)
            .where(
                and(
                    gt(paymentCharges.id, 0),
                    like(paymentCharges.onBehalfOf, user.accountId)
                )
            )
            .limit(Number(pageSize))
            .offset(skip)
            .prepare();

There are two filters applied in this query, however many more filters need to be applied here based on filters sent by client. I am used to using kysely where you would just chain WHERE statements one after another and it would all add up. Here, if i try doing so it removes previous WHERE filter. This is a very basic requirement for a query builders, that you are able to chain up conditions one after another. I have looked into documentation but could not find any.

Doing so in kysely was simple as I would just create a helper function which would go through all filters and add them to WHERE blocks in the query. I want to leave number of filters to client team and this was a decent solution for that. However, prepared statements in drizzle dont seem to have that capability.

Am i missing anything or is there any approach to handle this condition?

Thanks

saurabh7481 avatar Jun 06 '23 13:06 saurabh7481

You can just store the condition in a variable and update it according to your logic, then pass to .where(). Its type would be SQL.

dankochetov avatar Jun 06 '23 13:06 dankochetov

You can just store the condition in a variable and update it according to your logic, then pass to .where(). Its type would be SQL.

Yes, but template string is something I would like to use as a last resort. It's a very basic requirement to have flexibilty to add building blocks to a query and adding multiple WHERE based on conditions. Currently, I had to parse all my filters in a SQL template string and use 'sql' inside WHERE to do the work but it would be better if I could just update the query itself by adding WHERE blocks with appropriate conditions given by drizzle just like how kysely does this.

Just a suggestion which would drastically improve the DX, at least for me.

saurabh7481 avatar Jun 07 '23 13:06 saurabh7481

you can do this as well Don't look at the logic. This is just an example of how to build a where statement and then use it. No need to parse or do sql template

import { and, eq, lt, or, SQL } from "drizzle-orm";

const where: SQL[] = []

const id = ...
if (id > 3) {
  where.push(eq(schema.users.id, id))
}

if (id > 3) {
  where.push(or(lt(schema.users.id, 3), eq(schema.users.id, 3))!)
}

await db.select().from(schema.users).where(and(...where))

Instead of id > 3 you can make any assumption based on the input by the user. Like if(name)/if(search)/etc.

AndriiSherman avatar Jun 07 '23 13:06 AndriiSherman

Perfect. I was already using SQLChunks but I didn't know that you can use conditional operators inside also. Though, I am going to stick with sql.raw for now because of some reason but I can definitely see this being useful in other scenarios.

Thanks all for making drizzle.

saurabh7481 avatar Jun 09 '23 05:06 saurabh7481

If I can also suggest to put example like this in documentation. It's a very common scenario, in example, when user has search form with several optional criteria. I'm pretty sure issue #226 was the same thing but the author gave up. Took me an hour to figure this out because ternary operators within with() were not working.

psiho avatar Aug 25 '23 10:08 psiho

Would be very helpful to have this in the docs. Thanks for the example @AndriiSherman

adicco avatar Oct 19 '23 09:10 adicco

this pattern won't work in prepared statements.

for a prepared stmt I have to apply one of the following pattern:

-- supports MySQL5
WHERE CASE WHEN :cond <> null AND :cond <> "undefined" THEN column = :cond ELSE 1 END

-- able to use index starts from mysql8
WHERE COALESCE(column = :cond, :cond IS NULL)

do we have better patterns for prepare statements?

arily avatar Nov 30 '23 05:11 arily

This can also be done using undefined in and() and or() calls, as undefined is removed by Drizzle when compiling the query:

db
  .select()
  .from(users)
  .where(
    and(
      subscribedOnly ? users.subscribed : undefined,
      companyID ? eq(users.companyID, companyID) : undefined,
      userID ? eq(users.id, userID) : undefined,
    )
  )

See also https://github.com/drizzle-team/drizzle-orm/issues/1644#issuecomment-1877442097 for stacking multiple .where() calls.

net avatar Jan 04 '24 17:01 net

you can do this as well Don't look at the logic. This is just an example of how to build a where statement and then use it. No need to parse or do sql template

import { and, eq, lt, or, SQL } from "drizzle-orm";

const where: SQL[] = []

const id = ...
if (id > 3) {
  where.push(eq(schema.users.id, id))
}

if (id > 3) {
  where.push(or(lt(schema.users.id, 3), eq(schema.users.id, 3))!)
}

await db.select().from(schema.users).where(and(...where))

Instead of id > 3 you can make any assumption based on the input by the user. Like if(name)/if(search)/etc.

❤️🔥🚀 I'd say that adding an example on the doc page related to dynamic query building would suffice to close this issue https://orm.drizzle.team/docs/dynamic-query-building

janvorwerk avatar Jan 16 '24 14:01 janvorwerk

This can also be done using undefined in and() and or() calls, as undefined is removed by Drizzle when compiling the query:

db
  .select()
  .from(users)
  .where(
    and(
      subscribedOnly ? users.subscribed : undefined,
      companyID ? eq(users.companyID, companyID) : undefined,
      userID ? eq(users.id, userID) : undefined,
    )
  )

See also #1644 (comment) for stacking multiple .where() calls.

Wouldn't it be more efficient to do

db
  .select()
  .from(users)
  .where(
    and(
      subscribedOnly && users.subscribed,
      companyID && eq(users.companyID, companyID),
      userID && eq(users.id, userID),
    )
  )

Where it would just resolve the expression and just ignore the falsy values?

Bahnasawy avatar Jan 18 '24 14:01 Bahnasawy

Wouldn't it be more efficient to do

db
  .select()
  .from(users)
  .where(
    and(
      subscribedOnly && users.subscribed,
      companyID && eq(users.companyID, companyID),
      userID && eq(users.id, userID),
    )
  )

Where it would just resolve the expression and just ignore the falsy values?

Nope, since falsy values would still go into filter: where '', where false, which would make query return nothing. While undefined values would be excluded from the filters array.

orlovol avatar Mar 14 '24 18:03 orlovol