How to get nested documents in FaunaDB with a filter? - faunadb

The following query:
Paginate(Documents(Collection("backyard"))),
Lambda(
"f",
Let(
{
backyard: Get(Var("f")),
user: Get(Select(["data", "user"], Var("backyard")))
},
{
backyard: Var("backyard"),
user: Var("user")
}
)
)
)
results to:
{
data: [
{
backyard: {
ref: Ref(Collection("backyard"), "333719283470172352"),
ts: 1654518359560000,
data: {
user: Ref(Collection("user"), "333718599460978887"),
product: "15358",
date: "2022-06-06",
counter: "1"
}
},
user: {
ref: Ref(Collection("user"), "333718599460978887"),
ts: 1654517707220000,
data: {
email: "<email>",
name: "Paolo"
}
}
},
{
backyard: {
ref: Ref(Collection("backyard"), "333747850716381384"),
ts: 1654545603400000,
data: {
user: Ref(Collection("user"), "333718599460978887"),
product: "15358",
date: "2022-06-08",
counter: "4"
}
},
user: {
ref: Ref(Collection("user"), "333718599460978887"),
ts: 1654517707220000,
data: {
email: "<email>",
name: "Paolo"
}
}
}
]
}
How can I filter backyard by date without losing the nested users?
I tried:
Map(
Paginate(Range(Match(Index("backyard_by_date")), "2022-05-08", "2022-06-08")),
Lambda(
"f",
Let(
{
backyard: Get(Var("f")),
user: Get(Select(["data", "user"], Var("backyard")))
},
{
backyard: Var("backyard"),
user: Var("user")
}
)
)
)
However, the resultset is an empty array and the following already returns an empty array:
Paginate(Range(Match(Index("backyard_by_date")), "2022-05-08", "2022-06-08"))
My index:
{
name: "backyard_by_date",
unique: false,
serialized: true,
source: "backyard"
}
Maybe I have to adjust my index? The following helped me a lot:
How to get nested documents in FaunaDB?
How to Get Data from two collection in faunadb
how to join collections in faunadb?

Your index definition is missing details. Once that gets fixed, everything else you were doing is exactly right.
In your provided index, there are no terms or values specified, which makes the backyard_by_date index a "collection" index: it only records the references of every document in the collection. In this way, it is functionally equivalent to using the Documents function but incurs additional write operations as documents are created or updated within the backyard collection.
To make your query work, you should delete your existing index and (after 60 seconds) redefine it like this:
CreateIndex({
name: "backyard_by_date",
source: Collection("backyard"),
values: [
{field: ["data", "date"]},
{field: ["ref"]}
]
})
That definition configures the index to return the date field and the reference for every document.
Let's confirm that the index returns what we expect:
> Paginate(Match(Index("backyard_by_date")))
{
data: [
[ '2022-06-06', Ref(Collection("backyard"), "333719283470172352") ],
[ '2022-06-08', Ref(Collection("backyard"), "333747850716381384") ]
]
}
Placing the date field's value first means that we can use it effectively in Range:
> Paginate(Range(Match(Index("backyard_by_date")), "2022-05-08", "2022-06-08"))
{
data: [
[ '2022-06-06', Ref(Collection("backyard"), "333719283470172352") ],
[ '2022-06-08', Ref(Collection("backyard"), "333747850716381384") ]
]
}
And to verify that Range is working as expected:
> Paginate(Range(Match(Index("backyard_by_date")), "2022-06-07", "2022-06-08"))
{
data: [
[ '2022-06-08', Ref(Collection("backyard"), "333747850716381384") ]
]
}
Now that we know the index is working correctly, your filter query needs a few adjustments:
> Map(
Paginate(
Range(Match(Index("backyard_by_date")), "2022-05-08", "2022-06-08")
),
Lambda(
["date", "ref"],
Let(
{
backyard: Get(Var("ref")),
user: Get(Select(["data", "user"], Var("backyard")))
},
{
backyard: Var("backyard"),
user: Var("user")
}
)
)
)
{
data: [
{
backyard: {
ref: Ref(Collection("backyard"), "333719283470172352"),
ts: 1657918078190000,
data: {
user: Ref(Collection("user"), "333718599460978887"),
product: '15358',
date: '2022-06-06',
counter: '1'
}
},
user: {
ref: Ref(Collection("user"), "333718599460978887"),
ts: 1657918123870000,
data: { name: 'Paolo', email: '<email>' }
}
},
{
backyard: {
ref: Ref(Collection("backyard"), "333747850716381384"),
ts: 1657918172850000,
data: {
user: Ref(Collection("user"), "333718599460978887"),
product: '15358',
date: '2022-06-08',
counter: '4'
}
},
user: {
ref: Ref(Collection("user"), "333718599460978887"),
ts: 1657918123870000,
data: { name: 'Paolo', email: '<email>' }
}
}
]
}
Since the index returns a date string and a reference, the Lambda inside the Map has to accept those values as arguments. Aside from renaming f to ref, the rest of your query is unchanged.

