MySQL JSON_SET() to work with empty arrays - mysql

I have a table like this:
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`settings` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `test` (`id`, `settings`) VALUES
('1', '{\"foo\": {\"bar\": 1}}'),
('2', '{\"foobar\": 2}'),
('3', '[]');
I want to add a new setting to a row, so I tried JSON_SET:
SELECT *, JSON_SET(settings, '$.newFoo', 10) FROM test;
As you can see, it doesn't work on the item 3. The expected result is of course the same as on line 4; [] is the result of json-encoding an empty array in php, if that item doesn't have any setting.
Can make a single query that works for all the cases?
P.S.: What I need to do is an UPDATE, i.e. UPDATE test SET settings=JSON_SET(...) WHERE id=?;

This stumped me as well for quite awhile but try using JSON_ARRAY() instead of the double bracket notation '[]'. For example:
INSERT INTO `test` (`id`, `settings`) VALUES
('1', '{\"foo\": {\"bar\": 1}}'),
('2', '{\"foobar\": 2}'),
('3', JSON_ARRAY());

The reason your call to JSON_SET is not behaving as you expect is that the syntax you need to work with JSON arrays differs from what you need to work with individual JSON. Consider the following query, which adds a JSON to an empty array:
SELECT JSON_SET('[]', '$[0]', '{"newFoo": 10}') AS output;
This prints:
["{\"newFoo\": 10}"]
Demo
So the JSON_SET function appears to have two behaviors to it. When operating on JSON proper, it can either insert new keys or update keys which already exist. When operating on arrays, it can insert/update elements of the array, which might be entire JSON objects.

I solved by always storing the values as objects instead of arrays, in PHP you need to add a flag to the json_encode function:
json_encode($value, JSON_FORCE_OBJECT)
Or, as an alternative, make the field nullable and use COALESCE:
SELECT *, JSON_SET(COALESCE(settings, '{}'), '$.newFoo', 10) FROM test;

You can use CASE .. WHEN conditional operator to handle this in single query. Also, since your datatype for settings is Json, you will need to Cast() it to string (Char) first for checking []:
SELECT *,
CASE WHEN CAST(settings AS CHAR) = '[]'
THEN JSON_SET('{}', '$.newFoo', 10)
ELSE JSON_SET(settings, '$.newFoo', 10)
END AS modified_settings
FROM test;
Update query for the same would be as follows:
UPDATE test
SET settings = CASE WHEN CAST(settings AS CHAR) = '[]'
THEN JSON_SET('{}', '$.newFoo', 10)
ELSE JSON_SET(settings, '$.newFoo', 10)
END

Related

MySql INSERT...ON DUPLICATE UPDATE with partial VALUES

I have a list of possibly-incomplete set of values that will be used to append to or update a MySql table using the INSERT...ON DUPLICATE KEY UPDATE construct. The requirements are as follows:
If an operation resolves to an INSERT and the field value IS supplied, use the value supplied;
If an operation resolves to an INSERT and the field value IS NOT supplied, use the field's table DEFAULT value;
If an operation resolves to an UPDATE and the field value IS supplied, use the value supplied;
If an operation resolves to an UPDATE and the field value IS NOT supplied, retain the current (table) field value.
I've come up with the following statement, but the clauses wrapped in ** are erroneous and I'm having difficulty expressing them:
INSERT INTO `test`
(`id`, `num`, `text`)
VALUES
('1', 100, 'aaa'),
('2', 200, DEFAULT),
('3', DEFAULT, 'ccc')
ON DUPLICATE KEY UPDATE
`num` = IF (**VALUES(`num`) = DEFAULT**, `num`, VALUES(`num`)),
`text` = IF (**VALUES(`text`) = DEFAULT**, `text`, VALUES(`text`));
Notes: id is the unique key. Both num and text have default (NOT NULL) values set.
Things I've tried, but aren't satisfactory:
Replacing DEFAULT in VALUES with NULL, and then test for, e.g., IF (VALUES (num) = NULL .... This works, but will insert NULL on INSERT (and generate a warning - e.g., "Column 'text' cannot be null"), which is not acceptable - I need to have the default value applied to the missing fields;
Using something like 'xxx' instead of DEFAULT for missing values, and testing for 'xxx' (STRCMP), but this will insert 'xxx' in case of INSERT;
I've not tried this as I can't find the command/proper syntax, but the idea is to test (in the IF clause) whether num and text in VALUES are literals (num or string) or a MySql keyword (i.e., DEFAULT) - possibly using regex? - and then act accordingly.
Of course, an alternative to the above might entail obtaining existing values from the database and/or hardcoding into the query the default values for the missing fields, but I trust the same result can be achieved more elegantly using a single MySql statement.
Thanks in advance for your feedback.

N1QL - Insert-Select

I'm trying to execute an insert-select statement in N1QL (inserting documents that their key/value are the result of a select statement) and I'm failing to understand the syntax.
I tried executing:
insert into tempbucket (KEY payload.id,VALUE select * from default where payload.fooId in [100,101 ] ) RETURNING * ;
in some variations but nothing worked.
Edit: The SELECT statement is
select * from default where payload.fooId in [100,101 ]
The KEY of the documents I want to create is the value of the field key and the VALUE is the entire JSON of the SELECT statement mentioned above.
The KEY and VALUE must reference expressions from your query.
INSERT INTO tempbucket (KEY d.`key`, VALUE d)
SELECT d
FROM default d
WHERE payload.fooId IN [100,101 ]
;

Default values not working in phpmyadmin/mysql database

I can't get a table to accept "" or '' and use the default value. It is inserting NULL instead.
I am trying these commands in the direct input sql window.
INSERT INTO test01 VALUES ("", now(), "");
INSERT INTO test01 VALUES ('', now(), '');
But both just give NULL in the 3rd column. The structure is set to non-null with a default value of "yes". (Without quotation marks).
Here is a screenshot of the structure. You can see NULL is not checked.
http://garryjones.se/extras/so3.png
Default values only work if no value is inserted/updated. If you explicitly set it to an empty string (which is NOT the same as a NULL value) then it will end up with an empty string in the column. Instead of the code above you should eliminate the column from the INSERT statement at all:
INSERT INTO test01 (t1, t2) VALUES ('', now())
Other is already explain the reason here I am adding one more point you are also using current time stamp on update so do not need to use this column as well.
INSERT INTO test01 (t1) VALUES ('')
You could use the DEFAULT keyword: INSERT INTO test01 VALUES ("", now(), DEFAULT);

node-mysql use default if null on insert

I'm fairly new to mysql, and I'm trying to get up and running with it in node using node-mysql. I have created a simple table like so:
CREATE TABLE myTable (
id SERIAL,
display BOOLEAN NOT NULL DEFAULT 1,
active BOOLEAN NOT NULL DEFAULT 0
) DEFAULT CHARSET utf8 COLLATE utf8_bin;
I am inserting rows like so:
db.query('INSERT INTO myTable SET ?', {
display: myObject.display,
active: myObject.active
}, callback);
As the docs say, node-mysql converts the object i'm passing in to key value pairs. This works great if myObject.display and myObject.active are both defined. If one or both aren't, node-mysql tries to insert NULL into the columns, which is not allowed. I intended for the default value to be used in this situation, but its throwing an error about the NULL value. So my question is:
1) Is there some special syntax to use when creating a table that will use the default when a null value is given, or 2) Is there some elegant way to do this with node-mysql that doesn't involve a bunch of object parsing?
Feel free to expand your answer if you see something else I could improve. My larger goal is to learn the best way to create a robust, safe, and concise mysql insert in node.
Your SQL syntax is incorrect for an INSERT statement.
INSERT
INSERT into TableName(id, display, active)
VALUES (?, ?, ?)
UPDATE
UPDATE TableName SET display = ?
WHERE id = ?
You would then populate the ? placeholders using db.query() and passing in your arguments.

