FOREACH expression must not be null - postgresql

At declaration level i have:
sqlDel text := 'DELETE FROM %s WHERE %s IS NULL';
fields text[];
field_name text;
ptable := 'myTable';
Somewhere behind i fill in fields so it contains 3 items - i checked it's fine. Nevertheless down below i have this for loop statement which worked fine until i added this line :
EXECUTE format(sqlDel, ptable, field_name);
error says:
ERROR: FOREACH expression must not be null
Foreach loop:
FOREACH field_name IN ARRAY fields
LOOP
EXECUTE format(sqlDel, ptable, field_name);
raise notice 'Primary key column: %', field_name;
END LOOP;

The error message is clean - variable fields is null. You should to set it first.
fields = ARRAY['id'];
FOREACH ...

Related

How to use FOREACH in a PostgreSQL LOOP

I am trying to write a very simple pgsql statement to loop through a simple array of state abbreviations.
CREATE OR REPLACE FUNCTION my_schema.showState()
RETURNS text AS
$$
DECLARE
my_array text[] := '["az","al", "ak", "ar"]'
BEGIN
FOREACH state IN my_array
LOOP
RETURN SELECT format('%s', state);
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM showState();
I am using PostgresSQL version 11+. I keep getting an error ERROR: syntax error at or near "BEGIN" The output I want to see here is just seeing the state abbreviation printed in the results window for now. Like this:
What am I doing wrong here?
There's a ; missing after my_array text[] := '["az","al", "ak", "ar"]'.
'["az","al", "ak", "ar"]' isn't a valid array literal.
If you want a set returning function, you need to declare its return type as a SETOF.
The ARRAY keyword is missing in the FOREACH's head.
state must be declared.
You need to use RETURN NEXT ... to push a value into the set to be returned.
format() is pointless here, it doesn't effectively do anything.
With all that rectified one'd get something along the lines of:
CREATE
OR REPLACE FUNCTION showstate()
RETURNS SETOF text
AS
$$
DECLARE
my_array text[] := ARRAY['az',
'al',
'ak',
'ar'];
state text;
BEGIN
FOREACH state IN ARRAY my_array
LOOP
RETURN NEXT state;
END LOOP;
END;
$$
LANGUAGE plpgsql;
db<>fiddle

Postgres split and convert string value to composite array

I have a Type with below structure.
create type t_attr as (id character varying(50), data character varying(100));
I get text/varchar value through a user-defined function. The value looks like below.
txt := 'id1#data1' , 'id2#data2';
(In the above example, there are 2 values separated by comma, but the count will vary).
I'm trying to store each set into the t_attr array using the below code. Since each set will contain a # as separator I use it to split the id and data values. For testing purpose I have used only one set below.
DO
$$
DECLARE
attr_array t_attr[];
txt text := 'id1#data1';
BEGIN
attr_array[1] := regexp_split_to_array(txt, '#');
END;
$$
LANGUAGE plpgsql;
But the above code throws error saying 'malformed record literal' and 'missing left paranthesis'.
Can someone please help on how to store this data into array? Thanks.
It is because you are trying to assign an array value to the record. Try:
do $$
declare
attr_array t_attr[];
txt text := 'id1#data1';
begin
attr_array[1] := (split_part(txt, '#', 1), split_part(txt, '#', 2));
raise info '%', attr_array;
end $$;
Output:
INFO: {"(id1,data1)"}
DO
However if you really need to split values using arrays:
do $$
declare
a text[];
begin
....
a := regexp_split_to_array(txt, '#');
attr_array[1] := (a[1], a[2]);
end $$;

An error related to arrays in postgresql "ERROR: malformed record literal:"

I am trying to get values from a table into an array and the array definition is different from the table's column definition. I have tried do cast but it is not working. Basically I need as an array (tab_small_str) the values in the table. Can someone please suggest on it:
CREATE TYPE tab_small_str AS (
str CHARACTER VARYING(50)
);
create table test_emp(emp_id integer, ename character varying (10));
insert into test_emp values(1,'a1')
insert into test_emp values(2,'a2')
insert into test_emp values(3,'a3')
CREATE OR REPLACE function test_fn () RETURNS VARCHAR[] as
$$
DECLARE
v_ename tab_small_str[];
i tab_small_str;
BEGIN
SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp) INTO v_ename;
RAISE INFO 'array is: %',v_ename;
RETURN v_ename;
FOREACH i IN ARRAY v_ename
LOOP
RAISE info 'value of ename is%', i;
END LOOP;
END;
$$
language plpgsql;
(function compiles fine).
select test_fn()
--gives below error
ERROR: malformed record literal: "a1"
DETAIL: Missing left parenthesis.
CONTEXT: SQL statement "SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp)"
PL/pgSQL function test_fn() line 7 at SQL statement
********** Error **********
ERROR: malformed record literal: "a1"
SQL state: 22P02
Detail: Missing left parenthesis.
Context: SQL statement "SELECT ARRAY(SELECT ename::tab_small_str FROM test_emp)"
PL/pgSQL function test_fn() line 7 at SQL statement
Hi 404,
i modified as suggested:
CREATE OR REPLACE function test_fn () RETURNS tab_small_str[] as
$$
DECLARE
v_ename tab_small_str[];
i tab_small_str;
BEGIN
SELECT ARRAY(SELECT ROW(ename)::tab_small_str FROM test_emp) INTO v_ename;
RAISE INFO '%',v_ename;
FOREACH i IN ARRAY v_ename
LOOP
RAISE NOTICE '%', i;
END LOOP;
RETURN v_ename;
END;
$$
language plpgsql;
it returns output as:
INFO: {(a1),(a2),(a3)}
CONTEXT: PL/pgSQL function test_fn() line 9 at RAISE
NOTICE: (a1)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
NOTICE: (a2)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
NOTICE: (a3)
CONTEXT: PL/pgSQL function test_fn() line 13 at RAISE
My question is why the output is surrounded by bracket - why not just a1 but (a1). Can you please suggest on it?
Your new type is not a "single field data type", for want of a better description, where you can cast something like a VARCHAR(10) directly to it; it's a ROW containing a single field. So something like 'blah'::tab_small_str fails because it's trying to cast that text to the type which contains a field, rather than the field itself.
To resolve, using your existing query:
SELECT ename::tab_small_str FROM test_emp
Change to:
SELECT ROW(ename)::tab_small_str FROM test_emp
As to why your results are surrounded by brackets: that is how a ROW or composite type is displayed when shown as a single field (or, non-expanded): for example, if you do SELECT * FROM test_emp, the * returns all fields individually as separate columns; however if you do SELECT test_emp FROM test_emp, that will return the table row unexpanded, so it will look like so:
(1,a1)
(2,a2)
(3,a3)
And composite types are exactly the same. i tab_small_str; - think of i as test_emp, which contains fields which can be expanded. In your code you are printing the object i, rather than i.* or i.str. So change your code to:
FOREACH i IN ARRAY v_ename
LOOP
RAISE NOTICE '%', i.str;
END LOOP;

