Sequelize Op.or order by exact match first - mysql

I have a airports table with every row containing following fields: IATA, city, country... How can I search for all of these fields but return the results with the exact match first? For example if I type ALB, I want to see first result as ALB, Albany, USA and then all other matches rather than TIA, Tirana, Albania and Albany after that?
Here is my code:
const airports = await Airport.findAll({
attributes: [
'IATA',
'city',
'country',
],
where: {
[sequelize.Op.or]: [
{ IATA: filter.toUpperCase() },
{ city: { [sequelize.Op.like]: `%${filter}%` } },
{ country: { [sequelize.Op.like]: `%${filter}%` } },
],
status: 1,
},
});

I solved a related problem with this approach. I created a column on order that returns 1 if is a exact match on IATA like else 2. Ordering this column will solve your problem.
As your column could be different and using literal(), you may adapt the order property but the concept is this.
const airports = await Airport.findAll({
attributes: [
'IATA',
'city',
'country',
],
where: {
[sequelize.Op.or]: [
{ IATA: filter.toUpperCase() },
{ city: { [sequelize.Op.like]: `%${filter}%` } },
{ country: { [sequelize.Op.like]: `%${filter}%` } },
],
status: 1,
},
order: {
[
sequelize.literal('CASE WHEN `IATA` like '%${filter}%' THEN 1 ELSE 2 END',
'desc'
],
['city', 'desc']
}
});
In my purpose, I got 4 CASEs and worked very well.
Obs: If your model have alias on column IATA, here, you need to put your original column name, as we building an literal term.

Related

getting the values from 3 tables in sequelize using nested includes and sequelize.col as single object

I am new to nodejs as well as sequelize and any kind of ORMs
I wish to get all the values from 3 tables linked together through belongsTo associations
3 tables :
item - [id, itemName, itemCategoryID]
itemCategory - [id, itemCategoryName]
itemRequirement - [id, itemID, quantity, requirementDate, requirementStatusID]
requirementStatus - [id, requirementStatusName]
this is my get api req for getting the item requirements
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{
model: itemCategory,
attributes: [],
},
],
attributes: [
//gets error in this line
[Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",],
//alternatively this line works fine
['itemCategoryID']
],
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
],
})
.then((itemRequirements) => {
console.log(itemRequirements);
res.json(itemRequirements);
});
});
I get error when trying to do a sequelize.col but I am able to get the ID alone if I don't use the sequelize.col in the above code at the mentioned line
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlState: '42S22',
sqlMessage: "Unknown column 'item.itemCategory.itemCategoryName' in 'field list'",
currently i am getting this if i directly get the id
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"item": {
"itemCategoryID": 1
}
}
]
i wish to get this
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"itemCategoryName":"someCategoryName"
}
]
You should use DB column name in Sequelize.col instead of its field counterpart in a model:
// let's say the model field is itemCategoryName and the column name in a table is item_category_name
Sequelize.col("itemCategory.item_category_name")
To query more than 2 tables using joins in sequelize we will have to use reference the table and column name correctly.
Instead of adding [Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",] as an attribute to the referencing table and to get the response as a single json object without nesting we need to add this [Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"] as the attribute to the table from which you are querying now
below is the edited code which returns json as expected
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{model:itemCategory,attributes:[]},
{model:quantityType,attributes:[]}
],
attributes:[]
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
//actual way of referencing the different tables to get an object without
//nesting
[Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"],
[Sequelize.col("item.quantityType.quantityName"),"quantityTypeName"]
],
})
.then((itemRequirements) => {
console.log(JSON.stringify(itemRequirements,null,2));
res.json(itemRequirements);
});
});
module.exports = router;
output
[
{
"id": 4,
"quantity": 10,
"requiredBy": "2022-02-03T00:00:00.000Z",
"itemName": "choco",
"requirementStatusName": "pending",
"itemCategoryName": "Essence",
"quantityTypeName": "ml"
}
]

sequelize count associated table rows

