Sequelize Select column WHERE other column has not contain value - javascript

I have the following Accounts_Users table:
account_id | user_id
--------------------
1 | 60
2 | 60
1 | 50
3 | 50
And I want to retrieve all the user_id's which do not have rows with certain account_id
For example, if the account_id = 2 I want the result should be:
user_id
-------
50
Since user_id = 60 have record with account = 2.
my current query looks like this:
let existingUserIdsWithAccountUser = await AccountUserModel.findAll({
raw: true,
where: {
account_id: account_id,
user_id: {
[Sequelize.Op.in]: existingUsersIds
}
},
attributes: [Sequelize.fn('DISTINCT', Sequelize.col('user_id')), 'user_id']
}).map(user => user.user_id);
const existingUserIdsWithoutAccountUser = existingUsersIds.filter(user_id => !existingUserIdsWithAccountUser.includes(user_id));
I want to do a single query without having to filter the results.
I also tired the following:
let existingUserIdsWithoutAccountUser = await AccountUserModel.findAll({
raw: true,
where: {
account_id: {
[Sequelize.Op.not]: account_id
},
user_id: {
[Sequelize.Op.in]: existingUsersIds
}
},
attributes: [Sequelize.fn('DISTINCT', Sequelize.col('user_id')), 'user_id']
}).map(user => user.user_id);
but in this case, if I have a record with a different account_id then it still gets returned obviously.

I was finally able to do it using the following query:
const existingUserIdsWithoutAccountUser = await AccountUserModel.findAll({
raw: true,
where: {
user_id: {
[Sequelize.Op.and]: {
[Sequelize.Op.notIn]: Sequelize.literal(`(SELECT user_id FROM \`Accounts_Users\` WHERE account_id = ${account_id})`),
[Sequelize.Op.in]: existingUsersIds
}
}
},
attributes: [Sequelize.fn('DISTINCT', Sequelize.col('user_id')), 'user_id']
}).map(user => user.user_id);

Related

Sequelize - Count Based on Include Condition