PostgreSQL: insert data into table from json

Now I use to manually parse json into insert string like so
insert into Table (field1, field2) values (val1, val2)
but its not comfortable way to insert data from json!
I've found function json_populate_record and tried to use it:
create table test (id serial, name varchar(50));
insert into test select * from json_populate_record(NULL::test, '{"name": "John"}');
but it fails with the message: null value in column "id" violates not-null constraint
PG knows that id is serial but pretends to be a fool. Same it do for all fieds with defaults.
Is there more elegant vay to insert data from json into a table?
There's no easy way for json_populate_record to return a marker that means "generate this value".
PostgreSQL does not allow you to insert NULL to specify that a value should be generated. If you ask for NULL Pg expects to mean NULL and doesn't want to second-guess you. Additionally it's perfectly OK to have a generated column that has no NOT NULL constraint, in which case it's perfectly fine to insert NULL into it.
If you want to have PostgreSQL use the table default for a value there are two ways to do this:
Omit that row from the INSERT column-list; or
Explicitly write DEFAULT, which is only valid in a VALUES expression
Since you can't use VALUES(DEFAULT, ...) here, your only option is to omit the column from the INSERT column-list:
regress=# create table test (id serial primary key, name varchar(50));
CREATE TABLE
regress=# insert into test(name) select name from json_populate_record(NULL::test, '{"name": "John"}');
INSERT 0 1
Yes, this means you must list the columns. Twice, in fact, once in the SELECT list and once in the INSERT column-list.
To avoid the need for that this PostgreSQL would need to have a way of specifying DEFAULT as a value for a record, so json_populate_record could return DEFAULT instead of NULL for columns that aren't defined. That might not be what you intended for all columns and would lead to the question of how DEFAULT would be treated when json_populate_record was not being used in an INSERT expression.
So I guess json_populate_record might be less useful than you hoped for rows with generated keys.
Continuing from Craig's answer, you probably need to write some sort of stored procedure to perform the necessary dynamic SQL, like as follows:
CREATE OR REPLACE FUNCTION jsoninsert(relname text, reljson text)
RETURNS record AS
$BODY$DECLARE
ret RECORD;
inputstring text;
BEGIN
SELECT string_agg(quote_ident(key),',') INTO inputstring
FROM json_object_keys(reljson::json) AS X (key);
EXECUTE 'INSERT INTO '|| quote_ident(relname)
|| '(' || inputstring || ') SELECT ' || inputstring
|| ' FROM json_populate_record( NULL::' || quote_ident(relname) || ' , json_in($1)) RETURNING *'
INTO ret USING reljson::cstring;
RETURN ret;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Which you'd then call with
SELECT jsoninsert('test', '{"name": "John"}');