Using sequelize and mySQL, I have two tables: User and Post.
Relation between two tables is M : N
db.User.belongsToMany(db.Post, { through: "Likes", as: "Liked" });
db.Post.belongsToMany(db.User, { through: "Likes", as: "Likers" });
What I want is getting post with whole likers id and count of whole likers.
I know how to get whole likers like this.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"id": 13
},
{
"id": 16
}
]
}
And, I also know how to get count of whole likers.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: [[sequelize.fn("COUNT", "id"), "likersCount"]],
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"likersCount": 2
}
]
}
But, I don't know how to get both of them at once.
Check the result when I use both of them.
{
model: User,
as: "Likers",
attributes: ["id", [sequelize.fn("COUNT", "id"), "likersCount"]],
through: { attributes: [] },
}
// result
"Likers": [
{
"id": 13,
"likersCount": 2
}
]
It only shows 1 liker(id: 13)
It must show another liker(id: 16).
What is the problem?
It shows only one because COUNT is an aggregating function and it groups records to count them. So the only way to get both - use a subquery to count records in a junction table while getting records on the other end of M:N relationship.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl",
// you probably need to correct the table and fields names
[Sequelize.literal('(SELECT COUNT(*) FROM Likes where Likes.postId=Post.id)'), 'LikeCount']],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})

Eager load multiple and nested associations

I have three models, A, B and C, where:
A.hasMany(B);
B.belongsTo(A);
B.hasMany(C);
C.belongsTo(B);
I'm querying like this:
await A.findOne({
where: { id: id },
include: [
{
model: B,
},
],
});
How can I return the C objects that belongs to B when querying A?
In the end found the solution in the comments of another question.
I need to pass the include with a array like this [{ all: true, nested: true }], I end up having something like this:
await A.findOne({
where: { id: id },
include: [{ all: true, nested: true }],
});
I didn't tested what happens when it loops and also didn't found the docs about it, if a good soul find it feel free to comment it.
Edit:
Nested includes also works:
await A.findOne({
where: { id: id },
include: [
{
model: B,
include: [
{
model: C,
},
],
},
],
});

How to do where condition on include relation in Loopback

I want to get the result with include relation with where condition on include model.
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
},
},
],
where: { name:'Welcome', "nmos.name":'agile'}
});
This where is condition work for name of htc model not for noms module.
I want query like
Select htc.*, nmos.* FROM htc LEFT JOIN nmos ON nmos.id = htc.n_id where htc.name = 'abc' and nmos.name = 'abc';
How can add where condition on the "relation" table?
Simply you need to add where clause in 'scope' object which lies inside the 'include' object. So the code would be like :
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
where:{name:'agile'}
},
},
],
where: { name:'Welcome'}
});
In your query, you just need to add the property where within the scope property, like this:
return this.htcApi.find({
include: [
{
relation: 'nmos',
scope: {
include: 'countries',
where: {
and [
{ id: htc.n_id },
{ name: 'abc' },
],
},
},
},
],
where: { name: 'abc' }
});
This should return the htcApi objects named 'abc' with the related nmos objects that have the name 'abc' and the id 'n_id'.

Sequelize: Exclude LEFT JOIN where column value is null

I have two tables: "tags"; and "tags_meta".
I have no problem performing joins on these two tables, I have a situation where I need to find rows in "tags" that don't have a corresponding row in the "tags_meta" table, where "tag_id" is the foreign key, and "tag_type_id" is — as the name suggests — the type of tag.
Tags.getTagsAsUnclassified = function(params) {
const paginate = ({ page, pageNumber }) => {
const offset = (parseInt(page) > 0) ? parseInt(page) : 0
const limit = parseInt(pageNumber)
return {
offset,
limit
}
}
const Op = Sequelize.Op
return this.findAndCountAll({
attributes: [
['tag_id', 'tagID'],
'user_id',
'tag'
],
include: [{
model: TagsMeta,
as: 'TagsMeta',
where: {
tag_type_id: null
},
required: false
}],
where: {
user_id: params.userID
},
group: [
[[sequelize.col("tags.tag_id"), "ASC"]]
],
order: [
['tag', 'ASC']
],
...paginate({ ...params })
})
.then(result => {
return {
count: result.count.length,
rows: result.rows
}
})
}
While this returns some correct rows, it's a bit of a mixed bag.
I've tried an answer to a similar question, but that hasn't worked.
I leave it to the experts!