Couchbase N1QL - Nest within a Nest - couchbase
Nesting within a Nest.
I've adapted my need into the following restaurant example:
Desired Output:
{
"restaurant": {
"id": "restaurant1",
"name": "Foodie",
"mains": [ // < main nested in restaurant
{
"id": "main1",
"title": "Steak and Chips",
"ingredients": [ // < ingredient nested in main (...which is nested in restaurant)
{
"id": "ingredient1",
"title": "steak"
},
{
"id": "ingredient2",
"title": "chips"
}
]
},
{
"id": "main2",
"title": "Fish and Chips",
"ingredients": [
{
"id": "ingredient3",
"title": "fish"
},
{
"id": "ingredient2",
"title": "chips"
}
]
}
]
"drinks": [ you get the idea ] // < drink nested in restaurant
}
}
Example Docs:
// RESTAURANTS
{
"id": "restaurant1",
"type": "restaurant",
"name": "Foodie",
"drinkIds": [ "drink1", "drink2" ],
"mainIds: [ "main1", "main2" ]
},
// MAINS
{
"id": "main1",
"type": "main",
"restaurantIds": [ "restaurant1" ],
"title": "Steak and Chips"
},
{
"id": "main2",
"type": "main",
"restaurantIds": [ "restaurant1" ],
"title": "Fish and Chips"
},
// INGREDIENTS
{
"id": "ingredient1",
"type": "ingredient",
"title": "steak",
"mainIds": [ "main1" ]
},
{
"id": "ingredient2",
"type": "ingredient",
"title": "chips",
"mainIds": [ "main1", "main2" ]
},
{
"id": "ingredient3",
"type": "ingredient",
"title": "fish",
"mainIds": [ "main2" ]
},
// DRINKS
{ you get the idea.... }
The closest I can get without error is:
SELECT restaurant, mains, drinks
FROM default restauant USE KEYS "restaurant1"
NEST default mains ON KEYS restaurant.mainIds
NEST default drinks ON KEYS restaurant.drinkIds;
But:
1. Obviously the nested nest is missing
2. The returned order is incorrect - the drinks nest comes first instead of last
(3. Since I'm also using Sync Gateway - it returns all the "_sync" fields with every doc - can't figure out how to omit this on each doc.)
UPDATE 1: ADAPTED SOLUTION
NB: I should have specified above that a main cannot hold ingredientIds.
Based on geraldss' v helpful input below, I added a doc which tracks keys per restaurant, eg:
{
"id": "restaurant1-JoeBloggs",
"dinerId": "JoeBloggs",
"ingredientIds": [ "ingredient1", "ingredient2" "ingredient3" ],
"mainOrdered": [ "main1" ], // < other potential uses...
"drinkOrdered": [ "drink2" ]
}
I added this to geraldss' first solution below as a JOIN to make it available to the query, eg:
SELECT *
FROM
(
SELECT
r.*,
(
SELECT
drink.*
FROM default AS drink
USE KEYS r.drinkIds
) AS drinks,
(
SELECT
main.*,
(
SELECT
ingredient.*
FROM default AS ingredient
USE KEYS keyIndex.ingredientIds // < keyIndex
WHERE ingredient.mainId=main.id
) AS ingredients
FROM default AS main
USE KEYS r.mainIds
) AS mains
FROM default AS r
USE KEYS "restaurant1"
JOIN default AS keyIndex ON KEYS "restaurant1-JoeBloggs" // < keyIndex JOINed
) AS restaurant
;
geraldss' second solution below also looks good - unfortunately it won't work for my case as this query requires that mains are found via ingredients; for my needs a main can exist without any ingredients. EDIT: > he came up with another solution. See 2.
UPDATE 2: FINAL SOLUTION
So, again, with geraldss' help I have a solution which does not require an additional doc to track keys:
SELECT *
FROM
(
SELECT
restaurant.id, restaurant.name,
(
SELECT
drink.id, drink.title
FROM default AS drink
USE KEYS restaurant.drinkIds
)
AS drinks,
(
SELECT
main.id, main.title,
ARRAY_AGG({"title":ingredient.title, "id":ingredient.id}) AS ingredients
FROM default AS ingredient
JOIN default AS main
ON KEYS ingredient.mainIds
WHERE main.restaurantId="restaurant1"
AND meta().id NOT LIKE '_sync:%' // < necessary only if using Sync Gateway
GROUP BY main
UNION ALL
SELECT
mainWithNoIngredients.id, mainWithNoIngredients.title
FROM default AS mainWithNoIngredients
UNNEST mainWithNoIngredients AS foo // < since this is being flattened the AS name is irrelevant
WHERE mainWithNoIngredients.restaurantId="restaurant1"
AND mainWithNoIngredients.type="main"
AND meta().id NOT LIKE '_sync:%' // < necessary only if using Sync Gateway
AND META(mainWithNoIngredients).id NOT IN
(
SELECT RAW mainId
FROM default AS ingredient
)
)
AS mains
FROM default AS restaurant
USE KEYS "restaurant1"
)
AS restaurant
;
NB - the AND meta().id NOT LIKE '_sync:%' lines are only necessary if using Sync Gateway.
With just 1 key I can pull all the related docs - even if they are unknown to the immediate 'parent'.
Thank you geraldss.
If the mains contain ingredientIds:
SELECT *
FROM
(
SELECT
r.*,
(
SELECT
drink.*
FROM default AS drink
USE KEYS r.drinkIds
) AS drinks,
(
SELECT
main.*,
(
SELECT
ingredient.*
FROM default AS ingredient
USE KEYS main.ingredientIds
) AS ingredients
FROM default AS main
USE KEYS r.mainIds
) AS mains
FROM default AS r
USE KEYS "restaurant1"
) AS restaurant
;
EDIT: Updated to include mains not referenced by any ingredients.
If the mains do not contain ingredientIds:
SELECT *
FROM
(
SELECT
r.*,
(
SELECT
drink.*
FROM default AS drink
USE KEYS r.drinkIds
) AS drinks,
(
SELECT
main.*,
ARRAY_AGG(ingredient) AS ingredients
FROM default AS ingredient
JOIN default AS main
ON KEYS ingredient.mainIds
WHERE "restaurant1" IN main.restaurantIds
GROUP BY main
UNION ALL
SELECT
main.*
FROM default AS main
WHERE "restaurant1" IN main.restaurantIds
AND META(main).id NOT IN (
SELECT RAW mainId
FROM default AS ingredient
UNNEST mainIds AS mainId
)
) AS mains
FROM default AS r
USE KEYS "restaurant1"
) AS restaurant
;
Related
Creating JSON Nested Level Arrays with Query
I'm trying to recreate a JSON output that is about 5 levels deep. I can recreate 2 levels deep, but after that, I'm stumped. It has worked using FOR JSON AUTO, but I need control over the naming and how they appear, so I need to be able to use FOR JSON PATH. Below is sample code and would allow for a 3 level deep output. I'm hoping that whatever is needed to make it 3 levels, I can learn from to go to 5 levels. CREATE TABLE [FamilyTree]( [ID] INT NOT NULL , [Name] VARCHAR(250) NOT NULL, [ParentID] INT NOT NULL, ) ON [PRIMARY] GO INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(1,N'Person1',0) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(2,N'Person2',0) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(3,N'Person3',1) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(4,N'Person4',2) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(5,N'Person5',3) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(6,N'Person6',3) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(7,N'Person7',4) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(8,N'Person8',4) INSERT [FamilyTree]([ID],[Name],[ParentID]) VALUES(9,N'Person9',4) Using this query: SELECT FT1.Name AS [name], (SELECT Name FROM FamilyTree WHERE ParentID = FT1.ID FOR JSON PATH) children FROM FamilyTree FT1 WHERE FT1.ParentID = 0 FOR JSON PATH I can get the following output: [ { "name": "Person1", "children": [ { "Name": "Person3" } ] }, { "name": "Person2", "children": [ { "Name": "Person4" } ] } ] but I want to be able to get it to go even further to look like this: [ { "name": "Person1", "children": [ { "Name": "Person3", "children": [ { "name": "Person 5", "name": "Person 6", } ] } ] }, { "name": "Person2", "children": [ { "name": "Person4", "children": [ { "name": "Person 7", "name": "Person 8", "name": "Person 9" } ] } ] } ]
You can just keep nesting subqueries if you want SELECT ft1.ID, ft1.Name, ft2.children FROM FamilyTree ft1 CROSS APPLY ( SELECT ft2.ID, ft2.Name, ft3.children FROM FamilyTree ft2 CROSS APPLY ( SELECT ft3.ID, ft3.Name, ft4.children FROM FamilyTree ft3 CROSS APPLY ( SELECT ft4.ID, ft4.Name, ft5.children FROM FamilyTree ft4 CROSS APPLY ( SELECT ft5.ID, ft5.Name FROM FamilyTree ft5 WHERE ft5.ParentID = ft4.ID FOR JSON PATH ) ft5(children) WHERE ft4.ParentID = ft3.ID FOR JSON PATH ) ft4(children) WHERE ft3.ParentID = ft2.ID FOR JSON PATH ) ft3(children) WHERE ft2.ParentID = ft1.ID FOR JSON PATH ) ft2(children) WHERE ft1.ParentID IS NULL FOR JSON PATH; db<>fiddle To do this recursively, or for that matter to prevent duplication of code, you cannot use a Table Valued Function. You can only do this with a scalar UDF (oh the horror!). CREATE OR ALTER FUNCTION dbo.GetJson (#ParentID int) RETURNS nvarchar(max) AS BEGIN RETURN ( SELECT ft.ID, ft.Name, children = dbo.GetJson(ft.ID) FROM FamilyTree ft WHERE EXISTS (SELECT ft.ParentID INTERSECT SELECT #ParentID) -- null compare FOR JSON PATH ); END; SELECT dbo.GetJson(NULL); db<>fiddle Note that in both of these examples, root nodes have a ParentID of NULL not 0. This is the correct way to do it, as there is no 0 row.
Try this, Mabe its usful: DECLARE #FamilyTree TABLE ( [ID] INT NOT NULL , [Name] VARCHAR(250) NOT NULL, [ParentID] INT NOT NULL ) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(1,N'Person1',0) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(2,N'Person2',0) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(3,N'Person3',1) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(4,N'Person4',2) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(5,N'Person5',3) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(6,N'Person6',3) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(7,N'Person7',4) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(8,N'Person8',4) INSERT #FamilyTree([ID],[Name],[ParentID]) VALUES(9,N'Person9',4) SELECT FT1.Name AS [name],children.Name,grandchild.Name AS grandchild FROM #FamilyTree FT1 INNER JOIN (SELECT * FROM #FamilyTree ) children ON children.ParentID = FT1.ID INNER JOIN (SELECT * FROM #FamilyTree ) grandchild ON grandchild.ParentID = children.ID WHERE FT1.ParentID = 0 FOR JSON AUTO For each level, you can set another "INNER JOIN" It's the result: [ { "name": "Person1", "children": [ { "Name": "Person3", "grandchild": [ { "grandchild": "Person5" }, { "grandchild": "Person6" } ] } ] }, { "name": "Person2", "children": [ { "Name": "Person4", "grandchild": [ { "grandchild": "Person7" }, { "grandchild": "Person8" }, { "grandchild": "Person9" } ] } ] } ]
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 ;
How to deal with not existing values using JSON_EXTRACT?
I have a list ob objects. Each object contains several properties. Now I want to make a SELECT statement that gives me a list of a single property values. The simplified list look like this: [ [ { "day": "2021-10-01", "entries": [ { "name": "Start of competition", "startTimeDelta": "08:30:00" } ] }, { "day": "2021-10-02", "entries": [ { "name": "Start of competition", "startTimeDelta": "03:30:00" } ] }, { "day": "2021-10-03", "entries": [ { "name": "Start of competition" } ] } ] ] The working SELECT is now SELECT JSON_EXTRACT(column, '$.days[*].entries[0].startTimeDelta') AS list FROM table The returned result is [ "08:30:00", "03:30:00" ] But what I want to get (and also have expected) is [ "08:30:00", "03:30:00", null ] What can I do or how can I change the SELECT statement so that I also get NULL values in the list?
SELECT startTimeDelta FROM test CROSS JOIN JSON_TABLE(val, '$[*][*].entries[*]' COLUMNS (startTimeDelta TIME PATH '$.startTimeDelta')) jsontable https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=491f0f978d200a8a8522e3200509460e Do you also have a working idea for MySQL< 8? – Lars What is max amount of objects in the array on the 2nd level? – Akina Well it's usually less than 10 – Lars SELECT JSON_EXTRACT(val, CONCAT('$[0][', num, '].entries[0].startTimeDelta')) startTimeDelta FROM test -- up to 4 - increase if needed CROSS JOIN (SELECT 0 num UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) nums WHERE JSON_EXTRACT(val, CONCAT('$[0][', num, '].entries[0]')) IS NOT NULL; https://www.db-fiddle.com/f/xnCCSTGQXevcpfPH1GAbUo/0
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')
NEST in COUCHBASE
How to nest data in couhcbase similar to mongo DB we do on reference key. we have two table In a bucket first table is "CHAIN", and second table is "STORE". I am MONGO user previously and very new to couchbase. Please suggest how I can nest using N1QL for couchbase Table 1 CHAIN { "chId": "chid_1", "chName": "Walmart", "type": "CHAIN" } 2nd table STORE { "chId": "chid_1", "csName": "store1", "type": "STORE" } { "chId": "chid_1", "csName": "store2", "type": "STORE" } I want to get data by joing these table as { "chId": "chid_1", "chName": "Walmart", "type": "CHAIN", "stores": [ {"csName": "store1", "type": "STORE"}, {"csName": "store2", "type": "STORE"}] }
Use JOIN, GROUP BY. Also checkout https://blog.couchbase.com/ansi-join-support-n1ql/ CREATE INDEX ix1 ON (chId) WHERE type = "CHAIN"; CREATE INDEX ix2 ON (chId) WHERE type = "STORE"; SELECT c.*, ARRAY_AGG({s.type, s.csName}) AS stores FROM default AS c JOIN default AS s ON c.chId = s.chId WHERE c.type = "CHAIN" AND s.type = "STORE" GROUP BY c; You Can also use ANSI NEST if you want include whole document SELECT c.*, s AS stores FROM default AS c NEST default AS s ON c.chId = s.chId AND s.type = "STORE" WHERE c.type = "CHAIN";