How do I convert a complex GROUP BY to Sequelize? - postgresql

My SQL query is
SELECT t."id", t."topicText", COUNT(t."id") AS topicCount
FROM "WorkbookQuestions" wq, "Workbooks" w, "Questions" q, "Topics" t
WHERE w."StudentId" = 3
AND wq."QuestionId" = q."id"
AND wq."WorkbookId" = w."id"
AND q."TopicId" = t."id"
GROUP BY "t"."id"
ORDER BY "topiccount" DESC
How can I write this in Sequelize code so I can avoid writing SQL directly? Or is it not possible?
This is what I've tried (unsuccessfully) so far:
db.WorkbookQuestion.findAll({
attributes: ['Topics.id', [db.sequelize.fn('COUNT', db.sequelize.col('Topics.id'))], 'TopicCount'],
include: [
{
model: db.Workbook,
where: {
StudentId: newWorkbook.StudentId,
SubjectId: newWorkbook.SubjectId
}
}, {
model: db.Question,
include: [
{
model: db.Topic
}
]
}
],
group: ['Topics.id']
});

Just by looking at it, i would write the code like this
db.Workbook.findAll({
attributes: ['wbquestion.question.topic.id', [db.sequelize.fn('COUNT', db.sequelize.col('wbquestion.question.topic.id'))], 'topiccount'],
include: [
{
model: db.WorkbookQuestion,
as: wbquestion,
where: {
id: wbquestion.workbookId
},
include: [
model: db.Question,
as: question,
where: {
id: wbquestion.questionId
},
include: [
model: db.Topic,
as: topic,
where: {
id: question.topicId
}
]
]
}
],
where: {
studentId : 3
}
group: ['topic.id']
});
Maybe you can get more readable code by defining the models and relations separately and using eager loading afterwards.
Edit:
Generated the migrations and models
node_modules/.bin/sequelize model:create --name Student --attributes firstName:string,lastName:string
node_modules/.bin/sequelize model:create --name Topic --attributes topicText:string
node_modules/.bin/sequelize model:create --name Question --attributes TopicId:integer
node_modules/.bin/sequelize model:create --name Workbook --attributes studentId:integer
node_modules/.bin/sequelize model:create --name WorkbookQuestion --attributes QuestionId:integer,WorkbookId:integer
A simple program for testing the query in console log.
var Sequelize = require("sequelize");
var models = require('./models/index');
models.WorkbookQuestion.belongsTo(models.Question, {foreignKey: 'QuestionId'});
models.WorkbookQuestion.belongsTo(models.Workbook, {foreignKey: 'WorkbookId'});
models.Question.hasOne(models.WorkbookQuestion);
models.Workbook.hasOne(models.WorkbookQuestion);
models.Workbook.belongsToMany(models.Question, {through: models.WorkbookQuestion});
models.Workbook.belongsToMany(models.Topic, {through: models.Question});
var res = models.Workbook.findAll({
attributes: ['topicId', Sequelize.fn('count', Sequelize.col('topiccount'))],
include: [models.WorkbookQuestion, models.Question, models.Topic],
where: {
studentId: 3
},
group: ['topic.id']
}).then(function(data) {
console.log(data);
}).catch(function(err) {
console.trace(err);
});
The result is:
SequelizeDatabaseError: column Topics.Question.WorkbookId does not exist
Maybe you can fix the relations between the models or you should create the query not using findAll functionality.

Related

Bicep variable creation from module output

