search for multiple values in comma separated column (postgresql) - postgresql

I need to fetch all records where these (5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5) values exists in categories_id column.
The value that I need to search in categories_id can be multiple because it coming from the form.
+--------------------------------------+-------------+-------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| id | name | alias | description | categories_id |
+--------------------------------------+-------------+-------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 5565c08d-9f18-4b76-9cae-4a8261af48f5 | Honeycolony | honeycolony | null | ["5565bffd-7f64-494c-8950-4bef61af48f5"] |
+--------------------------------------+-------------+-------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| c8f16660-32cf-11e6-b73c-1924f891ba4d | LOFT | loft | null | ["5565bffd-25bc-4b09-8a83-4bef61af48f5","5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"] |
+--------------------------------------+-------------+-------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 5565c17f-80d8-4390-aadf-4a8061af48f5 | Fawn Shoppe | fawn-shoppe | null | ["5565bffd-25bc-4b09-8a83-4bef61af48f5","5565bffd-0744-4740-81f5-4bef61af48f5","5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"] |
+--------------------------------------+-------------+-------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------+
I have this function which work as in_array function php.
CREATE OR REPLACE FUNCTION public.arraycontain(
x json,
y json)
RETURNS boolean
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE a text;b text;
BEGIN
FOR a IN SELECT json_array_elements_text($1)
LOOP
FOR b IN SELECT json_array_elements_text($2)
LOOP
IF a = b THEN
RETURN TRUE;
END IF;
END LOOP;
END LOOP;
RETURN FALSE ;
END;
$BODY$;
ALTER FUNCTION public.arraycontain(json, json)
OWNER TO postgres;
But when I do this:
select * from "stores"
where arrayContain(stores.categories_id::JSON,'["5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"]')
it shows
ERROR: invalid input syntax for type json
DETAIL: The input string
ended unexpectedly.
CONTEXT: JSON data, line 1: SQL state: 22P02
here is the sqlfiddle (I couldn't update the arraycontain function in fiddle.)
My expected output from the fiddle is it should return last 3 rows that is Furbish Studio,Fawn Shoppe AND LOFT if search using this values ["5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"])
I am open for any recommendation.
I also tried this query below but it returns empty.
select id
from stores
where string_to_array(categories_id,',') && array['5565bffd-cd78-4e6f-ae13-4bef61af48f5','5565bffd-b1c8-4556-ae5d-4bef61af48f5'];
EDIT:
This code is actually a filter to filter data. So if I only filter using categories it didn't work but if there is a query before it it works
select * from "stores"
where name like '%ab%' and arrayContain(stores.categories_id::JSON,'["5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"]')
also the thing that amaze me is that the '%ab%' must contain more than two character if there's below <2 it will throw error. what could be wrong.

Click: demo:db<>fiddle
You can use the ?| operator, which takes a jsonb array (your column in that case) and checks a text array if any elements are included:
SELECT
*
FROM
mytable
WHERE categories_id ?| '{5565bffd-b1c8-4556-ae5d-4bef61af48f5,5565bffd-cd78-4e6f-ae13-4bef61af48f5}'
If your categories_id is not of type json (which is what the error message says) but a simple text array, you can compare two text arrays directly using the && operator:
Click: demo:db<>fiddle
SELECT
*
FROM
mytable
WHERE categories_id && '{5565bffd-b1c8-4556-ae5d-4bef61af48f5,5565bffd-cd78-4e6f-ae13-4bef61af48f5}'

Your code seems to work fine so perhaps sqlfiddle is the problem.
Try changing the separator in the schema building part to / (instead of ;) and make sure you have the correct version for Postgresql. json_array_elements_text is not supported in 9.3 (you can use json_array_elements instead in this case).
Also skip the " in the select statement.
Look here http://sqlfiddle.com/#!17/918b75/1
There might be an error in your data. Perhaps categories_id is an empty string somewhere.
Try this to see the offending data if any.
do $$
declare
r record;
b boolean;
begin
for r in (select * from stores) loop
b:= arrayContain(r.categories_id::JSON,'["5565bffd-b1c8-4556-ae5d-4bef61af48f5","5565bffd-cd78-4e6f-ae13-4bef61af48f5"]') ;
end loop;
exception
when others
then raise notice '%,%',r,r.categories_id;
return;
end;
$$
Best regards.
Bjarni

Related

Returning columns not records while calling a function [duplicate]