I'm using the sequelize count function as follows:
Definition:
const countAllOrdersWhere = async (conditions) =>
Order.count({
where: conditions,
})
.then((count) => ({ count, error: null }))
.catch((error) => ({ count: null, error }));
Usage:
const { count, error: countError } = await countAllOrdersWhere({
[Op.or]: [
{ userId: userIds, status },
{ status, venueId },
],
});
This works great, however I now need to add a condition based on an associated model:
Each order has many orderItems associated with it (there's an orderId on each row in the orderItem table.)
Each orderItem has one item associated with it - (there's an itemid on each row in the orderItem table.)
I have an array of itemIds and I would like to only count the orders which are associated with an orderItem which has an itemId which is in my list.
When querying the orders, I enforce this clause in a findAll function by doing
include: [
{
model: OrderItem,
where: Sequelize.literal(
itemIds && itemIds.length > 0
? `"orderItems"."itemId" IN (${itemIds})`
: 'true'
)}]
however, I'm not sure how to do this in a count function
The count method also has include option so you can use it the ame way as you did with findAll, see count
const countAllOrdersWhere = async (conditions) =>
Order.count({
include: [
{
model: OrderItem,
// this option is important in order to get the correct result (leads to INNER JOIN
// instead of LEFT OUTER JOIN)
required: true,
where: (itemIds && itemIds.length > 0) ? {
itemId: {
[Op.in]: itemIds
}
} : {}
}]
}).then((count) => ({ count, error: null }))
.catch((error) => ({ count: null, error }));

Is there a way to make the queries on the Parse Server shorter?

Original Query
const Post = Parse.Object.extend('Post')
const queryPost = new Post()
queryPost.startsWith('title', 'Good Programming')
queryPost.greaterThan('totalLike', 1000)
queryPost.limit(100)
queryPost.include('author')
query.find()
Expected Query
const Post = Parse.Object.extend('Post')
const queryPost = new Post()
queryPost.find({
startsWith: {
title: 'Good Programming'
},
greaterThan: {
totalLike: 1000
},
include: ['post'],
limit: 100
})
The advantage of the method above, I can do the copy and paste as usual when I want to do trials across places.
You can do something like the following but it is not documented:
const query = Parse.Query.fromJSON('ClassName', {
where: {
title: {
'$regex': '^Good Programming'
},
totalLike: {
'$gt': 1000
}
},
include: 'post',
limit: 100
});
query.find();
If you need a JSON version of the query, just run toJSON method to get formatted query as a JSON:
Example
const Post = Parse.Object.extend('Post')
const queryPost = new Post()
queryPost.startsWith('title', 'Good Programming')
queryPost.greaterThan('totalLike', 1000)
queryPost.limit(100)
queryPost.include('author')
queryPost.select(['id', 'name'])
console.log(queryPost.toJSON())
Output
{
where: {
title: {
'$regex': '^\QGood Programming\E'
},
totalLike: {
'$gt': 1000
}
},
include: 'post',
keys: 'id,name',
limit: 100
}
and you can re-execute the above JSON Query using the fromJSON method.
Example
const query = Parse.Query.fromJSON('ClassName', {
where: {
title: {
'$regex': '^Good Programming'
},
totalLike: {
'$gt': 1000
}
},
include: 'post',
keys: 'id,name',
limit: 100
});
query.find();

Map over collection to upsert into the database. How to batch upsert?

Say, I have a data structure coming in from the frontend as follows:
const userData = [
{
id: 11223,
bb: [
{
id: 12,
},
{
id: 34,
bbb: "bbb",
},
],
},
{
id:4234,
...
},
];
Because, none/ some/ all of the data may already be in the database, here is what I have come up with:
const collection = [];
for (let i = 0; i < userData.length; i++) {
const cur = userData[i];
const subCur = cur.bb;
const updatedCur = await db.cur.upsert({
where: {
id : cur.id
},
update: {
...
},
create: {
...
},
})
);
collection.push(updatedCur);
for (let j = 0; j < subCur.length; j++) {
const latest = subCur[j];
await db.subcur.upsert({
where: {
id : latest.id
},
update: {
...
},
create: {
...
},
});
}
}
To summarise, I am mapping over the userData & upsert each object one by one. Within the loop, I map over the child collections & upsert them in the db.
My concern is that I am making a lot of entries into the Db this way. Is this the best way to do this?
Aside:
I previously, tried to do multiple inserts within the upsert, however, I got stuck with the update section as to my knowledge, we cannot upsert multiple records within the update nested within upsert. Is this correct?
UPDATE:
As requested by Ryan, here is what the Schema looks like:
model Cur {
id Int,
subCur SubCur[]
...
}
model SubCur {
id Int,
cur Cur #relation(fields: [curId], references : [id])
curId Int
...
}
To summarise, there are many models like 'SubCur' with 1-n relation with 'Cur' model. As the 'UserData' payload, may have some data that is new, some that is update for existing data already in Db, I was curious, whats the best approach to upsert the data into the db. To be specific, do I have to insert each one, one at a time?
I assumed your schema to be this:
model Cur {
id Int #id
}
model Subcur {
id Int #id
bbb String?
}
And here's a better version:
const collection = await prisma.$transaction(
userData.map(cur =>
prisma.cur.upsert({
where: { id: cur.id },
update: {},
create: { id: cur.id },
})
)
)
await prisma.$transaction(
userData
.flatMap(cur => cur.bb)
.map(latest =>
prisma.subcur.upsert({
where: {
id: latest.id,
},
update: {
bbb: latest.bbb,
},
create: {
id: latest.id,
bbb: latest.bbb,
},
})
)
)

prisma2: how to fetch nested fields?

In prisma 1 I have used fragment to fetch the nested fields.
For example:
const mutations = {
async createPost(_, args, ctx) {
const user = await loginChecker(ctx);
const post = await prisma.post
.create({
data: {
author: {
connect: {
id: user.id,
},
},
title: args.title,
body: args.body,
published: args.published,
},
})
.$fragment(fragment);
return post;
},
};
but seems like in prisma2 it is not supported. because by running this on playground,
mutation CREATEPOST {
createPost(
title: "How to sleep?"
body: "Eat, sleep, repaet"
published: true
) {
title
body
published
author {
id
}
}
}
I am getting,
"prisma.post.create(...).$fragment is not a function",
The include option is used to eagerly load relations in Prisma.
Example from docs:
const result = await prisma.user.findOne({
where: { id: 1 },
include: { posts: true },
})
Assuming a user table with a one-to-many posts relation, this will return back the user object with the posts field as well.
Prisma also supports nesting as well, for example:
const result = await prisma.user.findOne({
where: { id: 1 },
include: {
posts: {
include: {
author: true,
}
},
},
})

Sequelize associations: set[Models] adds new models instead of associating existing ones

I'm using Sequelize and I'm trying to create associations between two different tables, where x.belongsTo(y) and y.hasMany(x). After having done x.setY(yInstance) and y.getXs() it seems only new rows have been added to x and no associations to my already created instances have been created.
var Promise = require("bluebird"),
Sequelize = require("sequelize");
var sequelize = new Sequelize("Test", "postgres", "password", {
host: "localhost",
dialect: "postgres",
pool: {
max: 5,
min: 0,
idle: 10000
}
});
var Schedule = sequelize.define("Schedule", {
website: {
type: Sequelize.STRING
}
});
var SiteConfig = sequelize.define("SiteConfig", {
systemType: {
type: Sequelize.STRING
}
});
var Selector = sequelize.define("Selector", {
type: {
type: Sequelize.STRING
},
content: {
type: Sequelize.STRING
}
});
Selector.belongsTo(SiteConfig);
SiteConfig.hasMany(Selector);
var testSchedule = {
website: "google.com"
};
var testSiteConfig = {
systemType: "one"
};
var testSelectors = [
{type: "foo", content: "foo"},
{type: "foo", content: "bar"}
];
Promise.all([
Schedule.sync({force: true}),
SiteConfig.sync({force: true}),
Selector.sync({force: true})
]).then(function () {
return Promise.all([
Schedule.create(testSchedule),
SiteConfig.create(testSiteConfig),
Selector.bulkCreate(testSelectors)
]);
}).spread(function (schedule, siteConfig, selectors) {
return Promise.map(selectors, function (selector) {
return selector.setSiteConfig(siteConfig);
}).then(function (array) {
return siteConfig.getSelectors();
}).each(function (selector) {
// This is where I expect "foo" and "bar" but instead get null
console.log("Selector content:", selector.get("content"));
});
});
I'd expect this code to add a SiteConfigId column to my Selectors so that my siteConfig.getSelectors() would return my testSelectors. How can I achieve this?
[UPDATE]
It turns out what I had earlier was wrong. The method setSiteConfig() is not what you want to use. I checked the db and it looks like Sequelize created two new records instead of associating the existing foo/bar selectors:
test=# select * from "Selectors";
id | type | content | createdAt | updatedAt | SiteConfigId
----+------+---------+----------------------------+----------------------------+--------------
1 | foo | foo | 2015-04-05 20:38:55.282-07 | 2015-04-05 20:38:55.282-07 |
2 | foo | bar | 2015-04-05 20:38:55.282-07 | 2015-04-05 20:38:55.282-07 |
3 | | | 2015-04-05 20:38:55.282-07 | 2015-04-05 20:38:55.311-07 | 1
4 | | | 2015-04-05 20:38:55.282-07 | 2015-04-05 20:38:55.31-07 | 1
So what is different? You can't use setSiteConfig on the child rows, instead you call addSelectors on siteConfig and pass in the selectors you want to associate. See updated code below.
Changed Promise variable to BPromise because node has a native Promise module now which would cause a conflict. Also I believe Sequelize has bluebird built-in so you can also just use Sequelize.Promise.
Removed the nested promise in your spread call because there is no need for it.
Side note: Promise.all returns a single result array so I don't think you should be using .spread().
var BPromise = require("bluebird");
var Sequelize = require("sequelize");
var sequelize = new Sequelize('test', 'root', 'password', {
host: "localhost",
dialect: "postgres",
pool: {
max: 5,
min: 0,
idle: 10000
}
});
var Schedule = sequelize.define("Schedule", {
website: {
type: Sequelize.STRING
}
});
var SiteConfig = sequelize.define("SiteConfig", {
systemType: {
type: Sequelize.STRING
}
});
var Selector = sequelize.define("Selector", {
type: {
type: Sequelize.STRING
},
content: {
type: Sequelize.STRING
}
});
Selector.belongsTo(SiteConfig);
SiteConfig.hasMany(Selector);
var testSchedule = {
website: "google.com"
};
var testSiteConfig = {
systemType: "one"
};
var testSelectors = [
{type: "foo", content: "foo"},
{type: "foo", content: "bar"}
];
sequelize.sync({ force: true })
.then(function(result) {
return BPromise.all([
Schedule.create(testSchedule),
SiteConfig.create(testSiteConfig),
Selector.bulkCreate(testSelectors, { returning: true })
]);
})
.then(function(result) {
var siteConfig = result[1];
var selectors = result[2];
return siteConfig.addSelectors(selectors);
})
.then(function (result) {
return this.siteConfig.getSelectors();
})
.each(function(result) {
console.log('boomshakalaka:', result.get());
})
.catch(function(error) {
console.log(error);
});

Categories

Resources