Query jsonb column in Postgres against multiple values - postgresql

I have a column 'carInfo' of type jsonbin my PostgreSQL database and I want to be able to query against multiple values. I want the query to return only those rows that match all criteria. For example, if I had multiple columns, I would write the query like this:
select *
from car
where name = 'BMW'
AND 'year' = 2020
Since my column is of type jsonb I can make use of the containment operator (#>) like this:
select *
from car
where carInfo #> cast('{"name":"BMW"}') as jsonb
AND carInfo #> cast('{"year":"2020"}')
but I want the query to be dynamic, so that the user can query by any attributes they want.
So, they will send a list of search terms (in this case, the elements of that list would be {"name":"BMW"} and {"year":"2020"}.
Assuming, that I have the list as above, how would the query look like if I wanted to achieve the same result as when using the AND operator?
I tried it like this:
select *
from car
where carInfo #> any(array['{"name":"BMW"}', '{"year":"2020"}']::jsonb[])
but it acts the same way as when using the OR operator. I need to find those rows that contain BOTH the search terms

You could use #> ALL, but this should be better:
WHERE carInfo #> cast('{"name":"BMW", "year": "2020"}' as jsonb)

Related

Error when filtering some data with like and jsonb in PostgreSQL

I keep having a problem when filtering some data in postgresql.
For example, I want to filter by a json.
My jsons are saved in the following way
"[{\"Brand\":\"Leebo\"},{\"Housing Color\":\"Black\"},{\"Beam Type\":\"High Beam, Low Beam\"}]"
And let's say that I want to filter after
[{\"Brand\":\"Leebo\"}]
Shouldn't I write something like that in the query?
SELECT * FROM public.products
WHERE attributes is not NULL
AND attributes::text LIKE '%{\"Brand\":\"Leebo\"}%';
I tried also
SELECT * FROM public.products WHERE attributes::jsonb #> '"[{\"Material\":\"Artificial Leather\"}]"'
Because I won't receive data
Do you know how I could proceed differently?
But it only works if the column has all the data (eg if I give the exact data that is in the column)
Also, how could I search with whereIn?
You have an array in your JSONB because those characters ([ and ]) are array characters. If you are sure that you will always have only one array in your JSONB so you can use this:
SELECT * FROM public.products
WHERE attributes is not NULL
AND attributes[0]->>'Brand' = 'Leebo'
But if you can have several arrays inside JSONB then use jsonb_array_elements for extracting array elements, after then you can use JSONB operations like as ->>

Postgres/jOOQ replace jsonb[] element

I'm having a Spring application with jOOQ and Postgresql database having a table (issues) with the following two columns:
id (Long)
documents (jsonb[]) <- array of jsonb (not jsonb array)
The document json structure is on the following format:
{
"id": (UUID),
"name": (String),
"owner"; (String)
}
What I want to achieve is to be able to replace documents with matching id (normally only one) with a new document. I'm struggling with the jOOQ or even the plain SQL.
I guess I need to write some plain SQL in jOOQ to be able to do this but that is ok (to a minimum). I had an idea to do the following:
Unnest the document column
Filter out the document that should be updated of the array
Append the document that should be updated
Store the whole array
Raw SQL looks like this but missing the new document to be added:
UPDATE issues SET documents = (SELECT ARRAY_AGG(doc) FROM issues, UNNEST(issues.documents) AS doc WHERE doc->>'id' != 'e4e3422f-83a4-493b-8bf9-37980d532538') WHERE issues.id = 1;
My final goal is to write this in jOOQ and append the document to be replaced. I'm using jOOQ 3.11.4.
You should be able to just concatenate arrays in PostgreSQL:
UPDATE issues
SET documents = (
SELECT ARRAY_AGG(doc) || '{"id":"e4e3422f-83a4-493b-8bf9-37980d532538","name":"n"}'::jsonb
FROM issues, UNNEST(issues.documents) AS doc
WHERE doc->>'id' != 'e4e3422f-83a4-493b-8bf9-37980d532538'
)
WHERE issues.id = 1
Some common array functions will be added to jOOQ in the near future, e.g. array concatenation, but you can get away for now with plain SQL templating I suspect?

PostgreSQL full text search - prioritizing results for results matching with all terms

Hi I'd like to define a query for PostgreSQL full-text search such that the results matching with all the terms are ranked higher while results matching with only some of the terms are also retrieved.
Example:
query: tree wood
I'd like to get results including both words in higher ranks. However, I'd also like to get results including only tree or wood.
I tried to use plainto_tsquery ('tree | wood') but this does not prioritize the records including all query terms.
I have a database column typed ts_vector and I'm doing the search on this column.
Thanks in advance for your answers.
Solution: I was able to achieve what I want after sorting the results by rank explicitly with something like:
select * from table, to_tsquery('english', 'wood | tree') query where vector_column ## query order by ts_rank_cd(search_index, query) desc;
The built in ranking functions ts_rank and ts_rank_cd should do this automatically. Note that the query fed to the ranking functions should be the one with the |, not &.
Note that it will probably be faster to first do the & query, and then do the | query only if the first one found no rows. But of course then you don't get any | rows if there is at least one & row, so you have to decide whether that is acceptable.

How do I check if an array column contains a value in Postgres using Objection JS?

I tried using an array of integers, then an array of strings. However, I keep getting the same error:
ERROR: operator does not exist: jsonb ?| integer[]
My query looks like this:
Bet.query()
.select(
'id',
'status'
)
.whereJsonSupersetOf('participants_ids', [userId])
.range()
.limit(10)
.orderBy('id', 'DESC')
.throwIfNotFound();
This is how the arrays are stored:
Each user has a screen where they can see their own bets against another users. In order to list bets for a logged in user, I need to check the participants_ids column. This is an array that contains the ids for the 2 users betting against each other.
The purpose of my query is to return a list of bets where the current user's Id is contained inside each bet's participants_ids array.
Originally, I tried user .where() and .orWhere() to check if the current user's id was either the bet host's id, or the bet challenger's id. This didn't give me the result I wanted though. So I decided an array column would be much better.
I can't seem to get this to work though. I've looked at a few posts, but they seem to be arrays of objects rather than arrays of ints or strings. I simply want to check the participants_ids array column contains the userId I am passing into the query.
I am also using Knex JS.
Any idea what I could be doing wrong here?
Thanks in advance.
.whereJsonXXX methods works only for postgresql jsonb columns.
For querying arrays column types you need to use array operators https://www.postgresql.org/docs/12/functions-array.html
Bet.query()
.select(
'id',
'status'
)
.where('participants_ids', '#>', [userId])
.range()
.limit(10)
.orderBy('id', 'DESC')
.throwIfNotFound();
Or maybe .where('participants_ids', '#>', val([userId]).asArray().castTo('integer[]')) if the array is not passed properly in the first example.

Index on JSON field with dynamic keys

I'm on PG 9.5 and I have a table Visitors(id, data::json)
Example:
Visitor(id: 1, data: {name: 'Jack', age: 33, is_user: true })
I'd like to perform queries like
Give me all visitors named Jack and age > 25
Give me all visitors who are users, but where name is unspecified (key not in json)
The keys inside the data column user-specified and as such are dynamic.
Which index makes the most sense in this situation?
You can use a GIN index on a jsonb column, which gives you generalized, dynamic indexing of keys and values inside JSON value.
CREATE TABLE visitors (
id integer,
data jsonb
);
CREATE INDEX idx_visitors_data ON cards USING GIN (data);
SELECT * FROM visitors
WHERE data -> 'is_user' AND NOT data ? 'name';
Unfortunately, GIN indexes don't support numeric range comparisons. So while you could still issue a query for visitors named Jack aged over 25:
SELECT * FROM visitors
WHERE data #> '{"name": "Jack"}' AND ((data ->> 'age')::integer) > 25;
This will only use the index to find the name "Jack", and possibly to find rows which have an "age" key, but the actual test that the ages are over 25 will be done as a scan over the matching rows.
Note that if you really need range comparisons, you can still add non-GIN indexes on specific paths inside the JSON value, if you expect them to appear often enough to make that worthwhile. For example, you could add an index on data -> 'age' that supports range comparisons:
CREATE INDEX idx_visitors_data_age ON visitors ( ((data ->> 'age')::integer) );
(note the extra parentheses; you'll get an error without them).
See this excellent blog post for further information.
You can look at additional extension JsQuery – is a language to query jsonb data type, it provides an additional functionality to jsonb (currently missing in PostgreSQL), such as a simple and effective way to search in nested objects and arrays, more comparison operators with indexes support. Read more here: https://github.com/postgrespro/jsquery.
In your cases, you can create jsonb_path_value_ops index:
CREATE INDEX idx_visitors ON visitors USING GIN (jsonb jsonb_path_value_ops);
and use the next queries:
select * from visitors where jsonb ## 'name = "Jack" and age > 25';
select * from visitors where jsonb ## 'not name = * and is_user=true';
I believe the best approach here is to create a raw sql migration:
Run ./manage.py makemigrations --empty yourApp where yourApp is the app of the model you want to change indexes for.
Edit the migration i.e.
operations = [
migrations.RunSQL("CREATE INDEX idx_content_taxonomies_categories ON common_content((taxonomies->>'categories'));")
]
Where idx_content_taxonomies_categories is the name of the index, common_content is your table, taxonomies is your JSONField, and categories in this case is the key you want to index.
That should do it. Cheers!