Returning VALUE or NULL in Azure Cosmos DB subquery - azure-cosmosdb

The structure of the Person document I store in Azure Cosmos DB looks like this:
{
"id": "some-id",
"firstName": "John",
"lastName": "Doe",
"emails": [
{
"id": "email1",
"email": "test#testsite.com",
"isPrimary": true
},
{
"id": "email2",
"email": "test2#testsite.com",
"isPrimary": false
}
]
},
{
"id": "some-other-id",
"firstName": "Jane",
"lastName": "Doe",
"emails": []
}
I'm trying to return the basic info of a person, including his/her primary email if they have one and NULL for email if there isn't a primary email.
This is the SQL statement I'm using but it's not returning Jane Doe at all because "I think" the JOIN is making the email required data.
SELECT c.id, c.firstName, c.lastName, email
FROM c JOIN (SELECT e.email FROM e IN c.emails WHERE e.isPrimary = true) AS email
I also tried the following but that doesn't Jane Doe at all.
SELECT c.id, c.firstName, c.lastName, e.email
FROM c JOIN e IN c.emails
WHERE e.isPrimary = true
I thought the following worked but that also seems to filter out some documents where there's an email but not a primary one.
SELECT c.id, c.firstName, c.lastName, e.email
FROM c JOIN e IN c.emails
WHERE ARRAY_LENGTH(c.emails) = 0 OR e.isPrimary = true
Just to reiterate the requirements: I want ALL persons. If the person has one or more emails and one is the primary one, I want to include only the primary email in the result. If the person doesn't have any emails or has emails but not a primary one, I still want the person in the results but email should be NULL for that particular person.
It's easy to do a SELECT * FROM c but I only want to get the primary email if the person has one or a NULL if they don't.

SELECT c.firstName, c.lastName,
ARRAY(SELECT VALUE e.email FROM e in c.emails WHERE e.isPrimary = true or ARRAY_LENGTH(c.emails) = 0) as email
FROM c

Related

How does one create a GSI for an Item in a Array in DyanmoDB?

I have a DyanmmoDB table that has:
Partition key: State (IE: two letter State ID)
Sort Key: City (Name of city in the state)
Items in the "record" is an array, let's say Advertisements
"StateShort": "AK",
"City": "Anchorage",
"Ads": [
{
"AdDateAdded": 1674671363999,
"AdDateExpire": 1682447536551,
"AdIDKey": "ABC-123-GUID-Here",
"AdTitle": "This is the Title to Ad 1"
"AdDescription": "Description of the Details to Ad 1",
"AdOwner": "bob#example.com",
},
{
"AdDateAdded": 1674671363999,
"AdDateExpire": 1682447536551,
"AdIDKey": "DEF-456-GUID-Here",
"AdTitle": "This is the Title to Ad 2"
"AdDescription": "Description of the Details to Ad 2",
"AdOwner": "bob#example.com",
}
]
Query to retrieve all ads in State and City, easy-peasy, as they are PK and SK.
but, I do NOT want to Scan to find all the ads that AdOwner has ("bob#example.com"). They may have Ads in other states and city requiring me to Scan entire table.
FEELS like a perfect use case for a Global secondary indexes.
I've added AdOwner as a GSI but, clearly it can't find the key in the array.
Question: Is this solvable with a GSI? If so, what structure would that look like?
After creating the GSI, I've tried this code but, it returns no items
const params = {
"TableName": "My_table",
"IndexName": "AdEmail-index",
"KeyConditionExpression": "#IndexName = :AccountID",
"ExpressionAttributeNames": {
"#IndexName": "AdOwner",
},
ExpressionAttributeValues: {
":AccountID": "bob#example",
},
"ScanIndexForward": false
}
try{
const item = await dynamo.query(params).promise()
console.log("what: ", item)
}
catch (e) {
console.log("ERROR", e)
}
No, a global secondary index key must be a top level attribute and be of type string, number or binary.
You should vertically shard your items, giving you more flexibility:
pk
sk
data
AdOwner
AK
Anchorage#BC-123-GUID-Here
{}
bob#example.com
AK
Anchorage#BDEF-456-GUID-Here
{}
bob#example.com
All ads in a state and city, still easy, using Query:
SELECT * FROM MY TABLE WHERE pk = 'AK' AND sk BEGINS_WITH 'Anchorage'
You can now create a GSI on the AdOwner to fulfill your second access pattern.
SELECT * FROM MY TABLE.INDEX WHERE AdOwnder = 'bob#example.com'

How to query data in Cosmos db from nested json

