Query with Dynamic field values of Couchbase - couchbase

With the below structure of Couchbase bucket, how do I query if the nested doc has dynamic field name?
Here, I would like to return the customer docs who have account in Hyderabad
I tried to query this way but couldn't succeed.
select * from bucket where accounts.$.city = 'Hyderabad'
I was expecting to return the customer doc with email kp711#yahoo.com but couldn't succeed.
Couchbase docs
[
{
"type": "customer",
"customer_id": <UUID4>,
"user_type": "owner",
"first_name": "",
"last_name": "",
"email": "kp711#yahoo.com",
"password": "",
"phone_number": 11111,
"accounts": {
<account_id which is UUID4>: {
"amount": "500",
"city": "Hyderabad"
}
}
},
{
"type": "customer",
"customer_id": <UUID4>,
"user_type": "employee",
"first_name": "",
"last_name": "",
"email": "px800#yahoo.com",
"password": "",
"phone_number": 33333,
"accounts": {
<account_id which is UUID4>: {
"amount": "500",
"city": "Chennai"
}
}
}
]
Is there a way in Couchbase to fetch in this way?

SELECT b.*
FROM bucket AS b
WHERE ANY v IN OBJECT_VALUES(b.accounts) SATISFIES v.city = 'Hyderabad' END;
OR
SELECT b.*
FROM bucket AS b
WHERE ANY n:v IN b.accounts SATISFIES v.city = 'Hyderabad' END;

The answer from VSR provides a neat solution to the problem. Remember also that you will want to provide an index in order to leverage the Couchbase query services optimally. I'm guessing (based on your doc examples) that you will have multiple document types included in your bucket. With that in mind, here is an example index create statement:
create index idxTypeCustomer on bucket(type) where type = 'customer';
Now you can use it as part of your WHERE clause:
SELECT b.*
FROM bucket AS b
WHERE type = 'customer'
AND ANY v IN OBJECT_VALUES(b.accounts) SATISFIES v.city = 'Hyderabad' END;

Related

N1QL Query to join array fields with an array in another document