I was reading online about function on PostgreSQL and returns results
In this links:
SQL function return-type: TABLE vs SETOF records
How do I reference named parameters in Postgres sql functions?
http://www.postgresqltutorial.com/plpgsql-function-returns-a-table/
I have written this Function:
create or replace function brand_hierarchy(account_value int)
RETURNS table (topID INTEGER, accountId INTEGER, liveRowCount bigint,archiveRowCount bigint)
AS
$BODY$
SELECT * FROM my_client_numbers
where accountId = coalesce($1,accountId);
$BODY$
LANGUAGE sql;
Which works and return the results in a single column Type of record.
Note that might more than one row will return.
Now the response is:
record
(1172,1172,1011,0)
(1172,1412,10,40)
.....
I would like to get my results not as a record but as multiple columns
|---------|---------|------------|----------------|
| topID |accountId|liveRowCount|archiveRowCount |
|---------|---------|------------|----------------|
| 1172 |1172 | 1011 | 0 |
| 1172 |1412 | 10 | 40 |
Is there a way to return multiple columns from a PostgreSQL function
Functions returning a table (or setof) should be used in the FROM clause:
select *
from brand_hierarchy(1234)
I was able to see it as expected with this query:
SELECT * FROM brand_hierarchy (id)
I found this function crosstab I think it is what you're looking for
https://www.postgresql.org/docs/9.3/tablefunc.html

How to return result of dynamic SELECT inside a function in PostgreSQL?

A very similar question here but not quite the same as this one.
I have a function that uses IF statements to determine what type of SELECT query to return.
How can I declare what a CREATE FUNCTION statment should return when I will never know the exact columns a SELECT query within it might return? That is, I can't setup a RETURNS TABLE declaration with a list of columns because I don't know which columns might come back. All I know is that I definitely will want a table of results to be returned.
Here is my function (uncompleted, pseudo):
CREATE OR REPLACE FUNCTION functiona(_url character varying DEFAULT NULL)
RETURNS -- what type? if TABLE how do I know what columns to specify
LANGUAGE plpgsql
AS
$$
DECLARE
_urltypeid int;
BEGIN
IF _url IS NOT NULL
THEN
_urltypeid := reference.urltype(_url);
IF _urltypeid = 1
THEN
RETURN QUERY
SELECT location, auxiliary, response FROM tablea -- unique columns from one table
END IF;
IF _urltypeid = 2
THEN
RETURN QUERY
SELECT ip, location, host, authority FROM tableb -- unique columns from another table
END IF;
END IF;
END;
$$;
I come from a MS SQL Server background where I don't have to specify in the CREATE FUNCTIONstatement what I'm returning, hence this is very confusing for me.
Not an answer, but an explanation of why answer from #JonathanJacobson will not work using a simple example:
\d animals
Table "public.animals"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | integer | | not null |
cond | character varying(200) | | not null |
animal | character varying(200) | | not null |
CREATE OR REPLACE FUNCTION public.animal(a_type character varying)
RETURNS record
LANGUAGE plpgsql
AS $function$
BEGIN
SELECT row(id, cond, animal) FROM animals where animal = a_type;
END;
$function$
select * from animal('cat');
ERROR: a column definition list is required for functions returning "record"
LINE 1: select * from animal('cat');
CREATE OR REPLACE FUNCTION public.animal(a_type character varying)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT id, cond, animal FROM animals where animal = a_type;
END;
$function$
;
select * from animal('cat') as t(i integer, c varchar, a varchar);
i | c | a
---+------+-----
1 | fat | cat
2 | slim | cat
6 | big | cat
In order to use the output of a function returning a record or setof record you need to declare the output fields and types when you run the function.
You could use the record type. Not tested.
CREATE OR REPLACE FUNCTION functiona(_url character varying DEFAULT NULL)
RETURNS record
LANGUAGE plpgsql
AS
$$
DECLARE
_broadcasttypeid int;
BEGIN
IF _url IS NOT NULL
THEN
_urltypeid := reference.urltype(_url);
IF _urltypeid = 1
THEN
RETURN
(SELECT row(location, auxiliary, response) FROM tablea);
END IF;
IF _urltypeid = 2
THEN
RETURN
(SELECT row(ip, location, host, authority) FROM tableb);
END IF;
END IF;
END;
$$;
Other composite types, such as jsonb and hstore are also a solution.

How to count how many entries in a column are numeric using PostgreSQL