Can't understand why I got this "malformed array literal" error

Can anybody explain to me what I am doing wrong ?
CREATE OR REPLACE FUNCTION validation(
string_to_match varchar[],
pattern varchar,
validation_type varchar
) RETURNS boolean AS $$
DECLARE a_length ALIAS FOR $1;
DECLARE result_validation ALIAS FOR $2;
BEGIN
IF validation_type = 'login' THEN
$1 := array_length(string_to_match,1);
RAISE NOTICE 'Login if , Array length is %', $1;
ELSIF validation_type = 'register' THEN
$1 := array_length(string_to_match,1);
RAISE NOTICE 'Array length is %', $1;
ELSIF validation_type = 'contact' THEN
$1 := array_length(string_to_match,1);
RAISE NOTICE 'Array length is %', $1;
END IF;
RETURN $1;
END;
$$ lANGUAGE plpgsql;
Invocation:
SELECT validation (
'{1,2,3,4,5}',
'([a-zA-Z]{2,20}[\.\-\_])([a-zA-Z]{2,20}[0-9]{2,20})#([a-z]{2,6})\.([a-z]{2,3})',
'login');
The error:
ERROR: malformed array literal: "5"
DETAIL: Array value must start with "{" or dimension information.
CONTEXT: PL/pgSQL function validation(character varying[],character varying,character varying) line 7 at assignment
SQL state: 22P02
$1 is the first function parameter, so $1, string_to_match and a_length point to the same object of type character varying[].
So the assignment
$1 := array_length(string_to_match,1);
tries to assign 5 to an array variable, which causes the error you observe.
It seems like you are confused about the role of $1. Omit the ALIAS FOR $1 part and use a_length instead of $1.

Postgres - Dynamically referencing columns from record variable

I'm having trouble referencing record variable type columns dynamically. I found loads of tricks online, but with regards to triggers mostly and I really hope the answer isn't "it can't be done"... I've got a very specific and simple need, see my example code below;
First I have an array containing a list of column names called "lCols". I loop through a record variable to traverse my data, replacing values in a paragraph which exactly match my column names.
DECLARE lTotalRec RECORD;
DECLARE lSQL text;
DECLARE lCols varchar[];
p_paragraph:= 'I am [names] and my surname is [surname]';
lSQL :=
'select
p.names,
p.surname
from
person p
';
FOR lTotalRec IN
execute lSQL
LOOP
-- Loop through the already created array of columns to replace the values in the paragraph
FOREACH lVal IN ARRAY lCols
LOOP
p_paragraph := replace(p_paragraph,'[' || lVal || ']',lTotalRec.lVal); -- This is where my problem is, because lVal is not a column of lTotalRec directly like this
END LOOP;
RETURN NEXT;
END LOOP;
My return value is the paragraph amended for each record in "lTotalRec"
You could convert your record to a json value using the row_to_json() function. Once in this format, you can extract columns by name, using the -> and ->> operators.
In Postgres 9.4 and up, you can also make use of the more efficient jsonb type.
DECLARE lJsonRec jsonb;
...
FOR lTotalRec IN
execute lSQL
LOOP
lJsonRec := row_to_json(lTotalRec)::jsonb;
FOREACH lVal IN ARRAY lCols
LOOP
p_paragraph := replace(p_paragraph, '[' || lVal || ']', lJsonRec->>lVal);
END LOOP;
RETURN NEXT;
END LOOP;
See the documentation for more details.
You can convert a row to JSON using row_to_json(), and then retrieve the column names using json_object_keys().
Here's an example:
drop table if exists TestTable;
create table TestTable (col1 text, col2 text);
insert into TestTable values ('a1', 'b1'), ('a2', 'b2');
do $$declare
sql text;
rec jsonb;
col text;
val text;
begin
sql := 'select row_to_json(row) from (select * from TestTable) row';
for rec in execute sql loop
for col in select * from jsonb_object_keys(rec) loop
val := rec->>col;
raise notice 'col=% val=%', col, val;
end loop;
end loop;
end$$;
This prints:
NOTICE: col=col1 val=a1
NOTICE: col=col2 val=b1
NOTICE: col=col1 val=a2
NOTICE: col=col2 val=b2
DO