PostgreSQL: Extract specific information from json - sql

I'm using PostgreSQL 9.6 and I have the following table, let's call it table1:
id | json_data
____________________
200 | {"state": [3, 4, 5]}
I want to be able to perform the following query: extract the array inside the "state" key in json_data for the record with id 1, but also remove some of the integers from the array in the process.
For example (in pseudo-code):
extract_state(id = 200, remove_numbers_from_json_data_state = [3, 5]) should return [4]

There is no built-in function for that, but you can easily write your own:
create function remove_numbers(p_array jsonb, p_nr variadic int[] )
returns jsonb
as
$$
select jsonb_agg(x)
from jsonb_array_elements(p_array) as t(x)
where t.x::int <> ALL(p_nr);
$$
language sql
immutable;
Then you can use it like this:
select id, remove_numbers(json_data -> 'state', 4,5)
from t1
where id = 1;
If you prefer to pass a JSON array value, you can define the function like this:
create function remove_numbers(p_array jsonb, p_to_remove jsonb)
returns jsonb
as
$$
select jsonb_agg(x)
from jsonb_array_elements(p_array) as t(x)
where t.x not in (select *
from jsonb_array_elements(p_to_remove))
$$
language sql;
Then you would need to use remove_numbers(json_data -> 'state', '[4,5]')

You'd use a query like this:
SELECT json_data->'state'->>1 FROM table1 WHERE id = 200;
You can test this way, without having a table:
SELECT '{"state": [3, 4, 5]}'::jsonb->'state'->>1
Here is a reference for using JSON in Postgres:
https://www.postgresql.org/docs/current/functions-json.html
It returns 4, not [4], so if you need to, you could select it as an array:
SELECT ARRAY[('{"state": [3, 4, 5]}'::jsonb->'state'->>1)::int]

Related

postgresql triggers to insert json data into columns and keep left over fields

I have found this question PostgreSQL: Efficiently split JSON array into rows
I have a similar situation but for inserts instead.
Considering I do not have a table but raw json in a ndjson file...
{"x": 1}
{"x": 2, "y": 3}
{"x": 8, "z": 3}
{"x": 5, "y": 2, "z": 3}
I want to insert the data into a table of the form (where json fields which do not have a column are stored in the json column)
x
y
json
1
NULL
NULL
2
3
NULL
8
NULL
{"z": 3}
5
2
{"z": 3}
How do I define my table such that postgresql does it automatically on insert or \copy
Use the operator -> and cast the value to the proper type for values of existing regular columns. Use the delete operator to get the remaining JSON values.
I have used CTE in the example. Instead, create the table json_data with a single JSONB column and copy the JSON file to it with \copy
with json_data(json) as (
values
('{"x": 1}'::jsonb),
('{"x": 2, "y": 3}'),
('{"x": 8, "z": 3}'),
('{"x": 5, "y": 2, "z": 3}')
)
select
(json->'x')::int as x,
(json->'y')::int as y,
nullif(json- 'x'- 'y', '{}') as json
from json_data
Read about JSON Functions and Operators in the documentation.
Note. In Postgres 10 or earlier use the ->> operator instead of ->.
To automate the conversion when importing json data, define a trigger:
create table json_data(json jsonb);
create or replace function json_data_trigger()
returns trigger language plpgsql as $$
begin
insert into target_table
select
(new.json->>'x')::int,
(new.json->>'y')::int,
nullif(new.json- 'x'- 'y', '{}');
return new;
end $$;
create trigger json_data_trigger
before insert on json_data
for each row execute procedure json_data_trigger();
Test it in Db<>Fiddle.

How to get the value of jsonb data using only the key number id?

I have a jsonb column with the following data:
{"oz": "2835", "cup": "229", "jar": "170"}
I have the key number 0 that represents the first item "oz". How can I pull this value using the 0?
I'm thinking something similar to:
SELECT units->[0] as test
I only have the key ID to reference this data. I do not have the key name "oz".
Sounds like a horrible idea. But you can still create a function to implement this horrible idea:
create function jsonb_disaster(jsonb,int) returns jsonb language SQL as $$
select value from jsonb_each($1) with ordinality where ordinality=1+$2
$$;
select jsonb_disaster('{"oz": "2835", "cup": "229", "jar": "170"}',0);
jsonb_disaster
----------------
"2835"
You could also create your own operator to wrap up this disaster:
create operator !> ( function = jsonb_disaster, leftarg=jsonb, rightarg=int);
select '{"cup": "229", "jar": "170", "oz": "2835"}' !> 1;
?column?
----------
"229"

How to use ARRAY contains operator with ANY

