How to perform a search with conditional where parameters using Sequelize - sql

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
]
})

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
});

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;
}

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.

Related

Sequelize Parens in WHERE Clause

I have a WHERE clause that looks like this.
WHERE (`version` = FALSE
OR (name LIKE '%8%')
OR (name LIKE '%9%')
AND (sId IN (1)))
I need it to look like this.
WHERE (`version` = FALSE
OR (name LIKE '%8%')
OR (name LIKE '%9%'))
AND (sId IN (1))
I'm using where.push and I can't seem to figure it out.
Sequelize code.
where.push(version: false)
where.push(Sequelize.literal(`OR (name LIKE '%${id1}%')`)
where.push(Sequelize.literal(`OR (name LIKE '%${id2}%')`)
where.push({sId: {$in: sIds}})
Don't use Sequelize.literal (and definitely don't try to compose such conditions yourself in a string to avoid SQL injections) if you can write such conditions using some Op operators.
In your case you can use Op.or and Op.like operators:
where = {
[Op.or]: [{
version: false,
}, {
name: {
[Op.like]: `%${id1}%`
}
}, {
name: {
[Op.like]: `%${id2}%`
}
},
],
sId: {
[Op.in]: sIds
}
}

How to chain get and map the result with lodash?

I've got a list I'm trying to pull an object from using _.get but following that selection I need to loop over the object to create a new property. So far I've been successful using a combination of _.get and _.map as shown below but I'm hoping I can use _.chain in some way.
var selected = _.get(results, selectedId);
return _.map([selected], result => {
var reviews = result.reviews.map(review => {
var reviewed = review.userId === authenticatedUserId;
return _.extend({}, review, {reviewed: reviewed});
});
return _.extend({}, result, {reviews: reviews});
})[0];
Is it possible to do a transform like this using something other than map (as map required me to break this up/ creating an array with a solo item inside it). Thank you in advance!
I can see that you're creating unnecessary map() calls, you can simply reduce all those work into something like this:
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
The defaults() method is similar to extend() except once a property is set, additional values of the same property are ignored.
var selectedId = 1;
var authenticatedUserId = 1;
var results = {
1: [
{ userId: 1, text: 'hello' },
{ userId: 2, text: 'hey' },
{ userId: 1, text: 'world?' },
{ userId: 2, text: 'nah' },
]
};
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
document.body.innerHTML = '<pre>' + JSON.stringify(output, 0, 4) + '</pre>';
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

How to pass function in sql query of DocumentDB for NodeJS?

Q.1: I want to run one query on DocumentDB with user defined function,
var udf = function(users, userid) {
var s, _i, _len;
for (_i = 0, _len = users.length; _i < _len; _i++) {
s = users[_i];
if (s.userid === userid) {
return true;
}
}
return false;
};
conversationsQuerySpec = {
query: 'SELECT * FROM root r WHERE #fn(r.users, #userid) AND r.id=#id',
parameters: [{
name: '#fn',
value: udf
}, {
name: '#userid',
value: userid
}, {
name: '#id',
value: id
}]
};
But problem is that this query is not running throwing error.
Q.2: I have 1 object in documentDB with array like:
var student = {
name: 'piyush',
classes: [{
level: '1st',
medium: 'spanish'
},{
level: '2nd',
medium: 'german'
}]
}
I want to run query where medium=german, I don't want to pass level=2nd, how can I run this query?
Q1. You have to upload the UDF independently rather than as a parameter. Just upsert it using the appropriate SDK call before running that query.
First to register the UDF using createUserDefinedFunction within the collection with a name like myUserDefinedFunction.
Then you can use it inside the query by name, e.g. SELECT * FROM root r WHERE udf.myUserDefinedFunction(r.users, #userid) AND r.id=#id
Q2. This should work (untested):
SELECT VALUE student FROM student JOIN c in student.classes WHERE c.medium = "german"

Function parameter as this.variable

I'm using Vue.js and would like to use one method for multiple things:
data: {
genders: [],
months: [],
}
methods: {
getModels:function(cat,model) {
$.getJSON('/api/models/' + cat + '/' + model, function(data) {
this.model = data;
}.bind(this));
},
},
created: {
this.getModels('core', 'genders');
this.getModels('core', 'months');
},
In the method I want to be able to select the correct array with the data that has been fetched. But the code is instead looking for the 'model' data when I need it to look for the 'genders' and 'months' data.
If you want to access to some data by its name, you should do
model = 'genders' // just to ilustrate the example
this[model] = data
because this.model is equal to this['model'], and in the above code, this[model] is equal to this['genders'] or this.genders

How use dynamic routes to find people in my mongoose database with node.js

app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
id: param
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
I have this route, and this EJS:
<% for(var i = 0; i < data.length; i++) { %>
<td> <a href= <%="/admin/reservas/" + data[i].id + ""%>><%= data[i].id %></a></td>
<td> <a href= <%="/admin/reservas/" + data[i].name + ""%>><%= data[i].name %></a></td>
<% } %>
And is fine, when i click in the id, i find what the id in the db that i want, but I want to be able to find by the name too.
So i was trying to change the routes:
app.get('/names/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
id: param,
name: param
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
but is returning anything, both the id and the name
MongoDB query arguments are by default always a logical and condition. In order to use a logical or there is the $or operator:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
mongoose.model('Something').findOne(
{
"$or": [
{ id: param },
{ name: param }
]
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
The $or operator takes an array of query documents to consider the conditions of. It is a "short circuit" match where the first condition to evaluate as true makes the statement true.
At least that would be true if not of a specific problem here. See _id and it's id alias is a special value to both MongoDB and Mongoose. By default this will try to "cast" to an ObjectID type but it cannot if the string supplied is invalid, such as "fred" for example.
This would cause an exception as the query arguments are supplied. So the bottom line rule is your cannot "mix types" in an $or condition in this way with something that would not survive the conversion from String. You would have to do it "logically" in a different way by testing the value
for what it is:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
var query = {};
try {
var id = mongoose.mongo.ObjectID(param);
query = { "id": id };
} catch {
query = { "name": param };
}
mongoose.model('Something').findOne(
query, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
That means that the query is now built in a way that mongoose will parse it considering the schema "types" to apply then there is no error since the decision of which field to query on was made elsewhere.
Alternately of course you can "bypass" the mongoose method behavior and just use the raw driver:
app.get('/admin/reservas/:param', function(req, res) {
var param = req.param("param");
console.log(param);
var id ="";
try {
id = mongoose.mongo.ObjectID(param);
}
mongoose.model('Something').collection.findOne(
{
"$or": [
{ id: id },
{ name: param }
]
}, function(err, obj) {
res.send(obj);
console.log(obj);
}
);
});
But of course you need to do the type conversion yourself and not only for "testing" otherwise even a correct "String" value representing an ObjectID would not work.