I-m trying to create multiple app services with bicep using a for loop on a module.
The module has the following output:
output id string = appServiceAppResource.id
output name string = appServiceAppResource.name
output vnetIntegrationOn bool = allowVnetIntegration[appServicePlan.sku.name]
output principalId string = appServiceAppResource.identity.principalId // <-- important
After this, i want to create a key vault, and provide "get" access to all the app services previously created using an access policy:
...
param appServices object
...
module appServicePlanModules 'modules/appServicePlan.bicep' = [for appServicesConfig in appServicePlans.appServicesConfig: {
name: '${appServicesConfig.name}-appserviceplan-module'
params: {
location: location
name: appServicesConfig.name
sku: appServicesConfig.sku
}
}]
...
var accessPolicy = [ for i in range(0, appServices.instanceCount) : {
objectId: appServiceModule[i].outputs.principalId
permissions: {
secrets: ['get']
}
tenantId: subscription().tenantId
}]
module keyValutModule 'modules/keyvault.bicep' = {
name: 'key-valut-module'
dependsOn: [appServiceModule]
params: {
location: location
accessPolicies: accessPolicy
publicNetworkAccess: keyvault.publicNetworkAccess
keyVaultName: keyvault.name
}
}
The problem is that when i try to create that access policy, it fails
What puzzles me is that, this is working:
var accessPolicy = [{
objectId: appServiceModule[0].outputs.principalId
permissions: {
secrets: ['get']
}
tenantId: subscription().tenantId
}
{
objectId: appServiceModule[1].outputs.principalId
permissions: {
secrets: ['get']
}
tenantId: subscription().tenantId
}]
And also this:
var accessPolicies = [ for i in range(0,1):{
objectId: '52xxxxxx-25xx-4xxf-axxx-xxdxx3axxdff'
permissions: {
secrets: ['get']
}
tenantId: subscription().tenantId
}]
Since I want to use this template for multiple env, I want it to be more generic (so that i can have 1 or 5 service apps), so that for loop wold be very useful for me.
I'm not sure why, if I use a for loop in combination of a module output this is not working.
Do you have any idea why, or of there is a workaround on this.
Thank you!
Best regards,
Dorin

TypeOrm Joins without using QueryBuilder

