Compare two arrays in PostgreSQL - sql

I have a table in postgres with a value column that contains string arrays. My objective is to find all arrays that contain any of the following strings: {'cat', 'dog'}
id value
1 {'dog', 'cat', 'fish'}
2 {'elephant', 'mouse'}
3 {'lizard', 'dog', 'parrot'}
4 {'bear', 'bird', 'cat'}
The following query uses ANY() to check if 'dog' is equal to any of the items in each array and will correctly return rows 1 and 3:
select * from mytable where 'dog'=ANY(value);
I am trying to find a way to search value for any match in an array of strings. For example :
select * from mytable where ANY({'dog', 'cat'})=ANY(value);
Should return rows 1, 3, and 4. However, the above code throws an error. Is there a way to use the ANY() clause on the left side of this equation? If not, what would be the workaround to check if any of the strings in an array are in value?

You can use && operator to find out whether two array has been overlapped or not. It will return true only if at least one element from each array match.
Schema and insert statements:
create table mytable (id int, value text[]);
insert into mytable values (1,'{"dog", "cat", "fish"}');
insert into mytable values (2,'{"elephant", "mouse"}');
insert into mytable values (3,'{"lizard", "dog", "parrot"}');
insert into mytable values (4,'{"bear", "bird", "cat"}');
Query:
select * from mytable where array['dog', 'cat'] && (value);
Output:
id
value
1
{dog,cat,fish}
3
{lizard,dog,parrot}
4
{bear,bird,cat}
db<>fiddle here

Related

Splitting a row containing JSONB string array into two different rows (PostgreSQL)

Given a row that looks like this (PostgreSQL 10 and 11):
CREATE TABLE examples (
"id" varchar NOT NULL,
"type" varchar NOT NULL,
"relation_id" varchar NOT NULL,
"things" jsonb,
PRIMARY KEY ("id")
);
INSERT INTO examples(id, type, relation_id, things) values
('7287b283-f2d8-4940-94ae-c8253599d479', 'letter-number', 'relation-id-1', '["A", "B", "1", "2", "C"]');
INSERT INTO examples(id, type, relation_id, things) values
('7287b283-f2d8-4940-94ae-c8253599d480', 'letter-number', 'relation-id-2', '["A", "2", "C"]');
INSERT INTO examples(id, type, relation_id, things) values
('7287b283-f2d8-4940-94ae-c8253599d481', 'letter-number', 'relation-id-3', '[]');
How would you go ahead and split those rows into:
'7287b283-f2d8-4940-94ae-c8253599d480', 'relation-id-1', 'number', '["2"]'
'7287b283-f2d8-4940-94ae-c8253599d482', 'relation-id-1', 'letter', '["A", "C"]'.
Essentially split the "type" field and conditionally divide the jsonb array.
Assumptions:
Values in the JSONB are always there (they can be an empty array) but they are always structured like that.
There are other types in the same table.
Keeping the relation_id is crucial.
The values that go inside the JSONB array are know, there is only a couple of them (lets say five or six) so we can hardcode them in the query.
Changes have to be persisted. The original row can be removed/updated.
https://www.db-fiddle.com/f/4VV1tZD3pBYMiCmtTFtQnj/5 <- DB-fiddle.
I tried messing around with json_array_elements and INSERT ... SELECT with a subquery but that got me nowhere for now.
Click: demo:db<>fiddle
SELECT
id,
relation_id,
CASE WHEN elems ~ '[0-9]' THEN 'number' ELSE 'letter' END AS type, -- 2
jsonb_agg(elems) -- 3
FROM
examples,
jsonb_array_elements_text(things) elems -- 1
GROUP BY
1,2,3 -- 3
Expand the array into one row per element
Check if element is digit using a regular expression. If so, create new type number, otherwise letter
Group by this new type and reaggregate the elements
With extensions:
Update the table with the split rows (remove old, insert new)
Empty arrays generate rows with both types
Click: demo:db<>fiddle
WITH del AS (
DELETE FROM examples
RETURNING id, relation_id, type, things
)
INSERT INTO examples
SELECT
id || '_' || type,
relation_id,
type,
COALESCE(jsonb_agg(elems) FILTER (WHERE elems IS NOT NULL), '[]')
FROM (
SELECT
id,
relation_id,
CASE WHEN elems ~ '[0-9]' THEN 'number' ELSE 'letter' END AS type,
elems
FROM
del,
jsonb_array_elements_text(things) elems
UNION ALL
SELECT
id,
relation_id,
t,
null
FROM
examples,
unnest(array['number', 'letter']) as t
WHERE things = '[]'
) s
GROUP BY 1,2,3;

SQL query, which search all rows with specific items of array column

