Full text search in concrete node in json - 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')

Related

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!

Search for a value in a JSON field in MYSQL

I am trying to understand the "new" MYSQL JSON field.
I have this table:
id (int-11, not_null, auto_inc)
customer_id (int-11, not null)
labels (json)
With this data:
id: 1
customer_id: 1
labels: [{"isnew": "no", "tagname": "FOO", "category": "CAT_1", "isdeleted": "no"}, {"isnew": "yes", "tagname": "BAR", "category": "CAT_2", "isdeleted": "no"}]
JSON beautify
[
{
"tagname": "FOO",
"category": "CAT_1",
"isnew": "no",
"isdeleted": "no"
},
{
"tagname": "BAR",
"category": "CAT_2",
"isnew": "yes",
"isdeleted": "no"
}
]
And now I want to SELECT all the customers (by customer_id) in the table that have a specific category and a specific tagname
I tried this one:
SELECT * FROM labels_customers_json
WHERE JSON_SEARCH(labels, 'all', 'BAR') IS NOT NULL
But this is not what I want. This one is searching in every json attribute.
I have seen some examples of JSON_EXTRACT:
SELECT * FROM `e_store`.`products`
WHERE
`category_id` = 1
AND JSON_EXTRACT(`attributes` , '$.ports.usb') > 0
AND JSON_EXTRACT(`attributes` , '$.ports.hdmi') > 0;
SELECT c, c->"$.id", g, n
FROM jemp
WHERE JSON_EXTRACT(c, "$.id") > 1
ORDER BY c->"$.name";
So I tried this
SELECT * FROM labels_customers_json
WHERE JSON_EXTRACT(labels, '$.tagname') = 'BAR'
SELECT labels, JSON_EXTRACT(labels, "$.customer_id"), customer_id
FROM labels_customers_json
WHERE JSON_EXTRACT(labels, "$.customer_id") > 0
You could probably try using SELECT * FROM labels_customers_json WHERE JSON_SEARCH(labels, 'all', "BAR", NULL, "$[*].tagname") is not null - although i cannot say if that is the best way to perform this query.
You can use JSON_SEARCH to search on a specific path too. So you can use the following query:
SELECT *
FROM labels_customers_json
WHERE JSON_SEARCH(labels, 'all', 'BAR', NULL, '$[*].tagname') IS NOT NULL
You can also use JSON_EXTRACT and JSON_CONTAINS together:
SELECT *
FROM labels_customers_json
WHERE JSON_CONTAINS(JSON_EXTRACT(labels, '$[*].tagname'), '["BAR"]') > 0;
You can also use only JSON_CONTAINS to check:
SELECT *
FROM labels_customers_json
WHERE JSON_CONTAINS(labels, '{"tagname":"BAR"}') > 0;
demos: https://www.db-fiddle.com/f/rufrThAQPfXHrK9YyibFSm/2

How to parse JSON into relational format in SQL Server 2016?

I have some Json stored in SQL Server 2016 table as under (partitial)
{
"AFP": [
{
"AGREEMENTID": "29040400001330",
"LoanAccounts": {
"Product": "OD003",
"BUCKET": 0,
"ZONE": "MUMBAI ZONE",
"Region": "MUMBAI METRO-CENTRAL REGION",
"STATE": "GOA",
"Year": 2017,
"Month": 10,
"Day": 13
},
"FeedbackInfo": {
"FeedbackDate": "2017-10-13T12:07:44.2317198",
"DispositionDate": "2017-10-13T12:07:44.2317198",
"DispositionCode": "PR"
},
"PaymentInfo": {
"ReceiptNo": "2000000170",
"ReceiptDate": "2017-10-13T12:07:42.1218299",
"PaymentMode": "Cheque",
"Amount": 200,
"PaymentStatus": "CollectionBatchCreated"
}
}
]
}
table schema as under
create table tblHistoricalDataDemo(
AGREEMENTID nvarchar(40)
,Year_Json nvarchar(4000)
)
I would like to fetch the records from JSON into relational format as
AgreementID Product Bucket .... PaymentStatus
I tried with below but something wrong i am doing for which I am not able to get the result
SELECT AGREEMENTID,
JSON_VALUE(Year_Json, '$.LoanAccounts') AS records
FROM tblHistoricalDataDemo
Use the OPENJSON built in table value function:
SELECT *
FROM tblHistoricalDataDemo
CROSS APPLY
OPENJSON(Year_Json, '$.AFP') WITH
(
-- You don't have to specify the json path
-- if the column name is the same as the json name
AGREEMENTID bigint
)
As afp
CROSS APPLY
OPENJSON(Year_Json, '$.AFP') WITH
(
Product varchar(10) '$.LoanAccounts.Product',
bucket int '$.LoanAccounts.BUCKET'
)
As LoanAccounts
In case the array in JSON has a fixed number of element, use
$.P1[x]
If AFP has only 1 element,
SELECT t.AGREEMENTID,
JSON_Value(Year_Json, '$.AFP[0].LoanAccounts.Product') Product,
JSON_Value(Year_Json, '$.AFP[0].LoanAccounts.BUCKET') Bucket,
JSON_Value(Year_Json, '$.AFP[0].PaymentInfo.PaymentStatus') PaymentStatus
FROM tblHistoricalDataDemo t
Run it in SQLFiddle, thx Jacob H.

TSQL Select basic hierarchical data from JSON

This is a rediculously simple question, but I cannot find a single working example anywhere. MSDN hints it is possible (here and here), but misses the actual example, and Google presents a myriad of examples outputting JSON from TSQL, whereas I need the reverse.
Taking a most basic JSON structure:
DECLARE #json nvarchar(max) = N'[{
"Id": 1,
"name": "John",
"skills": [
{"title": "Azure" },
{"title": "VB" },
{"title": "JavaScript" }]
}, {
"Id": 2,
"name": "Jane",
"skills": [
{"title": "Azure" },
{"title": "SQL" },
{"title": "C#" }]
}]';
I figured how to get the highest-level values, such as Id and name:
SELECT
*
FROM
OPENJSON(#json) WITH (
ID int '$.Id',
name nvarchar(50) '$.name'
);
What I'd like is to output PersonId, and the respective skill titles for each, e.g.
PersonId SkillTitle
-----------------------
1 Azure
1 VB
1 JavaScript
2 Azure
2 SQL
2 C#
Google only provides me with the reverse logic. My badly-broken attempt based on what I can find is here:
SELECT
*
FROM
OPENJSON(#json, '$.skills') WITH (
PersonId int './Id',
SkillTitle nvarchar(50) '$.title'
);
The below code snippet would give you the required results -
SELECT
JSON_Value (c.value, '$.Id') as ID,
JSON_Value (p.value, '$.title') as SkillTitle
FROM OPENJSON (#json) as c
CROSS APPLY OPENJSON(c.value,'$.skills') as p
Have implemented the same by CROSS APPLYing the JSON child node with the parent node and using the JSON_Value() function.

Couchbase N1QL - Nest within a Nest

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
;