I have 3 tables
attendance
user
attendance_verification.
attendance->ManyToOne->user
attendance->OneToOne->attendanceVerification
now I want to query attendance for a specific user and want to embed complete user and attendanceVerification data in attendance object and return it.
This is what I have tried but it is giving me error Invalid syntax near where.
I have tried
const listOfAttendance = await this.attendanceRepository.find({
where: {
punchInDateTime: Between(dateFrom, dateTo),
},
order: {
id: 'ASC',
},
join: {
alias: 'attendance',
leftJoinAndSelect: {
user: 'attendance.user',
outlet: 'attendance.outlet',
punchAck: 'attendance.punchAck',
verification: 'attendance.attendanceVerification',
},
},
})
Query being parsed by TypeORM
SELECT
"attendance"."id" AS "attendance_id",
"attendance"."punchInImage" AS "attendance_punchInImage",
"attendance"."punchInDateTime" AS "attendance_punchInDateTime",
"attendance"."punchInComment" AS "attendance_punchInComment",
"attendance"."punchOutDateTime" AS "attendance_punchOutDateTime",
"attendance"."punchOutComment" AS "attendance_punchOutComment",
"attendance"."punchOutImage" AS "attendance_punchOutImage",
"attendance"."status" AS "attendance_status",
"attendance"."approvalStatus" AS "attendance_approvalStatus",
"attendance"."userId" AS "attendance_userId",
"attendance"."outletId" AS "attendance_outletId",
"attendance"."createdAt" AS "attendance_createdAt",
"user"."id" AS "user_id",
"user"."firstName" AS "user_firstName",
"user"."lastName" AS "user_lastName",
"user"."userName" AS "user_userName",
"user"."contact" AS "user_contact",
"user"."gender" AS "user_gender",
"user"."dob" AS "user_dob",
"user"."country" AS "user_country",
"user"."state" AS "user_state",
"user"."postalCode" AS "user_postalCode",
"user"."isAdmin" AS "user_isAdmin",
"user"."isEmployee" AS "user_isEmployee",
"user"."byEmail" AS "user_byEmail",
"user"."byContact" AS "user_byContact",
"user"."address" AS "user_address",
"user"."email" AS "user_email",
"user"."orgId" AS "user_orgId",
"user"."password" AS "user_password",
"user"."salt" AS "user_salt",
"user"."createdBy" AS "user_createdBy",
"user"."status" AS "user_status",
"user"."apiAccessKey" AS "user_apiAccessKey",
"user"."createdAt" AS "user_createdAt",
"user"."metaId" AS "user_metaId",
"outlet"."id" AS "outlet_id",
"outlet"."name" AS "outlet_name",
"outlet"."comment" AS "outlet_comment",
"outlet"."location" AS "outlet_location",
"outlet"."status" AS "outlet_status",
"outlet"."orgId" AS "outlet_orgId",
"outlet"."validated" AS "outlet_validated",
"punchAck"."id" AS "punchAck_id",
"punchAck"."isPunchedIn" AS "punchAck_isPunchedIn",
"punchAck"."isAck" AS "punchAck_isAck",
"punchAck"."userId" AS "punchAck_userId",
"punchAck"."dateTime" AS "punchAck_dateTime",
"punchAck"."rejectComment" AS "punchAck_rejectComment",
"punchAck"."attendanceId" AS "punchAck_attendanceId",
"verification"."id" AS "verification_id",
"verification"."status" AS "verification_status",
"verification"."punchIn" AS "verification_punchIn",
"verification"."punchOut" AS "verification_punchOut",
"verification"."supervisorId" AS "verification_supervisorId",
"verification"."attendanceId" AS "verification_attendanceId",
"verification"."comment" AS "verification_comment",
"verification"."createdAt" AS "verification_createdAt"
FROM
"tenant"."attendance" "attendance"
LEFT JOIN "tenant"."users" "user" ON "user"."id" = "attendance"."userId"
LEFT JOIN "tenant"."outlet" "outlet" ON "outlet"."id" = "attendance"."outletId"
LEFT JOIN "tenant"."punch_ack" "punchAck" ON "punchAck"."attendanceId" = "attendance"."id"
LEFT JOIN "tenant"."attendance_verification" "verification" ON
WHERE
"attendance"."punchInDateTime" BETWEEN $ 1
AND $ 2
ORDER BY
"attendance"."id" ASC
PARAMETERS:[
"2021-02-28T19:00:00.000Z",
"2021-03-31T18:59:59.000Z"
]
AttendanceEntity (Exluded some fields to make code clean)
import { ApiPropertyOptional } from '#nestjs/swagger'
import {
BaseEntity,
Column,
Entity,
ManyToOne,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
} from 'typeorm'
import { AttendanceVerification } from '#modules/attendance/attendance-verification/attendance_verification.entity'
import { Break } from '#modules/attendance/break/break.entity'
import { PunchAck } from '#modules/attendance/punch-ack/punch_ack.entity'
import { Outlet } from '#modules/outlet/outlet.entity'
import { User } from '#modules/users/users.entity'
#Entity('attendance')
export class Attendance extends BaseEntity {
#ApiPropertyOptional()
#PrimaryGeneratedColumn()
id: number
#ApiPropertyOptional()
#Column()
punchInImage: string
#ApiPropertyOptional()
#Column({ type: 'timestamp' })
punchInDateTime: Date
#ApiPropertyOptional()
#Column()
approvalStatus: string
#ApiPropertyOptional()
#Column()
userId: number
#ApiPropertyOptional()
#Column()
outletId: number
#ManyToOne(type => User, user => user.attendance)
user: User
#OneToOne(type => Outlet, outlet => outlet.attendance)
outlet: Outlet
#OneToMany(type => Break, breaks => breaks.attendance)
breaks: Break[]
#OneToMany(type => PunchAck, punchAck => punchAck.attendance)
punchAck: PunchAck[]
#OneToOne(
type => AttendanceVerification,
attendanceVerification => attendanceVerification.attendance
)
attendanceVerification: AttendanceVerification
}
All solutions I find on internet uses queryBuilder. I don't want to use that lengthy method.
From typeorm docs: https://typeorm.io/#/find-options
You can use something like:
userRepository.find({
join: {
alias: "user",
leftJoinAndSelect: {
profile: "user.profile",
photo: "user.photos",
video: "user.videos"
}
}
});
Or your example may look like:
const returnedResult = await this.attendanceRepository.findOne({
where: { userId: id, status: 'PUNCHED IN' },
join: {
alias: "attendance",
leftJoinAndSelect: {
user: "attendance.user",
outlet: "attendance.outlet",
}
},
});
From my personal experience if you'd need to write complex queries you have to work with QueryBuilder. It looks awkward on the first glance only.