I have some difficulty in writing a query to query data from nested json in Cosmos db.
Sample json -
{
"id": xyz
"items": [
{
"arr_id": 1,
"randomval": "abc"
},
{
"arr_id": 2,
"randomval": "mno"
},
{
"arr_id": 1,
"randomval": "xyz"
}
]
}
Lets say in above case, if i want to get all jsons data with arr_id = 1.
Expected Result -
{
"id": xyz
"items": [
{
"arr_id": 1,
"randomval": "abc"
},
{
"arr_id": 1,
"randomval": "xyz"
}
]
}
If i write a query like below, it still gives me entire json.
Select * from c where ARRAY_CONTAINS(c.items, {"arr_id": 1},true)
I want it to filter it items level too. I guess it just filters at header level and provides entire json where even a single arr_id matches.
You can use either
SELECT c.id, ARRAY(SELECT VALUE i FROM i in c.items where i.arr_id = 1) as items
FROM c
WHERE EXISTS(SELECT VALUE i FROM i in c.items where i.arr_id = 1)
or
SELECT c.id, ARRAY(SELECT VALUE i FROM i in c.items where i.arr_id = 1) as items
FROM c
depending on whether you expect an empty array if no arrayItem with arr_id=1 exists or you wnat to filter out those records compeletely.
Also see this link for a good overview of query options across arrays - https://devblogs.microsoft.com/cosmosdb/understanding-how-to-query-arrays-in-azure-cosmos-db/

Union Results of Multiple OPENJSON calls

I have a table that stores 1 json object per row, I want to call OPENJSON on the json object stored in each row and union together all of the results. I don't know the number of rows I will have ahead of time.
Here is some example data to reference
DROP TABLE #tmp_json_tbl
DECLARE #json1 NVARCHAR(2048) = N'{
"members": [
{
"name": "bob",
"status": "subscribed"
},
{
"name": "larry",
"status": "unsubscribed"
}
]
}';
SELECT #json1 as json_obj,1 as jid into #tmp_json_tbl
INSERT into #tmp_json_tbl
VALUES ( N'{
"members": [
{
"name": "bob",
"status": "subscribed"
},
{
"name": "larry",
"status": "unsubscribed"
}
]
}',2 );
SELECT * from #tmp_json_tbl
--how can i recursively union together results for all values of jid?
-- I could use a cursor but I would rather figure out a way to do it using a recursive cte
SELECT * FROM OpenJson((SELECT json_obj from #tmp_json_tbl where jid=1), '$.members')
WITH (
name VARCHAR(80) '$.name',
mstatus varchar(100) '$.status'
)```
This is what I wanted
SELECT name, m_status
FROM #tmp_json
CROSS APPLY OPENJSON(j, '$.members')
WITH (
name VARCHAR(80) '$.name',
m_status varchar(100) '$.status'
)
Found my answer here: How to use OPENJSON on multiple rows

Cosmos DB - How to access fields that have a space in their name?

I have some data in Cosmos DB where the fields have spaces in their names, like this:
{
"First Name": "John",
"Middle Name": null,
"Last Name": "Doe",
...
}
I'm trying to run some SQL queries from an Azure Notebook, but I can't access these fields in either of the following ways
select
a.[First Name]
from SomeTable a
where a.id = "00000000-0000-0000-0000-000000000000"
select
a.["First Name"]
from SomeTable a
where a.id = "00000000-0000-0000-0000-000000000000"
It works fine for fields that don't have a space. Is there a way to access these fields with spaces in the SELECT or WHERE clause?
Apparently the way to do this is:
select
a["First Name"]
from SomeTable a
where a.id = "00000000-0000-0000-0000-000000000000"
Don't add a period after the table/table alias...

Return the content of a specific object in an array — CosmosDB

This is a follow up to question 56126817
My current query
SELECT c.EventType.EndDeviceEventDetail FROM c
WHERE c.EventType.EndDeviceEventType.eventOrAction = '93'
AND c.EventType.EndDeviceEventType.subdomain = '137'
AND c.EventType.EndDeviceEventType.domain = '26'
AND c.EventType.EndDeviceEventType.type = '3'
AND ARRAY_CONTAINS(c.EventType.EndDeviceEventDetail,{"name":
"RCDSwitchReleased","value": "true" })
My Query Output
[
{
"EndDeviceEventDetail": [
{
"name": "Spontaneous",
"value": "true"
},
{
"name": "DetectionActive",
"value": "true"
},
{
"name": "RCDSwitchReleased",
"value": "true"
}
]
}
]
Question
How could change my query so that I select only the "value" of the array that contains the "name" "DetectionActive" ?
The idea behind is to filter the query on one array entry and get as output the "value" of another array entry. From reading here, UDF (not the best in this case) and JOIN should be used.
First attempt
SELECT t.value FROM c JOIN t in c.EventType.EndDeviceEventDetail
WHERE c.EventType.EndDeviceEventType.eventOrAction = '93'
AND c.EventType.EndDeviceEventType.subdomain = '137'
AND c.EventType.EndDeviceEventType.domain = '26'
AND c.EventType.EndDeviceEventType.type = '3'
AND ARRAY_CONTAINS(c.EventType.EndDeviceEventDetail,{"name":
"RCDSwitchReleased","value": "true" })
Gets Bad Request (400) error
Your idea and direction is right absolutely, I simplified and tested your sql.
SELECT detail.value FROM c
join detail in c.EventType.EndDeviceEventDetail
WHERE c.EventType.EndDeviceEventType.eventOrAction = '93'
AND ARRAY_CONTAINS(c.EventType.EndDeviceEventDetail,{"name":
"RCDSwitchReleased","value": "true" })
Found the error message as below:
It because that the value is the reserved word in cosmos db sql syntax,please refer to this case:Using reserved word field name in DocumentDB
You could try to modify the sql like:
SELECT detail["value"] FROM c

Resources