I am trying to count how many entries are in a column that are both numeric and fulfill other conditions. I understand how that script is meant to look in SQL:
SELECT COUNT(ingredients)
FROM data.pie
WHERE description LIKE 'cherry'
AND is.numeric(price) = true
But I'm not sure how to translate that into a PostgreSQL script. Any help would be appreciated.
Thank you.
Another alternative to the one shown by Tim is to create a function ...
CREATE OR REPLACE FUNCTION is_numeric(val VARCHAR) RETURNS BOOLEAN AS $$
DECLARE x NUMERIC;
BEGIN
x = val::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
END;
$$
STRICT
LANGUAGE plpgsql IMMUTABLE;
.. that can be used like this:
db=# SELECT is_numeric('foo'), is_numeric('1'), is_numeric('1.39');
is_numeric | is_numeric | is_numeric
------------+------------+------------
f | t | t
(1 Zeile)
Your current query, slightly modified, should work:
SELECT COUNT(ingredients)
FROM data.pie
WHERE
description LIKE 'cherry' AND
price ~ '^[0-9]+([.][0-9]+)?$';

How do I create a function in postgres that updates a field with a series of 'REGEXP_REPLACE's

I'm trying to create a function that homogenises text columns. It's a series of regex_replaces in a case when function.
I believe that the following (shortened) code should give me the solution:
CREATE OR REPLACE FUNCTION clean_data(address_token text) RETURNS
setof text
AS
$$
BEGIN
return case when address_token like '%allee' OR address_token LIKE '%ally' OR address_token LIKE '%aly' then regexp_replace(address_token,'(allee|ally|aly)$', 'alley')
when address_token like '%annex' OR address_token LIKE '%annx' OR address_token LIKE '%anx' then regexp_replace(address_token,'(annex$|annx$|anx$)', 'anex')
when address_token like '%arc' then regexp_replace(address_token ,'arc$', 'arcade')
.
.
.
when address_token like '%wls' then regexp_replace(address_token ,'wls$', 'wells') else address_token;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE newtable AS
select postcode, (clean_data(address1)) as address1 (clean_data(address2)) as address2, (clean_data(address3)) as address3
from oldtable where postcode SIMILAR TO '(a|b)%';
However when I run this, I get the error message:
RETURN cannot have a parameter in function returning set
LINE 5: return case when address_token like '%allee' OR address_to...
^
HINT: Use RETURN NEXT or RETURN QUERY.
When I take its advice and use 'RETURN QUERY' instead, I'm told:
syntax error at or near "case"
LINE 5: return query case when address_token like '%allee' OR addr...
Which I'm not finding very helpful.
What is the correct way to write this function?
I'm relatively new to SQL functions and am not 100 % sure about:
'returns setof text': is this going to return a field as expected?
language: is this SQL or plpgsql
'RETURN" vs 'RETURN NEXT' vs 'RETURN QUERY': I'm not sure of the difference here
I've been googling for the past couple of hours with very little progress and very little understanding gained so any help would be appreciated
The key error is that setof returns multiple rows of data: your function operates on one row at a time, so should simply return text. You've tagged this as plpgsql, but as it's only a single statement it would work equally well as SQL: the BEGIN and END statements are not required in SQL, and you would select rather than return the result. While your CASE statement is quite long, this is still a simple function with one input and one output as in the docs.
A second error is that you missed out the END of the CASE statement: you need to end the case, then end the plpgsql function, so you'll have a double end.
CREATE OR REPLACE FUNCTION clean_data(address_token text) RETURNS text
AS
$$
BEGIN
return case when address_token like '%allee' OR address_token LIKE '%ally' OR address_token LIKE '%aly' then regexp_replace(address_token,'(allee|ally|aly)$', 'alley')
.
.
.
when address_token like '%wls' then regexp_replace(address_token ,'wls$', 'wells') else address_token
end;
END;
$$ LANGUAGE plpgsql;
The PostgreSQL manual says:
SQL function can be declared to return a set (that is, multiple rows)
by specifying the function's return type as SETOF sometype, or
equivalently by declaring it as RETURNS TABLE(columns)
So, you use setof to return rows. In your case you are returning a 'field'. So you should change your return to RETURNS TEXT (without setof) or if you need a table, you should set the output of your case into a record or row.
Why bother with the case expression at all. Just use your regexp_replace functions as is, they already embody the predicates in your case statements.
Better yet put your expressions into a table and just loop through them:
SQL Fiddle
PostgreSQL 9.3 Schema Setup:
create table samples(address_token text);
create table replacements(exp varchar(30), value varchar(30), flags varchar(10));
INSERT INTO samples
(address_token)
VALUES
('DB Ally'),
('SQL Annex'),
('Penny Arc'),
('CPU Wls')
;
INSERT INTO replacements
(exp, value, flags)
VALUES
('(allee|ally|aly)$', 'alley', 'i'),
('(annex$|annx$|anx$)', 'anex', 'i'),
('arc$', 'arcade', 'i'),
('wls$', 'wells', 'i')
;
create or replace function clean_data(address_token text) returns text
as
$$
DECLARE
r record;
result text;
BEGIN
result := address_token;
for r in (select exp, value, flags from replacements) loop
result := regexp_replace(result, r.exp, r.value, r.flags);
end loop;
return result;
end;
$$ LANGUAGE plpgsql;
/
Query 1:
select * from replacements
Results:
| exp | value | flags |
|---------------------|--------|-------|
| (allee|ally|aly)$ | alley | i |
| (annex$|annx$|anx$) | anex | i |
| arc$ | arcade | i |
| wls$ | wells | i |
Query 2:
select address_token, clean_data(address_token) new_val
from samples
Results:
| address_token | new_val |
|---------------|--------------|
| DB Ally | DB alley |
| SQL Annex | SQL anex |
| Penny Arc | Penny arcade |
| CPU Wls | CPU wells |

