I need to create a function like this (scaled down to a minimum) where I send an array of strings that should be matched. But I cant make the query to work.
create or replace function bar(x text[]) returns table (c bigint) language plpgsql as $$
begin
return query select count(1) as counter from my_table where my_field in (x);
end;$$;
and call it like this
select * from bar(ARRAY ['a','b']);
I could try to let the parameter x be a single text string and then use something like
return query execute 'select ... where myfield in ('||x||')';
So how would I make it work with the parameter as an array?
would that be better or worse compared to let the parameter be a string?
Yes, an array is the cleaner form. String matching would leave corner cases where separators and patterns combined match ...
To find strings that match any of the given patterns, use the ANY construct:
CREATE OR REPLACE FUNCTION bar(x text[])
RETURNS bigint LANGUAGE sql AS
$func$
SELECT count(*) -- alias wouldn't visible outside function
FROM my_table
WHERE my_field = ANY(x);
$func$;
count(*) is slightly faster than count(1). Same result.
Note, I am using a plain SQL function (instead of plpgsql). Either has its pros and cons.
That's fixed with the help of unnest that converts an array to a set (btw, the function doesn't have to be plpgsql):
CREATE OR REPLACE FUNCTION bar(x text[]) RETURNS BIGINT LANGUAGE sql AS $$
SELECT count(1) AS counter FROM my_table
WHERE my_field IN (SELECT * FROM unnest(x));
$$;
The problem with using the array seems to be fixed by using
return query select count(1) as counter from my_table where my_field in (array_to_string(x,','));
The point of effiency still remains unsolved.
Related
On SQL Server 2019, I would like to write a Scalar-value Function returning a string stripped from a series of possible substrings.
Assuming all of the substrings I want to replace with '' are in table MY_TABLE, in column MY_SUBSTRINGS, what would be the most efficient way to use the results of SELECT MY_SUBSTRINGS FROM MY_TABLE as string patterns in a REPLACE function to strip #MyString from the substrings.
Should I store the result of the SELECT in a table variable and loop through each of the substrings and call REPLACE for each substring possibilities or is there a better way to do this?
If I understand your question:
Here is a little demonstration how you can replace multiple values
Let's pretend the table variable #Map is an actual table
Example
Declare #Map Table (sFrom varchar(100))
Insert Into #Map values
('some')
,('begin')
,('curDateTime')
Declare #S varchar(max) = 'This is some string to begin testing [curDateTime]'
Select #S=replace(#S,sFrom,'')
From (Select top 1000 * From #Map Order By len(sFrom) Desc) A
Select ltrim(rtrim(replace(replace(replace(#S,' ','†‡'),'‡†',''),'†‡',' ')))
Returns
This is string to testing []
Note: The final Select ltrim(rtrim(...)) strips any number of duplicating spaces. This is optional
I have a stored procedure that has a customized sort (after order by). In other words, after order by I do some substring and length calculations on some fields and sort on two criteria:
Order By
LEFT(InvoiceID, PATINDEX('%[0-9]%', InvoiceID) - 1), --first sort value
CONVERT(INT, SUBSTRING(InvoiceID, PATINDEX('%[0-9]%', InvoiceID), LEN(InvoiceID))) -- second sort value
The problem is that I need to use the same sort on many other stored procedures. So I want to create a function that applies the same sort. While I can make a function that returns one value, I need a function that returns two values as above.
This sql function does not work:
CREATE FUNCTION [dbo].[fn_AlphaNumreicSort] (#AlphaNumreicValue nvarchar(MAX))
RETURNS (
#SortedVar1 nvarchar(MAX)
,#SortedVar2 nvarchar(MAX)
)
AS
....
What is the best way to handle this?
To return more than one value, you need to declare a table valued function.
In your case you can make an inline function too, which has the added benefit of it working much faster, as if it was a part of the query.
create function [dbo].[fn_AlphaNumreicSort] (#AlphaNumreicValue nvarchar(MAX))
returns table
as
return (
select
LEFT(#AlphaNumreicValue, PATINDEX('%[0-9]%', #AlphaNumreicValue) - 1) as SortedVar1,
CONVERT(INT, SUBSTRING(#AlphaNumreicValue, PATINDEX('%[0-9]%', #AlphaNumreicValue), LEN(#AlphaNumreicValue))) as SortedVar2
);
You then use it in the query with outer apply:
select ...
from
some_table
outer apply [dbo].[fn_AlphaNumreicSort](InvoiceID) as sort_values
...
order by
sort_values.SortVar1,
sort_values.SortVar2
Let's say I have a table called SomeTable with a primary key row_id and json array field called some_json.
some_json would look something like this:
[{'some_key': 'some_value_1'}, {'some_key': 'some_value_2'},
{'some_key': 'some_value_3'}, {'some_key': 'some_value_4'}]
and I have a function that takes a text array parameter called values_to_remove like this:
['some_value_2', 'some_value_3', etc]
I want to update some_json by removing all of its json objects that contain a text value that's also in the values_to_remove array. I have a rough idea of what I need to do but don't know how to piece it all together with the correct syntax, so forgive me if doesn't make sense.
Here's what I have so far (forgive me for butchering the syntax):
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
BEGIN
UPDATE SomeTable
SET some_json = array_to_json(array_remove(ARRAY(SELECT *
FROM json_array_elements(some_json)),
(SELECT *
FROM json_array_elements(some_json),
unnest(values_to_remove)
WHERE some_json->>some_key = values_to_remove.value
)))
WHERE row_id = removal_id;
END;
$$ LANGUAGE plpgsql;
What's the correct solution for achieving this? Thanks.
Here is the function made with plain SQL
CREATE OR REPLACE FUNCTION remove_json_items (removal_id smallint,
values_to_remove text[])
RETURNS void AS $$
UPDATE sometable
SET some_json =
(SELECT json_agg(ae)
FROM (
SELECT json_array_elements(some_json) AS ae from sometable WHERE row_id = removal_id
) sq1
WHERE NOT ARRAY[(SELECT * FROM json_to_record(ae) AS x(some_key text))] <# ARRAY['some_value_3', 'some_value_2'])
WHERE row_id = removal_id;
$$ LANGUAGE SQL;
I want to create a query using something like the following:
select id, array(id_adj(id)) from existingtable
which would be two columns: 1 with the id, and the 2nd column with an array of integers.
The function id_adj returns a set of rows (single column of integers) and is written as follows:
DROP FUNCTION IF EXISTS id_adj(hz_id int);
CREATE FUNCTION id_adj(id int) returns SETOF int AS $$
select b.id
from existingtable a, existingtable b
where a.id != b.id
and a.id=$1
and ST_Distance(a.wkb_geometry, b.wkb_geometry) <= 0.05
$$LANGUAGE SQL
The above function works for a single id. For example:
select id_adj(462);
returns a single column with integer values.
I know that the array() function returns an array of values given a query result from a SELECT statement. For example:
select array(select id from existingtable where id<10);
returns an array "{6,5,8,9,7,3,4,1,2}".
But combining the two together does not seem to work. Note that although I'm using a postgis ST_Distance function above, it is not required to test a solution to my problem.
I'm also open to having the function return an array instead of a setof records, but that seemed more complicated at first.
You are missing a select statement
select
id,
array(select id_adj(id))
from existingtable
I am a newbie in PostgreSQL (using v9.0) and wanted to know if using return next and Return query in the same function is possible without exiting the function before attaching the results in the resultset?
CREATE TYPE return_type AS
(paramname character varying,
value character varying);
CREATE OR REPLACE FUNCTION myfuntion(param1 character varying)
RETURNS SETOF return_type AS
declare
r return_type;
message varchar;
status integer;
$BODY$
BEGIN
o_call_status := 0;
o_call_message := '';
Return query Select 'mystatus' as paramName, status::varchar as value;
Return query Select 'mymessage' as paramName, message as value;
for r in SELECT 'mycolumnname1' as paramName,mycolumn1 as value FROM tb1
WHERE column1 = val
UNION ALL
SELECT 'mycolumnname2' as paramName,mycolumn2 as value FROM tb1
WHERE column1 = val
UNION ALL
SELECT 'mycolumnname3' as paramName,mycolumn3 as value FROM tb2
WHERE column1 = val1 AND
column4 = val4 loop
return next r;
end loop;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
Returning from a plpgsql function
You can mix RETURN NEXT and RETURN QUERY freely and repeatedly. The underlying principle is that plpgsql builds the table locally and does not return until the function is finished:
Per documentation:
Note: The current implementation of RETURN NEXT and RETURN QUERY
stores the entire result set before returning from the function, as discussed above.
That means you can even raise en exception to abort the operation if you are unsatisfied with the results so far and the client won't see a thing. We also included an example in the manual demonstrating this, just above said quote.
Cross tabulation
As for what you are trying do achieve, consider the crosstab() function for the tablefunc extension. Related answer with sample details:
PostgreSQL Crosstab Query
I'm unsure if you can mix the two (try it…).
That said, it'll be much more efficient in you use-case to write a single query with a union all clauses that returns all rows directly:
return query
select 'mystatus' as paramName, status::varchar as value
union all
select 'mymessage' as paramName, message as value
union all
SELECT 'mycolumnname1' as paramName,mycolumn1 as value
FROM tb1
WHERE column1 = val
union all
…
You might also find this contrib module helpful, btw:
http://www.postgresql.org/docs/current/static/tablefunc.html
And, if possible, revisit your schema or the way you use it, so you don't need this kind of function to begin with — set returning functions that return potentially large sets can be particularly inefficient. In your case, it seems like you want three columns. Why not simple use?
select col1, col2, col3 from tbl