Correct way to seed MongoDB with references via mongoose

I have three schemas, one which references two others:
userSchema
{ name: String }
postSchema
{ content: String }
commentSchema
{
content: String,
user: { ObjectID, ref: 'User' },
post: { ObjectID, ref: 'Post' }
}
How can I seed this database in a sane, scalable way? Even using bluebird promises it quickly becomes a nightmare to write.
My attempt so far involves multiple nested promises and is very hard to maintain:
User
.create([{ name: 'alice' }])
.then(() => {
return Post.create([{ content: 'foo' }])
})
.then(() => {
User.find().then(users => {
Post.find().then(posts => {
// `users` isn't even *available* here!
Comment.create({ content: 'bar', user: users[0], post: posts[0] })
})
})
})
This is clearly not the correct way of doing this. What am I missing?
Not sure about bluebird, but the nodejs Promise.all should do the job:
Promise.all([
User.create([{ name: 'alice' }]),
Post.create([{ content: 'foo' }])
]).then(([users, posts]) => {
const comments = [
{ content: 'bar', user: users[0], post: posts[0] }
];
return Comment.create(comments);
})
If you want to seed database with automatically references, use Seedgoose.
This is the easiest seeder for you to use. You don't need to write any program files, but only data files. And Seedgoose handles smart references for you. And by the way, I'm the author and maintainer of this package.
Try this it will work fine:
Note: Node Promise.all will make sure that the both query is executed properly and then return the result in Array:[Users, Posts],
If you get any error during execution of any query, it will be handle by catch block of the Promise.all.
let queryArray = [];
queryArray.push(User.create([{ name: 'alice' }]));
queryArray.push(Post.create([{ content: 'foo' }]));
Promise.all(queryArray).then(([Users, Posts]) => {
const comments = [
{ content: 'bar', user: Users[0], post: posts[0] }
];
return Comment.create(comments);
}).catch(Error => {
console.log("Error: ", Error);
})

Sequelize.js - How to create non-trivial associations without raw SQL?

Here is my situation:
I'm using postgres 9.4, Sequelize ORM and have following models:
Service
serviceCode - primary key, string of 6 characters
serviceTitle - string
ServiceGroup
serviceCodePrefixes - array of strings that are prefixes for Service.serviceCode
serviceGroupTitle - string
Task
serviceCode - reference to Service
I need to build Task object populated with Service and ServiceGroup objects. Example:
In database:
Service {
serviceCode: '123232',
serviceTitle: 'svc title #1',
}
ServiceGroup {
serviceCodePrefix: ['12', '13', '92', ...],
serviceGroupTitle: 'svc grp title #1',
}
Task {
serviceCode: '123232',
}
Result:
Task {
service: {
serviceTitle: 'svc title #1',
},
serviceGroup: {
serviceGroupTitle: 'svc grp title #1',
},
}
The problem is that serviceCodePrefix contains not simple IDs, which can be used to create association using hasOne/belongsTo/etc., but prefix for ID.
So questions is: how this can be done without raw sql?
Turns out that right now Sequelize has experimental feature: 'on' option for 'include'. This option allows users to customize joining conditions. So my problem can be solved this way:
const Service = sequelize.define('service', {
serviceTitle: Sequelize.STRING,
serviceCode: Sequelize.STRING,
});
const ServiceGroup = sequelize.define('service_group', {
serviceGroupTitle: Sequelize.STRING,
// Array of prefixes (e.g. ['01%', '023%'])
serviceCodePrefix: Sequelize.ARRAY(Sequelize.STRING),
});
const Task = sequelize.define('task', {
taskTitle: Sequelize.STRING,
serviceCode: Sequelize.STRING,
});
Task.belongsTo(Service, { foreignKey: 'serviceCode' });
// Hack needed to allow 'include' option to work
Task.hasMany(ServiceGroup, { foreignKey: 'serviceCodePrefix', constraints: false });
// And finally
Task.findAll({
include: [
{ model: Service },
{
model: ServiceGroup,
on: [' "task"."serviceCode" LIKE ANY("serviceGroup"."serviceCodePrefix") '],
},
],
});
Not sure about the performance though.

