Is it possible to replace text by other text in SqlBase? - guptateamdeveloper

Is it possible to replace text in a SqlBase? I found there is a #REPLACE function but I see it just replaces using positions in the string:
The following expression returns the value 'RALPH':
#REPLACE('RALF', 3, 1, 'PH')
What I need is to replace a substring by another, In Sql Server it's like this:
This returns 'ABA':
SELECT REPLACE('ABC', 'C', 'A')
Thanks!

Using the literals in your 'ABC' to 'ABA' example....
Select #REPLACE( 'ABC', #FIND( 'C', 'ABC', 0 ), #LENGTH('C'), 'A' )
Using the literals in your 'RALF' to 'RALPH' example....
Select #REPLACE( 'RALF', #FIND( 'F', 'RALF', 0 ), #LENGTH('F'), 'PH' )

Related

Why does gsub fail for this pair of string and hash?

I am trying to replace parts of the string by picking certain words and then replacing them with values corresponding to them, stored in a hash. The example I'm working with is
s = '{{content}}, {{expected}}, {{description}}'
h = {"contents"=>"new int[]{1, 2, 3, 4}", "expected"=>"new java.util.ArrayList<Integer>(){{add(4);add(3);add(2);add(1);}}", "description"=>"example"}
result = s.gsub(/\{\{(.*?)\}\}/) {|x| h[x]}
Basically trying to replace all keys inside double curly braces with their values but this does not seem to work and result remains ', , '. Not sure if the issue is with the way the hash is created or if the regex is written incorrectly.
Any help figuring this out is greatly appreciated. Thanks
s = '{{contents}}, {{expected}}, {{description}}'
result = s.gsub(/\{\{(.*?)\}\}/)
irb(main):013:0> result.as_json
=> ["{{contents}}", "{{expected}}", "{{description}}"]
Your key was like "{{contents}}" so when you access the h[x] the x would have the value with the curly braces. You need to run the following.
irb(main):014:0> result = s.gsub(/\{\{(.*?)\}\}/) {|x| h[x.delete('{}')]}
=> "new int[]{1, 2, 3, 4}, new java.util.ArrayList<Integer>(){{add(4);add(3);add(2);add(1);}}, example"
Also the first key should be contents rather than content

Query jsonb array for integer member

Background: We use PaperTrail to keep the history of our changing models. Now I want to query for a Item, which belonged to a certain customer. PaperTrail optionally stores the object_changes and I need to query this field to understand, when something was created with this ID or changed to this ID.
My table looks simplified like this:
item_type | object_changes
----------|----------------------------------------------------------
"Item" | {"customer_id": [null, 5], "other": [null, "change"]}
"Item" | {"customer_id": [4, 5], "other": ["unrelated", "change"]}
"Item" | {"customer_id": [5, 6], "other": ["asht", "asht"]}
How do I query for elements changed from or to ID 5 (so all rows above)? I tried:
SELECT * FROM versions WHERE object_changes->'customer_id' ? 5;
Which got me:
ERROR: operator does not exist: jsonb ? integer
LINE 1: ...T * FROM versions WHERE object_changes->'customer_id' ? 5;
^
HINT: No operator matches the given name and argument type(s).
You might need to add explicit type casts.
For jsonb the contains operator #> does what you ask for:
Get all rows where the number 5 is an element of the "customer_id" array:
SELECT *
FROM versions
WHERE object_changes->'customer_id' #> '5';
The #> operator expects jsonb as right operand - or a string literal that is valid for jsonb (while ? expects text). The numeric literal without single quotes you provided in your example (5) cannot be coerced to jsonb (nor text), it defaults to integer. Hence the error message. Related:
No function matches the given name and argument types
PostgreSQL ERROR: function to_tsvector(character varying, unknown) does not exist
This can be supported with different index styles. For my query suggested above, use an expression index (specialized, small and fast):
CREATE INDEX versions_object_changes_customer_id_gin_idx ON versions
USING gin ((object_changes->'customer_id'));
This alternative query works, too:
SELECT * FROM versions WHERE object_changes #> '{"customer_id": [5]}';
And can be supported with a general index (more versatile, bigger, slower):
CREATE INDEX versions_object_changes_gin_idx ON versions
USING gin (object_changes jsonb_path_ops);
Related:
Index for finding an element in a JSON array
Query for array elements inside JSON type
According to the manual, the operator ? searches for any top-level key within the JSON value. Testing indicates that strings in arrays are considered "top-level keys", but numbers are not (keys have to be strings after all). So while this query would work:
SELECT * FROM versions WHERE object_changes->'other' ? 'asht';
Your query looking for a number in an array will not (even when you quote the input string literal properly). It would only find the (quoted!) string "5", classified as key, but not the (unquoted) number 5, classified as value.
Aside: Standard JSON only knows 4 primitives: string, number, boolean and null. There is no integer primitive (even if I have heard of software adding that), integer is a just a subset of number, which is implemented as numeric in Postgres:
https://www.postgresql.org/docs/current/static/datatype-json.html#JSON-TYPE-MAPPING-TABLE
So your question title is slightly misleading as there are no "integer" members, strictly speaking.
Use a lateral join and the jsonb_array_elements_text function to process each row's object_changes:
SELECT DISTINCT v.* FROM versions v
JOIN LATERAL jsonb_array_elements_text(v.object_changes->'customer_id') ids ON TRUE
WHERE ids.value::int = 5;
The DISTINCT is only necessary if the customer_id you're looking for could appear multiple times in the array (if a different field changed but customer_id is tracked anyway).

