i'm creating a procedure to update/insert a table using merge statement(upsert).now i have a problem: using procedure parameters i have to do this upsert.
procedure xyz( a in table.a%type,b in table.b%type,....)
is
some local variables;
begin
merge into target_table
using source_table --instead of the source table, i have to use procedure parameters here
on (condition on primary key in the table)
when matched then
update the table
when not matched then
insert the table ;
end xyz;
so how to use procedure parameters instead of source table in merge statement?? or
suggest me a query to fetch the procedure parameters and use it as source table values.
help me please.
Thanks in advance.
I know that I'm eight years late to the party, but I think that I was trying to do something similar to what you were doing, but trying to Upsert based on parameters passed into a stored procedure that returns an empty string on success and an error on failure back to my VB Code. Below is all of my code along with comments explaining what I did, and why I did it. Let me know if this helps you or anyone else. This is my first time answering a post.
PROCEDURE UpsertTSJobData(ActivitySeq_in IN NUMBER,
Owner_in In VARCHAR2,
NumTrailers_in IN NUMBER,
ReleaseFormReceived_in IN NUMBER,
Response_out OUT VARCHAR2) AS
err_num NUMBER;
err_msg VARCHAR2(4000);
BEGIN
--This top line essentially does a "SELECT *" from the named table
--and looks for a match based on the "ON" statement below
MERGE INTO glob1app.GFS_TS_JOBDATA_TAB tsj
--This select statement is used for the INSERT when no match
--is found and the UPDATE when a match is found.
--It creates a "pseudo-table"
USING (
SELECT ActivitySeq_in AS ActSeq,
Owner_in As Owner,
NumTrailers_in As NumTrailers,
ReleaseFormReceived_in As ReleaseFormReceived
FROM DUAL) input
--This ON statement is what we're doing the match on to find
--matching records. This decides whether it will be an
--INSERT or an UPDATE
ON (tsj.Activity_seq = ActivitySeq_in)
WHEN MATCHED THEN
--Here we UPDATE based on the passed in input table
UPDATE
SET OWNER = input.owner,
NUMTRAILERS = input.NumTrailers,
RELEASEFORMRECEIVED = input.releaseformreceived
WHEN NOT MATCHED THEN
--Here we INSERT based on the passed in input table
INSERT (
ACTIVITY_SEQ,
OWNER,
NUMTRAILERS,
RELEASEFORMRECEIVED
)
VALUES (
input.actseq,
input.owner,
input.numtrailers,
input.releaseformreceived
);
Response_out := '';
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 3900);
Response_out := TO_CHAR (err_num) || ': ' || err_msg;
END;
Maby something like
DECLARE V_EXISTS NUMBER;
BEGIN SELECT COUNT(*) INTO V_EXISTS FROM TARGET_TABLE WHERE PK_ID = :ID;
IF V_EXISTS > 0 THEN
-- UPDATE
ELSE
-- INSERT
END IF;
END;
Also, you may try to use so-called tempotary table (select from DUAL)
CREATE TABLE TEST (N NUMBER(2), NAME VARCHAR2(20), ADRESS VARCHAR2(100));
INSERT INTO TEST VALUES(1, 'Name1', 'Adress1');
INSERT INTO TEST VALUES(2, 'Name2', 'Adress2');
INSERT INTO TEST VALUES(3, 'Name3', 'Adress3');
SELECT * FROM TEST;
-- test update
MERGE INTO TEST trg
USING (SELECT 1 AS N, 'NameUpdated' AS NAME,
'AdressUpdated' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
-- test insert
MERGE INTO TEST trg
USING (SELECT 34 AS N, 'NameInserted' AS NAME,
'AdressInserted' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
DROP TABLE TEST;
see here
Its very difficult to tell from you question exactly what you what, but I gather you want the table that you are merging into ( or on ) to be dynamic. In that case, what you should be using is the DBMS_SQL package to create dynamic SQL
Related
I am a newbie in the area of redshift data modeling and got myself into trouble with an error.ERROR:
--Final version
syntax error ERROR: operator does not exist: text | record Hint: No
operator matches the given name and argument type(s). You may need to
add explicit type casts. Where: SQL statement "SELECT 'create temp
table ' || $1 || ' as select * from' | $2 |" PL/pgSQL function "egen"
line 36 at execute statement [ErrorId:
1-61dc32bf-0a451f5e2c2639235abb8876]
I am trying to do a simple transformation that gets returned in output when the procedure is called. (As of now I got to find from the documentation we have to use either temp table or cursors to achieve this)
Pseudocode:
I am trying to restrict data to its latest one in (2019) Get the
list of managers create columns if a person is a manager or not from the list.
Return it as a result
Data looks as follows Employee Data
My Select query works fine out of the procedure, please find my complete code below.
CREATE OR REPLACE PROCEDURE EGEN(tmp_name INOUT varchar(256) )
AS $$
DECLARE
--As i have less data managed to create it as an array or please use temp or table and join it with the actual query to perform transformation
MGR_RECORD RECORD;
DATAS RECORD;
item_cnt int := 0;
V_DATE_YEAR int := 0;
BEGIN
--EXECUTE (select cast(extract(year from current_date) as integer)-3) INTO V_DATE_YEAR;
--Manager Records are stored here below
SELECT DISTINCT managerid from "dev"."public"."emp_salary" INTO MGR_RECORD;
SELECT employeeid,
managerid,
promotion,
q_bonus,
d_salary,
case when contractor = 'x'
then 'TemporaryEmployee'
else 'PermanentEmployee'
END as EmployeeType,
-- IFstatement not supported under select query
case when employeeid in (select distinct managerid FROM "dev"."public"."emp_salary" )
then 'Manager'
else 'Ordinary FTE'
END as FTETYPE
FROM "dev"."public"."emp_salary" where cast(extract(year from promotion) as int ) >= 2019 into DATAS;
--COMMIT;
tmp_name := 'ManagerUpdatedTable';
EXECUTE 'drop table if exists ' || tmp_name;
EXECUTE 'create temp table ' || 'ManagerUpdatedTable' || ' as select * from' |DATAS| ;
END;
$$ LANGUAGE plpgsql;
-- Call tests CALL EGEN('myresult'); SELECT * from myresult;
Also, additional query (Can we replace )
case when employeeid in (select distinct managerid FROM "dev"."public"."emp_salary" )
then 'Manager'
else 'Ordinary FTE'
END as FTETYPE
this transform in query to IF , if possible please provide details.
Thanks and Regards,
Gabby
I have to copy data from one table to another with below two conditions
table names will be known at run time
records need to be copied one at a time so that modifications can be done in column values when required
I have created a procedure to to do this through dynamic query. Since the column list is not known already I am not able to declare a rowtype variable. I saw an example of DBMS_SQL where you can define the columns for select clause. Below is the format
DBMS_SQL.DEFINE_COLUMN(cursor_var,position,column_var);
Problem here is that in all the examples I found the column_var were already declared. However in my case I will get to know the no of columns that will be in cursor sql and their data type at run time. so I need to find a way to pass the data type of "column_var" as part of DBMS_SQL.DEFINE_COLUMN. Is there a way to do that? Is there a better way?
Below is just a sample code
CREATE OR REPLACE PROCEDURE pr_test (P_TABLE_NAME IN VARCHAR2)
IS
V_SQL VARCHAR2(500);
SRC_CUR INT;
DEST_CUR INT;
TYPE COL_DTL_TYPE IS RECORD
(
COLUMN_ID INT,
COLUMN_NAME VARCHAR2(250),
DATA_TYPE VARCHAR2(250),
DATA_LENGTH INT
);
COL_DTL_REC COL_DTL_TYPE;
TYPE TBL_COL_LIST_TYPE IS TABLE OF COL_DTL_TYPE;
TBL_COL_LIST TBL_COL_LIST_TYPE;
V_CNT INT := 0;
BEGIN
V_SQL := 'SELECT * FROM ' || P_TABLE_NAME;
SRC_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SRC_CUR,V_SQL,DBMS_SQL.NATIVE);
TBL_COL_LIST := TBL_COL_LIST_TYPE();
FOR COL_DTL_REC IN (
SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH
FROM ALL_TAB_COLUMNS WHERE TABLE_NAME =P_TABLE_NAME
)
LOOP
V_CNT := V_CNT + 1;
TBL_COL_LIST.EXTEND;
TBL_COL_LIST(V_CNT) := COL_DTL_REC;
-- Here is where I am stuck and not able to give column data type
DBMS_SQL.DEFINE_COLUMN(SRC_CUR,V_CNT,COL_DTL_REC.COLUMN_NAME COL_DTL_REC.DATA_TYPE , COL_DTL_REC.DATA_LENGTH)
END LOOP;
END;
copying to destination table will come later.
Been a while since I have done any sereious T-SQL. I thought I had this right but I am receiving an error that I cannot figure out the cause of. This is for a stored procedure that is not complex. The code is below:
--======================================================
-- Create Natively Compiled Stored Procedure Template
--======================================================
USE Viper;
GO
-- Drop stored procedure if it already exists
IF OBJECT_ID('API.newCategory','P') IS NOT NULL
DROP PROCEDURE API.newCategory;
GO
CREATE PROCEDURE API.newCategory
-- Add the parameters for the stored procedure here
#Category varchar(20) = null
WITH NATIVE_COMPILATION, SCHEMABINDING
AS BEGIN ATOMIC WITH
(
TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english'
)
--Insert statements for the stored procedure here
DECLARE #tmp int = 0;
IF #Category IS NOT NULL
AND #Category != ''
SET #tmp = ISNULL(
(SELECT idCategory
FROM Products.Category
WHERE Category = #Category),0);
IF #tmp = 0
INSERT INTO Products.Category (Category)
OUTPUT inserted.idCategory INTO #tmp
VALUES (#Category);
ELSE
UPDATE Category
SET active = 1
WHERE idCategory = #tmp;
RETURN #tmp;
END;
GO
The error message that I am receiving is:
Msg 1087, Level 16, State 1, Procedure newCategory, Line 22 [Batch
Start Line 11]
Must declare the table variable "#tmp".
I hope someone can point me in the right direction. I am sure that it is something stupidly simple, I just can't see it right now. A bit rusty I'm afraid.
Just to be clear, the operational goal of the SP is to:
1/ Check that there is actually a Category supplied to work with
2/ If there is then try and get its primary key id (idCategory)
3/ If there is no PK for the Category then insert it and return the idCategory into #tmp
4/ If there is a PK then make sure the active column is set to 1
5/ return #tmp as the result (either the PK or 0)
Cheers and thanks for any help
The Frog
Your problem is in this statement:
IF #tmp = 0
INSERT INTO Products.Category (Category)
OUTPUT inserted.idCategory INTO #tmp
VALUES (#Category);
You are doing an OUTPUT INTO where what you are output into your previously declare #tmp which is declared as an int. OUTPUT statements can only be against tables, temp tables or table variables.
A workaround could be to declare a table variable #catTab: DECLARE #catTab AS TABLE(CatID int) and OUTPUT into that variable followed by a: SELECT #tmp = CatID FROM #catTab. That should do it.
I want to create a stored procedure where I can pass in variable to the WHERE clause below.
DROP TABLE fan0ia_mstr.Store_List;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = 'VARIABLE' ) WITH DATA
PRIMARY INDEX (ANA_Code)
The VARIABLE will be a character string. I don't need to display the results, I just want the table to be created.
Also how do I trap any errors e.g. if the table doesn't exist for some reason I still want it to be created
thanks
As you don't have variable database/table/column names you simply need to wrap your existing code (slightly modified) into a Stored Procedure:
replace procedure myproc(IN variable varchar(100))
begin
BEGIN
-- simply try dropping the table and ignore the "table doesn't exist error"
DECLARE exit HANDLER FOR SQLEXCEPTION
BEGIN -- 3807 = table doesn't exist
IF SQLCODE <> 3807 THEN RESIGNAL; END IF;
END;
DROP TABLE fan0ia_mstr.Store_List;
END;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = :variable ) WITH DATA
PRIMARY INDEX (ANA_Code);
end;
Of course a DELETE/INSERT or a Temporary table might be more efficient.
Edited... second option needs a execute immediate too...
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE fan0ia_mstr.Store_List';
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
EXECUTE IMMEDIATE 'CREATE TABLE fan0ia_mstr.Store_List AS
(SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = ''' || v_area_desc || '''
) WITH DATA PRIMARY INDEX (ANA_Code)';
END PROCEDURE1;
but you can avoid drop / create with truncate / insert
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
execute immediate 'truncate TABLE fan0ia_mstr.Store_List';
insert into fan0ia_mstr.Store_List (SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = v_area_desc
);
commit;
END PROCEDURE1;
I would like to change a couple of column values before they get inserted.
I am using Informix as database.
I have a table consisting of 3 columns: Name (NVARCHAR), Type (INT), Plan (NVARCHAR).
Every time a new record is inserted, I would like to check the Name value before inserting it. If the Name starts with an F, I would like to set the Type value to 1 and the Plan Name to "Test"
In short, what I want the trigger to do is:
For every new insertion, first check if Name value starts with F.
If yes, set the Type and Plan to 1 and "Test" then insert.
If no, insert the values as-is.
I have looked up the CREATE TRIGGER statement with BEFORE and AFTER. However, I would like to have a clearer example. My case would probably involve BEFORE though.
The answer of #user3243781 get close, but did not work because it returns the error:
-747 Table or column matches object referenced in triggering statement.
This error is returned when a triggered SQL statement acts on the
triggering table, or when both statements are updates, and the column
that is updated in the triggered action is the same as the column that
the triggering statement updates.
So the alternative is handle with the NEW variable directly.
For that you need to use a procedure with the triggers reference resource, which means the procedure will able to act like the trigger by self.
Below is my example which I run with dbaccess over a Informix v11.70.
This resource is available only for versions +11 of the engine, as far I remember.
create table teste ( Name NVARCHAR(100), Type INT , Plan NVARCHAR(100) );
Table created.
create procedure check_name_values()
referencing new as n for teste ;;
define check_type integer ;;
define check_plan NVARCHAR ;;
if upper(n.name) like 'F%' then
let n.type = 1;;
let n.plan = "Test";;
end if
end procedure ;
Routine created.
;
create trigger trg_tablename_ins
insert on teste
referencing new as new
for each row
(
execute procedure check_name_values() with trigger references
);
Trigger created.
insert into teste values ('cesar',99,'myplan');
1 row(s) inserted.
insert into teste (name) values ('fernando');
1 row(s) inserted.
insert into teste values ('Fernando',100,'your plan');
1 row(s) inserted.
select * from teste ;
name cesar
type 99
plan myplan
name fernando
type 1
plan Test
name Fernando
type 1
plan Test
3 row(s) retrieved.
drop table if exists teste;
Table dropped.
drop procedure if exists check_name_values;
Routine dropped.
create trigger trg_tablename_ins
insert on tablename
referencing new as new
for each row
(
execute procedure check_name_values
(
new.name,
new.type,
new.plan
)
);
create procedure check_name_values
(
name NVARCHAR,
new_type integer,
new_plan NVARCHAR,
)
define check_type integer ;
define check_plan NVARCHAR ;
let check_type = 1;
let check_plan = "Test";
if name = 'F%'
then
insert into tablename (name,type,plan) values (name,check_type,check_plan);
else
insert into tablename (name,type,plan) values (name,new_type,new_plan);
end if ;
end procedure ;
Here is my version an adaptation of an old example I found in the informix usenet group.
It is possible to update columns in a trigger statement but not very straight forward. You have to use stored procedures an the into statement with the execute procedure command.
It worked here for IBM Informix Dynamic Server Version 12.10.FC11WE.
drop table if exists my_table;
drop sequence if exists my_table_seq;
create table my_table (
id INTEGER
NOT NULL,
col_a char(32)
NOT NULL,
col_b char(20)
NOT NULL,
hinweis char(64),
uslu char(12)
DEFAULT USER
NOT NULL,
dtlu DATETIME YEAR TO SECOND
DEFAULT CURRENT YEAR TO SECOND
NOT NULL
)
;
create sequence my_table_seq
increment 1
start 1;
drop procedure if exists get_user_datetime();
create function get_user_datetime() returning char(12),datetime year to second;
return user, current year to second;
end function
;
drop trigger if exists ti_my_table;
create trigger ti_my_table insert on my_table referencing new as n for each row (
execute function get_user_datetime() into uslu, dtlu
)
;
drop trigger if exists tu_my_table;
create trigger tu_my_table update on my_table referencing new as n for each row (
execute function get_user_datetime() into uslu, dtlu
)
;
insert into my_table values (my_table_seq.nextval, "a", "b", null, "witz", mdy(1,1,1900)) ;
SELECT *
FROM my_table
WHERE 1=1
;