There is a table 'EXAMPLE_TABLE' which contains two columns. The first column 'ID' store value '5555' and the second 'IS_EXIST' store char 1 byte '0'. How create a procedure which will do 'INSERT INTO' if this values doesn't exist, or 'UPDATE' if 'ID' the same as in a query and 'IS_EXIST' == 0, or throw some exception which will be handled in java if 'ID' the same and 'IS_EXIST' != 0. I considered the merge and primarily insert ways to resolve this problem.
it have to approximately look like :
if(ID doesn't exist)
insert into
if(ID exist and IS_EXIST equals 0)
update
else
throw Exception
but how this will look in procedure?
This is a simple way to do it if you want to throw or raise some exception using procedure without merging:
procedure PC_INSERT_OR_UPDATE(P_ID number) as
cursor C_1 is
select M.ID,
C.IS_EXIST
from MY_TABLE M
where M.ID = P_ID;
MSG clob;
begin
for C in C_1 loop
begin
if C.ID is null then
insert into MY_TABLE
(ID,
IS_EXIST)
values
(P_ID,
1);
elsif C.ID is not null and C.IS_EXIST = 0 then
update MY_TABLE M
set M.IS_EXIST = 1
where M.ID = P_ID;
else
RAISE_APPLICATION_ERROR(-20001, 'My exception was raised');
end if;
exception
when others then
rollback;
MSG := 'Error - ' || TO_CHAR(sqlcode) || ' - ' || sqlerrm;
end;
end loop;
end;
Related
For example I have a list of houses which have parameters below
price
numberOfRooms
WithBalcony
I need to create a global SQL Query that will filter that list by provided parameters. That is like
select * from database where price = x and numberOfRooms = y and WithBalcony = z;
But there may be cases when one of the parameters will be null in request, so my query wouldn't work.
For example if parameter "price = null" my query must be without price logically like this
select * from database where numberOfRooms = y and WithBalcony = z;
That is normal for 3 parameters, but what if I have 10 parameters? Should I write all queries, the number of which will be factorial of 10?
Is there one any other solution?
Here is a general pattern you may use:
SELECT *
FROM yourTable
WHERE (price = x OR x IS NULL) AND
(numberOfRooms = y OR y IS NULL) AND
(WithBalcony = z OR z IS NULL);
The IS NULL clauses as used above will effectively cause each restriction to be ignored should the parameter be undefined.
Java can invoke functions.
Explanation: function input argument value correspond with column value.
for example: on_null_column_omit({'1', null}') means that col1 value is 1 and col2 value is null. In my demo, the function input value correspond to col1 to col10 column's value. Obviously you can reorder the input argument, but in the mean time, you also need to reorder the column's name.
There is many raise info, raise notice, that's for debug purpose.
demo
CREATE OR REPLACE FUNCTION on_null_column_omit (args text[10])
RETURNS json
LANGUAGE plpgsql
AS $$
DECLARE
str text;
i int := 1;
all_col_name text[];
_partial_sql text := ' select row_to_json(t.*) from on_null_omit t where ';
_temp1 text := '';
_tempnull text[];
_result json;
_m text;
_str1 text;
BEGIN
SELECT
ARRAY (
SELECT
column_name::text
FROM
information_schema.columns
WHERE
table_name = 'on_null_omit') INTO all_col_name;
RAISE info 'all_col_name[1]: %', all_col_name[1];
foreach str IN ARRAY args LOOP
IF str IS NULL THEN
_m := ' and ' || all_col_name[i] || ' = null ';
_m := TRIM(BOTH FROM _m);
_tempnull := array_append(_tempnull, _m);
END IF;
_temp1 := _temp1 || all_col_name[i] || ' = ' || COALESCE(quote_literal(str), 'null') || ' and ';
i := i + 1;
END LOOP;
_temp1 := TRIM(BOTH FROM _temp1);
RAISE info '_temp_null: %', _tempnull;
RAISE info '_temp1: %', _temp1;
IF _temp1 ILIKE '%and col10 = null and%' THEN
SELECT
regexp_replace(_temp1, 'and col10 = null and', '') INTO _temp1;
END IF;
RAISE info '1_temp1:%', _temp1;
IF _temp1 ILIKE '%col1 = null%' THEN
SELECT
regexp_replace(_temp1, 'col1 = null and', '') INTO _temp1;
END IF;
RAISE info '2_temp1:%', _temp1;
foreach _str1 IN ARRAY _tempnull LOOP
SELECT
regexp_replace(_temp1, _str1, '') INTO _temp1;
END LOOP;
_temp1 := TRIM(BOTH FROM _temp1);
RAISE info '3_temp1:%', _temp1;
EXECUTE _partial_sql || _temp1 INTO _result;
RETURN _result;
END;
$$;
CREATE OR REPLACE FUNCTION sp_process_ticket_block_receipt(receipt_id INTEGER, OUT status VARCHAR, OUT message VARCHAR)
AS $$
DECLARE
header record;
detail_list record;
boo boolean;
BEGIN
SELECT * INTO header FROM ticket_block_receipt WHERE id = receipt_id;
FOR detail_list IN(SELECT * FROM ticket_block_receipt_detail WHERE ticket_block_receipt_id = receipt_id) LOOP
/* A insert Query here */
select INTO boo sp_save_ticket_stock_cost_centre(5); // function which returns true or false
IF boo = false THEN
/* here transaction should Rollback current changes and also should return a proper error message */
END IF;
END LOOP;
status = 'success';
message = 'Success message';
END; $$
LANGUAGE 'plpgsql';
function which is used inside For loop above
CREATE OR REPLACE FUNCTION sp_save_ticket_stock_cost_centre(receipt_detail_id INTEGER)
RETURNS boolean AS $$
DECLARE
detail_list record;
BEGIN
SELECT * INTO detail_list FROM ticket_block_receipt_detail WHERE id = receipt_detail_id;
counter := detail_list.no_of_blocks;
LOOP
EXIT WHEN counter = 0 ;
/* Insert Query here */
counter := counter - 1 ;
END LOOP ;
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RETURN FALSE;
END; $$
so... basic question is how can I RollBack with proper output
if this is not possible i am ok with suggestions..
i need to RollBack inside IF condition (IF boo = false THEN) which is based on another function which returns true or false..
i am going to use this in Spring project, with JPA and Hibernate.
please ask questions, i am not able explain to my self here..
If I understand correctly you think about something like this:
CREATE OR REPLACE FUNCTION sp_process_ticket_block_receipt(receipt_id INTEGER, OUT status VARCHAR, OUT message VARCHAR) AS $sql$
DECLARE
res bool;
BEGIN
WITH detail_list AS (
SELECT tbr.r,
tbrd.*
FROM ticket_block_receipt AS tbr
JOIN ticket_block_receipt_detail AS tbrd ON (ticket_block_receipt_id = tbr.id)
WHERE tbr.id = receipt_id
), inserted AS (
INSERT INTO for_insert (ticket_block_receipt_id, cnt, calc)
SELECT ticket_block_receipt_id, i, i * r
FROM detail_list JOIN LATERAL generate_series(1, detail_list.no_of_blocks) AS i ON true
RETURNING *
)
SELECT count(*) = (SELECT no_of_blocks FROM detail_list) INTO res
FROM inserted
;
IF RES THEN
status = 'success';
message = 'Success message';
RETURN;
END IF;
RAISE EXCEPTION 'Error!!!';
EXCEPTION WHEN OTHERS THEN
status = 'error';
message = 'Error message';
END;
$sql$ LANGUAGE plpgsql;
SELECT sp_process_ticket_block_receipt(1);
But looks like you can just use pure sql:
WITH detail_list AS (
SELECT tbr.r,
tbrd.*
FROM ticket_block_receipt AS tbr
JOIN ticket_block_receipt_detail AS tbrd ON (ticket_block_receipt_id = tbr.id)
WHERE tbr.id = :receipt_id
), inserted AS (
INSERT INTO for_insert (ticket_block_receipt_id, cnt, calc)
SELECT ticket_block_receipt_id, i, i * r
FROM detail_list JOIN LATERAL generate_series(1, detail_list.no_of_blocks) AS i ON true
RETURNING *
)
SELECT count(*) = (SELECT no_of_blocks FROM detail_list)
FROM inserted;
Right now I have created a sequence and a trigger to auto increment the ID value like this:
CREATE OR REPLACE TRIGGER "RTH"."TBL_USER_TRIGGER"
BEFORE INSERT ON TBL_USER
FOR EACH ROW
BEGIN
SELECT TBL_USER_SEQ.nextval
INTO :new.USR_ID
FROM dual;
END;
ALTER TRIGGER "RTH"."TBL_USER_TRIGGER" ENABLE;
Let say I have three rows:
User ID FIRSTNAME LASTNAME
====================================
1 John smith
2 James smith
3 Pat smith
When I delete the first row(1) I want the ID values to auto correct itself to correct numbers so that second row ID values becomes 1 and third row ID values becomes 2
Is it possible in oracle? or do I have do it through code as I am using Java to insert records into table when user submits a form.
Java DAO class:
public class RegistrationDAO {
public void insert(User user) {
try {
Connection con = DBConnection.getConnection();
String query = "insert into TBL_USER(USR_FIRST_NAME,USR_LST_NAME,USR_PRIMARY_EMAIL,USR_PASSWORD) values(?,?,?,?)";
PreparedStatement pst = con.prepareStatement(query);
pst.setString(1, user.getFirstName());
pst.setString(2, user.getLastName());
pst.setString(3, user.getEmail());
pst.setString(4, user.getPassword());
pst.executeUpdate();
} catch (Exception e) {
System.out.println("####Record insertion error in Registration DAO####");
System.out.println(e);
}
}
}
The simple answer is: "No, you don't want to do that." The purpose of an id is to uniquely identify each row. A sequential id also has the feature that it provides insertion order. It is not intended to change over time. Row 1 is Row 1 is Row 1.
If you want ordering, then declare the id to be the primary key and use a query such as this:
select t.*, row_number() over (order by usr_id) as seqnum
from t;
Ideally, you should not be changing USER ID values. But still if there is requirement. Then below are the steps.
Create a procedure to reset the sequence TBL_USER_SEQ. This is needed as you are resetting the USER ID values. So after resetting, whenever you insert new record, Sequence should start with proper value ( i.e. MAX USER_ID value of the table). So that there will be no gap in USER_ID
Write Delete Trigger on table which will adjust USER_ID values.
Procedure to reset sequence
CREATE OR REPLACE PROCEDURE reset_sequence_p (p_seq IN VARCHAR2, p_new_seq NUMBER)
AS
l_value NUMBER;
BEGIN
-- Select the next value of the sequence
EXECUTE IMMEDIATE 'SELECT ' || p_seq || '.NEXTVAL FROM DUAL' INTO l_value;
-- Alter the sequnce to increment by the difference
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_seq || ' INCREMENT BY ' || ( p_new_seq - l_value ) ;
-- Increment to next value after diference
EXECUTE IMMEDIATE 'SELECT ' || p_seq || '.NEXTVAL FROM DUAL' INTO l_value;
-- Set the increment back to 1
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_seq || ' INCREMENT BY ' || 1 ;
END reset_sequence_p;
Delete Trigger
CREATE OR REPLACE TRIGGER "RTH"."TBL_USER_TRIG_DEL"
BEFORE DELETE
ON TBL_USER
FOR EACH ROW
DECLARE
v_new_user_id_seq NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
-- Update USER_ID value for all remaining records greater than USER_ID that got deleted.
UPDATE TBL_USER
SET USER_ID = USER_ID - 1
WHERE USER_ID > :old.USER_ID;
-- Retrieve max USER_ID available in table.
SELECT MAX (user_id) INTO v_new_user_id_seq FROM TBL_USER;
-- Call procedure to reset sequence
reset_sequence_p ('TBL_USER_SEQ', v_new_user_id_seq);
COMMIT;
END;
NOTE : Make sure that Primary key on table is disabled before execution of Delete statement.
I hope this helps.
I have big SQL script with creating temp tables and inserting data in them from selecting data from another tables, many inserts, like
SET #EndDate = DATEADD(dd, -1, #EndDate);
DECLARE #EndDatePlusOneDay SMALLDATETIME
SET #EndDatePlusOneDay = DATEADD(dd, 1, #EndDate);
CREATE TABLE #cntrs
(
ContractID DSIDENTIFIER,
ContractDateFrom dsdatetime,
ContractNumber dsvarfullname40,
ContractClientID DSIDENTIFIER,
ContractClientName VARCHAR(500),
CreditDateTo dsdatetime,
Amount dsmoney,
LoanDept dsmoney,
PledgeRate dsmoney,
CollatVal dsmoney,
WarrantyType dsfullname,
WarrantyNumber VARCHAR(20),
WarrantyDate dsoperday,
WarrantyQty dsmoney,
)
INSERT INTO #cntrs
SELECT c.ContractID,
cc.CreditDateFrom,
c.Number,
ti.InstitutionID,
(
CASE
WHEN ti.PropDealPart = 0 THEN ti.Name + ' ' + ti.Name1 + ' ' +
ti.Name2 + ' '
ELSE ti.Name
END
) AS ContractClientName,
cc.CreditDateTo,
c.Amount,
0 AS LoanDept,
70 AS PledgeRate,
0 AS CollatVal,
'' AS WarrantyType,
'' AS WarrantyNumber,
'19000101' AS WarrantyDate,
0 AS WarrantyQty
FROM dataTable1 c(NOLOCK)
INNER JOIN dataTable2 cc(NOLOCK)
ON c.ContractID = cc.ContractCreditID
INNER JOIN dataTable3 o(NOLOCK)
ON c.ContractID = o.ID
AND o.ObjectTypeID = 105
INNER JOIN dataTable4 p(NOLOCK)
ON o.CurrProtocolID = p.ProtocolID
INNER JOIN dataTable5 t(NOLOCK)
ON p.TransitionID = t.TransitionID
INNER JOIN dataTable6 n(NOLOCK)
ON t.TargetStateID = n.NodeID
INNER JOIN dataTable7 ti WITH(NOLOCK)
ON ti.InstitutionID = c.InstitutionID
WHERE 1 = 1
-- AND #BranchID IN (0, ISNULL(NULLIF(c.BranchExtId, 0), c.BranchID))
AND n.Brief IN ('Предоставл', 'НеОплВовр', 'Завершен')
AND cc.CreditDateFrom BETWEEN #StartDate AND #endDate
ORDER BY
cc.CreditDateFrom
IF OBJECT_ID('tempdb..#AccInner') IS NOT NULL
DROP TABLE #AccInner
CREATE TABLE #AccInner
(
ContractID NUMERIC(15, 0),
ResourceID NUMERIC(15, 0)
)
CREATE UNIQUE INDEX x1 ON #AccInner(ContractID)
DECLARE #DepParentID DSIDENTIFIER, -- Субконто КатегорияСредств
#DepRepaymentID DSIDENTIFIER, -- Субконто ТипОперВУ - Упл/Погаш
#DepAccrualID DSIDENTIFIER -- Субконто ТипОперВУ - Выд/Нач
SELECT #DepParentID = d.DepartmentID
FROM tDepartment d(NOLOCK INDEX = XAK3tDepartment)
WHERE d.Brief = 'КатСрдств'
UPDATE c
SET c.CollatVal = c.LoanDept * (c.PledgeRate / 100)
FROM #cntrs c
SELECT *
FROM #cntrs
ORDER BY
ContractDateFrom
I need execute this T-SQL code in one batch query. I plan to read T-SQL code from the file to String and execute this String at once.
How I can do this with JDBC for MS SQL Server?
You can always save this T-SQL code as MSSQL stored procedure and execute it using JDBC CallableStatement.
DELIMITER //
DROP TRIGGER insert_orderinward_trig; //
CREATE TRIGGER insert_orderinward_trig AFTER INSERT ON tblm_tlmngorderinward FOR EACH ROW
BEGIN
call temp_proc(NEW.itemcode);
END; //
I write procedure as
DELIMITER //
DROP PROCEDURE temp_proc; //
CREATE PROCEDURE temp_proc(IN code VARCHAR(80))
BEGIN
DECLARE inwardstock,issuestock,updatestock DECIMAL(15,0) DEFAULT 0;
SET inwardstock = (SELECT SUM(stock) FROM tblm_tlmngorderinward WHERE itemcode = code);
IF(#inwardstock > 0) THEN
SET updatestock =inwardstock;
END IF;
SET issuestock = (SELECT SUM(stock) FROM tblt_tlmngissueitem WHERE itemcode = code);
IF(#issuestock > 0) THEN
SET updatestock = #updatestock-#issuestock ;
END IF;
UPDATE tblm_tlmngitem SET stock=updatestock WHERE itemcode=code;
END; //
DELIMITER ;
My insert trigger work fine when i manually insert record. But it can not work when i insert record using preparestatement in java.
On first glance you seem to have 2 variables for inwardstock/#inwardstock and issuestock/#issuestock which looks wrong:
DECLARE inwardstock,issuestock,updatestock DECIMAL(15,0) DEFAULT 0;
IF(#inwardstock > 0) THEN <--- #inwardstock or inwardstock !!
...
IF(#issuestock > 0) THEN <--- #issuestock or issuestock !!
The other suggestion I have is to drop the trigger completely and just call a stored proc:
delimiter #
create procedure insert_tblm_tlmngorderinward
(
in p_itemcode varchar(80) -- p_ is for param
)
begin
-- v_ is for variable
declare v_inwardstock, v_issuestock, v_updatestock decimal(15,0) default 0;
-- do this in stored proc
insert into tblm_tlmngorderinward (itemcode) values (p_itemcode);
-- now do all the trigger stuff
select sum(stock) into v_inwardstock from tblm_tlmngorderinward where itemcode = p_itemcode;
select sum(stock) into v_issuestock from tblt_tlmngissueitem where itemcode = p_itemcode;
if(v_inwardstock > 0) then
set v_updatestock = v_inwardstock;
end if;
if(v_issuestock > 0) then
set v_updatestock = v_updatestock - v_issuestock;
end if;
update tblm_tlmngitem set stock = v_updatestock where itemcode = p_itemcode;
end#
delimiter ;
call insert_tblm_tlmngorderinward('why_use_a_trigger');
Hope this helps :)