How to use RETURN NEXT in a PL/pgSQL function?

I am trying to write a loop in a PL/pgSQL function in PostgreSQL 9.3 that returns a table. I used RETURN NEXT; with no parameters after each query in the loop, following examples I found, like:
plpgsql error "RETURN NEXT cannot have a parameter in function with OUT parameters" in table-returning function
However, I am still getting an error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
A minimal code example to reproduce the problem is below. Can anyone please help explain how to fix the test code to return a table?
Minimal example:
CREATE OR REPLACE FUNCTION test0()
RETURNS TABLE(y integer, result text)
LANGUAGE plpgsql AS
$func$
DECLARE
yr RECORD;
BEGIN
FOR yr IN SELECT * FROM generate_series(1,10,1) AS y_(y)
LOOP
RAISE NOTICE 'Computing %', yr.y;
SELECT yr.y, 'hi';
RETURN NEXT;
END LOOP;
RETURN;
END
$func$;
The example given may be wholly replaced with RETURN QUERY:
BEGIN
RETURN QUERY SELECT y_.y, 'hi' FROM generate_series(1,10,1) AS y_(y)
END;
which will be a lot faster.
In general you should avoid iteration wherever possible, and instead favour set-oriented operations.
Where return next over a loop is unavoidable (which is very rare, and mostly confined to when you need exception handling) you must set OUT parameter values or table parameters, then return next without arguments.
In this case your problem is the line SELECT yr.y, 'hi'; which does nothing. You're assuming that the implicit destination of a SELECT is the out parameters, but that's not the case. You'd have to use the out parameters as loop variables like #peterm did, use assignments or use SELECT INTO:
FOR yr IN SELECT * FROM generate_series(1,10,1) AS y_(y)
LOOP
RAISE NOTICE 'Computing %', yr.y;
y := yr.y;
result := 'hi';
RETURN NEXT;
END LOOP;
RETURN;
What #Craig already explained.
Plus, if you really need a loop, you can have this simpler / cheaper. You don't need to declare an additional record variable and assign repeatedly. Assignments are comparatively expensive in plpgsql. Assign to the OUT variables declared in RETURNS TABLE directly. Those are visible everywhere in the code and the FOR loop can also assign to a list of variables. The manual:
The target is a record variable, row variable, or comma-separated list of scalar variables.
CREATE OR REPLACE FUNCTION test0()
RETURNS TABLE(y integer, result text)
LANGUAGE plpgsql AS
$func$
DECLARE
yr RECORD; -- now unneeded
BEGIN
FOR y, result IN
SELECT g, 'text_'::text || g
FROM generate_series(1,10) g
LOOP
RAISE NOTICE 'Computing %', y;
RETURN NEXT;
END LOOP;
END
$func$;
Additional points
Do not use the identifier y twice (as OUT param and column alias) while you can easily avoid it. That's a loaded footgun. If this can't be avoided, table-qualify columns.
A final RETURN without params is good form, but totally optional. When control reaches the final END, the complete result is returned automatically.
g in FROM generate_series(1,10) g is both table alias and column alias automatically, unless an explicit column alias is given. It is effectively the same as FROM generate_series(1,10) g(g).
One way to do it
CREATE OR REPLACE FUNCTION test0()
RETURNS TABLE(y integer, result text) AS $$
BEGIN
FOR y, result IN
SELECT s.y, 'hi' result FROM generate_series(1,10,1) AS s(y)
LOOP
RETURN NEXT;
END LOOP;
END
$$ LANGUAGE plpgsql;
SELECT * FROM test0();
Outcome:
| Y | RESULT |
|----|--------|
| 1 | hi |
| 2 | hi |
| 3 | hi |
| 4 | hi |
| 5 | hi |
| 6 | hi |
| 7 | hi |
| 8 | hi |
| 9 | hi |
| 10 | hi |
Here is a SQLFiddle demo