Related

How do I index a value which is a #relation in a document in FaunaDB?

Is it possible to create an index of a value from a #relation type in FaunaDB? Here is the schema but I just cannot figure out how to create an index for what would be the data.testing.status value.
type TestType {
testing: Testing!
}
type Testing {
status: PaymentStatus!
testType: [TestType!] #relation
}
enum PaymentStatus {
PAID
UNPAID
}
I don't know if the enum is causing an issue? I can't find any documentation on this.
Here is the query:
Map(
Paginate(Match(Index("certificate_remittance_by_remittance"), "UNPAID")),
Lambda("ref", Get(Var("ref")))
)
and the relevant document data:
"ref": Ref(Collection("Certificate"), "302119834927235593"),
"ts": 1624382777140000,
"data": {
"remittance": Ref(Collection("Remittance"), "302119834830766601"),
}
and remittance document:
{
"ref": Ref(Collection("Remittance"), "302119834830766601"),
"ts": 1624382777140000,
"data": {
"status": "UNPAID",
"chequeNumber": "",
"remittanceOwed": 245,
"remittanceAmount": 245
}
}
Certainly.
The enum portion of your schema isn't causing an issue. If it were, you would not have been able to import the schema successfully.
With your provided schema, note that the GraphQL API has already created a relationship index for you:
Get(Index("testType_testing_by_testing"))
{
ref: Index("testType_testing_by_testing"),
ts: 1624315929200000,
active: true,
serialized: true,
name: "testType_testing_by_testing",
source: Collection("TestType"),
data: {
gql: {
ts: Time("2021-06-21T22:52:08.969203Z")
}
},
terms: [
{
field: ["data", "testing"]
}
],
unique: false,
partitions: 1
}
To add an index for the TestType collection, on the status field, you'd run:
CreateIndex({
name: "TestType_by_status",
source: Collection("TestType"),
terms: [
{ field: ["data", "status"] },
]
})

How to select all documents where one date field is after another date field in FaunaDB

I have a very simple collection with documents that look like this:
{
...
latestEdit: Time(...),
lastPublished: Time(...)
}
I would like to query all documents that have a latestEdit time that's after lastPublished time.
I find FQL to be very different to SQL and I'm finding the transition quite hard.
Any help much appreciated.
Fauna's FQL is not declarative, so you have to construct the appropriate indexes and queries to help you solve problems like this.
Fauna indexes have a feature called "bindings", which allow you to provide a user-defined function that can compute a value based on document values. The binding lets us index the computed value by itself (rather than having to index on latestEdit or lastPublished). Here's what that might look like:
CreateIndex({
name: "edit_after_published",
source: {
collection: Collection("test"),
fields: {
needsPublish: Query(
Lambda(
"doc",
Let(
{
latestEdit: Select(["data", "latestEdit"], Var("doc")),
lastPublished: Select(["data", "lastPublished"], Var("doc")),
},
If(
GT(Var("latestEdit"), Var("lastPublished")),
true,
false
)
)
)
)
}
},
terms: [ { binding: "needsPublish" } ]
})
You can see that we define a binding called needsPublish. The binding uses Let to define named values for the two document fields that we want to compare, and then the If statement checks to see if the latestEdit value is greather than lastPublished value: when it is we return true, otherwise we return false. Then, the binding is used in the index's terms definition, which defines the fields that we want to be able to search on.
I created sample documents in a collection called test, like so:
> Create(Collection("test"), { data: { name: "first", latestEdit: Now(), lastPublished: TimeSubtract(Now(), 1, "day") }})
{
ref: Ref(Collection("test"), "306026106743423488"),
ts: 1628108088190000,
data: {
name: 'first',
latestEdit: Time("2021-08-04T20:14:48.121Z"),
lastPublished: Time("2021-08-03T20:14:48.121Z")
}
}
> Create(Collection("test"), { data: { name: "second", lastPublished: Now(), latestEdit: TimeSubtract(Now(), 1, "day") }})
{
ref: Ref(Collection("test"), "306026150784664064"),
ts: 1628108130150000,
data: {
name: 'second',
lastPublished: Time("2021-08-04T20:15:30.148Z"),
latestEdit: Time("2021-08-03T20:15:30.148Z")
}
}
The first document subtracts one day from lastPublished and the second document subtracts one day from latestEdit, to test both conditions of the binding.
Then we can query for all documents where needsPublish results in true:
> Map(Paginate(Match(Index("edit_after_published"), true)), Lambda("X", Get(Var("X"))))
{
data: [
{
ref: Ref(Collection("test"), "306026106743423488"),
ts: 1628108088190000,
data: {
name: 'first',
latestEdit: Time("2021-08-04T20:14:48.121Z"),
lastPublished: Time("2021-08-03T20:14:48.121Z")
}
}
]
}
And we can also query for all documents where needsPublish is false:
> Map(Paginate(Match(Index("edit_after_published"), false)), Lambda("X", Get(Var("X"))))
{
data: [
{
ref: Ref(Collection("test"), "306026150784664064"),
ts: 1628108130150000,
data: {
name: 'second',
lastPublished: Time("2021-08-04T20:15:30.148Z"),
latestEdit: Time("2021-08-03T20:15:30.148Z")
}
}
]
}