my sql case in postgres:9.6 database
CREATE TABLE my_table (
id serial PRIMARY KEY,
numbers INT []
);
INSERT INTO my_table (numbers) VALUES ('{2, 3, 4}');
INSERT INTO my_table (numbers) VALUES ('{2, 1, 4}');
-- which means --
test=# select * from my_table;
id | numbers
----+---------
1 | {2,3,4}
2 | {2,1,4}
(2 rows)
I need to find all rows with numbers 1 and/or 2. According this answer I use query like this:
SELECT * FROM my_table WHERE numbers = ANY('{1,2}'::int[]);
And got following error:
LINE 1: SELECT * FROM my_table WHERE numbers = ANY('{1,2}'::int[]);
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
How does correct sql query look?
Using var = ANY(array) works well for finding if a single value (var) is contained in an array.
To check if an array contains parts of another array, you would use the && operator
&& -- overlap (have elements in common) -- ARRAY[1,4,3] && ARRAY[2,1] --> true
SELECT * FROM my_table WHERE numbers && '{1,2}'::int[];
To check if an array contains all members of another array, you would use the #> operator
#> -- contains -- ARRAY[1,4,3] #> ARRAY[3,1] --> true
SELECT * FROM my_table WHERE numbers #> '{1,2}'::int[];

query column names for a single row where value is null?

I'm attempting to return all of the column names for a single row where the value is null. I can parse the entire row afterward but curious if there's a function that I can leverage.
I can return a JSON object containing key value pairs where the value is not null using row_to_json() and json_strip_nulls where conditional references a single unique row:
SELECT json_strip_nulls(row_to_json(t))
FROM table t where t.id = 123
Is there a function or simple way to accomplish the inverse of this, returning all of the keys (column names) with null values?
You need a primary key or unique column(s). In the example id is unique:
with my_table(id, col1, col2, col3) as (
values
(1, 'a', 'b', 'c'),
(2, 'a', null, null),
(3, null, 'b', 'c')
)
select id, array_agg(key) as null_columns
from my_table t
cross join jsonb_each_text(to_jsonb(t))
where value is null
group by id
id | null_columns
----+--------------
2 | {col2,col3}
3 | {col1}
(2 rows)
key and value are default columns returned by the function jsonb_each_text(). See JSON Functions and Operators in the documentation.
Actually the JSON approach might work. First transform the rows to a JSON object with row_ro_json(). Then expand the JSON objects back to a set using json_each_text(). You can now filter for NULL values and use aggregation to get the columns, that contain NULL.
I don't know what output format you want. json_object_agg() is the "complement" to your json_strip_nulls()/row_to_json() approach. But you may also want a JSON array (json_agg), just an array (array_agg()) or a comma separated string list (string_agg()).
SELECT json_object_agg(jet.k, jet.v),
json_agg(jet.k),
array_agg(jet.k),
string_agg(jet.k, ',')
FROM elbat t
CROSS JOIN LATERAL row_to_json(t) rtj(j)
CROSS JOIN LATERAL json_each_text(rtj.j) jet(k, v)
WHERE jet.v IS NULL
GROUP BY rtj.j::text;
db<>fiddle

how to select postgres rows where at least one json element matches some criteria?

Here's what I'm working with:
create table test(id INT, data JSON);
INSERT into test values
(1, '[{"key": 2}, {"key": 1}]'),
(2, '[{"key": 3}]'),
(3, '[{"key": 1}]');
select * from test;
select id from test where 1 == ANY( json_array_elements(data) ->> 'key');
What I'm trying to do is select all rows where any of the json objects in the data column have a key key with a value of 1. I trying to extract rows 1 and 3. Note, I'm not sure if the equality comparison == right before the ANY clause is correct.
When I run the above, I get the following error: ERROR: set-returning functions are not allowed in WHERE
If you are free to use jsonb instead of json (which is preferable in most cases), use the jsonb "contains" operator #>:
SELECT *
FROM test
WHERE data #> '[{"key": 1"}]';
Can be supported with a GIN index with default operator class or with the more specialized jsonb_path_ops:
CREATE INDEX test_data_gin_idx ON test USING gin (data jsonb_path_ops);
db<>fiddle here
Related:
Index for finding an element in a JSON array
You can use EXISTS and a correlated subquery to accomplish what you want.
SELECT test.id
FROM test
WHERE EXISTS (SELECT *
FROM json_array_elements(test.data) jar(e)
WHERE jar.e->>'key' = '1');
SQL Fiddle

CREATE TABLE … AS SELECT with discrete values with explicit column types

I want to execute this statement
CREATE TABLE Tab2 AS SELECT 1, "abc", 123456789.12 UNION SELECT 2, "def", 25090003;
on an SQLite database. How can I explicitely define the resulting column types?
BTW: This is a follow-up question on: CREATE TABLE ... AS SELECT with discrete values with explicit column names
According to the documentation, column types come from the expression affinities of the result columns in the SELECT.
Plain values like 1 or 'abc' do not have an affinity; you have to add it with CAST:
CREATE TABLE Tab2 AS
SELECT CAST(1 AS INT) AS i,
CAST('abc' AS TEXT) as t,
CAST(123456789.12 AS REAL) AS r
UNION ALL SELECT 2, "def", 25090003 ...