I have a little problem figuring out how to get data from a linked table using queryBuilder
Currently i have this code:
const users = await databaseConntection
.getRepository(User)
.createQueryBuilder("user")
.select(["images.author_id", "user.id", "user.username"])
.leftJoinAndSelect(
(queryBuilder) =>
queryBuilder
.select(["title", "updated_at", "author_id"])
.from(Image, "image")
.orderBy({ updated_at: "ASC" })
.take(5),
"images",
"images.author_id = user.id"
)
.andWhere("username LIKE :profile")
.setParameters({
profile: `%${profile}%`,
})
.groupBy("images.author_id")
.take(limit)
.skip(page * limit - limit)
.getRawMany();
Current result:
column "user.id" must appear in the GROUP BY clause or be used in an aggregate function
Expected result:
[
{
"id": 1,
"username": "some username",
"images": [
{
"title": "some image title",
"updated_at": "2021-05-21T03:49:44.299Z",
"author_id": 1
}
...rest 4 images
]
}
...rest users
]
How i can achieve this result using query builder?
Thanks for any helping.
I don't know what is wrong with my code, but i was migrate to prisma orm, and he work like as expected.
Here the code using prisma orm, and this return the expected result (without ids):
const users = await prisma.user.findMany({
where: {
username: {
contains: profile as string,
},
},
select: {
username: true,
images: {
select: {
title: true,
updated_at: true,
},
orderBy: {
updated_at: "asc",
},
take: 5,
},
},
take: limit,
skip: page * limit - limit,
});
I've been trying to make a chat app and I use the following schema for messages:
const messageObject = {
sender: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
message: {type: String, require: true, min: 1, max: global.messageMaxLength},
time: Number,
seen: Boolean
}
const MessageSchema = mongoose.Schema({
_id: {type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
messages: [messageObject]
}, {versionKey: false}) ;
module.exports = mongoose.model('Messages', MessageSchema);
It takes in entries successfully. Example of entries:
"_id": "5fb3d971e6092d2da001bbad",
"messages": [
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
]
}
I want only the seen:false messages now, but when I try to code that in find, or aggregate, I get all the data, ie, the above records:
MessageModel.find({$and: [{_id: req.userData.userid}, {'messages.seen': false}]}).then(result => {
res.status(200).json({
message: "passed",
result,
});
})
It however works fine and returns [] if I give {"messages.seen": null}}, and 1 entry with seen: null will return an entire array.
Ive seen all forums there's no place where anybody has encountered this kind of error. Please help.
Thanks
Your message objects are nested inside a document and Mongo will only return either the full document or specific top level fields you have projected. Mongo will not filter out objects within a nested array (as is the case with your schema). So if any of the message objects within the array match the selector, the whole document itself passes the match and returns as a result.
Your options are to either:
Filter out the true/false messages within your code
Change your db structure so that you have a separate messages collection where each document is a single message. So in your example, the collection would look like:
{
"_id": "5fc58bfe0e0ffb313c27fa0a",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781949959,
"seen": false
},
{
"_id": "5fc58c010e0ffb313c27fa0b",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781953442,
"seen": false
},
{
"_id": "5fc58c020e0ffb313c27fa0c",
"parentId": "5fb3d971e6092d2da001bbad",
"message": "Hello user",
"time": 1606781954137,
"seen": false
}
And then you can query that collection with:
{ "parentId": "5fb3d971e6092d2da001bbad","seen": false}
I am trying desperately to find a object stored with mongodb, with nodejs and mongoose.
The model of the object looks like:
const SimpleResourceSchema = new mongoose.Schema(
{
_id: String,
title: String,
objective: String,
url: String,
content: String,
active: Boolean,
type: String,
owner: String,
},
{
timestamps: true,
// _id: false,
}
);
export const SimpleResourceModel = mongoose.model<
SimpleResource & mongoose.Document
>('simpleResource', SimpleResourceSchema);
The query is made with 'id' parameter value '5f1da9737917360dd038bfc0':
return await SimpleResourceModel.findById(id).exec();
The data stored in mongodb is:
{
"_id": {
"$oid": "5f1da9737917360dd038bfc0"
},
"title": "Learn cooking",
"objective": "<p>Is the fridge empty ?</p>",
"content": "...",
"url": "..",
"active": true,
"type": "simple",
"owner": "5efceb2f63b75c1750846b0a",
"createdAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"updatedAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"__v": 0
}
I have looked around to get a solution, but have not found any solution to this roadblock.
Anyone can help ?
The main issue that when you define the schema you defined the id as string remove _id: String from schema definition. and it automatic be added.
If you want to add _id to typescript you can create interface
export interface SimpleResource extends Document {
_id: schema.Types.ObjectId,
...
}
then in model you directly add it but _id already defined in Document interface
and make sure that you install #types/mongoose
export const SimpleResourceModel = mongoose.model<SimpleResource>('simpleResource', SimpleResourceSchema);
Have you tried?
var ObjectId = require('mongoose').Types.ObjectId;
return await SimpleResourceModel.findById(new ObjectId(id)).exec();
i still get a null response when I try:
await SimpleResourceModel.findById(mongoose.Types.ObjectId(id)).exec()
I want to get all RecurringEvents that have no excludeDates for today.
I have following models:
Recurring events
const RecurringEvent = sequelize.definge('recurringEvent,{
id: {type: Sequelize.INTEGER, primaryKey:true},
title: Sequelize.STRING
});
And ExcludeDates with a foreign key recurringEventId
const ExcludeDate = sequelize.define('exclude_date',{
id: {type: Sequelize.INTEGER, primaryKey:true},
recurringEventId: Sequelize.INTEGER,
date: Sequelize.DATE
});
As a relationship i defined
RecurringEvent.hasMany(ExcludeDate, {foreignKey: 'recurringEventId'});
I can get all my RecurringEvents including the the excludeDates with
RecurringEvent.findAll({include:[{model:ExcludeDate}]});
That will give me an output like:
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 1,
"date": "2019-02-13",
"recurringEventId": 1,
},
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
Now i would like to get the Recurring events but only if there is no exclude date for today.
so far i have tried
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})
But that only leaves out the ExcludeDate entry with the current Date like that :
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
How can i exclude the whole RecurringEvent if and ExcludeDate for it is set for today?
Edit:
I also read in the docs
To move the where conditions from an included model from the ON condition to the top level WHERE you can use the '$nested.column$'
So i have tried this:
RecurringEvent.findAll({where:{
'$exclude_dates.date$':{
[Op.ne]: moment().format('YYYY-MM-DD')
}
},
include: [{model: ExcludeDate}]
})
But without any luck, i'm still getting RecurringEvents just without the one exclude date in the exclude_dates property
Try to add required: true, to your include model to make inner join instead of left join. e.g.
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
required: true,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})
I'm having trouble 'upserting' to my array. The code below creates duplicates in my answers array which I definitely do not want and by now it's apparent $push will not work. I have tried using the different methodologies I see on SO for a while now but none are working for me. With this web app, users are allows to view a question on the website and respond with a 'yes' or 'no' response and they are allowed to change(upsert) their response at any one time meaning a sort of upsert takes place on the db at different times. How do get around this?
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[{type: Schema.Types.Mixed, ref: 'Answer'}]
});
var AnswerSchema = Schema ({
_question :{type: ObjectId, ref: 'Question'},
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
var UserSchema = Schema({
username : String,
isAdmin : {type: Boolean, default: false}
});
module.exports = mongoose.model('Question', QuestionSchema);
module.exports = mongoose.model('Answer', AnswerSchema);
module.exports = mongoose.model('User', UserSchema);
Question.update(
{_id: req.body.id},
{$push: {answers: {_question: req.body.id,
employee: req.body.employee,
response: req.body.response, //this variable changes (yes/no/null)
isAdmin: req.body.isAdmin}}},
{safe: true, upsert: true},
function(err, model) {
}
);
As I see it you seem a little confused and it's reflected in your schema. You don't seem to fully grasp the differences between "embedded" and "referenced" since your schema is actually an invalid "mash" of the two techniques.
Probably best to walk you through both of them.
Embedded Model
So instead of the schema you have defined, you should in fact have something more like this:
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[AnswerSchema]
});
var AnswerSchema = Schema ({
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
mongoose.model('Question', questionSchema);
NOTE: Question is the only actual model here. The AnswerSchema is completely "embedded".
Note the clear definition of the "schema" where the "answers" property in Question is defined as an "array" of AnswerSchema. This is how you do embedding and keep control of the types within the object inside the array.
As for the update, there is a clear logic pattern but you are simply not enforcing it. All you need to do is "tell" the update that you do not want to "push" a new item if something for that "unique" "employee" in the array already exists.
Also. This is NOT and "upsert". Upsert implies "creating a new one", which is different to what you want. You want to "push" to the array of an "existing" Question. If you leave "upsert" on there, then something not found creates a new Question. Which is of course wrong here.
Question.update(
{
"_id": req.body.id,
"answers.employee": { "$ne": req.body.employee },
}
},
{ "$push": {
"answers": {
"employee": req.body.employee,
"response": req.body.response,
"isAdmin": req.body.isAdmin
}
}},
function(err, numAffected) {
});
That will look to check that the "unique" "employee" in the array members already and will only $push where it is not already there.
As a bonus, if you intend to allow the user to "change their answer" then we do this incantation with .bulkWrite():
Question.collection.bulkWrite(
[
{ "updateOne": {
"filter": {
"_id": req.body.id,
"answers.employee": req.body.employee,
},
"update": {
"$set": {
"answers.$.response": req.body.response,
}
}
}},
{ "updateOne": {
"filter": {
"_id": req.body.id,
"answers.employee": { "$ne": req.body.employee },
},
"update": {
"$push": {
"answers": {
"employee": req.body.employee,
"response": req.body.response,
"isAdmin": req.body.isAdmin
}
}
}
}}
],
function(err, writeResult) {
}
);
This effectively puts two updates in one. The first to attempt to alter an existing answer and $set the response at the matched position, and the second to attempt to add a new answer where one was not found on the question.
Referenced Model
With a "referenced" model you actually have the real members of the Answer within their own collection. So instead the schema is defined like this:
var QuestionSchema = Schema ({
title :String,
admin :{type: String, ref: 'User'},
answers :[{ type: Schema.Types.ObjectId, ref: 'Answer' }]
});
var AnswerSchema = Schema ({
_question :{type: ObjectId, ref: 'Question'},
employee :{type: String, ref: 'User'},
response :String,
isAdmin :{type: Boolean, ref: 'User'}
})
mongoose.model('Answer', answerSchema);
mongoose.model('Question', questionSchema);
N.B The other ref's here to User such as :
employee :{type: String, ref: 'User'},
isAdmin :{type: Boolean, ref: 'User'}
These are also really incorrect, and also should be of Schema.Type.ObjectId as they will "reference" the actual _id field of User. But this is actually outside of the scope of this question as asked, so if you still don't grasp that after this read, then Ask a New Question so someone can explain. On with the rest of the answer.
That's the "general" shape of the schema though, with the important thing being the "ref" to the 'Anwser' "model", which is by the registered name. You can optionally just use your "_question" field in modern mongoose versions with a "virtual", but I'm skipping over "Adavanced Usage" for now and keeping it simple with an array of "references" still in the Question model.
In this case, since the Answer model is actually in it's own "collection", then the operations actually become "upserts". Where we only want to "create" when there is no "employee" response to the given "_question" id.
Also demonstrating with a Promise chain instead:
Answer.update(
{ "_question": req.body.id, "employee": req.body.employee },
{
"$set": {
"response": req.body.reponse
},
"$setOnInsert": {
"isAdmin": req.body.isAdmin
}
},
{ "upsert": true }
).then(resp => {
if ( resp.hasOwnProperty("upserted") ) {
return Question.update(
{ "_id": req.body.id, "answers": { "$ne": resp.upserted[0]._id },
{ "$push": { "answers": resp.upserted[0]._id } }
).exec()
}
return;
}).then(resp => {
// either undefined where it was not an upsert or
// the update result from Question where it was
}).catch(err => {
// something
})
This is actually a simple statement since "when matched" we want to change the "response" data with the payload of the request, and really only when "upserting" or "creating/inserting" is when we actually change other data such as the "employee" ( which is always implied for create as part of the query expression ) and the "isAdmin" which clearly should not change with each update request we then explicitly use $setOnInsert so it only writes those two fields on an actual "create".
In the "Promise Chain" we actually look to see if the update request to Answer actually resulted in an "upsert", and when it does we want to append to the array of Question where it does not already exist. In much the same way as the "embedded" example, it's best to look to see if the array actually has the item before modifying with the "update". Alternately you could $addToSet here and just let the query match the Question by _id. To me though, that's a wasted write.
Summary
Those are your different approaches to how you handle this. Each has their own use cases for which you can see a general summary some other answers of mine in:
Mongoose populate vs object nesting which is an overview of the different approaches and reasons behind them
How to Model a “likes” voting system with MongoDB which gives a bit more detail on the "unique array upserts" technique for "embedded" models.
Not "required" reading, but it may help expand your insight into which approach is best for your particular case.
Working Example
Copy these and put them in a directory and do an npm install to install local dependencies. The code will run and create the collections in the database making the alterations.
Logging is turned on with mongoose.set(debug,true) so you should look at the console output and see what it does, along with the resulting collections where answers will be recorded to the related questions, and overwritten instead of "duplicating" where that was also the intent.
Change the connection string if you have to. But that is all you should change in this listing for it's purpose. Both approaches described in the answer are demonstrated.
package.json
{
"name": "colex",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"async": "^2.4.1",
"mongodb": "^2.2.29",
"mongoose": "^4.10.7"
}
}
index.js
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = require('mongodb').ObjectID
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.connect('mongodb://localhost/coltest');
const userSchema = new Schema({
username: String,
isAdmin: { type: Boolean, default: false }
});
const answerSchemaA = new Schema({
employee: { type: Schema.Types.ObjectId, ref: 'User' },
response: String,
});
const answerSchemaB = new Schema({
question: { type: Schema.Types.ObjectId, ref: 'QuestionB' },
employee: { type: Schema.Types.ObjectId, ref: 'User' },
response: String,
});
const questionSchemaA = new Schema({
title: String,
admin: { type: Schema.Types.ObjectId, ref: 'User' },
answers: [answerSchemaA]
});
const questionSchemaB = new Schema({
title: String,
admin: { type: Schema.Types.ObjectId, ref: 'User' },
answers: [{ type: Schema.Types.ObjectId, ref: 'AnswerB' }]
});
const User = mongoose.model('User', userSchema);
const AnswerB = mongoose.model('AnswerB', answerSchemaB);
const QuestionA = mongoose.model('QuestionA', questionSchemaA);
const QuestionB = mongoose.model('QuestionB', questionSchemaB);
async.series(
[
// Clear data
(callback) => async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Create some data
(callback) =>
async.each([
{
"model": "User",
"object": {
"_id": "594a322619ddbd437193c759",
"name": "Admin",
"isAdmin": true
}
},
{
"model": "User",
"object": {
"_id": "594a323919ddbd437193c75a",
"name": "Bill"
}
},
{
"model": "User",
"object": {
"_id": "594a327b19ddbd437193c75b",
"name": "Ted"
}
},
{
"model": "QuestionA",
"object": {
"_id": "594a32f719ddbd437193c75c",
"admin": "594a322619ddbd437193c759",
"title": "Question A Model"
}
},
{
"model": "QuestionB",
"object": {
"_id": "594a32f719ddbd437193c75c",
"admin": "594a322619ddbd437193c759",
"title": "Question B Model"
}
}
],(data,callback) => mongoose.model(data.model)
.create(data.object,callback),
callback
),
// Submit Answers for Users - Question A
(callback) =>
async.eachSeries(
[
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Teds Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Changed Answer"
}
].map(d => ([
{ "updateOne": {
"filter": {
"_id": ObjectId(d._id),
"answers.employee": ObjectId(d.employee)
},
"update": {
"$set": { "answers.$.response": d.response }
}
}},
{ "updateOne": {
"filter": {
"_id": ObjectId(d._id),
"answers.employee": { "$ne": ObjectId(d.employee) }
},
"update": {
"$push": {
"answers": {
"employee": ObjectId(d.employee),
"response": d.response
}
}
}
}}
])),
(data,callback) => QuestionA.collection.bulkWrite(data,callback),
callback
),
// Submit Answers for Users - Question A
(callback) =>
async.eachSeries(
[
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a323919ddbd437193c75a",
"response": "Bills Answer"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Teds Anwser"
},
{
"_id": "594a32f719ddbd437193c75c",
"employee": "594a327b19ddbd437193c75b",
"response": "Ted Changed it"
}
],
(data,callback) => {
AnswerB.update(
{ "question": data._id, "employee": data.employee },
{ "$set": { "response": data.response } },
{ "upsert": true }
).then(resp => {
console.log(resp);
if (resp.hasOwnProperty("upserted")) {
return QuestionB.update(
{ "_id": data._id, "employee": { "$ne": data.employee } },
{ "$push": { "answers": resp.upserted[0]._id } }
).exec()
}
return;
}).then(() => callback(null))
.catch(err => callback(err))
},
callback
)
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
)
Here was my quick work around before Neill updated his answer (I used a $pull & $push). Works just as his but I'll mark his correct as I believe it's more efficient.
Question.update(
{_id: req.body.id},
{$pull: {answers: { employee: req.body.employee}}},
{safe: true, multi:true, upsert: true},
function(err, model) {
}
);
Question.update(
{_id: req.body.id},
{$push: {answers: {_question: req.body.id,
employee: req.body.employee,
response: req.body.response,
isAdmin: req.body.isAdmin}}},
{safe: true, upsert: true},
function(err, model) {
}
);