I am creating a function which will build and execute a dynamic sql statement. Basically it is building the ORDER BY clause, which will have the ability to ORDER multiple levels deep based on an array of VARCHARs passed in by the user.
For instance a user's input would be an array:
{{orderColumn, orderDirection},{orderColumn, orderDirection},...}
Obviously, I understand where the issue is in the statement, because postgres prints it out rather clearly. The problem is that I can't figure out the correct syntax in this particular case. Perhaps I am doing it incorrectly and there is a much better route for me to take, I am not set on this approach. If I can do this without dynamic sql it would be even better. Help or insight would be appreciated.
For reference I am using PostgreSQL 9.6.9
My code so far...
CREATE OR REPLACE FUNCTION profile.sp_rider_search(
_limit integer,
_offset integer,
_term text,
_orderBy VARCHAR[],
_registration_date timestamp with time zone DEFAULT
to_timestamp((0)::double precision))
RETURNS TABLE(customer_id integer
, first_name character varying
, last_name character varying
, dob date
, cell_phone character varying
, email character varying
, registration_date timestamp with time zone
, full_count integer)
AS $func$
DECLARE
sql TEXT := 'SELECT pp.user_id AS customer_id,
pp.first_name,
pp.last_name,
pp.date_of_birth AS dob,
pp.cell_phone,
aau.email,
pp.created AS registration_date,
count(*) OVER()::integer AS full_count
FROM profile.profile pp
LEFT JOIN authsvc.all_users aau ON aau.id = pp.user_id
WHERE pp.created > $5 AND (
pp.last_name ILIKE % || $3 || %
OR pp.first_name ILIKE % || $3 || %
OR pp.cell_phone ILIKE % || $3 || %
OR CAST(pp.user_id AS TEXT) ILIKE % || $3 || %
OR aau.email ILIKE % || $3 || %
OR CONCAT(pp.first_name,'' '',pp.last_name) ILIKE % || $3 || %
)
ORDER BY';
_item TEXT;
BEGIN
FOREACH _item IN ARRAY $4
LOOP
CASE WHEN _item[2] = 1 THEN
CASE WHEN _item[1] = 'last_name' THEN
sql := sql || ' pp.last_name, ';
WHEN _item[1] = 'first_name' THEN
sql := sql || ' pp.first_name, ';
WHEN _item[1] = 'email' THEN
sql := sql || ' aau.email, ';
WHEN _item[1] = 'phone' THEN
sql := sql || ' pp.cell_phone, ';
WHEN _item[1] = 'customer_id' THEN
sql := sql || ' pp.user_id::TEXT, ';
WHEN _item[1] = 'registration_date' THEN
sql := sql || ' pp.created::TEXT, ';
END
END
CASE WHEN _item[2] = -1 THEN
CASE WHEN _item[1] = 'last_name' THEN
sql := sql || ' pp.last_name DESC, ';
WHEN _item[1] = 'first_name' THEN
sql := sql || ' pp.first_name DESC, ';
WHEN _item[1] = 'email' THEN
sql := sql || ' aau.email DESC, ';
WHEN _item[1] = 'phone' THEN
sql := sql || ' pp.cell_phone DESC, ';
WHEN _item[1] = 'customer_id' THEN
sql := sql || ' pp.user_id::TEXT DESC, ';
WHEN _item[1] = 'registration_date' THEN
sql := sql || ' pp.created::TEXT DESC, ';
END
END
END LOOP;
sql := sql || 'LIMIT $1
OFFSET $2;'
RETURN QUERY EXECUTE sql
USING _limit,
_offset,
_term,
_orderBy,
_registration_date;
END;
$func$ LANGUAGE plpgsql;
On execution of this statement I get the error:
ERROR: syntax error at or near "END"
LINE 57: END
^
SQL state: 42601
Character: 998
I had a few syntax errors in my statement that I finally ironed out, hopefully this will help others. I needed to add in END CASE; for each case statement, I also had to add a semicolon after OFFSET $2; as well as remove a trailing comma from the order by statements.
Final working code -->
CREATE OR REPLACE FUNCTION profile.sp_rider_search(
_limit integer,
_offset integer,
_term text,
_orderBy VARCHAR[],
_registration_date timestamp with time zone DEFAULT
to_timestamp((0)::double precision),
_preferred_column TEXT DEFAULT '')
RETURNS TABLE(customer_id integer
, first_name character varying
, last_name character varying
, dob date
, cell_phone character varying
, email character varying
, registration_date timestamp with time zone
, full_count integer)
AS $func$
DECLARE
sql TEXT := 'SELECT pp.user_id AS customer_id,
pp.first_name,
pp.last_name,
pp.date_of_birth AS dob,
pp.cell_phone,
aau.email,
pp.created AS registration_date,
count(*) OVER()::integer AS full_count
FROM profile.profile pp
LEFT JOIN authsvc.all_users aau ON aau.id = pp.user_id
WHERE pp.created > $5 AND (
pp.last_name ILIKE % || $3 || %
OR pp.first_name ILIKE % || $3 || %
OR pp.cell_phone ILIKE % || $3 || %
OR CAST(pp.user_id AS TEXT) ILIKE % || $3 || %
OR aau.email ILIKE % || $3 || %
OR CONCAT(pp.first_name,'' '',pp.last_name) ILIKE % || $3 || %
)
ORDER BY';
_item TEXT;
BEGIN
FOREACH _item IN ARRAY $4
LOOP
CASE WHEN _item[2] = 1 THEN
CASE WHEN _item[1] = 'last_name' THEN
sql := sql || ' pp.last_name, ';
WHEN _item[1] = 'first_name' THEN
sql := sql || 'pp.first_name, ';
WHEN _item[1] = 'email' THEN
sql := sql || 'aau.email, ';
WHEN _item[1] = 'phone' THEN
sql := sql || 'pp.cell_phone, ';
WHEN _item[1] = 'customer_id' THEN
sql := sql || 'pp.user_id::TEXT, ';
WHEN _item[1] = 'registration_date' THEN
sql := sql || 'pp.created::TEXT, ';
END CASE;
WHEN _item[2] = -1 THEN
CASE WHEN _item[1] = 'last_name' THEN
sql := sql || ' pp.last_name DESC, ';
WHEN _item[1] = 'first_name' THEN
sql := sql || 'pp.first_name DESC, ';
WHEN _item[1] = 'email' THEN
sql := sql || 'aau.email DESC, ';
WHEN _item[1] = 'phone' THEN
sql := sql || 'pp.cell_phone DESC, ';
WHEN _item[1] = 'customer_id' THEN
sql := sql || 'pp.user_id::TEXT DESC, ';
WHEN _item[1] = 'registration_date' THEN
sql := sql || 'pp.created::TEXT DESC, ';
END CASE;
END CASE;
END LOOP;
sql := TRIM(TRAILING ',' FROM sql);
sql := sql || 'LIMIT $1
OFFSET $2;';
RETURN QUERY EXECUTE sql
USING _limit,
_offset,
_term,
_orderBy,
_registration_date;
END;
$func$ LANGUAGE plpgsql;
Related
Trying to update a table using stored proc. Getting error as
SQL Error [42601]: ERROR: query "SELECT 'update view.recon_dashboard
set dlv_count = (select count(*) from ' || $1 || ') where id='|| $2 , 'set alv_count = (select count(*) from ' || $3 || ') where id='|| $4 , CASE WHEN $5 is not null then 'set tgt_count = (select count(*) from ' || $6 || ') where id='|| $7 end" returned 3 columns
Where: PL/pgSQL function "sp_count_recon_refresh" line 7 at execute statement
1)I have a table recon_dashboard for which i loop through values of each column tgt,alv,dlv and query the db to fetch the count and update the recon_dashboard table. It is working when i update a single value in update statement like : execute 'update ca_adhoc_view.recon_dashboard set dlv_count = (select count(*) from ' || f.dlv || ') where id='|| f.id;
but it is throwing error when trying to updaate multiple columns in update statement.
create or replace procedure view.refresh_sp()
language plpgsql as
$$
declare
f record;
BEGIN
for f in select tgt,alv,dlv,id from view.recon_dashboard
loop
raise notice '% -#',f.dlv;
execute 'update view.recon_dashboard
set dlv_count = (select count(*) from ' || f.dlv || ') where id='|| f.id,
'set alv_count = (select count(*) from ' || f.alv || ') where id='|| f.id,
CASE
WHEN f.alv is not null then
'set tgt_count = (select count(*) from ' || f.tgt || ') where id='|| f.id
end;
end loop;
END;
$$;
I wanted to make a table that sanity checked record integrity for any duplications among my db.
I have a table currently with object names (tables) and their primary keys:
I want to create a procedure that loops through those objects with their keys, and inserts into a separate table the count of duplicates:
below is my code, but I've never done anything like this before and am new to postgres. What I have is from hours of googling/researching but every time I get closer I get a new error and am quite stuck :( Any insights would be greatly appreciated.
My newest error is I believe from my quote_ident(object_names). I don't want to query the column as postgres is reading it, I'd want that to be a raw string:
code:
do $$
declare
object_names varchar;
keys varchar;
rec record;
begin
for rec in select object_name, key from mfr_incentives.public.t_jh_dup_check
loop
object_names = rec.object_name;
keys = rec.key;
execute 'insert into mfr_incentives.public.t_jh_dup_check_final_output
select * from
(select ' || quote_ident(object_names) || ', ' || quote_ident(keys) || ', ' || ' count(*), current_date from
( select ' || keys || ', count(*)
from ' || object_names ||
' group by ' || keys || ' having count(*) > 1
) a
) a';
end loop;
end;
$$;
Found out my problem!
Being unfamiliar with the topic I finally found that I wanted quote_literal() instead of quote_ident().
The below works:
create or replace procedure public.proc_jh_dup_check()
language plpgsql
--IT WORKS NOW
as $$
declare
rec record;
begin
for rec in select object_name, key from mfr_incentives.public.t_jh_dup_check
loop
execute 'insert into mfr_incentives.public.t_jh_dup_check_final_output
select * from
(select ' || quote_literal(rec.object_name) || ', ' || quote_literal(rec.key) || ', ' || ' count(*), current_date from
( select ' || rec.key || ', count(*)
from ' || rec.object_name ||
' group by ' || rec.key || ' having count(*) > 1
) a
) a';
end loop;
end;
$$;
I'm trying to learn plsql and got stuck in understanding some basic stuff. Here is a challenge that I'm trying to solve. I have two tables. One holds information about owners and the other is information about cars.
I want to to write an anonymous block that joins these two tables and with a for loop based on amount of cars that is registered to each owner prints how many cars each person own. furthermore I want an if statement which distinguishes between 1 Car (singular) and 2, 3 Cars (plural).
the tables are these:
CREATE TABLE owners(
id_nr VARCHAR2(13) PRIMARY KEY,
f_name VARCHAR2(20),
s_name VARCHAR2(20)
);
CREATE TABLE cars(
reg_nr VARCHAR2(6) PRIMARY KEY,
id_nr REFERENCES owners(pnr),
model VARCHAR2(20),
year NUMBER(4),
date DATE
);
The result may look like something like this:
19380321-7799, Hans, Anderson, Owns: 1 car
19490321-7899, Mike, Erikson, Owns: 2 cars
. . . etc
I know the this question was already answered but when I try following:
declare
v_suffix varchar2(1);
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1) from fordon where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.pnr_fordon = 1
then v_suffix = 'bil'
else v_suffix = 'bilar'
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
/
I get:
ORA-06550: line 9, column 27:
PLS-00103: Encountered the symbol "=" when expecting one of the following:
:= . ( # % ;
any tips? Im not sure how to declare v_suffix
EDIT (copied from comment on answer below):
Updating my code:
declare
cursor c_BILÄGARE is
select fnamn,enamn,pnr
from BILÄGARE;
begin
for rec in c_BILÄGARE loop
if (rec.antal>1) then
dbms_output.put_line (rec.pnr||','|| rec.fnamn || ',' ||
rec.enamn || ',' || rec.antal || 'bilar');
else
dbms_output.put_line (rec.pnr||','|| rec.fnamn || ',' ||
rec.enamn || ',' || rec.antal || 'bil');
end if;
end loop;
end;
getting:
ORA-06550: line 9, column 9: PLS-00302: component 'ANTAL' must be declared (antal=Quantity)
As noted above, you need to use := as the assignment operator. You also need a semi-colon after each statement - thus, you should use
then v_suffix := 'bil';
instead of
then v_suffix := 'bil'
So your code should look like:
declare
v_suffix varchar2(1);
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1)
from fordon
where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.pnr_fordon = 1
then v_suffix := 'bil';
else v_suffix := 'bilar';
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
Please try this piece of code:
begin
for rec in (select
o.id_nr || ',' || o.f_name || ',' || o.s_name || ', Owns: ' || coalesce(car.cnt, 0) || ' ' || decode(car.cnt , 1, 'Car', 'Cars') as res
from owners o
left outer join (select id_nr, count(1) as cnt from cars group by id_nr) car on (car.id_nr = o.id_nr)) loop
dbms_output.put_line(rec.res);
end loop;
end;
Thanks.
There are multiple issues in your code which is highlighted and fixed in following code:
declare
v_suffix varchar2(5); -- data length should be 5 or more
begin
for o in (select bilägare_pnr, fnamn, enamn,
(select count(1) from fordon where fordon_pnr = bilägare_pnr) as bilar_ägda
from bilägare)
loop
if o.bilar_ägda <= 1 -- replaced it from pnr_fordon and <= is used for 0 or 1 car
then v_suffix := 'bil'; -- := and ; is used here and in next statement
else v_suffix := 'bilar';
end if;
dbms_output.put_line(o.pnr || ', ' || o.fnamn || ', ' || o.enamn
|| ' Äger: ' || o.pnr_fordon || ' bil' || v_suffix);
end loop;
end;
/
I am trying to generate a Create Table Statement. To do this, I have created 2 procedures to Extract Tables and Columns from my Schema. The Output of these procedures is then spooled to a SQL file and is supposed to look like this:
The Formatted Output
Right now, My output Looks like this:
----
---- Run on October 04, 2020 at 22:00
----
-- Start Extracting table IMAGE
CREATE TABLE IMAGE (
MFR CHAR(3,)
, PRODUCT CHAR(5,)
, IMAGE BLOB(4000,)
, TECHSPECS BFILE(530,)
);-- END of Table IMAGE creation
--
--
-- Start Extracting table ORDERS
CREATE TABLE ORDERS (
ORDERNUM NUMBER(227,0)
, ORDERDATE DATE(7,)
, CUST NUMBER(223,0)
, REP NUMBER(223,0)
, MANUF CHAR(3,)
, PROD CHAR(5,)
, QTY NUMBER(225,0)
, AMOUNT NUMBER(225,2)
);-- END of Table ORDERS creation
--
--
-- Start Extracting table PRODUCTS
CREATE TABLE PRODUCTS (
MFR CHAR(3,)
, PRODUCT CHAR(5,)
, DESCRIPTION VARCHAR2(100,)
, PRICE NUMBER(225,2)
, QTYONHAND NUMBER(225,0)
);-- END of Table PRODUCTS creation
--
--
---- Oracle Catalog Extract Utility V1.0 ----
---- Run on October 04, 2020 at 22:00
As you can see, the Data types, along with their Data_length, Data_scale, and DAta_precision are not perfectly formatted as per the requirements. I also haven't been able to make a column that displays the null status of each column. The Date, BFILE, and BLOB as shown in the Output are especially troublesome for me to format properly.
This is my code as of now:
SET ECHO OFF
SET FEEDBACK ON
SET WRAP OFF
--The Procedure to Extract The Columns
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE Extract_Columns (
--Creating Variables of passed values
wSee IN OUT varchar2,
wTable IN USER_TABLES.table_name%type
)
AS
--The Cursor to run through the Columns
CURSOR Extract_C IS
SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_SCALE, DATA_LENGTH
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = wTable
ORDER BY Column_ID;
CurrentRow Extract_C%ROWTYPE;
--Creating variables
wItterations NUMBER(1):=0;
BEGIN
FOR CurrentRow IN Extract_C LOOP
IF wItterations = 0 THEN
wsee := wsee || CHR(10) || ' ' || RPAD(currentrow.column_name, 15) || currentrow.data_type || '(' || currentrow.DATA_LENGTH || currentrow.DATA_SCALE || ')';
ELSE
wsee := wsee || CHR(10) || ', ' || RPAD(currentrow.column_name, 15) || currentrow.data_type || '(' || currentrow.DATA_LENGTH || currentrow.DATA_PRECISION || ',' || currentRow.DATA_SCALE || ')';
END IF;
wItterations:= wItterations +1;
END LOOP;
END;
/
SHOW ERRORS;
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE Extract_Tables
AS
l_crt varchar2(356);
CURSOR Extract_T IS
SELECT TABLE_NAME
FROM USER_TABLES
ORDER BY TABLE_NAME;
CurrentRow Extract_T%ROWTYPE;
--Creating Variables
wvers VARCHAR2(256) := 'V1.0';
wcur_Tim VARCHAR2(200) := '' || TO_CHAR( CURRENT_DATE, 'Month DD, YYYY') || ' at ' || To_CHAR(CURRENT_DATE, 'HH24:MI');
wheader VARCHAR2(45) := 'CREATE TABLE ' || CurrentRow.table_name ||' (';
wspacing NUMBER := length(wheader);
BEGIN
DBMS_OUTPUT.PUT_LINE('---- Oracle Catalog Extract Utility ' || wvers || ' ----');
DBMS_OUTPUT.PUT_LINE('----');
DBMS_OUTPUT.PUT_LINE('---- Run on ' || wcur_Tim);
DBMS_OUTPUT.PUT_LINE('----');
FOR CurrentRow IN Extract_T LOOP
DBMS_OUTPUT.PUT_LINE('-- Start Extracting table ' || CurrentRow.table_name || '');
DBMS_OUTPUT.PUT_LINE('CREATE TABLE ' || CurrentRow.table_name ||' (');
--This is where i should be calling the other procedure
Extract_Columns(l_crt, CurrentRow.table_name);
--This is where create tables procedure begins again
DBMS_OUTPUT.PUT_LINE(l_crt || chr(10) || LPAD(' ', 15 + length(currentrow.Table_name)) || ');' || '-- END of Table ' || currentrow.Table_name || ' creation');
DBMS_OUTPUT.PUT_LINE('--' || CHR(10) || '--' );
l_crt := NULL;
END LOOP;
--Ending Statement
DBMS_OUTPUT.PUT_LINE('---- Oracle Catalog Extract Utility ' || wvers || ' ----');
DBMS_OUTPUT.PUT_LINE('---- Run on ' || wcur_Tim);
END;
/
SHOW ERRORS;
To execute, I have to compile the 'Extract_column' procedure and then 'Extract_tables' procedure, then I run the tables procedure to generate my output.
How can I format my current output in a way that it exactly matches the picture above?
Below is an example query that I would like my procedure to generate
select *
from Registration
where Loc_ID = 6
AND CROP_ID = 163
AND REG_NAME = 'Apiro MX';
REG_NAME is varchar2()
I have created one procedure, where I want to execute one query like below
query := 'select REG_ID from Registration where loc_id = ' ||
countryid || ' AND Crop_id = ' || cropid ||
' AND Reg_name = '|| ''' || productid || ''' || ';
I am getting error in REG_NAME part, where it is taking productid as " || productid ||"
can you please help me with the exact query for that.
You don't need to use dynamic sql:
CREATE PROCEDURE get_registration (
i_countryid IN REGISTRATION.LOC_ID%TYPE,
i_crop_id IN REGISTRATION.CROP_ID%TYPE,
i_reg_name IN REGISTRATION.REG_NAME%TYPE,
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor FOR
SELECT *
FROM Registration
WHERE Loc_ID = i_countryid
AND CROP_ID = i_crop_id
AND REG_NAME = i_reg_name;
END;
/
If you do need dynamic SQL (however, you can almost always do it without):
CREATE PROCEDURE get_registration (
i_countryid IN REGISTRATION.LOC_ID%TYPE,
i_crop_id IN REGISTRATION.CROP_ID%TYPE,
i_reg_name IN REGISTRATION.REG_NAME%TYPE,
o_cursor OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN o_cursor
FOR 'SELECT *
FROM Registration
WHERE Loc_ID = :i
AND CROP_ID = :j
AND REG_NAME = :k'
USING i_countryid, i_crop_id, i_reg_name;
END;
/
In PLSQL you can escape quote by adding another one, therefore,
you should put double quotes around productid.
Try something like:
query := 'select REG_ID from Registration where loc_id = ' ||
countryid || ' AND Crop_id = ' || cropid ||
' AND Reg_name = '|| '''' || productid || '''' || ';
Or don't use dynamic SQL and try creating stored procedure
(check the link for the instruction).
http://plsql-tutorial.com/plsql-procedures.htm