MySQL JSON_OBJECT returning unparsable JSON - mysql

I have a table of users similar to the statement below, (relevant fields included)
CREATE TABLE User (
ID INT NOT NULL AUTO_INCREMENT,
Username VARCHAR(30) NOT NULL,
IsActive BIT NOT NULL DEFAULT 1,
PRIMARY KEY (ID)
);
When I query the table with a simple SELECT statements, I get the fields back exactly as expected.
+----------------------------+
| ID | Username | IsActive |
+----+------------+----------+
| 42 | CoolGuy92 | 1 |
+----+------------+----------+
That's all well and good. If, though, I try to run the following query, and send its output to the browser for use, I get this error on the client side when trying to parse the return: Uncaught SyntaxError: Unexpected token in JSON at position X.
SELECT JSON_OBJECT(
"ID", Users.ID,
"Username", Users.Username,
"IsActive", Users.IsActive
)
FROM Users
What is causing this parsing error?

The parsing error occurs due to your IsActive column being the BIT data type. BIT columns have a charset and collation of binary, and are thusly stored as binary. An apparent value of 1 is actually stored as 0x01. During a normal query, MySQL will output the value of a BIT column using the schema's default character set, often treating it as an integer*. So for most cases, an apparent value of 1, becomes 0x31 and 0 becomes 0x30. This allows the returned values to be readable, and easily used as numbers or strings.
In the case of JSON_OBJECT though,
Strings produced by converting JSON values have a character set of utf8mb4 and a collation of utf8mb4_bin*
By making this conversion, the field's literal value is used and is output as \u0001. This is not escaped in any way, so when your browser attempts to parse the output, it fails when it reaches such a value.
To get around this, you can cast your BIT columns to another numeric datatype in your JSON_OBJECT call, or add 0 to it*.
SELECT JSON_OBJECT(
"ID", Users.ID,
"Username", Users.Username,
"IsActive", Users.IsActive + 0
)
FROM Users

Related

Replace null values where the datatype is numeric with just a blank

UPDATE kpi.data
SET MetricValue = ''
WHERE (MetricValue IS NULL )
and PeriodDate = '2020-01-02'
and ReportID = 4
I got this error
Msg 8114, Level 16, State 5, Line 4 Error converting data type varchar
to numeric.
What you are trying to do does not make any sense, so it cannot work, and it will not work.
If the data type of a column is numeric then the column can only contain numbers.
If the data type of a column is numeric and nullable the column can contain either a number or null.
There is no other value that a numeric field can receive, either in MySQL or in any other relational database that I have ever heard of.
Perhaps what you want to do is to convert null to blank when selecting, (not when inserting/updating,) in which case you should look at some other Q&A like this one: MySql Query Replace NULL with Empty String in Select

How do I return a JSON updated document in Oracle?

From the docs I see an example:
SELECT json_mergepatch(po_document, '{"Special Instructions":null}'
RETURNING CLOB PRETTY)
FROM j_purchaseorder;
But When I try this code in SQL Developer I get a squiggly line under CLOB and an error when I run the query?
It works in Oracle 18c:
SELECT json_mergepatch(
po_document,
'{"Special Instructions":null}'
RETURNING CLOB PRETTY
) AS updated_po_document
FROM j_purchaseorder;
Which for the test data:
CREATE TABLE j_purchaseorder( po_document CLOB CHECK ( po_document IS JSON ) );
INSERT INTO j_purchaseorder ( po_document )
VALUES ( '{"existing":"value", "Special Instructions": 42}' );
Outputs:
| UPDATED_PO_DOCUMENT |
| :------------------------------- |
| {<br> "existing" : "value"<br>} |
Removing the Special Instructions attribute as per the documentation you linked to:
When merging object members that have the same field:
If the patch field value is null then the field is dropped from the source — it is not included in the result.
Otherwise, the field is kept in the result, but its value is the result of merging the source field value with the patch field value. That is, the merging operation in this case is recursive — it dives down into fields whose values are themselves objects.
db<>fiddle here

MySQL - Escaping double quotes and brackets when querying JSON column

