Postgresql: [42883] ERROR: operator does not exist: text || integer[] - postgresql

I getting error like below: [42883] ERROR: operator does not exist: text || integer[] Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts. I cannot fix this problem and tried so many times.
My Code:
DECLARE
arr_operators integer[1,2];
BEGIN
query1 := 'SELECT * FROM dist.' || _rec1.table_name || ' WHERE operator_id = ANY (''' || arr_operators || ''');';
FOR _rec IN EXECUTE query1 LOOP
END LOOP;
I think problem is happening when I am makin query string.But when I use this statement in query directly lik below is working well:
FOR _rec1 IN (SELECT * FROM dist.sirdarya WHERE id = any (arr_operators)) LOOP
INSERT INTO dist.justt(column1,column2) VALUES (_rec1.id,_rec1.msisdn);
END LOOP;
Any help is appreciated.

I suggest unnesting an array instead of concatenating string which could lead to SQL Injection:
SELECT *
FROM table_name
WHERE operator_id IN (SELECT * FROM unnest(arr_operators));
db<>fiddle demo
This part is particulary dangerous:
query1 := 'SELECT * FROM dist.' || _rec1.table_name
What if table name is let's say: ;DROP DATABASE ...;--?
It could be rewritten as:
query1 := FORMAT('SELECT * FROM dist.%I ...', _rec1.table_name);

Related

Dynamic query with single quote

I'm trying to create a stored procedure that will create a select statement.
My procedure looks like below.
CREATE OR REPLACE PROCEDURE record_example()
LANGUAGE plpgsql
AS $$
DECLARE
unload_query text;
BEGIN
unload_query := 'query = ('''select * from my_table''')';
insert into query values (unload_query);
END;
$$;
But its throwing error. Im not able to close the single quote properly.
LINE 1: SELECT 'query = ('''select * from my_table''')'
^
QUERY: SELECT 'query = ('''select * from my_table''')'
CONTEXT: SQL statement in PL/PgSQL function "record_example" near line 5
Expected output:
Unload query = query = (select * from my table)
If you want the result of a query inside a string, you need to use the string concatenation operator. Also, subqueries must be surrounded by parentheses.
unload_query := 'query = (''' || (SELECT * FROM my_table) || ''')';
This will fail if the query returns more than a single row.
If you want the literal string in query, you have too many quotes:
unload_query := 'query = (''select * from my_table'')';

PostgreSQL left join with alias in a loop

Is it possible to iterate over a table's records and make a left join with them in a stored procedure?
Something like this:
FOR r IN SELECT tablename FROM tablewithtablenames ORDER BY tablename ASC
LOOP
INSERT INTO temp_Results
SELECT
temp_ids.Key as Key,
loggedvalue.pk_timestamp,
FROM
(temp_idS AS temp_ids
LEFT JOIN
quote_ident(r.tablename) AS loggedvalue
ON temp_ids.Key = loggedvalue.pk_fk_id);
END LOOP;
Unfortunately i get the following error message when i want to execute the stored procedure. (Function creation was successful.)
Error message:
ERROR: column loggedvalue.pk_fk_id does not exist LINE 29:
ON temp_ids.Key = "loggedvalue...
I have the feeling that i convert the record in a wrong way maybe because when i manually replaced the quote_ident(r.tablename) to the name of the table that i know the r contains it was fine, also i traced out the r.tablename in the loop and it was correct also.
As a_horse_with_no_name pointed out i should have use dynamic sql because in plpgsql you can not use a variable as a table name so i eliminated the loop and i used a union all:
CREATE OR REPLACE FUNCTION getaffectedtables(
OUT tableNames TEXT)
as $$
BEGIN
SELECT TRIM(TRAILING ' UNION ALL ' FROM string_agg('','SELECT * FROM "' || "tablename" || '" UNION ALL '))
INTO tableNames
FROM exampleTable;
END;$$
LANGUAGE plpgsql;
Then i used dynamic execute:
DECLARE
affectednames TEXT;
BEGIN
affectednames := getaffectedtables();
EXECUTE '
SELECT
temp_ids.Key as Key,
loggedvalue.pk_timestamp,
FROM
(temp_idS AS temp_ids
LEFT JOIN
('|| affectednames ||') AS loggedvalue
ON temp_ids.Key = loggedvalue.pk_fk_id);';

PostgreSql pass array argument in WHERE NOT

I have a sql function that creates a materialized view:
CREATE FUNCTION reports_mt_views(exclude_ids int[]) RETURNS void AS
$BODY$
BEGIN
EXECUTE 'CREATE MATERIALIZED VIEW tx_materialized AS
SELECT tx.transaccion_id AS tx_transaccion_id,
...
WHERE creation_date > (current_date - interval '' 3 month '')
AND (account_id <> ALL (' || $1 || '))'
RETURN;
END;
$BODY$ LANGUAGE plpgsql STRICT;
I also tried as:
AND account_id NOT IN ' || $1|| ')'
But it does not work. When I execute:
SELECT reports_mt_views(ARRAY [1,2,8,538524]);
I have this error:
ERROR: operator does not exist: text || integer[]
LINE 171: AND (account_id <> ALL (' || $1 || '))'
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
There is not a problem with the array itself I tested it with a FOR loop and works. But into the condition it does not. What I have missed here?
The solution was to concanate the array as a text then it can be readby the clause where not in.
CREATE OR REPLACE FUNCTION pps.reports_mt_views(exclude_ids integer[])
...
DECLARE
in_accounts ALIAS FOR $1;
out_accounts TEXT;
BEGIN
out_accounts := in_accounts;
out_accounts := trim(leading '{' FROM out_accounts);
out_accounts := trim(trailing '}' FROM out_accounts);
And it can be used as:
WHERE (orden.cuenta_id NOT IN (' || out_accounts || '))

PL/pgSQL Looping through multiple schema, tables and rows

I have a database with multiple identical schemas. There is a number of tables all named 'tran_...' in each schema. I want to loop through all 'tran_' tables in all schemas and pull out records that fall within a specific date range. This is the code I have so far:
CREATE OR REPLACE FUNCTION public."configChanges"(starttime timestamp, endtime timestamp)
RETURNS SETOF character varying AS
$BODY$DECLARE
tbl_row RECORD;
tbl_name VARCHAR(50);
tran_row RECORD;
out_record VARCHAR(200);
BEGIN
FOR tbl_row IN
SELECT * FROM pg_tables WHERE schemaname LIKE 'ivr%' AND tablename LIKE 'tran_%'
LOOP
tbl_name := tbl_row.schemaname || '.' || tbl_row.tablename;
FOR tran_row IN
SELECT * FROM tbl_name
WHERE ch_edit_date >= starttime AND ch_edit_date <= endtime
LOOP
out_record := tbl_name || ' ' || tran_row.ch_field_name;
RETURN NEXT out_record;
END LOOP;
END LOOP;
RETURN;
END;
$BODY$
LANGUAGE plpgsql;
When I attempt to run this, I get:
ERROR: relation "tbl_name" does not exist
LINE 1: SELECT * FROM tbl_name WHERE ch_edit_date >= starttime AND c...
#Pavel already provided a fix for your basic error.
However, since your tbl_name is actually schema-qualified (two separate identifiers in : schema.table), it cannot be escaped as a whole with %I in format(). You have to escape each identifier individually.
Aside from that, I suggest a different approach. The outer loop is necessary, but the inner loop can be replaced with a simpler and more efficient set-based approach:
CREATE OR REPLACE FUNCTION public.config_changes(_start timestamp, _end timestamp)
RETURNS SETOF text AS
$func$
DECLARE
_tbl text;
BEGIN
FOR _tbl IN
SELECT quote_ident(schemaname) || '.' || quote_ident(tablename)
FROM pg_tables
WHERE schemaname LIKE 'ivr%'
AND tablename LIKE 'tran_%'
LOOP
RETURN QUERY EXECUTE format (
$$
SELECT %1$L || ' ' || ch_field_name
FROM %1$s
WHERE ch_edit_date BETWEEN $1 AND $2
$$, _tbl
)
USING _start, _end;
END LOOP;
RETURN;
END
$func$ LANGUAGE plpgsql;
You have to use dynamic SQL to parametrize identifiers (or code), like #Pavel already told you. With RETURN QUERY EXECUTE you can return the result of a dynamic query directly. Examples:
Return SETOF rows from PostgreSQL function
Refactor a PL/pgSQL function to return the output of various SELECT queries
Remember that identifiers have to be treated as unsafe user input in dynamic SQL and must always be sanitized to avoid syntax errors and SQL injection:
Table name as a PostgreSQL function parameter
Note how I escape table and schema separately:
quote_ident(schemaname) || '.' || quote_ident(tablename)
Consequently I just use %s to insert the already escaped table name in the later query. And %L to escape it a string literal for output.
I like to prepend parameter and variable names with _ to avoid naming conflicts with column names. No other special meaning.
There is a slight difference compared to your original function. This one returns an escaped identifier (double-quoted only where necessary) as table name, e.g.:
"WeIRD name"
instead of
WeIRD name
Much simpler yet
If possible, use inheritance to obviate the need for above function altogether. Complete example:
Select (retrieve) all records from multiple schemas using Postgres
You cannot use a plpgsql variable as SQL table name or SQL column name. In this case you have to use dynamic SQL:
FOR tran_row IN
EXECUTE format('SELECT * FROM %I
WHERE ch_edit_date >= starttime AND ch_edit_date <= endtime', tbl_name)
LOOP
out_record := tbl_name || ' ' || tran_row.ch_field_name;
RETURN NEXT out_record;
END LOOP;

PostgreSQL: Syntax error at or near "||"

I am creating function which return the select query result from it. The details as shown below in the example:
Example:
Create or replace function fun_test(cola text,colb text,rel text)
returns table(columna text,columnb text)as
$Body$
Declare
table_name varchar :='Table_';
Begin
table_name := table_name || rel;
return query select distinct || quote_ident(cola) ||,||quote_ident(colb)|| from || quote_ident(table_name) ;
end;
$Body$
language plpgsql;
Error:
ERROR: syntax error at or near "||"
LINE 10: ...| quote_ident(cola) ||,||quote_ident(colb)|| from || quote_i...
^
You're trying to construct a query dynamically, but you're not using EXECUTE. That won't work. You can't just put arbitrary expressions in place of identifiers, like:
return query select distinct || quote_ident(cola) ||,||quote_ident(colb)|| from || quote_ident(table_name) ;
which is why you're getting the error at:
from ||
^
as that's syntactically invalid nonsense.
Instead I think you want:
RETURN QUERY EXECUTE 'select distinct ' || quote_ident(cola) ||', '||quote_ident(colb)||' from '|| quote_ident(table_name);
which is better written as:
RETURN QUERY EXECUTE format('select distinct %I, %I from %I', cola, colb, table_name);