FaunaDB get document stored with foreign key - database

I've created two tables in FaunaDB, "customers" and "orders". I created an Index, to get a document by a data.transactionObj.transactionId.
So far so good, but how can I extend the query below, to get the document referenced in data.customerRef?
This query:
Map(
Paginate(Match(Index("orders_by_trx"), "220704142800610948")),
Lambda("x", Get(Var("x")))
)
returns
{
data: [
{
ref: Ref(Collection("orders"), "336256180042072264"),
ts: 1660641327310000,
data: {
customerRef: Ref(Collection("customers"), "340132521031237836"),
transactionObj: {
transactionId: "220704142200610948",
status: "transmitted",
refno: "l56pwwmiHuber",
currency: "CHF",
paymentMethod: "TWI",
amount: 100
},
cart: {
cards: [
{
id: "629f6e2f909b7c89f114a874",
name: "MCA Couchtisch Sakura",
verkaufspreis: 99,
anzahl: 1,
}
]
}
}
}
]
}

You can compose the response as an arbitrary JSON value.
You haven't specified what structure you need, so I'm assuming that you want something like:
{
order: <order document>,
customer: <customer document>,
}
To accomplish that, here's an updated query that should work:
Map(
Paginate(Match(Index("orders_by_trx"), "220704142800610948")),
Lambda(
"order_ref",
Let(
{
order: Get(Var("order_ref")),
customer_ref: Select(["data", "customerRef"], Var("order")),
customer: Get(Var("customer_ref")),
},
{
order: Var("order"),
customer: Var("customer")
}
)
)
)
The query used the Let function to hold intermediate results and compose the response structure.
Fauna queries can perform any number of reads and writes provided that you don't exceed the transaction limits.

Related

is there any possible way with upsert the document of array in mongodb [duplicate]

