How to search for/select by included entity but include all related entities into result set - sql

In my application, I am using sequelize ORM. There are several entities: A Tool can have Tags and Categories.
Now I want to search for all Tools, that have a specific Tag, but I want to include all relating Tags of that tool (not just the specific one). If I now place a where statement into the include, only specified Tags are included into the result set (see [2]). I tried to limit the Tags in the outer where statement (see [1]), but this does not help either.
Example
Tool A has Tags t1, t2 and t3. Now I want to search all Tools that have the Tag t3, but the result set shall contain all three tags.
Expected result:
Tool A
\
- Tag t1
- Tag t2
- Tag t3
db.Tool.scope('published').findAll({
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
// [2] Would limit the result specified tag
// where: {
// name: {
// [Op.and]: filter.tag
// }
// }
},
{
model: db.Category,
attributes: ['id', 'name', 'views'],
through: { attributes: ['relevance'] },
where: {
id: {
[Op.and]: filter.category
}
}
}
],
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I know I could perform this by performing a select via the Tag in the first place (db.Tag.findAll() instead of db.Tool.findAll(); I've already done this elsewhere in my project), but at the same time I also want to be able to filter by another entity (Category) the same way. So the Tool.findAll() should be the starting point.
Any help appreciated!

First off, you have two where clauses in your top-level query:
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
// ...
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
I think your best approach is going to be with a literal subquery in the WHERE clause. Basically we want to find the ids of all of the tools that have the right tag and that contain the filter.term.
The subquery part for the WHERE looks something like...
SELECT ToolId FROM ToolTags WHERE TagId='t2';
Inspired by the subquery solution from this post Sequelize - subquery in where clause
// assuming your join table is named 'ToolTags' in the database--we need the real table name not the model name
const tempSQL = sequelize.dialect.QueryGenerator.selectQuery('ToolTags',{
attributes: ['ToolId'],
where: {
TagId: filter.tag
}})
.slice(0,-1); // to remove the ';' from the end of the SQL
db.Tool.scope('published').findAll({
where: {
title: {
[Op.like]: `%${filter.term}%`,
},
id: {
[Op.In]: sequelize.literal(`(${tempSQL})`)
}
},
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
},
// {
// model: db.Category,
// attributes: ['id', 'name', 'views'],
// through: { attributes: ['relevance'] },
// where: {
// id: {
// [Op.and]: filter.category
// }
// }
// }
],
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I commented out your category join for now. I think you should try to isolate the solution for the tags before adding more onto the query.

Related

How to use an alias inside an include where condition using Sequelize

A table contains 2 user id cols, userOneId and userTwoId. My user id could be in either col. I'd like to get all rows where my userId is in either col, and also include the associated object of the other user.
Here's the latest attempt. I query where my id is in userOneId or userTwoId, make an alias for the other user id, otherUserId, with a case/when. Then use alias otherUserId in an include, to return the other user associated object.
where: {
[Op.or]: [{
userOneId: userId,
}, {
userTwoId: userId,
}]
},
attributes: [
'id',
[sequelize.literal("CASE WHEN \"userOneId\" = " + userId + " THEN \"userTwoId\" ELSE \"userOneId\" END"), 'otherUserId']
],
include: {
where: {
id: '$otherUserId$'
},
model: sequelize.models.User,
as: 'otherUser',
attributes: [ 'id', 'name', ...
Model associations are:
MyModel.belongsTo(models.User, {
as: 'userOne',
foreignKey: 'userOneId'
})
MyModel.belongsTo(models.User, {
as: 'userTwo',
foreignKey: 'userTwoId'
})
User.hasMany(models.MyModel, {
as: 'userOne',
foreignKey: {
name: 'userOneId',
allowNull: false
}
})
User.hasMany(models.MyModel, {
as: 'userTwo',
foreignKey: {
name: 'userTwoId',
allowNull: false
}
})
However, it errors with
User is associated to MyModel multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.
Any pointers on if or how to get this to work?

Using rally app lookback API - unable to fetch defects that are tagged

I am using rally lookback API and creating a defect trend chart. I need to filter defects that do not have a tag "xyz".
Using the following:
this.myTrendChart = Ext.create('Rally.ui.chart.Chart', {
storeType: 'Rally.data.lookback.SnapshotStore',
storeConfig: {
find: {
_TypeHierarchy: "Defect",
State: { $lt: "Closed"},
Tags.Name: { $nin: ["xyz"] },
_ProjectHierarchy: projectOid,
_ValidFrom: {$gte: startDateUTC}
}
},
calculatorType: 'Calci',
calculatorConfig: {},
chartConfig: {
chart: {
zoomType: 'x',
type: 'line'
},
title: {
text: 'Defect trend'
},
xAxis: {
type: 'datetime',
minTickInterval: 7
},
yAxis: {
title: {
text: 'Number of Defects'
}
}
}
});
This does not return any data. Need help with the filter for tags.
Tags is a collection of tag-oids so you'll need to find and use the oid of the tag vs the name, at which point it'll just be Tags: { $nin: [oid] }. Caveat: technically, due to how expensive it is, $nin is unsupported (https://rally1.rallydev.com/analytics/doc/#/manual/48e0589f681160fc316a8a4802dc389f)...but it doesn't throw an error so maybe it works anyway.

Column in list from include sequelize

provided that I have following statement in sequelize
return models.VehicleModel
.findAll({
include: [
{
attributes: ['id'],
model: models.Unit
},
{
model: models.myPattern,
where: {
VehicleModelId: { $in: data.VehicleModelIds }
},
include: [
{
model: models.Calendar,
include: [
{
model: models.Restrictions,
include: [
{
model: models.RestrictionType
}],
where: {
//THIS IS THE LINE I NEED TO CHANGE SOMEHOW
UnitId: { $in: [models.Unit.id] }
},
required: false
}
]
}
]
}
],
where: {
id: { $in: data.VehicleModelIds }
}
})
This gives me NULL in the query like so:
[myPattern.Calendar.Restrictions].UnitId IN (NULL) AND ....
I want to be able to get the following instead:
[myPattern.Calendar.Restrictions].UnitId IN (Units.id) AND ....
This is because they are not mandatory to be there but should of course be linked to the proper unit corresponding to the vehicle model.
The myPattern is linked to the Calendar who is in turn linked to the Restrictions and so to the Units.
This all using id column.
Any help would be appreciated!
Found it
So for people looking for the "how to":
//...
where: {
UnitId: { $in: [models.sequelize.literal('Units.id')] },
}
//...and so on
This will output in the query: [myPattern.Calendar.Restrictions].UnitId IN (Units.id)

Sequelize the nodejs throught model association

I have tree models User, Company and UserCompany.
I have associations
Company.belongsToMany(User, {
as: 'users',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
User.belongsToMany(Company, {
as: 'companies',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
I am trying to query companies buy runing this code
sql.Company.findAll({
where: query,
include: [{model:sql.User, as:'users', attributes: ['id', 'first_name', 'last_name', 'email']}],
order: sort,
limit: limit,
offset: offset
})
I have two elements of UserCompany with CompanyId=10 and UserId=50, but query returns only one of them.
It returns array
{id: 10,
...,
users: {
id: 50,
...,
UserCompany: {}
}
}
So UserCompany is not array, it is just one element. But I want to get them all.
How can I fix my association code?
Your associations are right. The UserCompany property isn't supposed to be showing all of each user's UserCompany relationships -- it's just showing the one that got the user included in that query's results.
If what you're looking for is a list of companies associated with each user in the returned array, you can add an include for that:
Company.findAll({
include: [{
model: User,
as: 'users',
attributes: ['id', 'first_name', 'last_name', 'email'],
// Add this
include: [{
model: Company,
as: 'companies',
attributes: ['id', 'name']
}],
}],
})
This gives you the following format in the returned array:
{
id: 1,
first_name: 'Nelson',
last_name: 'Bighetti',
email: 'bighead#hooli.com',
UserCompany: {...},
companies: [
{ id: 1, name: 'Hooli', UserCompany: {...} },
{ id: 2, name: 'Pied Piper', UserCompany: {...} }
]
}

Using group by and joins in sequelize

I have two tables on a PostgreSQL database, contracts and payments. One contract has multiple payments done.
I'm having the two following models:
module.exports = function(sequelize, DataTypes) {
var contracts = sequelize.define('contracts', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true
}
}, {
createdAt: false,
updatedAt: false,
classMethods: {
associate: function(models) {
contracts.hasMany(models.payments, {
foreignKey: 'contract_id'
});
}
}
});
return contracts;
};
module.exports = function(sequelize, DataTypes) {
var payments = sequelize.define('payments', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true
},
contract_id: {
type: DataTypes.INTEGER,
},
payment_amount: DataTypes.INTEGER,
}, {
classMethods: {
associate: function(models) {
payments.belongsTo(models.contracts, {
foreignKey: 'contract_id'
});
}
}
});
return payments;
};
I would like to sum all the payments made for every contract, and used this function:
models.contracts.findAll({
attributes: [
'id'
],
include: [
{
model: models.payments,
attributes: [[models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']]
}
],
group: ['contracts.id']
})
But it generates the following query:
SELECT "contracts"."id", "payments"."id" AS "payments.id", sum("payments"."payment_amount") AS "payments.total_cost"
FROM "contracts" AS "contracts"
LEFT OUTER JOIN "payments" AS "payments" ON "contracts"."id" = "payments"."contract_id" GROUP BY "contracts"."id";
I do not ask to select payments.id, because I would have to include it in my aggregation or group by functions, as said in the error I have:
Possibly unhandled SequelizeDatabaseError: error: column "payments.id"
must appear in the GROUP BY clause or be used in an aggregate function
Am I missing something here ? I'm following this answer but even there I don't understand how the SQL request can be valid.
This issue has been fixed on Sequelize 3.0.1, the primary key of the included models must be excluded with
attributes: []
and the aggregation must be done on the main model (infos in this github issue).
Thus for my use case, the code is the following
models.contracts.findAll({
attributes: ['id', [models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']],
include: [
{
model: models.payments,
attributes: []
}
],
group: ['contracts.id']
})
Try
group: ['contracts.id', 'payments.id']
Can you write your function as
models.contracts.findAll({
attributes: [
'models.contracts.id'
],
include: [
{
model: models.payments,
attributes: [[models.sequelize.fn('sum', models.sequelize.col('payments.payment_amount')), 'total_cost']]
}
],
group: ['contracts.id']
})
Is the issue that you might want to be selecting from payments and joining contracts rather than the other way around?