How to remove hasMany id attribute from sequelize join? - postgresql

I am attempting to join two tables defined below in a one-to-many relationship. One Characteristic to many ReviewCharacteristics. Using the following findAll function I am attempting to avg the values in the ReviewCharacteristics table. I have verified the necessary SQL command that should function as expected. Sequelize is including ReviewCharateristic.id in the selected attributes which is causing my grouping to fail.
Is there a way to specify just the avg value and not include the id from the ReviewCharacteristic table in the selected attributes?
exports.Characteristic = db.define('characteristic', {
product_id: {
type: DataTypes.INTEGER,
allowNull: false
},
name: {
type: DataTypes.STRING(7),
allowNull: false
}
}, { underscored: true });
exports.ReviewCharacteristic = db.define('review_characteristic', {
value: {
type: DataTypes.INTEGER,
allowNull: false
}
}, { underscored: true });
exports.Characteristic.hasMany(exports.ReviewCharacteristic, { as: 'rc' });
exports.ReviewCharacteristic.belongsTo(exports.Characteristic);
Characteristic.findAll({
attributes: ['name', 'id'],
where: {
product_id: req.query.product_id
},
group: 'characteristic.id',
include: [{
model: ReviewCharacteristic,
as: 'rc',
attributes: [[Sequelize.fn('AVG', Sequelize.col('rc.value')), 'value']],
required: true
}]
});
I have also tried specifying attributes: ['characteristic.name', 'characteristic.id']
Current:
SELECT
"characteristic"."name",
"characteristic"."id",
"rc"."id" AS "rc.id",
AVG("rc"."value") AS "rc.value"
FROM "characteristic" AS "characteristic"
INNER JOIN "review_characteristic" AS "rc"
ON "characteristic"."id" = "rc"."characteristic_id"
WHERE "characteristic"."product_id" = '18078'
GROUP BY "characteristic"."id";
Expected:
SELECT
"characteristic"."name",
"characteristic"."id",
AVG("rc"."value") AS "rc.value"
FROM "characteristic" AS "characteristic"
INNER JOIN "review_characteristic" AS "rc"
ON "characteristic"."id" = "rc"."characteristic_id"
WHERE "characteristic"."product_id" = '18078'
GROUP BY "characteristic"."id";

If anyone happens to stumble across this question and is dealing with the same issue here is the solution I found.
Characteristic.findAll({
attributes: ['name', 'id', [Sequelize.fn('AVG', Sequelize.col('rc.value')), 'value']],
where: {
product_id: req.query.product_id
},
group: 'characteristic.id',
include: [{
model: ReviewCharacteristic,
as: 'rc',
attributes: [],
required: true
}]
});
The attributes must be specified as an empty array for the joined table in order to stop the addition of the ReviewCharateristic.id attribute in my case.

Related

addToCollection and set intermediate table column values as well

I am using Sails v1.1 -
Following the example from the "Through" associations on sails - https://sailsjs.com/documentation/concepts/models-and-orm/associations/through-associations
They defined a "through" association as basically a custom model. So this really isn't "through", it's just controlling the join table for the many to many relation.
So in the intermediate model, I added a custom attribute of isTyping seen below.
Is it possible to add to collection and set this intermediate value at same time?
For exmaple pseudocode with setIntermediate:
User.addToCollection(userId, 'pets', petId).setIntermediate('isTyping', true);
So following the example on the docs:
myApp/api/models/User.js
module.exports = {
attributes: {
name: {
type: 'string'
},
pets:{
collection: 'pet',
via: 'owner',
through: 'petuser'
}
}
}
myApp/api/models/Pet.js
module.exports = {
attributes: {
name: {
type: 'string'
},
color: {
type: 'string'
},
owners:{
collection: 'user',
via: 'pet',
through: 'petuser'
}
}
}
myApp/api/models/PetUser.js
module.exports = {
attributes: {
owner: {
model:'user'
},
pet: {
model: 'pet'
},
// I ADDED THIS INTERMEDIATE COLUMN NAME in the join table
isTyping: {
type: 'boolean',
defaultsTo: false
}
}
}
I don't know if this is right, but the way to do this is instead of using Pet.addToCollection(petId, 'owners', userId)/User.addToCollection(userId, 'pets', petId) or Pet.removeFromCollection(petId, 'owners', userId)/User.removeFromCollection(userId, 'pets', petId), is to instead do:
PetUser.create({ owner: userId, pet: petId, isTyping: true }).populate('user').populate('pet')
I'm not sure if right, and this doesn't support the array argument that addToCollection/removeFromCollection does. And you also have to massage the data in order to get a list of owners/pets with the pivot attribute of isTyping.

