where clause on include column using sequilize - node.js

I'm using this query:
db.Spectrum.findAll({
attributes: ['title', [db.Sequelize.fn('SUM', db.Sequelize.col('quantity') ), 'count']],
include: [
{model: db.Varietal,
attributes : ['specID', 'id'],
include : [
{ model : db.Wine,
required: false,
attributes : ['varietal_id', 'id'],
where : {restaurant_id : restaurant_id,
owner_id : null} }
],
}
],
group : ['specID']
})
Which gives me the correct results, the problem is because it ands the where clause to the left join it runs really slow (about 2 seconds).
The raw query of the left join looks like this.
LEFT OUTER JOIN `wines` AS `Varietals.Wines` ON `Varietals`.`id` = `Varietals.Wines`.`varietal_id` AND `Varietals.Wines`.`restaurant_id` = 16 AND `Varietals.Wines`.`owner_id` IS NULL
I would like the query to have the left join and a where clause like this..
LEFT OUTER JOIN `wines` AS `Varietals.Wines` ON `Varietals`.`id` = `Varietals.Wines`.`varietal_id`
WHERE `Varietals.Wines`.`restaurant_id` = 16 AND `Varietals.Wines`.`owner_id` IS NULL
But everything I try puts 'Spectrum' before it and then doesn't find the row. How is this suppose to be done? Or am I going to have to just use a raw query?

include.where will always add the condition to the join.
However in Sequelize v3.13.0 support for special nested keys were added.
It's now possible to add a where object to the toplevel object containing dot seperated keys wrapped in $ to specify it being a nested field.
Applying it to your code:
db.Spectrum.findAll({
include: [
{
model: db.Varietal,
include : [
{
model : db.Wine
}
]
}
],
where: {
'$Varietals.Wines.restaurant_id$': restaurant_id,
'$Varietals.Wines.owner_id$': null
},
group : ['specID']
});
(I removed a few statements to focus on the important part)

Include array
include = [{
model: db.caregiverCategory,
as: 'categories',
attributes: ['name'],
required: true,
attributes: ['name'],
include: [{
model: db.category,
as: 'category',
required: true
}]
}]
Where Clause
where[Op.or] = [{
'$categories.name$': { [Op.like]: '%' + query.category_name + '%' }
}, {
'$categories.category.name$': { [Op.like]: '%' + query.category_name + '%' }
}]
You need to add all fields like name (which are required in or) in attributes: ['name'] otherwise it will give unknowm columns error.

Related

Rename column in sequelize findAll

