Sequelize - Count Based on Include Condition - javascript

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 }));

Related

saveData method saves twice

I am building a React app that includes one separate component for CRUD functionality of Products and another separate component for CRUD functionality of Suppliers.
I am using the same saveData method for both components (the Create functionality of CRUD.. that is triggered when the User presses Save after filling in the input fields of Product or Supplier). The saveData method is located in a central ProductsAndSuppliers.js file that is available to both the Products and Supplier components.
In both of the Product & Supplier components, there is a table showing the Products or Suppliers already present as dummy data.
I made a button at the bottom of each page to add a new Product or Supplier... depending on which tab the user has selected on the left side of the screen (Product or Supplier).
Since I am using the same saveData method in both cases, I have the same problem whenever I try to add a new Product or Supplier to each respective table after filling out the input fields. My new Product or Supplier is added.. but twice and I can't figure out why.
I have tried using a spread operator to add the new item to the collection but am having no success:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState((collection) => {
return { ...collection, item }
})
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
Here is my original saveData method that adds the new Product or Supplier, but twice:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState(state => state[collection]
= state[collection].concat(item));
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
my state looks like this:
this.state = {
products: [
{ id: 1, name: "Kayak", category: "Watersports", price: 275 },
{ id: 2, name: "Lifejacket", category: "Watersports", price: 48.95 },
{ id: 3, name: "Soccer Ball", category: "Soccer", price: 19.50 },
],
suppliers: [
{ id: 1, name: "Surf Dudes", city: "San Jose", products: [1, 2] },
{ id: 2, name: "Field Supplies", city: "New York", products: [3] },
]
}
There are issues with both of your implementations.
Starting with the top one:
// don't do this
this.setState((collection) => {
return { ...collection, item }
})
In this case, collection is your component state and you're adding a property called item to it. You're going to get this as a result:
{
products: [],
suppliers: [],
item: item
}
The correct way to do this with the spread operator is to return an object that represents the state update. You can use a computed property name to target the appropriate collection:
this.setState((state) => ({
[collection]: [...state[collection], item]
})
)
* Note that both this and the example below are using the implicit return feature of arrow functions. Note the parens around the object.
In the second code sample you're
mutating the existing state directly which you should not do.
returning an array instead of a state update object.
// don't do this
this.setState(state =>
state[collection] = state[collection].concat(item)
);
Assignment expressions return the assigned value, so this code returns an array instead of an object and I'd frankly be surprised if this worked at all.
The correct implementation is the same as above except it uses concat instead of spread to create the new array:
this.setState(state => ({
[collection]: state[collection].concat(item)
})
);
needlessly fancy, arguably silly id generators:
const nextId = (function idGen (start = 100) {
let current = start;
return () => current++;
})(100);
console.log(nextId()); // 100
console.log(nextId()); // 101
console.log(nextId()); // 102
// ----------------
// a literal generator, just for fun
const ids = (function* IdGenerator(start = 300) {
let id = start;
while (true) {
yield id++;
}
})();
console.log(ids.next().value); // 300
console.log(ids.next().value); // 301
console.log(ids.next().value); // 302

map method not working at some place using javascript / node js

I have Output in JSON format and i want to specific field from it
{
"id":"01",
"name":"fish",
"Data.id":"f01",
"Data.path":"/home/work/fish.jpg"
}
I am using map function to get the value but the problem is i can only fetch the value of id and name not Data.id and Data.path
so i am getting this value from my database and this is my code by how i am getting the value from database
function runRest(req, res) {
let data = req.body;
Owner.findAll({
raw: true,
where: {
id: data.id,
},
include: {
model: kingdom,
required: false,
},
attributes: ["id", "name"],
})
.then((parents) => {
parents.map((value) => {
console.log(value);
});
})
.catch(function (err) {
console.log(err);
});
}
let value={
"id":"01",
"name":"fish",
"Data.id":"f01",
"Data.path":"/home/work/fish.jpg"
};
value.map((data)=>{
console.log(data.id);
});
I can only fetch data which is in white font color which is ID and name any solution how can i get Data.id and Data.path by using map function
I even tried this
let links = value
.map((child) => {
for (let i in child)
if (i === "Data.id") {
return child[i];
}
})
but i don't want to use this for method any chance I can use Map function ?
The object
values = {
"id":"01",
"name":"fish",
"Data.id":"f01",
"Data.path":"/home/work/fish.jpg"
};
Has the keys: "id", "name", "Data.id", "Data.path"
To get the value "f01" you must use the key "Data.id":
foo = values["Data.id"]; // foo === "f01"
In the comments you mention that you want to map an array of these objects to the data id:
idArray = objects.map(value => value["Data.id"]);

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,
},
})
)
)

Rxjs filter observable with array of filters

