N1QL (Couchbase) - True LEFT JOIN in couchbase for non-existing relationships - couchbase

For simplicity's sake, lets say I have the following DB:
{
id: "a_id", // This is the key, not a field in the document
type: "a"
}
{
id: "b_id",
type: "b",
a: "a_id"
}
{
type: "c",
b: "b_id"
}
As you can see, there is a classic parent-child relationship ("a" HAS MANY "b", "b" HAS MANY "c")
Lets assume that there will not always be a type "c" document - for example, I have five "b" documents, but only one has a "c" document related to it.
I want to write a JOIN query that will give me all "b" that belong to "a", and if they exist - all "c" that belong to "b", but for "b" documents that don't have "c" I don't get any results - I would like to get all five "b" documents, as well as the one "c" document that is related.
My query looks like this:
SELECT *
FROM default AS a
JOIN default AS b ON KEY b.a FOR a
JOIN default AS c ON KEY c.b FOR b
WHERE a.type = "a"
AND b.type = "b"
AND c.type = "c";
What am I missing here?
Thanks!

WHERE clause post join filter if if c is MISSING c.type = ā€œcā€ is false.
INSERT INTO default VALUES("a_id", { "type": "a" });
INSERT INTO default VALUES("b_id", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("b_id1", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("b_id1", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("b_id2", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("b_id3", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("b_id4", { "type": "b", "a":"a_id"});
INSERT INTO default VALUES("c_id", { "type": "c", "b":"b_id"});
CREATE INDEX ia ON default(a);
CREATE INDEX ib ON default(b);
SELECT *
FROM default AS d
LEFT JOIN default AS d1 ON KEY d1.a FOR d
LEFT JOIN default AS d2 ON KEY d2.b FOR d1
WHERE d.type = "a"
AND d1.type = "b"
AND (d2 IS MISSING OR d2.type = "c");
Also checkout https://dzone.com/articles/visually-explaining-n1ql-joins

Related

Conditionally update JSON column

I have a table which has ID & JSON columns. ID is auto incrementing column. Here are my sample data.
Row 1
1 | {
"HeaderInfo":
{
"Name": "ABC",
"Period": "2010",
"Code": "123"
},
"HData":
[
{ "ID1": "1", "Value": "$1.00", "Code": "A", "Desc": "asdf" },
{ "ID1": "2", "Value": "$1.00", "Code": "B", "Desc": "pqr" },
{ "ID1": "3", "Value": "$1.00", "Code": "C", "Desc": "xyz" }
]
}
Row 2
2 | {
"HeaderInfo":
{
"Name": "ABC",
"Period": "2010",
"Code": "123"
},
"HData":
[
{ "ID1": "76", "Value": "$1.00", "Code": "X", "Desc": "asdf" },
{ "ID1": "25", "Value": "$1.00", "Code": "Y", "Desc": "pqr" },
{ "ID1": "52", "Value": "$1.00", "Code": "Z", "Desc": "lmno" },
{ "ID1": "52", "Value": "$1.00", "Code": "B", "Desc": "xyz" }
]
}
and it keep goes. Items inside the HData section is infinite. It can be any numbers of items.
On this JSON I need to update the Value = "$2.00" where "Code" is "B". I should be able to do this with 2 scenarios. My parameter inputs are #id=2, #code="B", #value="$2.00". #id sometimes will be null. So,
If #id is null then the update statement should go through all records and update the Value="$2.00" for all items inside the HData section which has Code="B".
If #id = 2 then the update statement should update only the second row which Id is 2 for the items which Code="b"
Appreciate your help in advance.
Thanks
See DB Fiddle for an example.
declare #id bigint = 2
, #code nvarchar(8) = 'B'
, #value nvarchar(8) = '$2.00'
update a
set json = JSON_MODIFY(json, '$.HData[' + HData.[key] + '].Value', #value)
from so75416277 a
CROSS APPLY OPENJSON (json, '$.HData') HData
CROSS APPLY OPENJSON (HData.Value, '$')
WITH (
ID1 bigint
, Value nvarchar(8)
, Code nvarchar(8)
, [Desc] nvarchar(8)
) as HDataItem
WHERE id = #id
AND HDataItem.Code = #Code
The update / set statement says we want to replace the value of json with a new generated value / functions exactly the same as it would in any other context; e.g. update a set json = 'something' from so75416277 a where a.column = 'some condition'
The JSON_MODIFY does the manipulation of our json.
The first input is the original json field's value
The second is the path to the value to be updated.
The third is the new value
'$.HData[' + HData.[key] + '].Value' says we go from our JSON's root ($), find the HData field, filter the array of values for the one we're after (i.e. key here is the array item's index), then use the Value field of this item.
key is a special term; where we don't have a WITH block accompanying our OPENJSON statement we get back 3 items: key, value and type; key being the identifier, value being the content, and type saying what sort of content that is.
CROSS APPLY allows us to perform logic on a value from a single DB rowto return potentially multiple rows; e.g. like a join but against its own contents.
OPENJSON (json, '$.HData') HData says to extract the HData field from our json column, and return this with the table alias HData; as we've not included a WITH, this HData column has 3 fields; key, value, and type, as mentioned above (this is the same key we used in our JSONMODIFY).
The next OPENJSON works on HData.Value; i.e. the contents of the array item under HData. Here we take the object from this array (i.e. that's the root from the current context; hence $), and use WITH to parse it into a specific structure; i.e. ID1, Value, Code, and Desc (brackets around Desc as it's a keyword). We give this the alias HDataItem.
Finally we filter for the bit of the data we're interested in; i.e. on id to get the row we want to update, then on HDataItem.Code so we only update those array items with code 'B'.
Try the below SP.
CREATE PROC usp_update_75416277
(
#id Int = null,
#code Varchar(15),
#value Varchar(15)
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQLStr Varchar(MAX)=''
;WITH CTE
AS
( SELECT ROW_NUMBER()OVER(PARTITION BY YourTable.Json ORDER BY (SELECT NULL))RowNo,*
FROM YourTable
CROSS APPLY OPENJSON(YourTable.Json,'$.HData')
WITH (
ID1 Int '$.ID1',
Value Varchar(20) '$.Value',
Code Varchar(20) '$.Code',
[Desc] Varchar(20) '$.Desc'
) HData
WHERE (#id IS NULL OR ID =#id)
)
SELECT #SQLStr=#SQLStr+' UPDATE YourTable
SET [JSON]=JSON_MODIFY(YourTable.Json,
''$.HData['+CONVERT(VARCHAR(15),RowNo-1)+'].Value'',
'''+CONVERT(VARCHAR(MAX),#value)+''') '+
'WHERE ID ='+CONVERT(Varchar(15),CTE.ID) +' '
FROM CTE
WHERE Code=#code
AND (#id IS NULL OR ID =#id)
EXEC( #SQLStr)
END

Recursive parent child JSON Postgresql

I have the following recursive PostgreSQL table.
CREATE TABLE public."data" (
id int4 NULL,
parentid int4 NULL,
menu varchar NULL
);
I am trying to create a JSON object from the table.
[
{
"menu": "home",
"children": [
{
"menu": "home 1",
"children": []
},
{
"menu": "home 2",
"children": [
{
"menu": "home 2.1",
"children": []
}
]
}
]
},
{
"menu": "config",
"children": []
}
]
How could I create a hierarchal object like this?
First you should use the jsonb format instead of the json format in postgres, see the documentation here :
In general, most applications should prefer to store JSON data as
jsonb, unless there are quite specialized needs, such as legacy
assumptions about ordering of object keys..
Then, assuming your table is a set of (id, menu) tuples and parentid is the id of the parent of this tuple, you can try :
CREATE VIEW parent_children (parent, children, root, cond) AS
( SELECT jsonb_build_object('menu', p.menu, 'children', '[]' :: jsonb) :: text AS parent
, jsonb_agg(jsonb_build_object('menu', c.menu, 'children', '[]' :: jsonb)) :: text AS children
, array[c.parentid] AS root
, array[c.parentid] AS cond
FROM public.data AS c
LEFT JOIN public.data AS p
ON p.id = c.parentid
GROUP BY c.parentid
) ;
WITH RECURSIVE list(parent, children, root, cond) AS
( SELECT parent, children, root, cond
FROM parent_children
WHERE root = array[NULL] -- start with the root parents
UNION
SELECT p.parent
, replace(p.children, c.parent, replace(c.parent, '[]', c.children))
, p.root
, p.cond || c.cond
FROM list AS p
INNER JOIN parent_children AS c
ON position(c.parent IN p.children) > 0
AND NOT p.cond #> c.root -- condition to avoid circular path
)
SELECT children :: jsonb
FROM list AS l
ORDER BY array_length(cond, 1) DESC
LIMIT 1 ;

Update JSON data type column in MySql table

I have started using MySQL 8 and trying to update JSON data type in a mysql table
My table t1 looks as below:
# id group names
1100000 group1 [{"name": "name1", "type": "user"}, {"name": "name2", "type": "user"}, {"name": "techDept", "type": "dept"}]
I want to add user3 to the group1 and written below query:
update t1 set names = JSON_SET(names, "$.name", JSON_ARRAY('user3')) where group = 'group1';
However, the above query is not working
I suppose you want the result to be:
[{"name": "name1", "type": "user"}, {"name": "name2", "type": "user"}, {"name": "techDept", "type": "dept"}, {"name": "user3", "type": "user"}]
This should work:
UPDATE t1 SET names = JSON_ARRAY_APPEND(names, '$', JSON_OBJECT('name', 'user3', 'type', 'user'))
WHERE `group` = 'group1';
But it's not clear why you are using JSON at all. The normal way to store this data would be to create a second table for group members:
CREATE TABLE group_members (
member_id INT PRIMARY KEY,
`group` VARCHAR(10) NOT NULL,
member_type ENUM('user','dept') NOT NULL DEFAULT 'user',
name VARCHAR(10) NOT NULL
);
Then store one per row.
Adding a new member would be like:
INSERT INTO group_members
SET `group` = 'group1', name = 'user3';
So much simpler than using JSON!

Full text search in concrete node in json

I has table "Product" with two columns:
Id - Bigint primary key
data - Jsonb
Here example of json:
{
"availability": [
{
"qty": 10,
"price": 42511,
"store": {
"name": "my_best_store",
"hours": null,
"title": {
"en": null
},
"coords": null,
"address": null,
I insert json to column "data".
Here sql get find "my_best_store"
select *
from product
where to_tsvector(product.data) ## to_tsquery('my_best_store')
Nice. It's work fine.
But I need to find "my_best_store" only in section "availability".
I try this but result is empty:
select *
from product
where to_tsvector(product.data) ## to_tsquery('availability & my_best_store')
Assuming you want to search in the name attribute, you can do the following:
select p.*
from product p
where exists (select *
from jsonb_array_elements(p.data -> 'availability') as t(item)
where to_tsvector(t.item -> 'store' ->> 'name') ## to_tsquery('my_best_store'))
With Postgres 12, you can simplify that to:
select p.*
from product p
where to_tsvector(jsonb_path_query_array(data, '$.availability[*].store.name')) ## to_tsquery('my_best_store')

couchbase n1qlQuery Delete with sub-query

I have one bucket contain 2 types of objects:
first:
{
"id": "123"
"objectNamespace": "a",
"value": "value1"
}
second:
{
"id": "234",
"objectNamespace": "b",
"value": "value2",
"association": ["123"]
}
now I want to delete the document from type a only if does NOT have any associations from type b:
I try this:
DELETE FROM `bukcet_name`
WHERE objectNamespace = 'a'
AND id = "123"
AND NOT EXISTS (
SELECT *
WHERE ANY item IN bukcet_name.association
SATISFIES item = "123" END);
BUT this always delete the a doc with id 123
How can I do that?
There are a couple of mismatches between your data and your query.
(1) You are missing a FROM clause.
(2) You use associations instead of association.
(3) bucket_name.
Here is a possible query.
DELETE FROM `bucket_name`
WHERE objectNamespace = 'a'
AND id = "123"
AND NOT EXISTS (
SELECT * FROM bucket_name b2
WHERE ANY item IN b2.association
SATISFIES item = "123" END);