using sequelize-cli db:seed, schema is ignored when accessing Postgres

i am building a web service using express.js and Sequilize with a Postgres DB.
Database holds a table 'country' under schema 'schema1'. Table 'country' has fields 'name', 'isoCode'.
Created a seed file to insert a list of countries inside table 'country'.
Seed file looks like :
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.bulkInsert(
'country',
[
{
"name":"Afghanistan",
"isoCode":"AF"
},
{
"name":"Ă…land Islands",
"isoCode":"AX"
},
{
"name":"Albania",
"isoCode":"AL"
},
{
"name":"Algeria",
"isoCode":"DZ"
},
{
"name":"American Samoa",
"isoCode":"AS"
},
{
"name":"Andorra",
"isoCode":"AD"
}
],
{
schema : 'schema1'
}
);
},
down: function (queryInterface, Sequelize) {
}
};
While running seed i get this error :
node_modules/sequelize-cli/bin/sequelize --url postgres://user:password#localhost:5432/database db:seed
Sequelize [Node: 0.12.6, CLI: 2.0.0, ORM: 3.11.0, pg: ^4.4.2]
Parsed url postgres://user:*****#localhost:5432/database
Starting 'db:seed'...
Finished 'db:seed' after 165 ms
== 20151029161319-Countries: migrating =======
Unhandled rejection SequelizeDatabaseError: relation "country" does not exist
at Query.formatError (node_modules/sequelize/lib/dialects/postgres/query.js:437:14)
at null.<anonymous> (node_modules/sequelize/lib/dialects/postgres/query.js:112:19)
at emit (events.js:107:17)
at Query.handleError (node_modules/pg/lib/query.js:108:8)
at null.<anonymous> (node_modules/pg/lib/client.js:171:26)
at emit (events.js:107:17)
at Socket.<anonymous> (node_modules/pg/lib/connection.js:109:12)
at Socket.emit (events.js:107:17)
at readableAddChunk (_stream_readable.js:163:16)
at Socket.Readable.push (_stream_readable.js:126:10)
at TCP.onread (net.js:538:20)
I think i am stuck on this. I would appreciate any provided help / guidance etc.
Thank you for your time.
I executed SQL query on Postgres :
ALTER ROLE <username> SET search_path TO schema1,public;
as noted here : Permanently Set Postgresql Schema Path
Then, executed seeder again succesfully :
node_modules/sequelize-cli/bin/sequelize --url postgres://user:password#localhost:5432/database db:seed
Sequelize [Node: 0.12.6, CLI: 2.0.0, ORM: 3.11.0, pg: ^4.4.2]
Parsed url postgres://user:*****#localhost:5432/database
Using gulpfile node_modules/sequelize-cli/lib/gulpfile.js
Starting 'db:seed'...
Finished 'db:seed' after 558 ms
== 20151029161319-Countries: migrating =======
== 20151029161319-Countries: migrated (0.294s)
Thanks #a_horse_with_no_name for the information about search_path. I wish the sequelize library could handle this situation, or maybe i misuse it.
update :
Opened a ticket on Github (https://github.com/sequelize/sequelize/issues/4778#issuecomment-152566806) and the solution is quite simple :
instead of setting only the table as the first argument, set
{tableName: 'country', schema : 'schema1'}
You can actually specify the schema and table name via object like is explained in this Github issue:
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
return queryInterface.bulkInsert(
{ tableName: 'account', schema: 'crm' },
{
name: 'Michael'
},
{}
);
},
down: function (queryInterface, Sequelize) {
return queryInterface.bulkDelete({ tableName: 'account', schema: 'crm' }, null, {});
}
};