I have a table where one column is array:
CREATE TABLE inherited_tags (
id serial,
tags text[]
);
Sample values:
INSERT INTO inherited_tags (tags) VALUES
(ARRAY['A','B','C']), -- id: 1
(ARRAY['D','E']), -- id: 2
(ARRAY['A','B']), -- id: 3
(ARRAY['C','D']), -- id: 4
(ARRAY['D','F']), -- id: 5
(ARRAY['A']); -- id: 6
I want to find rows which tags column contains some subset of words inside array. For example for input:
ARRAY[ARRAY['A','C'], ARRAY['F'], ARRAY['E']]::text[][]
I want to find all rows that contain ('A' and 'C') OR ('F') OR ('E'). So for example above I should get rows with ids: 1, 2, 5.
I was hoping that I could use syntax like this:
SELECT * FROM inherited_tags WHERE
tags #> ANY(ARRAY[ARRAY['A','C'], ARRAY['F'], ARRAY['E']]::text[][])
but I get error:
ERROR: operator does not exist: text[] #> text
LINE 1: SELECT * FROM inherited_tags where tags <# ANY(ARRAY[ARRAY['...
Postgres 9.6
plpgsql solution is acceptable but SQL is preferred.
DB-FIDDLE: https://www.db-fiddle.com/f/cKCr7Sfab6u8rqaCHhJvPk/0
The problem comes from the fact that the text[] and text[][] data types are internally the same data type. An array has a base type and dimensions, and the ANY operator will always extract the base type to compare, which will always be text and not text[]. It doesn't help that multidimensional arrays require that each subelement has the same length as every other. You can have ARRAY[ARRAY['A','C'],ARRAY['B','N']], but not ARRAY[ARRAY[2,3],ARRAY[1]].
In short, there is no direct way to make that particular query work. I tried to create a function and an operator for this as well, and that doesn't work, either, for different reasons. See how that went:
CREATE OR REPLACE FUNCTION check_tag_matches(
IN leftside text[],
IN rightside text)
RETURNS BOOLEAN AS
$BODY$
DECLARE rightarr text[];
BEGIN
SELECT CAST(rightside as text[]) INTO rightarr;
RETURN SELECT leftside #> rightarr;
END;
$BODY$
LANGUAGE plpgsql STABLE;
CREATE OPERATOR public.>>(
PROCEDURE = check_tag_matches,
LEFTARG = text[],
RIGHTARG = text,
COMMUTATOR = >>);
Then when testing it:
test=# SELECT * FROM inherited_tags WHERE
tags >> ANY(ARRAY[ARRAY['A','M'], ARRAY['F','E'], ARRAY['E','R']]::text[][]);
ERROR: malformed array literal: "A"
DETAIL: Array value must start with "{" or dimension information.
CONTEXT: SQL statement "SELECT CAST(rightside as text[])"
PL/pgSQL function check_tag_matches(text[],text) line 4 at SQL statement
It seems that when you try using a multidimensional array like ARRAY[ARRAY['A','M'], ARRAY['F','E'], ARRAY['E','R']]::text[][] in ANY(), it iterates not over ARRAY['A','M'], then ARRAY['F','E'], then ARRAY['E','R'], but over 'A','M','F','E','E','R'. The same thing happens when with unnest.
test=# SELECT unnest(ARRAY[ARRAY['A','M'], ARRAY['F','E'], ARRAY['E','R']]::text[][]);
unnest
--------
A
M
F
E
E
R
(6 rows)
Your remaining optiona are to define a function that will read array_length(rightside,1) and array_length(rightside,2) and use nested loops to check it all, or you can send multiple queries to get the inherited tags for each tag, or restructure your data somehow. And you can't even access the ARRAY['A','M'] element using rightside[1] to iterate over it, you're forced to go to the deepest level.
I don't think you can do that with a single condition because of the "contains A and C" requirement.
SELECT *
FROM inherited_tags
WHERE tags #> ARRAY['A','C']
OR tags && array['F', 'E'];
tags #> ARRAY['A','C'] selects those where tags contains all elements from ARRAY['A','C'] and tags && array['F', 'E'] selects those rows that contain at least one of the tags from array['F', 'E']
Updated DB Fiddle: https://www.db-fiddle.com/f/rXsjqEN3ry67uxJtEs3GM9/0
u can try
SELECT * FROM table WHERE
tags #> ARRAY['A','C']::varchar[]
OR
tags #> ARRAY['E']::varchar[]
OR
tags #> ARRAY['F']::varchar[]

Select where id = array (Oracle JDBC)

I'm currently working with trying to compare an ID in Oracle(A VARCHAR2) with an array of IDs I have as input.
This is what I want to do:
Select user, city where id = :arrayOfIds
In Postgres with JDBC I would use:
Select user, city where id = any(:arrayOfIds)
Is there an equivalent function in Oracle, or a way to do this?
You should use:
Select user, city where id in (:arrayOfIds)
and in you code, you need to trasform your array in string with ids:
arrayOfIds[0] --> 1
arrayOfIds[1] --> 3
arrayOfIds[2] --> 5
...
in
1, 3, 5, ...
and you can use:
Array array = conn.createArrayOf("arrayOfIds", new Object[]{"1", "2","3"});
pstmt.setArray(1, array);
How to use an arraylist as a prepared statement parameter

how to bind a list of tuples using Spring JDBCTemplate?

I have some queries like this:
List listOfIntegers = Arrays.asList(new Integer[] {1, 2, 3});
List objects =
namedParameterJdbcTemplate.query("select * from bla where id in ( :ids )",
Collections.singletonMap("ids", listOfIntegers),
myRowMapper);
This will send this SQL query to the database:
select * from bla where id in ( 1, 2, 3 )
Now I want to send this type of query to the database:
select * from bla where (id,name) in ( (1,'foo'), (2,'bar'), (3,'foobar'))
Do I need to pass a List<List<Object>> to accomplish this? Will it work with Spring JDBCTemplate?
I have debugged Spring code and found that it expects tuples to be provided as Object[], so for it to work with a List it should be a List<Object[]>.