I have the following findAll query:
let consumQuery = Manager.consumption.findAndCountAll({
where: query, attributes: ['ml', 'price', 'total', 'consumption_end', 'bar_id', 'sync', 'visit.device_id',
'visit.person.document','visit.person.person_id','visit.person.name','visit.person.surname',
'keg.product_id','keg.product.sku','keg.product.name'],
include: [
{
model: Manager.visit,
attributes: [],
include: [{
model: Manager.person,
attributes: [],
raw: true
}],
},
{
model: Manager.keg,
attributes: [],
include: [
{
model: Manager.product,
required: true,
attributes: []
}
]
}],
limit: PAGE_LIMIT_SIZE,
offset: offset,
raw: true,
order: [['consumption_start', 'ASC']]
}).then(({count, rows}) => {
I'm trying to rename the column keg.product.name to beer.
I've tried :
...'keg.product_id','keg.product.sku',['keg.product.name','beer']
But I got the error:
"column \"keg.product.name\" does not exist"
The sequelize output the following SQL:
sql: `SELECT "consumption"."ml", "consumption"."price", "consumption"."total", "consumption"."consumption_end", "consumption"."bar_id", "consumption"."sync", "visit"."device_id", "visit.person"."document", "visit.person"."person_id", "visit.person"."name", "visit.person"."surname", "keg"."product_id", "keg.product"."sku", "keg.product.name" AS "beer" FROM "public"."consumption" AS "consumption" LEFT OUTER JOIN "public"."visit" AS "visit" ON "consumption"."visit_id" = "visit"."visit_id" LEFT OUTER JOIN "public"."person" AS "visit.person" ON "visit"."person_id" = "visit.person"."person_id" LEFT OUTER JOIN "public"."keg" AS "keg" ON "consumption"."keg_id" = "keg"."keg_id" INNER JOIN "public"."product" AS "keg.product" ON "keg"."product_id" = "keg.product"."product_id" WHERE ("consumption"."bar_id" = '248' AND "consumption"."sync" = 'true' AND "consumption"."consumption_start" >= '2020-02-16 20:03:32.000 -03:00' AND "consumption"."consumption_end" <= '2020-09-17 20:03:32.000 -03:00') ORDER BY "consumption"."consumption_start" ASC LIMIT 500 OFFSET 0;},
The result is "keg.product.name" AS "beer", but I think should be something like that: "keg.product"."name" AS "beer"
Any ideia?
To rename an attribute you have to make it an array like [name_in_db, name_you_want].
Therefore your attributes value should be as it follows
attributes: ['ml', 'price', 'total', 'consumption_end', 'bar_id', 'sync', 'visit.device_id', visit.person.document', 'visit.person.person_id', 'visit.person.name', 'visit.person.surname', keg.product_id', 'keg.product.sku', **['keg.product.name', 'beer']**]
Source: https://sequelize.org/api/v6/class/src/model.js~model#static-method-findAll
"To rename an attribute, you can pass an array, with two elements - the first is the name of the attribute in the DB [...], and the second is the name you want the attribute to have in the returned instance"

sequelize indirect associations

I have 3 tables named projects,workspace,views and the associations are
projects.hasMany(workspace);
workspace.belongsTo(projects);
workspace.hasMany(views);
views.belongsTo(workspace);
I need to join these 3 tables to get the output of following query
select * from projects pro join workspace wrk on pro.id= wrk.project_id
join views on wrk.id = views.workspace_id
I tried an association
views.belongsTo(projects, { through: workspace.associations.projects });
Following is the code:
View.findAll({
include: [
{model: db.workspaces, required: true},
{model: db.projects, required: true}
]
});
But it generates the query
SELECT *
FROM "views" AS "views"
INNER JOIN "workspaces" AS "workspace" ON "views"."workspace_id" = "workspace"."id"
INNER JOIN "projects" AS "project" ON "views"."project_id" = "project"."id"
The project_id does not exists in views table.It associates the views only through workspace.How can it be achieved.
Try following
Project.hasMany(Workspace,{foreignKey : 'project_id'});
Workspace.belongsTo(Project);
Workspace.hasMany(views,{foreignKey: 'workspace_id'});
View.belongsTo(Workspace);
Workspace.findAll({
include: [
{model: View, required: true},
{model: Project, required: true}
]
});

Sequelize produces invalid query "model.id AS model.id"

Here is the sequelize call I'm making to count favorites along with my model:
Model.findAll({
group: [ 'model.id', 'favorites.id' ],
attributes: [
'*',
[ Sequelize.fn('count', Sequelize.col('favorites.id')), 'favorites_count' ]
],
include: [
{ attributes: [], model: Favorite },
]
});
Here's the query it produces:
SELECT
model.id,
model.*,
count(favorites.id) AS favorites_count,
favorites.id AS favorites.id # Invalid!
FROM models AS model ...
Here's the error
ERROR: syntax error at or near "."
LINE 1: SELECT model.*, favorites.id AS favorites.id, ...
^
Because favorites.id is not a valid AS alias. Why does Sequelize produce this
invalid alias and how do I prevent it?
You cannot prevent it - sequelize needs to alias the column. The query becomes invalid because you have set quoteIdentifiers to false. The query sequelize wants is "favorites"."id" AS "favorites.id"
Why is sequelize doing such a "stupid" thing you might say. Well lets take an example:
SELECT "model"."id", "favorites"."id" ...
This produces the following result set:
[{ id: 42 }]
Because the two columns have the same name, they override each other so only one of the columns is returned. So we need to alias the favorites column so we get
[{ id: 42, 'favorites.id': 87 }]

How to aggregate fields from embedded documents in Mongoose

Coverage Model.
var CoverageSchema = new Schema({
module : String,
source: String,
namespaces: [{
name: String,
types: [{
name: String,
functions: [{
name: String,
coveredBlocks: Number,
notCoveredBlocks: Number
}]
}]
}]
});
I need coveredBlocks aggregations on every level:
*Module: {moduleBlocksCovered}, // SUM(blocksCovered) GROUP BY module, source
**Namespaces: [{nsBlocksCovered}] // SUM(blocksCovered) GROUP BY module, source, ns
****Types: [{typeBlocksCovered}] // SUM(blocksCovered) BY module, source, ns, type
How do I get this result with Coverage.aggregate in Mongoose ?
{
module: 'module1',
source: 'source1',
coveredBlocks: 7, // SUM of all functions in module
namespaces:[
name: 'ns1',
nsBlocksCovered: 7, // SUM of all functions in namespace
types:[
{
name: 'type1',
typeBlocksCovered: 7, // SUM(3, 4) of all function in type
functions[
{name: 'func1', blocksCovered: 3},
{name:'func2', blocksCovered: 4}]
}
]
]
}
My ideas is to deconstruct everything using $unwind then reconstruct the document back again using group and projection.
aggregate flow:
//deconstruct functions
unwind(namesapces)
unwind(namespaces.types)
unwind(namespace.types.functions)
//cal typeBlocksCovered
group module&source ,ns,type to sum functions blocksCovered->typeBlocksCovered + push functions back to types
project to transform fields to be easier for next group
// cal nsBlocksCovered
group module&source ,ns to sum typeBlocksCovered -> nsBlocksCovered) + push types back to ns
project to transform fields to be easier for next group
// cal coveredBlocks
group module&source to sum nsBlocksCovered -> coveredBlocks
project to transform fields to match your mongoose docs
My sample query with mongo shell syntax and its seem working , guess is you are using collection name "Coverage"
db.Coverage.aggregate([
{"$unwind":("$namespaces")}
,{"$unwind":("$namespaces.types")}
,{"$unwind":("$namespaces.types.functions")}
,{"$group": {
_id: {module:"$module", source:"$source", nsName: "$namespaces.name", typeName : "$namespaces.types.name"}
, typeBlocksCovered : { $sum : "$namespaces.types.functions.blocksCovered"}
, functions:{ "$push": "$namespaces.types.functions"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source"
,namespaces:{
name:"$_id.nsName"
,types : { name: "$_id.typeName",typeBlocksCovered : "$typeBlocksCovered" ,functions: "$functions"}
}
,_id:0}}
,{"$group": {
_id: {module:"$module", source:"$source", nsName: "$namespaces.name"}
, nsBlocksCovered : { $sum : "$namespaces.types.typeBlocksCovered"}
, types:{ "$push": "$namespaces.types"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source"
,namespaces:{
name:"$_id.nsName"
,nsBlocksCovered:"$nsBlocksCovered"
,types : "$types"
}
,_id:0}}
,{"$group": {
_id: {module:"$module", source:"$source"}
, coveredBlocks : { $sum : "$namespaces.nsBlocksCovered"}
, namespaces:{ "$push": "$namespaces"}}}
,{"$project" :{module:"$_id.module", source:"$_id.source", coveredBlocks : "$coveredBlocks", namespaces: "$namespaces",_id:0}}
])

MongoDB: Point not in interval when using $near operator with $maxDistance

When I try to find all members within 50km of Salt Lake City, Utah from the Mongo shell I get the error:
error: {
"$err" : "point not in interval of [ -180, 180 ] :: caused by :: { 0: 0.0, 1: 50000.0 }",
"code" : 16433
}
Here is the query I am running:
db.members.find(
{ 'geo.point' :
{ $near :
{
$geometry : {
type : "Point" ,
coordinates : [ 111.000 , 40.000 ]
},
$maxDistance : 50000
}
}
}
)
Member schema is like this:
var memberSchema = mongoose.Schema({
name: {
first: {type:String, default:''},
last: {type:String, default:''},
},
geo: {
latitude: {type:String, default:''},
longitude: {type:String, default:''},
country: {type:String, default:''},
state: {type:String, default:''},
place: {type:String, default:''},
zip: {type:String, default:''},
point: {type: [Number], index: '2d'}
}
});
Member object in DB looks like this:
{
"_id" : ObjectId("xxxxxxxxxxxxxxxxxxx"),
"name": {
"first": "Thom",
"last": "Allen"
},
"geo" : {
"point" : [ -111.8833, 40.7500 ],
"zip" : "84115",
"state" : "UT",
"country" : "US",
"longitude" : "-111.8833",
"latitude" : "40.7500"
}
}
Is it possible that my fields are not stored in the correct format? If I change 50000 to anything below 180 it will work, but that is not how it should function according to the docs here:
http://docs.mongodb.org/manual/reference/operator/query/near/
** Just a heads up, the proper mongo location array IS in fact [longitude, latitude].
A few things. First, I think your query is off - you are querying for coordinates : [ 111.000 , 40.000 ] and it should be coordinates : [ -111.000 , 40.000 ]
Second, the example data point your provide [ -111.8833, 40.7500 ] is more than 50 km from your corrected query point, it's actually about 122 km (test it here: http://andrew.hedges.name/experiments/haversine/ )
So, correcting for those two issues if I store the data in mongodb as you have stored it I can do the following:
1) create the correct index:
db.members.ensureIndex({ "geo.point": "2dsphere" })
2) run this query:
db.members.find({ 'geo.point':
{$geoWithin:
{$centerSphere: [[ -111.000 , 40.000 ], 113/6371]}
}
} )
Note that I've divided 113 km/ 6371 which gives you radians which is what is required for this specific query.
Try it yourself. In general you will be better off if you can store things in the future using GeoJSON but with your existing schema and the above index and query I'm able to get the correct results.
What you have in your data is the format for legacy co-ordinate pairs but you are trying to query using the GeoJSON syntax.
The only valid index form for legacy co-ordinate pairs is a "2d" index, so if you have created a "2d sphere" index that will not work. So you need to remove any "2d sphere" index and create a "2d" index as follows:
db.members.ensureIndex({ "geo.point": "2d" })
If you actually intend to use the GeoJSON form and "2dsphere" index type, then you need the data to support it, for example:
{
"loc" : {
"type" : "Point",
"coordinates" : [ 3, 6 ]
}
}
So it needs that underlying structure of "type" and "coordinates" in order to use this index type and query form.

Resources