FaunaDB get entries by date range with index binding not working

I am struggling to get an Index by Date to work with a Range.
I have this collection called orders:
CreateCollection({name: "orders"})
And I have these sample entries, with one attribute called mydate. As you see it is just a string. And I do need to create the date as a string since in my DB we already have around 12K records with dates like that so I cant just start using the Date() to create them.
Create(Collection("orders"), {data: {"mydate": "2020-07-10"}})
Create(Collection("orders"), {data: {"mydate": "2020-07-11"}})
Create(Collection("orders"), {data: {"mydate": "2020-07-12"}})
I have created this index that computes the date to and actual Date object
CreateIndex({
name: "orders_by_my_date",
source: [
{
collection: Collection("orders"),
fields: {
date: Query(Lambda("order", Date(Select(["data", "mydate"], Var("order"))))),
},
},
],
terms: [
{
binding: "date",
},
],
});
If I try to fetch a single date the index works.
// this works
Paginate(
Match(Index("orders_by_my_date"), Date("2020-07-10"))
);
// ---
{
data: [Ref(Collection("orders"), "278496072502870530")]
}
But when I try to get a Range it never finds data.
// This does NOT work :(
Paginate(
Range(Match(Index("orders_by_my_date")), Date("2020-07-09"), Date("2020-07-15"))
);
// ---
{
data: []
}
Why the index does not work with a Range?
Range operates on the values of an index, not on the terms.
See: https://docs.fauna.com/fauna/current/api/fql/functions/range?lang=javascript
You need to change your index definition to:
CreateIndex({
name: "orders_by_my_date",
source: [
{
collection: Collection("orders"),
fields: {
date: Query(Lambda("order", Date(Select(["data", "mydate"], Var("order"))))),
},
},
],
values: [
{ binding: "date" },
{ field: ["ref"] },
],
})
Then you can get the results that you expect:
> Paginate(Range(Match(Index('orders')), Date('2020-07-11'), Date('2020-07-15')))
{
data: [
[
Date("2020-07-11"),
Ref(Collection("orders"), "278586211497411072")
],
[
Date("2020-07-12"),
Ref(Collection("orders"), "278586213229658624")
],
[
Date("2020-07-13"),
Ref(Collection("orders"), "278586215000703488")
],
[
Date("2020-07-14"),
Ref(Collection("orders"), "278586216887091712")
],
[
Date("2020-07-15"),
Ref(Collection("orders"), "278586218585784832")
]
]
}
Another alternative is to use a filter with a lambda expression to validate which values you want
Filter(
Paginate(Documents(Collection('orders'))),
Lambda('order',
And(
GTE(Select(['data', 'mydate'], Var('order')), '2020-07-09'),
LTE(Select(['data', 'mydate'], Var('order')), '2020-07-15')
)
)
)
You can update the conditions as you need
I believe this will work with the strings you have already
There are some mistakes here, first of all, you have to create documents that way:
Create(Collection("orders"), {data: {"mydate": ToDate("2020-07-10")}})
The index has to be created like this:
CreateIndex(
{
name: "orders_by_my_date",
source: Collection("orders"),
values:[{field:['data','mydate']},{field:['ref']}]
}
)
and finally, you can query your index and range:
Paginate(Range(Match('orders_by_my_date'),[Date("2020-07-09")], [Date("2020-07-15")]))
{ data:
[ [ Date("2020-07-10"),
Ref(Collection("orders"), "278532030954734085") ],
[ Date("2020-07-11"),
Ref(Collection("orders"), "278532033804763655") ],
[ Date("2020-07-12"),
Ref(Collection("orders"), "278532036737630725") ] ] }
or if you want to get the full doc:
Map(Paginate(Range(Match('orders_by_my_date'),[Date("2020-07-09")], [Date("2020-07-15")])),Lambda(['date','ref'],Get(Var('ref'))))
{ data:
[ { ref: Ref(Collection("orders"), "278532030954734085"),
ts: 1601887694290000,
data: { mydate: Date("2020-07-10") } },
{ ref: Ref(Collection("orders"), "278532033804763655"),
ts: 1601887697015000,
data: { mydate: Date("2020-07-11") } },
{ ref: Ref(Collection("orders"), "278532036737630725"),
ts: 1601887699800000,
data: { mydate: Date("2020-07-12") } } ] }

