How to perform a search with conditional where parameters using Sequelize

Usually whenever I write a search query for SQL, I do something similar to this:

SELECT * FROM users u
WHERE (@username IS NULL OR u.username like '%' + @username + '%')
AND (@id IS NULL OR u.id = @id)

Basically this simulates a conditional WHERE clause. We only want to compare @searchParam to the column if @searchParam was provided.

Is there a way to replicate this using Sequelize?

EDIT: Here is my best attempt which fails:

models.user.findAll({
  where: {
    username: searchParams.username || models.sequelize.col('user.username'),
    id: searchParams.id || models.sequelize.col('user.id')
  }
})

UPDATE: I found a way to do it, but it feels like a workaround. I’m certain there has to be a more elegant way. This works, but is ugly:

models.user.findAll({
  where: [
    '(? IS NULL OR "user"."username" LIKE ?) AND (? IS NULL OR "user"."id" = ?)',
    searchParams.username,
    `%${searchParams.username}%`,
    searchParams.id,
    searchParams.id
  ]
})

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

You can just prepare object with needed conditions. Simple and easy to understand

var whereStatement = {};
if(searchParams.id)
    whereStatement.id = searchParams.id;
if(searchParams.username)
    whereStatement.username = {$like: '%' + searchParams.username + '%'};
models.user.findAll({
  where: whereStatement
});

Solution 2

that’s pretty awesome. thank you. i use your ideas like that:

app.get('/', function (req, res) {
    ..
    let foo = await Foo.findAll(
    {
        offset: parseInt(req.query.offset | 0),
        limit: parseInt(req.query.limit | 10),
        where: getFooConditions(req),
    ...
}

function getFooConditions(req) {
  fooConditions = {};
  // Query param date
  if (req.query.date) {
    fooCondtions.start = {
      [Op.gte]: moment(parseInt(req.query.date)).utc().startOf('day'),
      [Op.lte]: moment(parseInt(req.query.date)).utc().endOf('day')
    }
  }
  // Query param name
  if (req.query.name) {
    fooCondtions.name = {
      [Op.like]: '%' + (req.query.name) + '%'
    }
  }
  // Query param id
  if (req.query.id) {
    fooCondtions.id = {
      [Op.equals]: '%' + (req.query.id) + '%'
    }
  }
  return fooConditions;
}

Solution 3

It’d be a little more complicated than you’ve outlined above. Sequelize has to have pretty explicit statements on ands and ors, and that means you have to use the $and and $or options to replicate what you’re looking for. What you’ve done above is merely create the values in JavaScript and pass them to the database. Try the following:

models.user.findAll({
                where: {
                    $and: [{
                        $or: [{
                            username: {
                                $like: '%' + searchParams.username '%'
                            }
                        }, {
                            username: null
                        }]
                    }, {
                        $or: [{
                                id: searchParams.id
                        }, {
                           id: null
                        }]
                    }]
                }
            })

For more on this, and some good examples, see their documentation.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply