Limit JSON Column to specific Objects by using Check Constraints - json

We are using Oracle 12.2 and have the following table structure:
create table myData
(
id number,
json_data varchar2(4000),
CONSTRAINT checkJson CHECK (json_data IS JSON STRICT WITH UNIQUE KEYS),
CONSTRAINT checkJson_F1 CHECK (json_exists(json_data, '$.FIELD1')),
CONSTRAINT checkJson_F2 CHECK (json_exists(json_data, '$.FIELD2'))
);
We use constraints(checkJson_F1 , checkJson_F2) to ensure that inserts/updates are only possible when FIELD1 and FIELD2 are existing:
insert into myData(id, json_data) values(1, '{"FIELD1" : "abc", "FIELD2" : "def"}'); -- OK
insert into myData(id, json_data) values(2, '{"FIELD1" : "abc"}'); -- Error
Is there a way to check for non existing objects, for example i want to limit that the following insert generates an error:
insert into myData(id, json_data) values(1, '{"FIELD1" : "abc", "FIELD2" : "def", "FIELD3" : "def"}');
In other words: i want a check constraint where i can limit the objects which can be stored in the JSON-column. My PseudoCode would look like:
CONSTRAINT checkJson_FX CHECK (json_getObjects(json_data, '$.*') in ("FIELD1", "FIELD2"))

create table myData (
id number,
json_data varchar2(4000),
CONSTRAINT checkJson CHECK (json_data IS JSON STRICT WITH UNIQUE KEYS),
CONSTRAINT checkJson_F1 CHECK (
json_exists(json_data, '$?(#.size() == 2 && exists(#.FIELD1) && exists(#.FIELD2))')
)
);

Related

Update the key value of a json field in mysql

I have the following json field
{
"Covid-19Vaccine Staus": "Not vaccinated (intent to in the future)",
"Date of last vaccine taken": "2021-08-09T00:00:00+04:00",
"If vaccinated, Name of vaccination received": "Other WHO Approved vaccine"
}
What i would like to do is update the key description i.e. Covid-19 Vaccine Staus to Covid19VaccineStaus.
On doing a direct update to the field on mysql workbench it generates the following query,
UPDATE `my_json_table` SET `containerValue` = '{\"Covid19VaccineStaus\": \"Vaccinated\", \"Date of last vaccine taken\": \"2021-07-13T00:00:00+04:00\", \"If vaccinated, Name of vaccination received\": \"Pfizer-BioNTech\"}' WHERE (`id` = '94');
where it looks like it takes the entire values for the field and then does the update.
What should the query look like if i want to update just the Covid19VaccineStatus key without putting in the values for the other data points for the json schema.
Please take a look at JSON functions
JSON_REPLACE,
Replace values in JSON document
JSON_REMOVE,
Remove data from JSON document
JSON_INSERT
Insert data into JSON document
UPDATE `my_json_table` SET `containerValue` = JSON_REPLACE(`containerValue`, '$."Covid-19Vaccine Staus"', 'Vaccinated') WHERE (`id` = '94');
UPDATE `my_json_table` SET `containerValue` = JSON_REMOVE(`containerValue`, '$."Covid-19Vaccine Staus"') WHERE (`id` = '94');
UPDATE `my_json_table` SET `containerValue` = JSON_INSERT(`containerValue`, '$."Covid-19Vaccine Staus"', 'Vaccinated') WHERE (`id` = '94');
To replace a key and keep value
UPDATE `my_json_table`
SET `containerValue` =
JSON_REMOVE(
JSON_INSERT(`containerValue`, '$."Covid19VaccineStaus"',
JSON_EXTRACT(`containerValue`, '$."Covid-19Vaccine Staus"')),
'$."Covid-19Vaccine Staus"')
WHERE (`id` = '94');
You can use JSON_SET which handles both insert and update actions.
UPDATE my_json_table
SET containerValue = JSON_SET(containerValue, '$."Covid-19Vaccine Staus"', 'Vaccinated')
WHERE id = 94;
So if your key does not exist yet in your JSON, it will be inserted with the value Vaccinated. Otherwise, the value corresponding to your key will be updated.
You can also find examples here on how to handle arrays or multiple values with JSON_SET.
If you only need to update the value but not perform any insertion in your JSON if the key does not exist, you can use JSON_REPLACE.
If you only need to insert the key and the value but not perform any update in your JSON if the key already exists, you can use JSON_INSERT.
If you want to update the name of your key:
UPDATE my_json_table
SET containerValue = JSON_INSERT(
JSON_REMOVE(containerValue, '$."Covid-19Vaccine Staus"'),
'$.Covid19VaccineStaus',
JSON_EXTRACT(containerValue, '$."Covid-19Vaccine Staus"')
)
WHERE id = 94;

How to update JSON column in oracle 12.1

Am trying to update a JSON column in oracle 12.1. Is there any json function that can be used to update the JSON. I have added a constraint JSON_COLUMN is JSON.
I have tried :
UPDATE tablename SET JSON =
json_mergepatch(JSON, '{"STRUCTURE_NAME":null}');
But this function is applicable only for 19c
This "STRUCTURE_NAME": "ABC:STATE:L12345", needs to be updated with "STRUCTURE_NAME":null
Pre-19c, if you want to change any values in a JSON document, you have to replace the whole thing:
create table t (
doc varchar2(100)
check ( doc is json )
);
insert into t values ('{
"changeMe" : "to null",
"leaveMe" : "alone"
}');
update t
set doc = '{
"changeMe" : null,
"leaveMe" : "alone"
}';
select * from t;
DOC
{
"changeMe" : null,
"leaveMe" : "alone"
}
Note that when you get to 19c and use json_mergepatch, setting an attribute to null removes it from the document:
update t
set doc = json_mergepatch (
doc,
'{
"changeMe" : null
}');
select * from t;
DOC
{"leaveMe":"alone"}
Below query will overwrite the JSON document column for all rows.
UPDATE JSON_TABLE
SET JSON_DOC_COL=JSON_OBJECT('name' VALUE var_name,
'age' VALUE var_age);
To append the JSON document column, below query can be used.
UPDATE JSON_TABLE
SET JSON_DOC_COL=JSON_MERGEPATCH(JSON_DOC_COL,JSON_OBJECT('name' VALUE var_name,
'age' VALUE var_age) RETURNING CLOB);

Create foreign key constraint on JSON column

JSON support in SQL Server is pretty new. See here for more information.
We would like to use this to store our translatable fields. This means in the database we would no longer have a separate table with translations but instead store them in the column itself.
Our column value would look like this:
"Name" : {
"en-US" : "Conditioning",
"nl-NL" : "Behandeling",
"fr-FR" : "Traitement",
"de-DE" : "Konditionierung"
}
Since we still have a table with all the different cultures I would still like to apply a foreign key relation between Name.Key and the Culture table.
I couldn't find any information on how to apply foreign key constraints to the data in the JSON. Does anyone have a clue how to?
I've tried the constraint below for testing but this does not work. The JSON_VALUE does not seem to work on JSON keys. Whereas JSON_QUERY returns more than just the key.
ADD CONSTRAINT
[FK_ItemCulture]
CHECK (JSON_VALUE([Name], '$.Name') IN ('en-US', 'nl-NL', 'fr-FR', 'de-DE'))
You can define a scalar function that will check a single json:
CREATE FUNCTION [dbo].[svf_CheckJsonNames](#json nvarchar(max))
RETURNS bit AS
BEGIN
declare #ok bit
declare #validNames table([name] nvarchar(50))
insert into #validNames values ('en-US'),('nl-NL'),('fr-FR'),('de-DE')
if exists (
select x.[key] , vn.name from OPENJSON (#json)
with (
[Name] nvarchar(max) as json
) t
cross apply
(
select [key] from openjson(t.[Name])
) x
left join #validNames vn
on x.[key] COLLATE DATABASE_DEFAULT = vn.[name] COLLATE DATABASE_DEFAULT
where vn.[name] is null
)
set #ok = 0
else set #ok = 1
RETURN #ok
END
The functions returns 1 if all names are valid, 0 if one or more names are invalid.
Now you can use this function in your constraint:
create table tmp(myId int, [Name] nvarchar(max))
alter table tmp ADD CONSTRAINT [FK_ItemCulture]
CHECK ([dbo].[svf_CheckJsonNames]([Name]) = 1)
if you run the following insert statements:
insert into tmp values(1, '{"Name" : { "en-US" : "Conditioning", "nl-NL" : "Behandeling", "fr-FR" : "Traitement", "de-DE" : "Konditionierung" }}')
insert into tmp values(2, '{"Name" : { "en-EN" : "Conditioning", "nl-NL" : "Behandeling", "fr-FR" : "Traitement", "de-DE" : "Konditionierung" }}')
the first will succed because all names are correct:
but the second will fail since the first name ("en-EN") is invalid. Here is the error:

MySQL Workbench: Error Code 1452. Cannot add or update a child row: a foreign key constraint fails

I'm still fairly new to SQL. I'm updating a DB and I came across this message. The problem is, I've already executed this insert before but had to delete it due to me entering the same address 3 times instead of once.
Can anybody help me, I don't understand what is wrong:
> insert into ort
(plz, name) values
('4900', 'Langenthal')
;
>insert into adresse
(strasse, strassennr, ortID) values
('Eisenbahnstrasse', '7', (select oid from ort where name = 'Langenthal' and plz='4900'))
;
>
insert into liegenschaft
(liegenschafttypid, adressid) values
((select ltypid from liegenschaft_typ where name = 'Wohnhaus / Firma'), (select oid from ort where name = 'Langenthal' and plz = '4900'))
;
I keep on getting this message:
> 0 16 14:09:25 insert into liegenschaft (liegenschafttypid, adressid) values
((select ltypid from liegenschaft_typ where name = 'Wohnhaus / Firma'), (select oid from ort where name = 'Langenthal' and plz = '4900')) Error Code: 1452. Cannot add or update a child row: a foreign key constraint fails (`parking`.`liegenschaft`, CONSTRAINT `FK_adresse` FOREIGN KEY (`adressID`) REFERENCES `adresse` (`AID`)) 0.015 sec
You don't have entry in column adresse.AID for liegenschaft.adressid that you want to insert.
You either specified wrong column in foreign key, insert or you forgot to insert data into that column.
You need to do one of those:
insert into adresse
(strasse, strassennr, AID) values
('Eisenbahnstrasse', '7', (select oid from ort where name = 'Langenthal' and plz='4900'));
or
insert into adresse
(strasse, strassennr, ortID, AID) values
('Eisenbahnstrasse', '7', (select oid from ort where name = 'Langenthal' and plz='4900'), (select oid from ort where name = 'Langenthal' and plz='4900'));
or alter that foreign key to point at ortID instead AID
The column adressid in liegenschaft shall have the key adressID and not oid.
Try (assume that there just one entry for each address)
insert into liegenschaft
(liegenschafttypid, adressid) values
((select ltypid from liegenschaft_typ where name = 'Wohnhaus / Firma'), (select adressID from ort where name = 'Langenthal' and plz = '4900'))

How to Perform an UPSERT so that I can use both new and old values in update part

Stupid but simple example:
Assume I have a table 'Item' where I keeps totals of the items that receive.
Item_Name Items_In_Stock
Item name is primary key here. How to i achieve the following when ever I receive item A in quantity X.
If the item does not exist, I insert a new recored for Item A and set the items in stock to X and if there exists a record where items in stock was Y then the new value in items in stock is (X + Y)
INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = 27 + (SELECT items_in_stock where item_name = 'A' )
My problem is that i have multiple column in my actual table. Is it a good idea to write multiple select statements in the update part?
Of course I can do it in code but is there a better way?
As mentioned in my comment, you don't have to do the subselect to reference to the row that's causing ON DUPLICATE KEY to fire. So, in your example you can use the following:
INSERT INTO `item`
(`item_name`, items_in_stock)
VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE
`new_items_count` = `new_items_count` + 27
Remember that most things are really simple, if you catch yourself overcomplicating something that should be simple then you are most likely doing it the wrong way :)
Although Michael's answer is the right one, you need to know a bit more to do the upsert programmatically:
First, create your table and specify which columns you want a unique index on:
CREATE TABLE IF NOT EXISTS Cell (
cellId BIGINT UNSIGNED,
attributeId BIGINT UNSIGNED,
entityRowId BIGINT UNSIGNED,
value DECIMAL(25,5),
UNIQUE KEY `id_ce` (`cellId`,`entityRowId`)
)
Then insert some values into it:
INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );
Try doing the same thing again, and you'll get a duplicate key error, because cellId and entityRowId are same:
INSERT INTO Cell VALUES( 1, 6, 199, 1.0 );
Duplicate entry '1-199' for key 'id_ce'
That's why we use the upsert command:
INSERT INTO Cell ( cellId, attributeId, entityRowId, value)
VALUES( 1, 6, 199, 300.0 )
ON DUPLICATE KEY UPDATE `value` = `value` + VALUES(`value`)
This command takes the value 1.0 that's already there as value and does a value = value + 300.0.
So even after executing the above command, there will be only one row in the table, and the value will be 301.0.
You can get idea from this example:
Suppose you want to add user wise seven days data
It should have unique value for userid and day like
UNIQUE KEY `seven_day` (`userid`,`day`)
Here is the table
CREATE TABLE `table_name` (
`userid` char(4) NOT NULL,
`day` char(3) NOT NULL,
`open` char(5) NOT NULL,
`close` char(5) NOT NULL,
UNIQUE KEY `seven_day` (`userid`,`day`)
);
And your query will be
INSERT INTO table_name (userid,day,open,close)
VALUES ('val1', 'val2','val3','val4')
ON DUPLICATE KEY UPDATE open='val3', close='val4';
Example:
<?php
//If your data is
$data= array(
'sat'=>array("userid"=>"1001", "open"=>"01.01", "close"=>"11.01"),
'sun'=>array("userid"=>"1001", "open"=>"02.01", "close"=>"22.01"),
'sat'=>array("userid"=>"1001", "open"=>"03.01", "close"=>"33.01"),
'mon'=>array("userid"=>"1002", "open"=>"08.01", "close"=>"08.01"),
'mon'=>array("userid"=>"1002", "open"=>"07.01", "close"=>"07.01")
);
//If you query this in a loop
//$conn = mysql_connect("localhost","root","");
//mysql_select_db("test", $conn);
foreach($data as $day=>$info) {
$sql = "INSERT INTO table_name (userid,day,open,close)
VALUES ('$info[userid]', '$day','$info[open]','$info[close]')
ON DUPLICATE KEY UPDATE open='$info[open]', close='$info[close]'";
mysql_query($sql);
}
?>
Your data will be in table:
+--------+-----+-------+-------+
| userid | day | open | close |
+--------+-----+-------+-------+
| 1001 | sat | 03.01 | 33.01 |
| 1001 | sun | 02.01 | 22.01 |
| 1002 | mon | 07.01 | 07.01 |
+--------+-----+-------+-------+
This is the syntax for an upsert
INSERT INTO `{TABLE}` (`{PKCOLUMN}`, `{COLUMN}`) VALUES (:value)
ON DUPLICATE KEY UPDATE `{COLUMN}` = :value_dup';
If you have value for PK Column, or Unique Index on a column which satisfies unicity, You can use INSERT IGNORE, INSERT INTO ... ON DUPLICATE, or REPLACE
Example with INSERT IGNORE
INSERT IGNORE INTO Table1
(ID, serverID, channelID, channelROLE)
VALUES
(....);
Example with INSERT INTO .. ON DUPLICATE KEY UPDATE
SET #id = 1,
#serverId = 123545,
#channelId = 512580,
#channelRole = 'john';
INSERT INTO Table1
(ID, serverID, channelID, channelROLE)
VALUES
(#id, #serverId, #channelId, #channelRole)
ON DUPLICATE KEY UPDATE
serverId = #serverId,
channelId = #channelId,
channelRole = #channelRole;
Example with Replace
REPLACE INTO table1
(ID, serverID, channelID, channelROLE)
VALUES
(...);
Example for upsert
INSERT INTO table1 (col1, col2, col3)
VALUES ($1, $2, $3)
ON CONFLICT (col1)
DO
UPDATE
SET col2 = $2, col3 = $3
WHERE col1 = $1
RETURNING col1