Using lodash to retrieve values from a complex array

I have the following complex array
[
{
label: "Country1",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 234184
},
{
label: "abc",
metric: "abc",
value: 145678
}
]
},
{
label: "Country2",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 123456
},
{
label: "abc",
metric: "abc",
value: 456789
}
]
},
{
label: "Country3",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 62389
},
{
label: "abc",
metric: "abc",
value: 4964738
}
]
}
]
I need to convert it to the following simple array wherein from the metrics sub array the values for label and value becomes a key value pair.
[
{label: “Country1”, xyz: 234184, abc: 145678},
{label: “Country2”, xyz: 123456, abc: 456789},
{label: “Country3”, xyz: 62389, abc: 4964738}
]
Can this conversion happen using lodash?
There might be a better / cleaner way to do this, but this is what I could come up with in just a few minutes.
const inputData = [
{
label: "Country1",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 234184
},
{
label: "abc",
metric: "abc",
value: 145678
}
]
},
{
label: "Country2",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 123456
},
{
label: "abc",
metric: "abc",
value: 456789
}
]
},
{
label: "Country3",
metrics: [
{
label: "xyz",
metric: "xyz",
value: 62389
},
{
label: "abc",
metric: "abc",
value: 4964738
}
]
}
];
const newData = _.map(inputData, first => {
const additional = _.map(first.metrics, metric => [metric.label, metric.value]);
return _.assign({}, _.fromPairs(additional), {
label: first.label
});
});
console.log(newData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.js"></script>
Here's a solution that doesn't require lodash, you simply have to take advantage of using Array#map to transform each item in the top-level array, and then use Array#reduce to transform the object and get the rest of the properties you need. The methodology used below takes advantage of the following ES6 features:
Destructuring assignment to extract the properties from the Array#map and Array#reduce callbacks.
Spread Syntax to combine properties of objects from different sources.
Computed property names for a dynamic property name in an object.
var result = data.map(({ label, metrics }) =>
metrics.reduce(
(result, { metric, value }) => ({ ...result, [metric]: value }),
{ label }
)
);
var data = [{label:"Country1",metrics:[{label:"xyz",metric:"xyz",value:234184},{label:"abc",metric:"abc",value:145678}]},{label:"Country2",metrics:[{label:"xyz",metric:"xyz",value:123456},{label:"abc",metric:"abc",value:456789}]},{label:"Country3",metrics:[{label:"xyz",metric:"xyz",value:62389},{label:"abc",metric:"abc",value:4964738}]}];
var result = data.map(({ label, metrics }) =>
metrics.reduce(
(result, { metric, value }) => ({ ...result, [metric]: value }),
{ label }
)
);
console.log(result);
.as-console-wrapper{min-height:100%;top:0}

MongoDB Query solution

What is the optimize Query for this situation
So the Situation is a User is following many XY user and these XY have got events, So what will the best and optimize query to get all the events from his followers XY in sorted form (sort by Date). I have got create Date in my schema
This is my User Schema
var userSchema = new Schema({
followers:[{
type: Schema.Types.ObjectId,
ref: 'XY'
}]
});
var User = mongoose.model('User',userSchema);
module.exports = User;
Here is My XY schema
var XY= new Schema({
events:[{
type: Schema.Types.ObjectId,
ref: 'Event'
}],
});
var XY= mongoose.model('XY',XY);
module.exports = XY;
Try this.
User.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(<userId here>) } },
{ $unwind: { path: "$followers" } },
{ $lookup: { from: 'XY', localField: 'followers', foreignField: '_id', as: 'followers' } },
{ $unwind: { path: "$followers" } },
{ $unwind: { path: "$followers.events" } },
{ $lookup: { from: 'Event', localField: 'followers.events', foreignField: '_id', as: 'followers.events' } },
{ $unwind: { path: "$followers.events" } },
{ $sort: { "followers.events.createdDate": **-1** } }, // -1 -> desc, 1 -> asc
{
"$project":
{
"_id": "$followers.events._id",
"createdDate": "$followers.events.createdDate",
// populate other event details here accordingly
}
}
], function (err, events, next) {
});
$lookup lets you populate a sub-document from a different schema.
After populating, the resultant documents will be an array, so $unwind is used before working on them.
note that $unwind is also used before doing a $lookup here as the field we are trying to populate is itself an array.