how do you escape double quotes and brackets when querying a JSON column containing an array of objects?
This works when I run it...
SELECT boarded, boarded->>"$[0].division.id" AS divisionId FROM onboarder
But this doesn't...
SELECT boarded, boarded->>"$[*].division.id" AS divisionId FROM onboarder
I thought double arrows escaped everything and bought back only the value. This is what I have...
The ->> operator does not escape anything. It just converts the result to a scalar data type, like text or integer. It's the equivalent of doing JSON_UNQUOTE(JSON_EXTRACT(...)):
Unquotes JSON value and returns the result as a utf8mb4 string.
We can demonstrate the difference between -> and ->> by using the result set to create a new table, and inspecting the data types it creates.
create table t as SELECT boarded, boarded->"$[*].division.id" AS divisionId FROM onboarder;
show create table t;
CREATE TABLE `t` (
`boarded` json DEFAULT NULL,
`divisionId` json DEFAULT NULL
);
select * from t;
+------------------------------------+--------------+
| boarded | divisionId |
+------------------------------------+--------------+
| [{"division": {"id": "8ac7a..."}}] | ["8ac7a..."] |
+------------------------------------+--------------+
Note divisionId is a json document, which is an array.
If we use ->> this is what happens:
create table t as SELECT boarded, boarded->>"$[*].division.id" AS divisionId FROM onboarder;
show create table t;
CREATE TABLE `t` (
`boarded` json DEFAULT NULL,
`divisionId` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin
)
select * from t;
+------------------------------------+--------------+
| boarded | divisionId |
+------------------------------------+--------------+
| [{"division": {"id": "8ac7a..."}}] | ["8ac7a..."] |
+------------------------------------+--------------+
There's no visible difference, because the square brackets are still present. But the latter is stored as a longtext data type.
Re your comment:
How can I return divisionId as a value so that it's not an array and quoted?
You used $[*] in your query, and the meaning of this pattern is to return all elements of the array, as an array. To get a single value, you need to query a single element of the array, as in your first example:
boarded->>'$[0].division.id'
This would be so much easier if you didn't use JSON, but stored your data in a traditional table, with one division on its own row, and each field of the division in its own column.
CREATE TABLE divisions (
division_id VARCHAR(...) PRIMARY KEY,
merchant ...
);
CREATE TABLE merchants (
merchant_id ... PRIMARY KEY,
division_id VARCHAR(...),
FOREIGN KEY (division_id) REFERENCES division(division_id)
);
The more examples I see of how developers try to use JSON in MySQL, the more I'm convinced it's a bad idea.

Unexpected result using SELECT ... WHERE id = 0 on VARCHAR id in MySQL

I'm using MySQL 8 with InnoDB with a node server with mysql2 driver.
My table looks like:
CREATE TABLE IF NOT EXISTS users(
id VARCHAR(36) NOT NULL,
name VARCHAR(32) NOT NULL,
email VARCHAR(255) NOT NULL,
...
PRIMARY KEY (id)
)
I use no auto increment and as VARCHAR ids, I use time based UUIDs.
If I now do my SELECT query:
SELECT * FROM users where id = 'some valid id';
I get my expected result.
If I do:
SELECT * FROM users where id = '0';
I get nothing, because no id in my table has the value '0'.
BUT, if i do:
SELECT * FROM users where id = 0;
I get the last inserted row, which has, of course, a valid VARCHAR id different from 0.
This behavior occured on my node server by accident, because JS sometimes interpretes undefined as 0 in http querys.
In consequence I can easyly avoid inserting 0 in my querys (what I do now), but I would like to understand why this happens.
Your id is varchar(), so this comparison:
WHERE id = 0
requires type conversion.
According to the conversion rules in SQL, the id is turned into a string. Now, in many databases, you would get an error if any values of id could not be converted into numbers.
However, MySQL supports implicit conversion with no errors. (You can read about such conversion in the documentation.) This converts all leading digits to a number -- ignoring the rest. If there are no leading digits, then the value is zero. So, all these are true in MySQL:
'a' = 0
'0a' = 0'
'anything but 0!' = 0
There are two morals to this story.
If you really want id to be a number, then use a number data type (int, bigint, decimal).
Don't mix types in comparisons.

mysql CONVERT() and CAST() to integer are increasing the value by 1

