json_remove is removing the wrong value from array on second run - sql

I am trying to remove a value from a json array, however I am having issues.
First I insert the array into into the table like so:
INSERT into demo (hint) VALUES ('["Hello","World"]');
Next when I run this query, the World value gets removed which is what is supposed to happen. However if I run it a second time, then the Hello value gets removed which is not supposed to happen. What am I doing wrong, or what is a better way to remove items from a json array?
UPDATE demo SET hint = json_remove(
hint,
(SELECT json_each.fullkey FROM demo, json_each(demo.hint) WHERE json_each.value = 'World')
);
select * from demo;

The subquery that you use as the path argument of json_remove() returns null the 2nd time that you execute the update statement because there is no "World" in the json array.
In this case json_remove() also returns null.
If your version of SQLite is 3.33.0+ you can use the UPDATE...FROM syntax:
UPDATE demo AS d
SET hint = json_remove(d.hint, j.fullkey)
FROM json_each(d.hint) AS j
WHERE j.value = 'Hello';

Related

How to check if array contains an item in JSON column using Sqlite?

I'm using sqlite to store JSON data that I have no control over. I have a logs table that looks like this.
id
value
s8i13s85e8f34zm8vikkcv5n
{"key":["a","b"]}
m2abxfn2n9pkyc9kjmko5462
{"key": "sometext"}
Then I use the following query to get the rows where value.key contains a:
SELECT * FROM logs WHERE EXISTS (SELECT * FROM json_each(json_extract(logs.value,'$.key')) WHERE json_each.value = 'a')
The query works fine if key is an array or if it doesn't exist. But it fails if is a string (like the second row of the table)
The error I get is:
SQL error or missing database (malformed JSON)
And it is because json_each throws if the parameter is an string.
Because of the requirements I can't control the user data or the queries.
Ideally I would like to figure out a query that either doesn't fail or that detects that the value is a string instead of an array and uses LIKE to see if the string contains 'a'.
Any help would be appreciated. Happy holidays :)
Use a CASE expression in the WHERE clause which checks if the value is an array or not:
SELECT *
FROM logs
WHERE CASE
WHEN value LIKE '{"key":[%]}' THEN
EXISTS (
SELECT *
FROM json_each(json_extract(logs.value,'$.key'))
WHERE json_each.value = 'a'
)
ELSE json_extract(value,'$.key') = 'a'
END;
See the demo.

Randomly insert 1 of 3 declared variables

I have three variables that are declared and have an integer value assigned to them.
I am trying to randomly assign the integer value to a field in an UPDATE statement, but get an error.
This is statement I am trying to execute:
FOR user_record IN (SELECT * FROM users_to_add) LOOP
UPDATE
customer."user"
SET
primary_site_id = ({site_GRO, site_WHS, site_SHR}[])[ceil(random()*3)],
WHERE
userid = (SELECT userID FROM customer.user
WHERE emailaddress=user_record.email_address);
END LOOP;
I am getting:
SyntaxError: syntax error at or near "{"
This same format works if the value being randomly selected is a string but since these are variables, the inside curly brackets can't be enclosed in quotes.
Use an ARRAY constructor instead of the (invalid) array literal.
(ARRAY[site_GRO, site_WHS, site_SHR])[ceil(random()*3)]
However, a set-based solution is typically more efficient than looping:
UPDATE customer."user" u
SET primary_site_id = CASE trunc(random()*3)::int
WHEN 0 THEN site_gro -- your variables here
WHEN 1 THEN site_whs
WHEN 2 THEN site_shr
END
FROM users_to_add ua
WHERE u.userid = ua.email_address;
Should achieve the same. Works inside a PL/pgSQL block or as standalone SQL DML command (then you need to interpolate variable values yourself).
A single multi-row UPDATE is much cheaper than many updates in a loop.
trunc() is slightly more correct than ceil(), as random() returns a value in the domain [0,1) (1 excluded). It's also faster.
And a CASE construct is substantially faster than building an array just to extract a single element from it.
Asides:
Avoid reserved words like user as identifiers. Always requires double-quoting, and can lead to confusing errors when forgotten.
Also avoid random capitalization in identifiers. This goes for SQL as well as for PL/pgSQL. See:
Are PostgreSQL column names case-sensitive?
Perhaps you can try splitting the index and array out into their own vars?
FOR user_record IN (SELECT * FROM users_to_add) LOOP
a := ARRAY[site_GRO, site_WHS, site_SHR];
i := ceil(random()*3);
UPDATE
customer."user"
SET
primary_site_id = a[i]
WHERE
userid = (SELECT userID FROM customer.user WHERE emailaddress=user_record.email_address);
END LOOP;

H2 - Setting variable does not work as expected

