Convert String to Array - PostgreSQL - postgresql

I have a column in a table that stores names separated by commas, example: "Mel's Hou Rest, Mel's Lad Rest". What I need is to convert this string into an array separated by commas.
The query I need is:
SELECT home_location, subs_state FROM cust
WHERE (home_location = ANY('{"Mel''s Hou Rest", Mel''s Lad Rest"}')) AND subs_state = 'active'
I have tried this, but I keep getting an error:
WHERE (home_location = ANY(string_to_array("Mel's Hou Rest, Mel's Lad Rest", ',')::text[])
Is there any way to accomplish this without me having to change the database from 'text' to 'array'

SQL uses single quotes for string literals. Your string "Mel's Hou Rest, Mel's Lad Rest" has double quotes around it which makes Postgres interpret it as an quoted identifier. You can use two single quotes to include one in the string.
SELECT * FROM cust WHERE home_location = ANY(string_to_array("Mel's Hou Rest, Mel's Lad Rest", ','))
-- ERROR: column "Mel's Hou Rest, Mel's Lad Rest" does not exist
SELECT * FROM cust WHERE home_location = ANY(string_to_array('Mel''s Hou Rest, Mel''s Lad Rest', ','))
-- OK
Also note that string_to_array does not remove whitespace around the delimiter which might not be what you expect.
For example:
-- With whitespace around the delimiter
=> SELECT string_to_array('foo, bar', ',')
string_to_array
-----------------
{foo," bar"}
=> select 'foo' = ANY(string_to_array('foo, bar', ','));
?column?
----------
t
=> select 'bar' = ANY(string_to_array('foo, bar', ','));
?column?
----------
f
-- Without extra whitespace
=> SELECT string_to_array('foo,bar', ',')
string_to_array
-----------------
{foo,bar}
=> select 'foo' = ANY(string_to_array('foo,bar', ','));
?column?
----------
t
=> select 'bar' = ANY(string_to_array('foo,bar', ','));
?column?
----------
t
This of course can be countered by normalising the input before using it in the query. In somes cases it might be feasible to strip the whitespace in the query with string_to_array(regexp_replace('foo, bar', '\s*,\s', ','), ',') but I would not complicate the queries like that without a good reason.

To supplement the accepted answer a bit...
Note that the array string literal notation (with curly braces) '{foo, ba''r, "b{a}z", "b,u,z"}' (equivalent to the more explicit ARRAY['foo', 'ba''r', 'b{a}z', 'b,u,z']) is still actually just a string. To be used as an array it needs to first be converted, which a few operations can do implicitly (like ANY()). In many cases though, you'd need to explicitly cast it (e.g. with CAST(array_literal as text[]) or array_literal::text[]).
Your first expression should therefore work if rewritten as
SELECT home_location, subs_state FROM cust
WHERE
home_location = ANY('{Mel''s Hou Rest, Mel''s Lad Rest}')
AND subs_state = 'active';

Related

Postgres: ANY function does not work on varchar array

I have a "product" table with a varchar[] column to keep excluded companies.
When I select the array for a specific product, I get it like this:
SELECT excluded_company_codes FROM product WHERE code = '123'
excluded_company_codes
----------
{'10'}
However, oddly enough, when I try to check if company code exists in the array with ANY function, it doesn't work:
SELECT '10'=ANY(product.excluded_company_codes) where code = '123'
?column?
----------
false
What am I doing wrong here?
The string in the array contains two single quotes. If you want to find that, you have to
SELECT '''10''' = ANY(product.excluded_company_codes)
FROM product
WHERE code = '123';
If you want to avoid doubling the quotes, you can use dollar quoting: $$'10'$$.

Use ilike any() with escape character

In PostgreSQL you can do a case insensitive query with ILIKE:
select * from test where value ilike 'half is 50$%' escape '$'
And you can query multiple values at once by combining ILIKE with ANY()
select * from test where value ilike any(array['half is 50%', 'fifth is 20%'])
The query above will match 'Fifth is 2019', which I do not want, but when I try to use ILIKE and ANY() with an escape character I get a syntax error.
Am I missing something stupid, or is this simply not supported? If not, is there another way to query in a case insensitive way with multiple values at once?
EDIT: To clarify, the query will accept parameters through JDBC, so the actual SQL will look something like
select * from test where value ilike any(?) escape '$'
This is why I'm looking make % and _ from the user input be interpreted as literals.
The ESCAPE clause in ILIKE refers only to literals and does not apply to expressions. You should use a backslash, or if not possible, you can try:
with test(value) as (
values
('half is 50%'),
('half is 50x'),
('fifth is 20%'),
('fifth is 2000')
)
select *
from test
where value ilike any(select replace(unnest(array['half is 50$%', 'fifth is 20$%']), '$', '\'))
value
--------------
half is 50%
fifth is 20%
(2 rows)
Looks a bit clumsy but works well.
To match them as raw strings, you may use the ~* operator for insensitive match.
knayak=# select 'Half is 50%' ~* any(array['half is 50%', 'fifth is 20%'])
knayak-# ;
?column?
----------
t --True
(1 row)
knayak=# select 'fifth is 20' ~* any(array['half is 50%', 'fifth is 20%']);
?column?
----------
f --False
(1 row)
If you wish to escape the right hand operands of ilike use "escape" string constants, which are an extension to the SQL standard. An escape string constant is specified by writing the letter E (upper or lower case) just before the opening single quote
knayak=# select 'Half is 50%' ilike any(array[E'half is 50\\%', E'half is 20\\%'])
knayak-# ;
?column?
----------
t
(1 row)
DEMO

How to append prefix match to tsquery in PostgreSQL

I'm trying to utilize the full text search feature of PostgreSQL, particularly when user types in some search text, I would like to display him results with an assumption that the last word is incomplete.
For that purpose the "*" wildcard character needs to be attached to the last tsquery lexeme. E.g. if the user types in "The fat ra" the tsquery should be 'fat' & 'ra':*.
If I append the wildcard to the input string and parse it with plainto_tsquery function then the wildcard is removed plainto_tsquery("The fat ra" || ":*") => 'fat' & 'ra'.
Constructing a tsquery manually with to_tsquery function requires a lot modifications to the string (such as trim spaces and other special characters, replace spaces with the ampersand character) to make the function accept it.
Is there an easier way to do that?
You can make the last lexeme in a tsquery a prefix match by casting it to a string, appending ':*', then casting it back to a tsquery:
=> SELECT ((to_tsquery('foo <-> bar')::text || ':*')::tsquery);
tsquery
-------------------
'foo' <-> 'bar':*
For your usecase, you'll want to use <-> instead of & to require the words to be next to each other. Here's a demonstration of how they're different:
=> SELECT 'foo bar baz' ## tsquery('foo & baz');
?column?
----------
t
(1 row)
=> SELECT 'foo bar baz' ## tsquery('foo <-> baz');
?column?
----------
f
(1 row)
phraseto_tsquery makes it easy to have specify many words that have to be next to each other:
=> SELECT phraseto_tsquery('foo baz');
phraseto_tsquery
------------------
'foo' <-> 'baz'
Putting it all together:
=> SELECT (phraseto_tsquery('The fat ra')::text || ':*')::tsquery;
tsquery
------------------
'fat' <-> 'ra':*
Depending on your needs, a simpler way might be to build a tsquery directly with a string then a cast:
=> SELECT $$'fat' <-> 'ra':*$$::tsquery;
tsquery
------------------
'fat' <-> 'ra':*

postgres coalesce fields, sum and group by

maybe someone can help me out with a postgres query.
the table structure looks like this
nummer nachname vorname cash
+-------+----------+----------+------+
2 Bert Brecht 0,758
2 Harry Belafonte 1,568
3 Elvis Presley 0,357
4 Mark Twain 1,555
4 Ella Fitz 0,333
…
How can I coalesce the fields where "nummer" are the same and sum the cash values?
My output should look like this:
2 Bert, Brecht 2,326
Harry, Belafonte
3 Elvis, Presley 0,357
4 Mark, Twain 1,888
Ella, Fitz
I think the part to coalesce should work something like this:
array_to_string(array_agg(nachname|| ', ' ||coalesce(vorname, '')), '<br />') as name,
Thanks for any help,
tony
SELECT
nummer,
string_agg(nachname||CASE WHEN vorname IS NULL THEN '' ELSE ', '||vorname END, E'\n') AS name,
sum(cash) AS total_cash
FROM Table1
GROUP BY nummer;
See this SQLFiddle; note that it doesn't display the newline characters between names, but they're still there.
The CASE statement is used instead of coalesce so you don't have a trailing comma on entries with a last name but no first name. If you want a trailing comma, use format('%s, %s',vorname,nachname) instead and avoid all that ugly string concatenation business:
SELECT
nummer, string_agg(format('%s, %s', nachname, vorname), E'\n'),
sum(cash) AS total_cash
FROM Table1
GROUP BY nummer;
If string_agg doesn't work, get a newer PostgreSQL, or mention the version in your questions so it's clear you're using an obsolete version. The query is trivially rewritten to use array_to_string and array_agg anyway.
If you're asking how to sum numbers that're actually represented as text strings like 1,2345 in the database: don't do that. Fix your schema. Format numbers on input and output instead, store them as numeric, float8, integer, ... whatever the appropriate numeric type for the job is.

handle escape sequence char in DB2

I want to search a column and get values where value containts \ .
I tried select * from "Values" where "ValueName" like '\'. But returns no value.
Also tried like "\" and like'\''%' etc. But no results.
See the DB2 Documentation on the LIKE predicate, in particular the parts about escape expressions.
What you want is
select * from Values where ValueName like '\\%' escape '\'
To give an example of usage:
create table backslash_escape_test
(
backslash_escape_test_column varchar(20)
);
insert into backslash_escape_test(backslash_escape_test_column)
values ('foo\');
insert into backslash_escape_test(backslash_escape_test_column)
values ('no slashes here');
insert into backslash_escape_test(backslash_escape_test_column)
values ('foo\bar');
insert into backslash_escape_test(backslash_escape_test_column)
values ('\bar');
select count(*) from backslash_escape_test where
backslash_escape_test_column like '%\\%' escape '\';
returns 3 (all 3 rows with \ in them).
select count(*) from backslash_escape_test where
backslash_escape_test_column like '\\%' escape '\';
returns 1 (the \bar row).
select * from Values where ValueName like '%\\%'
values is a not so good name because it may be confused with the values keyword
Don't escape it. You just need wildcards around it like this:
select count(*)
from escape_test
where test_column like '%\%'
But, suppose you really do need to escape the slash. Here's a simpler, more straightforward answer:
The escape-expression allows you to specify whatever character for escaping that you wish. So why use a character that you're looking for, thus requiring you to escape it? Use any other character instead. I'll use a plus sign as an example, but it could be a backslash, pound-sign, question-mark, anything other than a character you are looking for or one of the wildcard characters (% or _).
select count(*)
from escape_test
where test_column like '%\%' escape '+';
Now you don't have to add anything into your like-pattern.
To hold myself to the same standard of proof that #Michael demonstrated --
create table escape_test
( test_column varchar(20) );
insert into escape_test
(test_column)
values ('foo\'),
('no slashes here'),
('foo\bar'),
('\bar');
select 'test1' trial, count(*) result
from escape_test
where test_column like '%\%'
UNION
select 'test2', count(*)
from escape_test
where test_column like '%\\%' escape '\'
UNION
select 'test3', count(*)
from escape_test
where test_column like '%\%' escape '+'
;
Which returns the same number of rows for each method:
TRIAL RESULT
----- ------
test1 3
test2 3
test3 3