populate additional information of the 'through' table in sails

I have two models and there is many to many association in between them(I am using sails.js framework). I have added the addition field in the association table. I want to populate that addition field. How do I achieve this? My models are given below:
//Store.js file
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
name: "string",
slug: "string",
imageURL: "string",
termsAndConditions: "string",
link: "string",
productID: {
collection: 'product', //This is for association with the product model
via: 'storeID',
through: 'price'
}
}
};
Below is my Product.js file
//Product.js
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
name: 'string',
storeID: {
collection: 'stores',
via: 'productID', //This is for association with the Store model
through: 'price'
}
}
};
And below is my through model Price.js
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
storeID: {
model: 'stores'
},
productID: {
model: 'product'
},
price: 'integer' //I want to populate this additional field when calling api '/product' or '/store'
}
};
How to populate the additional field price of Price table from calling the api '/product' or '/store'?
Inside the callback function after populating (exec or then depending on your implementation) Find the record in the price table and perform update on that record to change the value of price from null to whatever value you want. Share your implementation code for more detailed answer.

How to filter a query based on collection from many-to-many

I have two model objects. Doctors and Hospitals. The model definitions look like:
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
hospitals: {
collection: 'hospital',
via: 'doctors',
dominant: true,
},
}
};
and
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
doctors: {
collection: 'doctor',
via: 'hospitals',
},
}
};
How can I query doctors that are mapped to certain hospitals? I read a couple posts about through keyword, but I wasn't able to get records to persist to the through/join table. Seems like if I could query the automatic join table, I could get it to work, but I'm curious if there is an "official" way to accomplish this type of query.
My current query looks like: Doctor.find().where({'hospitals': ['548303dcf49435ec4a01f2a2','548303cbf49435ec4a01f2a0']}).populate('hospitals').exec(function (err, doctors) { ... });
The underlying db is mongo, if that matters.
I did cheat a bit but things seem to be working. That said, I am interested if there's a better way to accomplish this type of query.
I created a model object that maps to the auto created join table. So in this case, my additional model object looks like:
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
tableName: 'doctor_hospitals__hospital_doctors',
attributes: {
doctor: {
model: 'doctor',
columnName: 'doctor_hospitals'
},
hospital: {
model: 'hospital',
columnName: 'hospital_doctors'
}
}
};
Now, I query the join table directly and use the results for a sub query:
DoctorHospital.find().where({'hospital': ['548303dcf49435ec4a01f2a2','548303cbf49435ec4a01f2a0']}).exec(function(err, doctorHospitals) {
if(err) return next(err);
Doctor.find().where({'id': _.pluck(doctorHospitals, 'doctor')}).populate('hospitals').exec(function (err, doctors){
if(err) return next(err);
return res.view({
doctors: doctors
});
});
});

sails.js join tables on mongodb native id