I have the following collection
{
"_id" : ObjectId("57315ba4846dd82425ca2408"),
"myarray" : [
{
userId : ObjectId("570ca5e48dbe673802c2d035"),
point : 5
},
{
userId : ObjectId("613ca5e48dbe673802c2d521"),
point : 2
},
]
}
These are my questions
I want to push into myarray if userId doesn't exist, it should be appended to myarray. If userId exists, it should be updated to point.
I found this
db.collection.update({
_id : ObjectId("57315ba4846dd82425ca2408"),
"myarray.userId" : ObjectId("570ca5e48dbe673802c2d035")
}, {
$set: { "myarray.$.point": 10 }
})
But if userId doesn't exist, nothing happens.
and
db.collection.update({
_id : ObjectId("57315ba4846dd82425ca2408")
}, {
$push: {
"myarray": {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
})
But if userId object already exists, it will push again.
What is the best way to do this in MongoDB?
Try this
db.collection.update(
{ _id : ObjectId("57315ba4846dd82425ca2408")},
{ $pull: {"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")}}
)
db.collection.update(
{ _id : ObjectId("57315ba4846dd82425ca2408")},
{ $push: {"myarray": {
userId:ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}}
)
Explination:
in the first statment $pull removes the element with userId= ObjectId("570ca5e48dbe673802c2d035") from the array on the document where _id = ObjectId("57315ba4846dd82425ca2408")
In the second one $push inserts
this object { userId:ObjectId("570ca5e48dbe673802c2d035"), point: 10 } in the same array.
The accepted answer by Flying Fisher is that the existing record will first be deleted, and then it will be pushed again.
A safer approach (common sense) would be to try to update the record first, and if that did not find a match, insert it, like so:
// first try to overwrite existing value
var result = db.collection.update(
{
_id : ObjectId("57315ba4846dd82425ca2408"),
"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")
},
{
$set: {"myarray.$.point": {point: 10}}
}
);
// you probably need to modify the following if-statement to some async callback
// checking depending on your server-side code and mongodb-driver
if(!result.nMatched)
{
// record not found, so create a new entry
// this can be done using $addToSet:
db.collection.update(
{
_id: ObjectId("57315ba4846dd82425ca2408")
},
{
$addToSet: {
myarray: {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
}
);
// OR (the equivalent) using $push:
db.collection.update(
{
_id: ObjectId("57315ba4846dd82425ca2408"),
"myarray.userId": {$ne: ObjectId("570ca5e48dbe673802c2d035"}}
},
{
$push: {
myarray: {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
}
);
}
This should also give (common sense, untested) an increase in performance, if in most cases the record already exists, only the first query will be executed.
There is a option called update documents with aggregation pipeline starting from MongoDB v4.2,
check condition $cond if userId in myarray.userId or not
if yes then $map to iterate loop of myarray array and check condition if userId match then merge with new document using $mergeObjects
if no then $concatArrays to concat new object and myarray
let _id = ObjectId("57315ba4846dd82425ca2408");
let updateDoc = {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
};
db.collection.update(
{ _id: _id },
[{
$set: {
myarray: {
$cond: [
{ $in: [updateDoc.userId, "$myarray.userId"] },
{
$map: {
input: "$myarray",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{ $eq: ["$$this.userId", updateDoc.userId] },
updateDoc,
{}
]
}
]
}
}
},
{ $concatArrays: ["$myarray", [updateDoc]] }
]
}
}
}]
)
Playground
Unfortunately "upsert" operation is not possible on embedded array. Operators simply do not exist so that this is not possible in a single statement.Hence you must perform two update operations in order to do what you want. Also the order of application for these two updates is important to get desired result.
I haven't found any solutions based on a one atomic query. Instead there are 3 ways based on a sequence of two queries:
always $pull (to remove the item from array), then $push (to add the updated item to array)
db.collection.update(
{ _id : ObjectId("57315ba4846dd82425ca2408")},
{ $pull: {"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")}}
)
db.collection.update(
{ _id : ObjectId("57315ba4846dd82425ca2408")},
{
$push: {
"myarray": {
userId:ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
}
)
try to $set (to update the item in array if exists), then get the result and check if the updating operation successed or if a $push needs (to insert the item)
var result = db.collection.update(
{
_id : ObjectId("57315ba4846dd82425ca2408"),
"myarray.userId": ObjectId("570ca5e48dbe673802c2d035")
},
{
$set: {"myarray.$.point": {point: 10}}
}
);
if(!result.nMatched){
db.collection.update({_id: ObjectId("57315ba4846dd82425ca2408")},
{
$addToSet: {
myarray: {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
);
always $addToSet (to add the item if not exists), then always $set to update the item in array
db.collection.update({_id: ObjectId("57315ba4846dd82425ca2408")},
myarray: { $not: { $elemMatch: {userId: ObjectId("570ca5e48dbe673802c2d035")} } } },
{
$addToSet : {
myarray: {
userId: ObjectId("570ca5e48dbe673802c2d035"),
point: 10
}
}
},
{ multi: false, upsert: false});
db.collection.update({
_id: ObjectId("57315ba4846dd82425ca2408"),
"myArray.userId": ObjectId("570ca5e48dbe673802c2d035")
},
{ $set : { myArray.$.point: 10 } },
{ multi: false, upsert: false});
1st and 2nd way are unsafe, so transaction must be established to avoid two concurrent requests could push the same item generating a duplicate.
3rd way is safer. the $addToSet adds only if the item doesn't exist, otherwise nothing happens. In case of two concurrent requests, only one of them adds the missing item to the array.
Possible solution with aggregation pipeline:
db.collection.update(
{ _id },
[
{
$set: {
myarray: { $filter: {
input: '$myarray',
as: 'myarray',
cond: { $ne: ['$$myarray.userId', ObjectId('570ca5e48dbe673802c2d035')] },
} },
},
},
{
$set: {
myarray: {
$concatArrays: [
'$myarray',
[{ userId: ObjectId('570ca5e48dbe673802c2d035'), point: 10 },
],
],
},
},
},
],
);
We use 2 stages:
filter myarray (= remove element if userId exist)
concat filtered myarray with new element;
When you want update or insert value in array try it
Object in db
key:name,
key1:name1,
arr:[
{
val:1,
val2:1
}
]
Query
var query = {
$inc:{
"arr.0.val": 2,
"arr.0.val2": 2
}
}
.updateOne( { "key": name }, query, { upsert: true }
key:name,
key1:name1,
arr:[
{
val:3,
val2:3
}
]
In MongoDB 3.6 it is now possible to upsert elements in an array.
array update and create don't mix in under one query, if you care much about atomicity then there's this solution:
normalise your schema to,
{
"_id" : ObjectId("57315ba4846dd82425ca2408"),
userId : ObjectId("570ca5e48dbe673802c2d035"),
point : 5
}
You could use a variation of the .forEach/.updateOne method I currently use in mongosh CLI to do things like that. In the .forEach, you might be able to set all of your if/then conditions that you mentioned.
Example of .forEach/.updateOne:
let medications = db.medications.aggregate([
{$match: {patient_id: {$exists: true}}}
]).toArray();
medications.forEach(med => {
try {
db.patients.updateOne({patient_id: med.patient_id},
{$push: {medications: med}}
)
} catch {
console.log("Didn't find match for patient_id. Could not add this med to a patient.")
}
})
This may not be the most "MongoDB way" to do it, but it definitely works and gives you the freedom of javascript to do things within the .forEach.

Adding object into an array of a document in mongoDB

Scenario:
There are two collections, one contains products and the other contains reviews. Both products and reviews collection have a field called "productID". Task is to gather all fields from individual review document and add them to the corresponding product document.
My approach:
I am trying to collect all the products and then iterate through each one.
This is to individually extract productID and pass it over to find all reviews that match with the productID.
All the reviews are then stored in a variable.
4.Finally, I try to update the current product by pushing the extracted fields from documents that match with productID
Code:
var cursor = db.products.find({}, {_id: 0})
while (cursor.hasNext()) {
var currDoc = cursor.next();
var pID = currDoc.productID;
var revs = db.reviews.find({productID: pID}, {
_id: 0,
stars: 1,
reviewTitle: 1,
reviewText: 1,
})
db.products.update({ productID: pID }, { $push: { reviews: revs } })
Expect:
products {
productID: ##,
productName: "asdfghjkl",
productPrice: ##.##,
reviews:
[
{
stars: 1,
reviewTitle: "Avoid",
reviewText: "Not very great"
}
]
}
Actual:
BSONError: cyclic dependency detected
Convert your PID to string. it will return as an object which needs to be changed as string
db.products.find().forEach(function(doc){
var reviews = db.reviews.find({'productId':str(doc._id)}, {
_id: 0,
stars: 1,
reviewTitle: 1,
reviewText: "Not very great",
})
db.products.update({ "_id": doc._id },{ "$set": { "reviews": reviews } });
})

Mongoose FindOne - only return fields which match condition

I am trying to query my collection of matches (games) and find if a certain user has already sent data to the 'reportMessages' array of Objects.
const results = await Match.findOne({ 'users': req.params.userIdOfReportSender, '_id': req.params.matchId, 'reportMessages.sentBy': req.params.userIdOfReportSender }, 'reportMessages' )
However, the above query returns the following:
{
_id: 5fd382c65d5395e0778f2f8a,
reportMessages: [
{
_id: 5fd610f27ae587189c45b6ca,
content: 'jajatest',
timeStamp: 2020-12-13T13:02:42.102Z,
sentBy: 'XbVvm6g3nsRmPg3P1pBvVl84h6C2'
},
{ sentBy: "'anotheruser123" }
]
}
How can I get it to only return the first reportMessage, i.e. the one sent by XbVvm6g3nsRmPg3P1pBvVl84h6C2?
Mongoose findOne docs (https://mongoosejs.com/docs/api.html#model_Model.findOne) show that you can provide arguments to say which fields to select (in their case 'name length' but don't show a way to only select the fields in case they match a certain condition.
Is this even possible? Tried googling this seemingly easy question for quite some time without success
Kind regards
You can get only the subdocument you want with this aggregation query:
Match.aggregate([
{
$match: { _id: req.params.matchId }
},
{
$project: {
reportMessages: {
$filter: {
input: '$reportMessages',
as: 'msg',
cond: { $eq: ['$$msg.sentBy', req.params.userIdOfReportSender] }
}
}
}
},
{
$project: {
reportMessage: { $arrayElemAt: [ '$reportMessages', 0 ] },
}
},
{ $replaceWith: '$reportMessage' }
]);
Note that you only need to specify the document _id to get a single result, since _ids are unique.

ElasticSearch multi field search in non-nested arrays

Consider this JSON content:
{
students: [
{
student_name: aaa,
classes: [
{
name: class1,
properties: {
id: 1
}
},
{
name: class2,
properties: {
id: 2
}
}
},
{
student_name: bbb,
classes: [
{
name: class2,
properties: {
id: 1
}
}
}
]
}
students is not nested. classes is not nested. I only want to match student_name aaa document by this below query:
{
"query": {
"bool": {
"must": [
{ "term": { "classes.name": "class1" }},
{ "term": { "classes.properties.id": "1" }}
]
}
}
}
But my query is matching both aaa and bbb, because it is considering my term statements as separate queries. How can I just match student aaa?
You have to make the student as a nested type. Otherwise it gets flattened and your query will match both the documents.
From the same ElasticSearch documentation:
When ingesting key-value pairs with a large, arbitrary set of keys,
you might consider modeling each key-value pair as its own nested
document with key and value fields. Instead, consider using the
flattened data type, which maps an entire object as a single field and
allows for simple searches over its contents. Nested documents and
queries are typically expensive, so using the flattened data type for
this use case is a better option.
Please refer to examples given in the same documentation and it will be clear to you. When student is changed to nested you should be able to get your expected results.

Graphql mutation query : how to access the data elements

const MUTATION_QUERY = gql`
mutation MUTATION_QUERY(
$name: bigint!
) {
insert_name(
objects: {
name: $name
}
) {
returning {
id
name
}
}
}
`;
const [onClick, { error, data }] = useMutation<{}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
My mutation query is inserting name in my table and autogenerating the id. On console logging the data variable I can view the fields id and name in the data object. But I am not able to access them them individually. How can I console.log "id". Thank you.
the console.log(data) looks like : {insert_name: {...}}
which expands to :
insert_name:
returning: Array(1)
0: {id: 1, name: 1234}
length: 1
_proto_: Array(0)
_typename: "insert_name_mutation_response
You can access the fields of an object with .
For example, if your object looks like this -
data = {
id: 1,
name: 'Jane',
}
You can get just the id with data.id
This works no matter how many layers deep your object may go, so take this example -
data = {
person: {
id: 1,
name: 'Jane',
}
}
You could get the id of person with data.person.id.
console.log(data.insert_name.returning[0].id) will give you the id returned.
For it to work in typescript we need to change the query to add the return type of data
const [onClick, { error, data }] = useMutation<{ReturnInsertNameProps}, {}>(MUTATION_QUERY, {
variables: {
name: 1234,
},
});
interface ReturnInsertNameProps {
insert_name: ReturnQueryProps;
}
interface ReturnProps {
returning: MessageProps[];
}
interface NameProps {
id: number;
name: number;
}
We can also use onCompleted method provided in useMutation if we want to process the result of the query.

Resources