I'm here because i have a problem for filtering with Rxjs.
I'm trying to filter an observable of products with an array of filters...
Let me explain, I would like to set the result of my filtering to filteredProducts.
For filtering i have to check, for each filter, if the product's filter array contains the name of the filter and if the products values array's contains filter id.
For the moment, the filter works but only with the last selected filter and i'd like to filter products list with all filters in my selectedFilters array
export class ProductsFilterComponent extends BaseComponent implements OnInit {
#Select(FiltersState.getAllFilters) filters$: Observable<any>;
#Input() products$: Observable<Product[]>;
filteredProducts$: Observable<Product[]>;
public selectedFilters = [];
constructor(
private store: Store) { super(); }
ngOnInit() {
this.store.dispatch(new GetAllFilters());
}
private filterProducts() {
this.filteredProducts$ = this.products$.pipe(
map(
productsArr => productsArr.filter(
p =>
p.filters.some(f => this.selectedFilters.some(([selectedF]) => selectedF === f.name.toLowerCase()) // Filter name
&& f.values.some(value => this.selectedFilters.some(([, filterId]) => filterId === value)) // Filter id
)
)
)
);
this.filteredProducts$.subscribe(res => console.log('filtered:', res));
}
}
Here's the structure of a product object
Here's the structure of selectedFilters
A big thank you in advance :-).
I think you have to change selectedFilters to BehaviorSubject and use that with products$ observable. There is combineLatest function which listens for latest values of multiple observables and returns an array
Example
window.products$ = rxjs.of([
{
id: 1,
name: "product 1",
category: {
id: 1,
name: 'test'
},
filters: [
{
name: "test1",
values: [1]
}
],
url: '/assets/test1.png'
},
{
id: 2,
name: "product 2",
category: {
id: 1,
name: 'test'
},
filters: [
{
name: "test2",
values: [2]
}
],
url: '/assets/test2.png'
}
])
window.filteredProducts$ = null;
window.selectedFilters = new rxjs.BehaviorSubject([])
function filterProducts() {
filteredProducts$ = rxjs.combineLatest(products$, selectedFilters)
.pipe(
rxjs.operators.map(
([products, filters]) => products.filter(
product =>
product.filters.some(filter => filters.some(([filterName]) => filterName === filter.name.toLowerCase())
&& filter.values.some(value => filters.some(([, filterId]) => filterId === value))
)
)
)
);
filteredProducts$.subscribe(res => console.log('filtered:', res));
}
filterProducts()
window.selectedFilters.next([...window.selectedFilters.value, ['test1', 1]]);
window.selectedFilters.next([...window.selectedFilters.value, ['test2', 2]]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
Your code seems fine, but can be modified a bit.
For start, I would clean the predicate, you can use lodash intersectionWith function to intersect two arrays with different values.
Also, you can use mergeAll operator to iterate filter by filter, which makes this look less nested.
Overall it should look something like this:
products$
.pipe(
mergeAll(),
filter(
product =>
_.intersectionWith(
product.filters,
selectedFilters,
(filter, [name, id]) =>
filter.name.toLowerCase() === name && filter.values.includes(id)
).length > 0
)
)
.subscribe(console.log);
You can run the full example code here
There's is the error i get. And the lines where there's the error.

Counting relationship in sequelize returnings 1 instead of 0

I have the following test case for sequelize where I am attempting to return the count of a relationship on a model.
test('Tag with file count', async () => {
const tagOne = await db.Tag.create({
label: 'One'
})
const tagTwo = await db.Tag.create({
label: 'Two'
})
const tagThree = await db.Tag.create({
label: 'Three'
})
const tagFour = await db.Tag.create({
label: 'Four'
})
const tagFive = await db.Tag.create({
label: 'Five'
})
const fileOne = await db.File.create()
const fileTwo = await db.File.create()
await fileOne.setTags([tagOne, tagTwo, tagFour])
await fileTwo.setTags([tagOne, tagTwo, tagThree])
const output = await db.Tag.findAll({
attributes: {
include: [
[Sequelize.fn('COUNT', 'Files.id'), 'fileCount']
]
},
include: [
{
model: db.File,
as: 'files',
attributes: [],
duplicate: false
}
],
group: 'Tag.id',
order: [
[Sequelize.literal('`fileCount`'), 'DESC']
]
})
expect(output.length).toBe(5)
expect(output.find(tag => tag.label === tagOne.label).dataValues.fileCount).toBe(2)
expect(output.find(tag => tag.label === tagTwo.label).dataValues.fileCount).toBe(2)
expect(output.find(tag => tag.label === tagThree.label).dataValues.fileCount).toBe(1)
expect(output.find(tag => tag.label === tagFour.label).dataValues.fileCount).toBe(1)
expect(output.find(tag => tag.label === tagFive.label).dataValues.fileCount).toBe(0)
})
For tagFive, I expected the column fileCount to be 0 but it returns 1. For others it seems to return the correct file count.
Is there an option missing in findAll or a bug?
Edit
I get the following query generated
Executing (default): SELECT `Tag`.`id`,
`Tag`.`label`,
`Tag`.`slug`,
`Tag`.`createdAt`,
`Tag`.`updatedAt`, COUNT('Files.id') AS `fileCount`,
`files->FileTags`.`createdAt` AS `files.FileTags.createdAt`,
`files->FileTags`.`updatedAt` AS `files.FileTags.updatedAt`,
`files->FileTags`.`FileId` AS `files.FileTags.FileId`,
`files->FileTags`.`TagId` AS `files.FileTags.TagId`
FROM `Tags` AS `Tag`
LEFT OUTER JOIN `FileTags` AS `files->FileTags`
ON `Tag`.`id` = `files->FileTags`.`TagId`
LEFT OUTER JOIN `Files` AS `files`
ON `files`.`id` = `files->FileTags`.`FileId`
GROUP BY `Tag`.`id` ORDER BY `fileCount` DESC;
Though there's so many alias and I am not comfortable with joins.
i made a few changes into something that would work in my database with a similar structure... This did return me some 0 count rows, however this is postgres i am running it on, and you are clearly using something different
SELECT tag.id,
COUNT(story.id) AS fileCount
FROM tag
LEFT OUTER JOIN story_tag
ON tag.name = story_tag.tag
LEFT OUTER JOIN story
ON story.id = story_tag.story_id
GROUP BY tag.id ORDER BY fileCount DESC;

Categories

Resources