Postgres - append to jsonb string - postgresql

In Postgres, I have a jsonb column foo which stores an array of strings
["a","b","c"]
I need a query which appends another string to whatever is currently there, at a specified index
e.g. Append "!" at index 1
run query: ["a","b","c"] -> ["a","b!","c"]
run again: ["a","b","c"] -> ["a","b!!","c"]
run again: ["a","b","c"] -> ["a","b!!!","c"]
I've implemented this in Postgres v11.2 as follows
UPDATE my_table
SET foo = jsonb_set(foo, '{1}', CONCAT('"', foo->>1, '!', '"')::jsonb)
WHERE id = '12345';
Note the index 1 and the string '!' are just hardcoded here for simplicity - but they'd be variables.
It works, but I find it quite inelegant. As you can see, I'm selecting out the string at the given index as text using the ->> operator, using that as input to CONCAT to append the '!', and also to build that back into the correct syntax to convert back to a jsonb string. There is just a lot more work going on here than seems necessary, simply to append to a string at a given path.
Is there a simpler way to do this? A built-in function or operator perhaps, or a simpler way of appending than using CONCAT? (I tried using the || operator in various ways but couldn't seem to make anything work with the syntax & types)

I don't think there is a better way than jsonb_set().
The concat can be replaced by || as follows:
jsonb_set(foo, '{1}', ('"' || (foo->>1) || '!"')::jsonb)

Related

Text and jsonb concatenation in a single postgresql query

How can I concatenate a string inside of a concatenated jsonb object in postgresql? In other words, I am using the JSONb concatenate operator as well as the text concatenate operator in the same query and running into trouble.
Or... if there is a totally different query I should be executing, I'd appreciate hearing suggestions. The goal is to update a row containing a jsonb column. We don't want to overwrite existing key value pairs in the jsonb column that are not provided in the query and we also want to update multiple rows at once.
My query:
update contacts as c set data = data || '{"geomatch": "MATCH","latitude":'||v.latitude||'}'
from (values (16247746,40.814140),
(16247747,20.900840),
(16247748,20.890570)) as v(contact_id,latitude) where c.contact_id = v.contact_id
The Error:
ERROR: invalid input syntax for type json
LINE 85: update contacts as c set data = data || '{"geomatch": "MATCH...
^
DETAIL: The input string ended unexpectedly.
CONTEXT: JSON data, line 1: {"geomatch": "MATCH","latitude":
SQL state: 22P02
Character: 4573
You might be looking for
SET data = data || ('{"geomatch": "MATCH","latitude":'||v.latitude||'}')::jsonb
-- ^^ jsonb ^^ text ^^ text
but that's not how one should build JSON objects - that v.latitude might not be a valid JSON literal, or even contain some injection like "", "otherKey": "oops". (Admittedly, in your example you control the values, and they're numbers so it might be fine, but it's still a bad practice). Instead, use jsonb_build_object:
SET data = data || jsonb_build_object('geomatch', 'MATCH', 'latitude', v.latitude)
There are two problems. The first is operator precedence preventing your concatenation of a jsonb object to what is read a text object. The second is that the concatenation of text pieces requires a cast to jsonb.
This should work:
update contacts as c
set data = data || ('{"geomatch": "MATCH","latitude":'||v.latitude||'}')::jsonb
from (values (16247746,40.814140),
(16247747,20.900840),
(16247748,20.890570)) as v(contact_id,latitude)
where c.contact_id = v.contact_id
;

How to pass array as a parameter for rowMode="array" in pg-promise

I would like to get the result of a query using rowMode="array" (as this is a potentially very large table and I don't want it formatted to object format) but I couldn't figure out how to pass in a array/list parameter for use in an IN operator.
const events = await t.manyOrNone({text: `select * from svc.events where user_id in ($1:list);`, rowMode: "array"}, [[1,2]]);
However, the above gives an error: syntax error at or near ":"
Removing the :list did not work either:
const events = await t.manyOrNone({text: `select * from svc.events where user_id in ($1);`, rowMode: "array"}, [[1,2]]);
Error: invalid input syntax for integer: "{"1","2"}"
I understand that this might be because I'm forced to use ParameterizedQuery format for rowMode="array" which does not allow those snazzy modifiers like :list, but this then leads to the question, if I were to use ParameterizedQuery format, then how do I natively pass in a Javascript array so that it is acceptable to the driver?
I guess an alternative formulation to this question is: how do I use arrays as parameters for ParameterizedQuery or PreparedStatements...
Answering my own question as I eventually found an answer to this issue: how to pass in arrays as params for use in the IN operator when using rowMode="array" | ParameterizedQuery | PreparedStatements.
Because this query is being parameterized in the server, we cannot use the IN operator, because the IN operator parameterize items using IN ($1, $2, $3...). Instead we need to use the ANY operator, where ANY($1) where for $1 an array is expected.
So the query that will work is:
const events = await t.manyOrNone({text: `select * from svc.events where user_id=ANY($1);`, rowMode: "array"}, [[1,2]]);

How use '?' in #Query? How use Jsonb of postgres in spring-boot at all?

How to use ?| operator in postgres query in spring repository? I need to use where in my query for text type column which content json.
#Query(value =
"SELECT * \n" +
"FROM tbl t \n" +
"WHERE t.some_ids::::jsonb ?| array['152960','188775']", nativeQuery = true
)
List<Model> getModelsByIds();
But that don't work and I catch the next exeception:
org.springframework.dao.InvalidDataAccessApiUsageException: At least 1 parameter(s) provided but only 0 parameter(s) present in query.
You can use the associated function of that operator instead. Most of the time the obfuscation layers also choke on the :: cast operator, so you might want to use cast() instead:
WHERE pg_catalog.jsonb_exists_any(cast(t.some_ids as jsonb), array['152960','188775'])
However I think this wouldn't be able to make use of an index defined on some_ids::jsonb
You didn't mention how exactly the content of some_ids looks like.
If that is a JSON array (e.g. '["123", "456"]'::jsonb) then you can also use the contains operator #>:
WHERE cast(t.some_ids as jsonb) #> '["152960","188775"]'
If your JSON array contains numbers rather than strings ('[123,456]') you need to pass numbers in the argument as well:
WHERE cast(t.some_ids as jsonb) #> '[152960,188775]'

Selecting all rows where an array intersects in postgresql

Currently I'm trying, in a rails 3 application, to select all objects where two arrays have some overlap.
I've tried the following:
Contact.where("possible_unique_keys && ?" c.possible_unique_keys)
Which gives:
array value must start with "{" or dimension information
So I tried to convert the latter record into a postgresql array, like so:
Contact.where("possible_unique_keys && string_to_array(?)", c.possible_unique_keys)
Which gives:
operator does not exist: character varying[] && text[]
How so I get both arrays to a format that will correctly evaluate &&?
I think I've solved it for now, and come up with a way to build a query from a Ruby array to a postgresql array (thanks in part to this bit of code written for rails/postgresql interoperability.
Given a list of strings ["bill", "joe", "stu", "katie", "laura"], we have to do a little bit in the way of acrobatics to get postgresql to recognize them. This is a solution to construct the request.
request = "SELECT * from Contacts where possible_unique_keys && \'{"
list.each do |key|
key.gsub("'", "\\'")
if key == c.possible_unique_keys.last
request << "\"#{key}\"}\'"
else
request << "\"#{key}\", "
end
end
dupes = Contacts.find_by_sql(request)

Zend Framework: Re-use WHERE clause returned from Zend_Db_Select::getPart()

I have a SELECT object which contains a WHERE.
I can return the WHERE using getPart(Zend_Db_Select::WHERE), this returns something like this:
array
0 => string "(clienttype = 'agent')"
1 => string "AND (nextpayment < (NOW() - INTERVAL 1 DAY))"
This array seems pretty useless as I cannot do this with it
$client->update(array("paymentstatus" => "lapsed"), $where);
Or even put it into another SELECT object. Is there a way of getting a more useful representation of the WHERE statement from the SELECT object?
Thanks
EDIT
The best I have come up with so far is
$where = new Zend_Db_Expr(implode(" ", $select->getPart(Zend_Db_Select::WHERE)));
Your first choice, $client->update(...) would work, if getParts omitted the 'AND' from the second part of the where clause.
I'm pretty sure your only choice is to:
use your second option (probably safest depending on how complex the where clauses are) -or-
iterate through the $where returned and remove the leading 'AND', 'OR' that may exist.