I'm trying to select records from a phone-call table where the value of an ENUM string field called Call_Rating is less than the integer value 4. The Call_Rating field can only contain the values '0','1','2','3','4','5'. Whenever I use CONVERT(Call_Rating, UNSIGNED INTEGER) or CAST(Call_Rating AS UNSIGNED), the values of the Call_Rating field are increased by 1. Why is it doing this and is there a way to avoid it other than just manually subtracting 1 from the CALL() or CAST() functions?
Also, this is an old DB that was set-up by someone else and is still in use by various systems, so some way of getting around this issue without changing the DB schema would be useful.
Create Table
CREATE TABLE `member_calls` (
`CallID` int(10) NOT NULL AUTO_INCREMENT,
`Call_Rating` enum('0','1','2','3','4','5') CHARACTER SET latin1 NOT NULL DEFAULT '0',
PRIMARY KEY (`CallID`)
) ENGINE=MyISAM AUTO_INCREMENT=538616 DEFAULT CHARSET=utf8;
Data in Table
INSERT INTO `member_calls`
(`CallID`, `Call_Rating`)
VALUES
(510515, '4'),
(510909, '0'),
(538614, '3'),
(538615, '5');
Select Statement
SELECT `CallID`, `Call_Rating`, CAST(`Call_Rating` AS UNSIGNED) AS 'Casted', CONVERT(`Call_Rating`, UNSIGNED INTEGER) AS 'Converted'
FROM `member_calls`
WHERE CONVERT(`Call_Rating`, SIGNED INTEGER) < 4
OR CAST(`Call_Rating` AS UNSIGNED) < 4;
Given Results
CallID Call_Rating Casted Converted
510909 0 1 1
Expected Results
CallID Call_Rating Casted Converted
510909 0 0 0
538614 3 3 3
Edit 2016-5-17
Thank you everyone for your input. I now understand why the issue was happening. Basically the ENUM was being treated like an array and CAST()/CONVERT() were returning the index of the array rather than the value. The best solution to this would be to change the ENUM to an INT field, but that is not desirable in my situation since the DB is being used live, and altering the data type could cause issues elsewhere. For that reason, lserni's solution was the most useful for me.
The direct conversion of ENUM to INTEGER yields the index of that ENUM, and since they start from 1, the first element is 0 and becomes 1, etc.
It's not increasing by 1 at all: it's returning an integer value that by chance seems as it's the correct value plus 1. But it could be anything else. If you had an enum of '1','2','3','4','5', without the '0', then the result would appear to be correct, even if it really isn't.
Either run a double convert passing from CHAR, or an implicit convert again after converting to CHAR:
SELECT CONVERT(CONVERT(Call_Rating, CHAR(1)), UNSIGNED), 0+CONVERT(Call_Rating, CHAR(1)), 0+Call_Rating, CAST(Call_Rating AS UNSIGNED) from member_calls;
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
| CONVERT(CONVERT(Call_Rating, CHAR(1)), UNSIGNED) | 0+CONVERT(Call_Rating, CHAR(1)) | 0+Call_Rating | CAST(Call_Rating AS UNSIGNED) |
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
| 2 | 2 | 3 | 3 |
+--------------------------------------------------+---------------------------------+---------------+-------------------------------+
I can't give a detailed explanation of what is happening here, but it's likely to do with the fact that you are using an ENUM with numeric values, instead of an INT type, which seems like the sane choice.
The mySQL manual strongly recommends against this:
We strongly recommend that you do not use numbers as enumeration values, because it does not save on storage over the appropriate TINYINT or SMALLINT type, and it is easy to mix up the strings and the underlying number values (which might not be the same) if you quote the ENUM values incorrectly. If you do use a number as an enumeration value, always enclose it in quotation marks. If the quotation marks are omitted, the number is regarded as an index. See Handling of Enumeration Literals to see how even a quoted number could be mistakenly used as a numeric index value.
mySQL is probably using the numeric index instead of the ENUM value and that's causing the weirdness.
Just switch to a proper INT field.
You should get that ENUM is not string or integer that is enum.
To me that is the type you should avoid in DB as much as possible.
Here is fiddle tha illustrate why that happens to you:
http://sqlfiddle.com/#!9/de53b2/1
as you can see when mysql casting ENUM to int - that returns something like index of saved value in array(enum) but not the value casted to int.
But just to illustrate mysql power functionality you can run this query to get expected result:
http://sqlfiddle.com/#!9/e2cf5/3
SELECT `CallID`, `Call_Rating`,
ELT(CAST(`Call_Rating` AS UNSIGNED),'0','1','2','3','4','5') AS 'Casted',
ELT(CONVERT(`Call_Rating`, SIGNED INTEGER),'0','1','2','3','4','5') AS 'Converted'
FROM `member_calls`
HAVING `Casted` < 4
OR `Converted` < 4;
But anyway that is not best solution.
I think you should better redesign your db schema.