I have 3 documents types :
Data
{
"formId": "7508e7b2-bcf7-437b-a206-9fee87256d01",
"dataValues": [
{
"questionId": "Someguid123",
"questionValue": "Question1"
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
],
"lastUpdateDateTime": "2023-01-04T10:56:49Z",
"type": "Data",
"templateId": "41e4cc2c-e9fb-4bdc-9dc2-af19e5988984",
"creationDateTime": "2022-12-28T11:20:46Z"
}
AttachedDocuments
{
"id": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"lastUpdateDateTime": "2023-01-05T11:47:17Z",
"documents": [
{
"isUploaded": false,
"id": "DocumentMetadata::001",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
},
{
"isUploaded": false,
"id": "DocumentMetadata::002",
"isDeleted": false,
"type": "photo",
"parentId": "Someguid123"
}
],
"type": "AttachedDocuments",
"parentDocId": "MyFormData::7508e7b2-bcf7-437b-a206-9fee87256d01",
"creationDateTime": "2022-12-28T11:20:46Z"
}
DocumentMetaData
{
"id": "DocumentMetadata::001",
"type": "DocumentMetadata",
"name": "MyForm_001.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
},
{
"id": "DocumentMetadata::002",
"type": "DocumentMetadata",
"name": "MyForm_002.png",
"documentId": "549c4da2-ad3a-4f92-bfa2-019750a11007",
"contentType": "FILE",
"parentDocumentId": "AttachedDocuments::77961b70-2071-4410-837a-436c908a4fa5",
"creationDateTime": "2023-01-04T10:56:49Z"
}
Every Data type document has only one AttachedDocuments document with parentDocId* field set to formId field of Data document.
If items in Data.dataValues has a document attached to it, AttachedDocuments.documents array have items with parentId field set to Data.dataValues[i].questionId.
Also every AttachedDocuments.documents[i] item has a DocumentMetadata document with id of AttachedDocuments.documents[i].id field.
I want to have a query which returns all Data.dataValues as an array but containing a field links that contains the DocumentMetadata.name field like below :
[
{
"questionId": "Someguid123",
"questionValue": "Question1",
"links": ["MyForm_001.png", "MyForm_002.png"]
},
{
"questionId": "Someguid",
"questionValue": "Question2"
},
{
"questionId": "AnotherGuid",
"questionValue": "Question3"
}
]
I tried unnest clause but couldn't output datavalues items without documents. How should I write the query to include those also?
Thank you
Assuming you have a 1:1 relationship between Data & AttachedDocuments, you can try:
CREATE SCOPE default.f;
CREATE COLLECTION default.f.Data;
CREATE COLLECTION default.f.AttachedDocuments;
CREATE COLLECTION default.f.DocumentMetaData;
CREATE INDEX ix1 ON default.f.DocumentMetaData(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues AS dataValues
LET links = (SELECT RAW DocumentMetaData.name
FROM default.f.DocumentMetaData
WHERE DocumentMetaData.parentDocumentId = AttachedDocuments.id
AND id IN ARRAY a.id FOR a IN AttachedDocuments.documents WHEN a.parentId = dataValues.questionId END
)
;
If you have a 1:n relationship between Data & AttachedDocuments but the attachments for a single question are wholly in a single attached document:
CREATE INDEX ix2 ON default.f.AttachedDocuments(parentDocId);
CREATE INDEX ix3 ON default.f.AttachedDocuments(id);
SELECT dataValues.questionId, dataValues.questionValue, links
FROM default.f.Data join default.f.AttachedDocuments ON "MyFormData::"||Data.formId = AttachedDocuments.parentDocId
UNNEST Data.dataValues as dataValues
LET links = (SELECT RAW md.name
FROM default.f.AttachedDocuments ad JOIN default.f.DocumentMetaData md ON ad.id = md.parentDocumentId
UNNEST ad.documents d
WHERE ad.parentDocId = "MyFormData::"||Data.formId
AND d.id = md.id
AND d.parentId = dataValues.questionId
)
WHERE ANY dv IN AttachedDocuments.documents SATISFIES dv.parentId = dataValues.questionId END
;
If attachments for a single question can be spread over multiple attached documents, add a DISTINCT to the above statement.
HTH.
(You can use the same logic without collections adding appropriate aliasing and type field filtering.)

Unable to retrieve details for the JSON value subsection using Cosmos DB SQL

I have a JSON data field that I am trying to process using the Cosmos DB SQL.
Here is the JSON
{
"id": "consultationservice||8acd52b4-f4c3-4124-b1c3-89bc57a25f4f",
"value": {
"Id": "8acd52b4-f4c3-4124-b1c3-89bc57a25f4f",
"timeZone": "Pacific Info",
"proposedMeetingTimes": [
"2019-01-06T17:16:40",
"2019-01-06T17:16:40",
"2019-01-06T17:16:40"
],
"proposedMeetingTime": "2019-01-06T17:16:40",
"SomeInnerData": [
{
"Id": "25c52709-4ef6-4045-a623-5eca3957a532",
"Email": [
"email#email.com",
"email1#email.com",
"email2#email.com"
],
"connectionInformation": "The best connection",
"sentOn": "2019-01-06T17:16:40"
}
]
},
"partitionKey": "UserAvailability",
"_rid": "8BMWALYVDbIHAAAAAAAAAA==",
"_self": "dbs/8BMWAA==/colls/8BMWALYVDbI=/docs/8BMWALYVDbIHAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-169d-6f5b4f8201d7\"",
"_attachments": "attachments/",
"_ts": 1615484346
}
I am able to retrieve any data inside the VALUE section using this format
SELECT UserAvailability["value"].SomeInnerData
FROM UserAvailability
WHERE UserAvailability["value"].Id = '8acd52b4-f4c3-4124-b1c3-89bc57a25f4f'
or
SELECT UserAvailability["value"].Email
FROM UserAvailability
WHERE UserAvailability["value"].Id = '8acd52b4-f4c3-4124-b1c3-89bc57a25f4f'
However, no matter what I try to do, I can't retrieve any data inside the SomeInnerData (such as Id, Email, connectionInformation, sentOn) section. Please assist
Thank you in advance
Your SomeInnerData is an array, you can't retrieve data by UserAvailability["value"].SomeInnerData.Id.
If you only have one item in this array, you can try this:
SELECT UserAvailability["value"].SomeInnerData[0].Id,UserAvailability["value"].SomeInnerData[0].Email,UserAvailability["value"].SomeInnerData[0].connectionInformation,UserAvailability["value"].SomeInnerData[0].sentOn
FROM UserAvailability
JOIN SID IN UserAvailability["value"].SomeInnerData
WHERE UserAvailability["value"].Id = '8acd52b4-f4c3-4124-b1c3-89bc57a25f4f'
Result:
[
{
"Id": "25c52709-4ef6-4045-a623-5eca3957a532",
"Email": [
"email#email.com",
"email1#email.com",
"email2#email.com"
],
"connectionInformation": "The best connection",
"sentOn": "2019-01-06T17:16:40"
}
]
If you have many items, you should use JOIN. Join will result in a complete cross product of the sets participating in the join.
SELECT UserAvailability["value"].SomeInnerData,SID.Id,SID.Email,SID.connectionInformation,SID.sentOn
FROM UserAvailability
JOIN SID IN UserAvailability["value"].SomeInnerData
WHERE UserAvailability["value"].Id = '8acd52b4-f4c3-4124-b1c3-89bc57a25f4f'
Result:
[
{
"SomeInnerData": [
{
"Id": "25c52709-4ef6-4045-a623-5eca3957a532",
"Email": [
"email#email.com",
"email1#email.com",
"email2#email.com"
],
"connectionInformation": "The best connection",
"sentOn": "2019-01-06T17:16:40"
}
],
"Id": "25c52709-4ef6-4045-a623-5eca3957a532",
"Email": [
"email#email.com",
"email1#email.com",
"email2#email.com"
],
"connectionInformation": "The best connection",
"sentOn": "2019-01-06T17:16:40"
}
]

Format JSON Postgresql

I'm using inner join to join 3 tables, Owner, Store and Machine.
I'm trying to view output JSON from multiple tables like this:
SELECT ow.*, st.*, ma.*
FROM owner ow
INNER JOIN st.store ON ow.OwnerId = st.OwnerId
INNER JOIN machine ma ON ma.StoreId = st.StoreId;
I want JSON formatted like this:
{
"OwnerId": "1d2dd",
"Name": "name test",
"Store":[{
"StoreId": "s3ss5",
"Name": "Store1",
"Code": "bla",
"Machine":[{
"MachineId": "axpeo",
"Name": "Machine1",
"Type": "type1"
}]
},
{
"StoreId": "ddf22",
"Name": "Store2",
"Code": "ble",
"Machine":[{
"MachineId": "weds",
"Name": "Machine2",
"Type": "type2"
},
{
"MachineId": "axdso",
"Name": "Machine3",
"Type": "type3"
}]
}]
}
but the return JSON is not formatted like this
I'm using PostgreSQL.
The easiest (and probably only sensible) way to do this is to build JSON sub-documents from individual records at table level and only then hierarchically joining them:
SELECT json_build_object('OwnerId', ownerid,
'Name', name,
'Store', stores)
FROM owner
JOIN (
SELECT ownerid,
json_agg(
json_build_object('StoreId', storeid,
'Name', name,
'Code', code,
'Machine', machines)) AS stores
FROM store
JOIN (
SELECT storeid,
json_agg(
json_build_object('MachineId', machineid,
'Name', name,
'Type', type)) AS machines
FROM machine
GROUP BY storeid) m USING (storeid)
GROUP BY ownerid) s USING (ownerid);
The output is not exactly what i want, but it is better...this is the output
[{
"OwnerId": "1d2dd",
"Name": "name test",
"Store":{
"StoreId": "s3ss5",
"Name": "Store1",
"Code": "bla",
"Machine":{
"MachineId": "axpeo",
"Name": "Machine1",
"Type": "type1"
}
}
},
{
"OwnerId": "1d2dd",
"Name": "name test",
"Store":{
"StoreId": "ddf22",
"Name": "Store2",
"Code": "ble",
"Machine":{
"MachineId": "weds",
"Name": "Machine2",
"Type": "type2"
}
}
},
{
"OwnerId": "1d2dd",
"Name": "name test",
"Store":{
"StoreId": "ddf22",
"Name": "Store2",
"Code": "ble",
"Machine":{
"MachineId": "axdso",
"Name": "Machine3",
"Type": "type3"
}
}
}]
it does not join the machines from the same store yet like an array
For one-to-many relationships formatted to JSON try something like this:
SELECT "owner"."id",
json_agg(DISTINCT "store".*) AS "stores",
json_agg(DISTINCT "machine".*) AS "machines"
FROM "owners"
INNER JOIN "stores"
ON "stores"."ownerId" = "owners"."id"
INNER JOIN "machines"
ON "machines"."storeId" = "stores"."id"
WHERE "owner" = 1
GROUP BY "owner"."id";

Mongo DB issue with findOne query

I am maintaining a rooms table, where it consists of records associated with the conversations. I want to get the room id between two users so using findOne query but it's bringing other records and not satisfying my need.
Suggest me where the query has gone wrong.
If I give the query:
rooms.findOne({ "userId" :"800", "userId" :"600"});
I am expectng conversation id of fsny11z742kpgb9 but it's giving 6puebew70kke29.
{
"_id": ObjectId("571c5724db62826826d28d08"),
"conversationId": "6puebew70kke29",
"userId": "600",
"firstName": "Test",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:18:28.753Z"),
"__v": 0
}
{
"_id": ObjectId("571c5724db62826826d28d09"),
"conversationId": "6puebew70kke29",
"userId": "900",
"firstName": "User",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:18:28.754Z"),
"__v": 0
}
{
"_id": ObjectId("571c574edb62826826d28d0b"),
"conversationId": "fsny11z742kpgb9",
"userId": "600",
"firstName": "FitTest",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:19:10.192Z"),
"__v": 0
}
{
"_id": ObjectId("571c574edb62826826d28d0c"),
"conversationId": "fsny11z742kpgb9",
"userId": "800",
"firstName": "Dev",
"profileImagePath": "",
"created": ISODate("2016-04-24T05:19:10.193Z"),
"__v": 0
}
You have to use aggregation to do so.
rooms.aggregate([
{ $group: { _id: '$conversationId', users: { $push: '$userId' } } },
{ $match: { users: { $all: ['800', '600'] }, groupType: 'PRIVATE' } },
])
The findOne() operation returns the first document according to the natural order which reflects the order of documents on the disk, see mongodb docs.
Second, the query document you provide as parameter to the findOne() operation contains two values for userId, this is not the same as the $in operator. The latter one overrides the first one.
As Mathieu suggested, a proper lookup would be to use an aggregation pipeline with two steps:
rooms.aggregate([
{ $group: { _id: '$conversationId', users: { $push: '$userId' } } },
{ $match: { users: { $all: ['800', '600'] }, groupType: 'PRIVATE' } },
])
create list with id matching the conversation id and a field of type array containing all the userIds ($group stage)
filter out all entries, where the user-array contains the ids of the both users your are looking for. ($match stage)
Bear in mind, that this will return all conversations of both users.

Get Count Of Nested Entities using Laravel Eloquent

I have got 3 database tables clients, coupons and categories
clients table
id,
name,
website,
description,
logo,
slug
categories table
id,
name,
slug
coupons table
id,
client_id,
category_id,
type,
coupon,
title,
description,
link,
views,
slug,
expiry
The relationship is
1) many coupons belongs to client ( many to one relationship)
2) many coupons belongs to category ( many to one relationship)
I am using laravel 5.1.
How can i get the unique count of clients with the clients details , number of coupons a client has and the count of total categories an individual client has.
simplified : i need to get the client details and display that xxx number of coupons are available in the xxx number of categories for a particular client.
so far i can get the unique client details and the number of the coupons count.
public function getAvailableClientsWithItemCountList($page = 1)
{
return Client::join('coupons', 'clients.id', '=', 'coupons.client_id')
->join('categories', 'coupons.category_id', '=', 'categories.id')
->where('coupons.expiry', '>', Carbon::today())
->groupBy('clients.id')
->skip(STORES_PER_REQUEST*($page-1))
->take(STORES_PER_REQUEST)
->get(['clients.id', 'clients.name', 'clients.slug', 'clients.logo', DB::raw('count(clients.id) as dealsCount'), DB::raw('count(categories.id) as categoriesCount')]);
}
STORES_PER_REQUEST = 9 (constant) for paginating.
thanks in advance.
If you have your relationships set up you could do something like:
/**
* Mock relationship for eager loading coupon count
*
* #return mixed
*/
public function couponCount()
{
return $this->hasOne(Coupon::class)
->selectRaw('client_id, count(*) as aggregate')
->groupBy('client_id');
}
public function getCouponCountAttribute()
{
// if relation is not loaded already, let's do it first
if (!$this->relationLoaded('couponCount')) {
$this->load('couponCount');
}
$related = $this->getRelation('couponCount');
// then return the count directly
return ($related) ? (int) $related->aggregate : 0;
}
The above can be used in your Client model, and then you can just alter the couponCount relationship method for your Category model (if you wanted to).
Then add the following for your Category count:
/**
* Mock relationship for eager loading category count
*
* #return mixed
*/
public function categoryCount()
{
return $this->hasOne(Coupon::class)
->selectRaw('category_id, count(*) as aggregate')
->groupBy('client_id, category_id');
}
public function getCategoryCountAttribute()
{
// if relation is not loaded already, let's do it first
if (!$this->relationLoaded('categoryCount')) {
$this->load('categoryCount');
}
$related = $this->getRelation('categoryCount');
// then return the count directly
return ($related) ? (int) $related->aggregate : 0;
}
You can then add a query scope in your Coupon model for getting coupons that haven't expired by something like:
public function scopeActive($query)
{
$query->where('expiry', '>', Carbon::today());
}
If you're only ever going to be getting the count for coupons that haven't expired you can add you can add this straight on to the relationship e.g.
groupBy('client)id')->active()
Now you should be able to eager load the relationship like so:
$clients = Client::with('couponCount', 'clientCount')
->skip(STORES_PER_REQUEST * ($page - 1))
->take(STORES_PER_REQUEST)
->get();
Or you could attach the query scope to the eager load i.e.
$clients = Client::with(['couponCount' => function ($q) {$q->active()}, 'clientCount' => function ($q) {$q->active()}]) ...
Hope this helps!
Okay i figured out myself with additional info as coupon type and the categories available.
The key i did was just added the case in count and removed the join of the categories table.
the final code looked like this
return App\Client::join('coupons', 'clients.id', '=', 'coupons.client_id')
->where('coupons.expiry', '>', \Carbon\Carbon::today())
->orderBy('clients.position', 'desc')
->groupBy('clients.id')
->skip(STORES_PER_REQUEST*(1-1))
->take(STORES_PER_REQUEST)
->get(['clients.id', 'clients.name', 'clients.slug', 'clients.logo', DB::raw('count(clients.id) as total'), DB::raw('count(CASE WHEN coupons.type=\'Coupon\' THEN 1 ELSE NULL END) as couponsCount'), DB::raw('count(CASE WHEN coupons.type=\'Deals\' THEN 1 ELSE NULL END) as dealsCount'), DB::raw('count(Distinct category_id) as categoriesCount')]);
The result was
[{
"id": "8",
"name": "Archies Online",
"slug": "archies-online",
"logo": "Archiesonline.jpg",
"total": "22",
"couponsCount": "20",
"dealsCount": "2",
"categoriesCount": "9"
}, {
"id": "5",
"name": "Shop Clues",
"slug": "shop-clues",
"logo": "Shopclues.jpg",
"total": "24",
"couponsCount": "24",
"dealsCount": "0",
"categoriesCount": "9"
}, {
"id": "6",
"name": "Lens Kart",
"slug": "lens-kart",
"logo": "Lenskart.jpg",
"total": "25",
"couponsCount": "25",
"dealsCount": "0",
"categoriesCount": "8"
}, {
"id": "7",
"name": "Amazer",
"slug": "amazer",
"logo": "Amzer.jpg",
"total": "21",
"couponsCount": "21",
"dealsCount": "0",
"categoriesCount": "8"
}, {
"id": "1",
"name": "Flipkart",
"slug": "flipkart",
"logo": "Flipkart.jpg",
"total": "17",
"couponsCount": "17",
"dealsCount": "0",
"categoriesCount": "9"
}, {
"id": "2",
"name": "Make My Trip",
"slug": "make-my-trip",
"logo": "Makemytrip.jpg",
"total": "11",
"couponsCount": "11",
"dealsCount": "0",
"categoriesCount": "8"
}]
This did the trick for now :);