I have two collections in mongodb database and model for each of them
App Model
module.exports = {
tableName: 'app',
attributes: {
_id : {
primaryKey: true,
unique: true,
type: 'string',
},
userId: {
model: 'user'
},
title: {
type: 'string',
required: true,
unique: true,
},
createdDate : 'string'
},
};
and User Model
module.exports = {
tableName: 'user',
attributes: {
id : {
primaryKey: true,
unique: true,
type: 'string',
collection: "app",
via : "userId"
},
password: {
type: 'string',
required: true
},
apps : {
collection: "app",
via : "userId"
}
},
};
When i use numeric values for join this collection, it works fine, but when i try do it with mongodb native id object, i get the empty result
How i call join query
User.find().populate('apps').exec(function(err, result) {});
You need to get rid of both the _id and id attribute definitions in your models. Waterline will handle the primary key fields for you automatically (normalizing them to id), so unless you need to change the field type, they can be safely left out. Also, I'm not sure what your intention was by adding collection and via to the id definition, but the primary key is never going to be an association.
Otherwise, your models look correct. If you get rid of those two attributes, things should work fine.

Mongoose document schema and validation

I Have a schema like so:
class Schemas
constructor: ->
#mongoose = require 'mongoose'
#schema = #mongoose.Schema
#EmployeeSchema = new #schema
'firstname': { type: String, required: true },
'lastname': { type: String, required: true },
'email': { type: String, required: true, index: { unique: true }, validate: /\b[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
'departmentId': { type: #schema.ObjectId, required: true }
'enddate': String,
'active': { type: Boolean, default: true }
#EmployeeSchemaModel = #mongoose.model 'employees', #EmployeeSchema
#DepartmentSchema = new #schema
'name': { type: String, required: true, index: { unique: true } }
'employees' : [ #EmployeeSchema ]
#DepartmentSchemaModel = #mongoose.model 'departments', #DepartmentSchema
So that my employees live in an array of employee documents inside a department
I have several department documents that have a number of employee documents stored in the employees array.
I then added a new department but it contained no employees. If I then attempt to add another department without employees, Mongoose produces a Duplicate key error for the employee.email field which is a required field. The employee.email field is required and unique, and it needs to be.
Is there anyway round this?
If you enable Mongoose debug logging with the coffeescript equivalent of mongoose.set('debug', true); you can see what's going on:
DEBUG: Mongoose: employees.ensureIndex({ email: 1 }) { safe: true, background: true, unique: true }
DEBUG: Mongoose: departments.ensureIndex({ name: 1 }) { safe: true, background: true, unique: true }
DEBUG: Mongoose: departments.ensureIndex({ 'employees.email': 1 }) { safe: true, background: true, unique: true }
By embedding the full EmployeeSchema in the employees array of DepartmentSchema (rather than just an ObjectId reference to it), you end up creating unique indexes on both employees.email and department.employees.email.
So when you create a new department without any employees you are 'using up' the undefined email case in the department.employees.email index as far a uniqueness. So when you try and do that a second time that unique value is already taken and you get the Duplicate key error.
The best fix for this is probably to change DepartmentSchema.employees to an array of ObjectId references to employees instead of full objects. Then the index stays in the employees collection where it belongs and you're not duplicating data and creating opportunities for inconsistencies.
Check out these references:
http://docs.mongodb.org/manual/core/indexes/#sparse-indexes
mongoDB/mongoose: unique if not null (specifically JohnnyHK's answer)
The short of it is that since Mongo 1.8, you can define what is called a sparse index, which only kicks in the unique check if the value is not null.
In your case, you would want:
#EmployeeSchema = new #schema
'firstname': { type: String, required: true },
'lastname': { type: String, required: true },
'email': { type: String, required: true, index: { unique: true, sparse: true }, validate: /\b[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
'departmentId': { type: #schema.ObjectId, required: true }
'enddate': String,
'active': { type: Boolean, default: true }
Notice the sparse: true added to your index on EmployeeSchema's email attribute.
https://gist.github.com/juanpaco/5124144
It appears that you can't create a unique index on an individual field of a sub-document. Although the db.collection.ensureIndex function in the Mongo shell appears to let you do that, it tests the sub-document as a whole for its uniqueness and not the individual field.
You can create an index on an individual field of a sub-document, you just can't make it unique.