I am using h2 to generate some data for tests.
I have a select statement, that selects a random row in a table.
Within the select statement I set a variable called 'K' with the SET function (http://h2database.com/html/functions.html#set).
In a second statement within the same session I try to read / get the current value of the variable.
To get a reproducible result I use SYSTEM_RANGE(1,10) in the select statement below. I use the h2 web console to execute the statements.
If I set the variable like in the following statement
SET #K = SELECT X FROM SYSTEM_RANGE(1,10) ORDER BY RANDOM() LIMIT 1;
and I execute a subsequent VALUES statement
VALUES(#K)
it works as expected. The variable K has been set to a random value. Subsequent executions of both these statements show, that value of the variable changes randomly.
I would like to assign the variable within a select statement. Setting the variable as shown above does not help in reaching my goal.
This does not work as expected
SELECT SET(#K,X) FROM SYSTEM_RANGE(1,10) ORDER BY RANDOM() LIMIT 1;
VALUES(#K); -- value of K is different than result of above select
-- subsequent executions show that the result of the select changes as expected
— but the result of the values statement and thus the value of K does not change
I would expect the result of the select statement to be the same as the value of the variable, but they are not. Subsequent executions of both statements show, that the select statements delivers randomly selected results, but the values statement delivers the same / a constant integer.
(I tried a little....) and it is possible (not to call RAND, anytime you call VALUES):
SET #K = FLOOR(RAND() * 9) + 1;
VALUES(#K);
"Just omit" the select statement in SET

MariaDB JSON Functions with Arrays

I have JSON arrays stored as strings in a MariaDB table. I want to return the rows when certain key-values are present anywhere in the array.
I performed the following test....
set #json='[{"name":"Albert","state":"IL"},{"name":"John","state":"CA"}]'
Executed the following query ...
select json_value(#json,'$[0].name')='Albert'
It got me the desired result which is...
1
Even the following query also gave me the same result(may be '*' is treated as the first element)...
select json_value(#json,'$[*].name')='Albert'
But when I provide the second name("John") in the condition, I do not get any result...
select json_value(#json,'$[*].name')='John'
Result...
0
So my observation is that when we provide the array index the "json_value" function is able to return the desired result.
Is there a way where I can avoid specifying the array index and search the desired key-value?
As commented by #dbfiddle I tried using JSON_SEARCH function.
Here is what I tried to get the desired result.
create table label_test(labels VARCHAR(1000));
insert into label_test values ('[{"name":"Albert","state":"IL"},{"name":"John","state":"CA"}]');
select * from label_test where JSON_SEARCH(labels,'all','John') like '"$[%].name"';
The result is the whole row as expected.
[{"name":"Albert","state":"IL"},{"name":"John","state":"CA"}]

Check if value exists in Postgres array

Using Postgres 9.0, I need a way to test if a value exists in a given array. So far I came up with something like this:
select '{1,2,3}'::int[] #> (ARRAY[]::int[] || value_variable::int)
But I keep thinking there should be a simpler way to this, I just can't see it. This seems better:
select '{1,2,3}'::int[] #> ARRAY[value_variable::int]
I believe it will suffice. But if you have other ways to do it, please share!
Simpler with the ANY construct:
SELECT value_variable = ANY ('{1,2,3}'::int[])
The right operand of ANY (between parentheses) can either be a set (result of a subquery, for instance) or an array. There are several ways to use it:
SQLAlchemy: how to filter on PgArray column types?
IN vs ANY operator in PostgreSQL
Important difference: Array operators (<#, #>, && et al.) expect array types as operands and support GIN or GiST indices in the standard distribution of PostgreSQL, while the ANY construct expects an element type as left operand and can be supported with a plain B-tree index (with the indexed expression to the left of the operator, not the other way round like it seems to be in your example). Example:
Index for finding an element in a JSON array
None of this works for NULL elements. To test for NULL:
Check if NULL exists in Postgres array
Watch out for the trap I got into: When checking if certain value is not present in an array, you shouldn't do:
SELECT value_variable != ANY('{1,2,3}'::int[])
but use
SELECT value_variable != ALL('{1,2,3}'::int[])
instead.
but if you have other ways to do it please share.
You can compare two arrays. If any of the values in the left array overlap the values in the right array, then it returns true. It's kind of hackish, but it works.
SELECT '{1}' && '{1,2,3}'::int[]; -- true
SELECT '{1,4}' && '{1,2,3}'::int[]; -- true
SELECT '{4}' && '{1,2,3}'::int[]; -- false
In the first and second query, value 1 is in the right array
Notice that the second query is true, even though the value 4 is not contained in the right array
For the third query, no values in the left array (i.e., 4) are in the right array, so it returns false
unnest can be used as well.
It expands array to a set of rows and then simply checking a value exists or not is as simple as using IN or NOT IN.
e.g.
id => uuid
exception_list_ids => uuid[]
select * from table where id NOT IN (select unnest(exception_list_ids) from table2)
Hi that one works fine for me, maybe useful for someone
select * from your_table where array_column ::text ilike ANY (ARRAY['%text_to_search%'::text]);
"Any" works well. Just make sure that the any keyword is on the right side of the equal to sign i.e. is present after the equal to sign.
Below statement will throw error: ERROR: syntax error at or near "any"
select 1 where any('{hello}'::text[]) = 'hello';
Whereas below example works fine
select 1 where 'hello' = any('{hello}'::text[]);
When looking for the existence of a element in an array, proper casting is required to pass the SQL parser of postgres. Here is one example query using array contains operator in the join clause:
For simplicity I only list the relevant part:
table1 other_name text[]; -- is an array of text
The join part of SQL shown
from table1 t1 join table2 t2 on t1.other_name::text[] #> ARRAY[t2.panel::text]
The following also works
on t2.panel = ANY(t1.other_name)
I am just guessing that the extra casting is required because the parse does not have to fetch the table definition to figure the exact type of the column. Others please comment on this.