JSONB query in Rails for a key that contains an array of hashes

I have a Rails 5 project with a Page model that has a JSONB column content. So the structure looks like this (reduced to the bare minimum for the question):
#<Page id: 46, content: {..., "media" => [{ "resource_id" => 143, "other_key" => "value", ...}, ...], ...}>
How would I write a query to find all pages that have a resource_id of some desired number under the media key of the content JSONB column? This was an attempt that I made which doesn't work (I think because there are other key/value pairs in each item of the array):
Page.where("content -> 'media' #> ?", {resource_id: '143'}.to_json)
EDIT: This works, but will only check the first hash in the media array: Page.where("content -> 'media' -> 0 ->> 'resource_id' = ?", '143')
Using sql, this should give you all pages which have resource id 143:
select * from pages p where '{"resource_id": 143}' <# ANY ( ARRAY(select jsonb_array_elements ( content -> 'media' ) from pages where id=p.id ) );
Postgresql has a function called ANY (postgres docs) which uses the form expression operator ANY (array). The left-hand expression is evaluated and compared to each element of the array using the given operator.
Since the right hand side parameter to ANY has to be an array (not a json array), we use the jsonb_array_elements method to convert the content->media json array into a set of rows which are then converted into an array by using ARRAY().
The <# operator checks if the expression on the right contains the expression on the left side. Ex: '{"a": 1}'::jsonb <# '{"b": 2, "a": 1}'::jsonb will return true.

Unexpected empty list from Erlang list-comprehension

I use list comprehension for transforming database rows from the list of tuples to the list of maps. One day I have added some new column into my database table and forget to change the code everywhere.
And because of that I discovered a strange effect: database rows become an empty list.
Example of code in erl console:
> DbRows = [{1, 1, 1}, {2, 2, 2}].
[{1,1,1},{2,2,2}]
> [#{<<"col1">> => Col1, <<"col2">> => Col2} ||{Col1, Col2} <- DbRows].
[]
Why Erlang does not generate exception error: no match of right hand side value in this case?
Is this code OK, or some other syntax is preferred for performing such data transforming?
Erlang does not generate any exception because that's a right syntax. Generator {Col1, Col2} <- DbRows is a filter in the same time. So any element that does not match pattern just skipped.
In your case I would do something like that:
-define(FIELDS, [id, some1, some2]).
DbRows = [{1, 1, 1}, {2, 2, 2}].
Prepare = fun(X) ->
maps:from_list(lists:zip(?FIELDS, tuple_to_list(X)))
end.
[ Prepare(Row) || Row <- DbRows].
And when you add new field you need to add that field in the macros.
I don't like this "feature", since in my experience it tends to mask bugs, but nikit's answer is correct about the reason for the result you see.
You can get the exception by moving the pattern matching to the left side of the list comprehension:
[ case Row of {Col1, Col2} -> #{<<"col1">> => Col1, <<"col2">> => Col2} || Row <- DbRows ]

Hstore ActiveRecord query using wildcards for both keys and values

I want to use a query like this in my rails app:
Series.where("dimensions #> 'a=>1, b=>2'::hstore")
Except that I need to use wildcards (or named params) in order not to introduce an SQL injection bug:
Series.where("dimensions #> '?=>?, ?=>?'::hstore", 'a', '1', 'b', '2')
The latter doesn't work however, because hstore doesn't expect the keys/values to be quoted with single quotes. Here's the error:
PG::SyntaxError: ERROR: syntax error at or near "a"
LINE 1: ... "series".* FROM "series" WHERE (dimensions #> ''a'=>'1', '...
^
: SELECT "series".* FROM "series" WHERE (dimensions #> ''a'=>'1', 'b'=>'2''::hstore)
What's a proper way to use wildcards with hstore?
Thanks!
There are other ways to construct an hstore than using a text-to-hstore cast:
hstore(text[]): construct an hstore from an array, which may be either a key/value array, or a two-dimensional array.
hstore(text[], text[]): construct an hstore from separate key and value arrays.
hstore(text, text): make single-item hstore.
So you can do these things:
hstore('k', 'v') -- "k"=>"v"
hstore(array['k1', 'k2'], array['v1', 'v2']) -- "k1"=>"v1", "k2"=>"v2"
hstore(array['k1', 'v1', 'k2', 'v2']) -- "k1"=>"v1", "k2"=>"v2"
hstore(array[['k1', 'v1'], ['k2', 'v2']]) -- "k1"=>"v1", "k2"=>"v2"
Perhaps you'll more success with this:
Series.where('dimensions #> hstore(array[:k1, :v1, :k2, :v2])',
:k1 => 'a', :v1 => '1',
:k2 => 'b', :v2 => '2'
)
That version only has one level of quotes so AR shouldn't make a mess. I also switched to named placeholders along the way, more than a couple anonymous placeholders gets confusing.

Resources