how to use bulk collect in db2 cursor - cursor

Here is my procedure, I don't know how to use bulk collecton in cursor, that we can batch process the cursor data. Please help me, thanks!
CREATE PROCEDURE PROC_AUTOACTIVE
BEGIN ATOMIC
DECLARE v_sql VARCHAR(800);
DECLARE v_customer_id BIGINT;
DECLARE v_cardnum varchar(500);
DECLARE v_cardtype varchar(20);
DECLARE v_status varchar(10);
DECLARE v_lastname varchar(200);
DECLARE v_email varchar(150);
DECLARE v_mobile varchar(30);
DECLARE v_phone varchar(30);
DECLARE v_zipcode varchar(20);
DECLARE v_crm_mobile varchar(30);
DECLARE v_address varchar(500);
DECLARE v_order_count BIGINT;
DECLARE v_order_no varchar(500);
DECLARE not_found CONDITION FOR SQLSTATE '02000';
DECLARE at_end INT DEFAULT 0;
DECLARE c_customers CURSOR FOR s_cardsinfo;
DECLARE CONTINUE HANDLER FOR not_found SET at_end = 1;
SET v_sql = 'select t.customer_id, v.CUSTOMER_ID, v.CARD_TYPE, v.STATUS
from customer_tempcard t,
vip_fields v
where t.tempcard_num=v.CUSTOMER_ID
and t.status=1
and v.STATUS=1
and exists (select id
from orders o
where o.FK_CUSTOMER=t.CUSTOMER_ID
and o.FK_ORDER_STATUS in (3,4,6)) ';
PREPARE s_cardsinfo FROM v_sql;
OPEN c_customers;
--fetch card info
LOOP_CUSTOMER_INFO:
LOOP
FETCH c_customers INTO v_customer_id,v_cardnum,v_cardtype,v_status;
IF at_end <> 0 THEN
SET at_end = 0;
LEAVE LOOP_CUSTOMER_INFO;
END IF;
select c.LOGON_ID, o.DEV_CUSTOMER_NAME,
o.DEV_MOBILE, o.DEV_PHONE, o.DEV_ZIP, o.DEV_ADDRESS, o.ORDER_NO
into v_email, v_lastname,
v_mobile, v_phone, v_zipcode, v_address, v_order_no
from orders o,customer c
where o.FK_CUSTOMER=c.ID
and o.FK_CUSTOMER=v_customer_id
and o.FK_ORDER_STATUS in (3,4,6)
order by o.ID desc
fetch first 1 rows only;
IF v_mobile <> null THEN
SET v_crm_mobile = v_mobile;
ELSE
SET v_crm_mobile = v_phone;
END IF;
update customer_tempcard ct
set ct.STATUS='0',
ct.UPDATE_TIME=current_timestamp
where ct.CUSTOMER_ID=v_customer_id;
update card_store cs
set cs.STATUS='0',
cs.UPDATE_TIME=current_timestamp
where cs.CARD_NUM=v_cardnum;
update vip_fields v
set v.LAST_NAME=v_lastname,
v.EMAIL=v_email, v.MOBILE=v_crm_mobile,
v.CUSTOMER_UPDATE_TIME=current_timestamp,
v.UPDATE_TIME=current_timestamp,
v.OPERATION_TYPE='2',
v.CREATE_SOURCE='2',
v.STATUS='0',
v.ZIP_CODE=v_zipcode,
v.ADDRESS=v_address
where customer_id = v_cardnum;
update customer c
set c.VIP_CARD_NUMBER=v_cardnum,
c.VIP_CARD_NAME=v_lastname,
c.VIP_EMAIL=v_email,
c.VIP_CARD_TYPE=v_cardtype,
c.LEVEL=v_cardtype,
c.VIP_ZIP=v_zipcode,
c.VIP_MOBILE=v_crm_mobile,
c.VIP_ADDRESS=v_address,
c.FK_CUSTOMER_GRADE='1'
where c.id=v_customer_id;
insert into beactiveinfo
values (default,v_cardnum,v_order_no,current_timestamp);
END LOOP;
CLOSE c_customers;
END

BULK COLLECT is part of the Oracle compatibility feature in DB2, so, firstly, you cannot use it in the DB2 SQL PL native context, which you are using in your procedure. Secondly, you don't use BULK COLLECT in a cursor. You use SELECT ... BULK COLLECT INTO an_array_variable ... to populate a PL/SQL array. If you intend then to loop over that array, you won't get any performance benefit over the cursor, while incurring the memory overhead for storing the entire result set in the application memory.

Related

T-SQL: How to avoid string manipulation on this XML?

I need to move a node from one place to another in some XML, but after becoming frustrated, I used string manipulation.
I'm trying to move <ReaderTypeID>5</ReaderTypeID> from under <SCPReplyMessage> to be under <SCPReplyMessage><tran>
The section of code where I take a node from outside tran and move it inside tran became troubling and I had to get it working, so I resorted to a more comfortable (but inefficient) approach: string manipulation.
-- move ReaderTypeID from outside <tran> to be inside <tran>
DECLARE #rtidXml VARCHAR(100)
SELECT #rtidXml = CONVERT(VARCHAR(100),#ReplyMessageXml.query('/SCPReplyMessage/ReaderTypeID'))
DECLARE #st NVARCHAR(max)
SET #st = CONVERT(NVARCHAR(MAX),#tranXml)
SET #st = REPLACE(#st,'</tran>',#rtidXml + '</tran>')
SET #tranXml.modify('delete /SCPReplyMessage/ReaderTypeID')
I'd like to accomplish the same result without the CONVERT to and from XML.
Thanks!
the function:
CREATE FUNCTION dbo.udf_mTranAddl (#ReplyMessageXml XML)
returns XML
AS
BEGIN
DECLARE #tranXml XML
SELECT #tranXml = #ReplyMessageXml.query('/SCPReplyMessage/tran')
-- Discard extraneous tran elements
SET #tranXml.modify('delete /tran/ser_num')
SET #tranXml.modify('delete /tran/time')
SET #tranXml.modify('delete /tran/sys')
SET #tranXml.modify('delete /tran/sys_comm')
-- move ReaderTypeID from outside <tran> to be inside <tran>
DECLARE #rtidXml VARCHAR(100)
SELECT #rtidXml = CONVERT(VARCHAR(100),#ReplyMessageXml.query('/SCPReplyMessage/ReaderTypeID'))
DECLARE #st NVARCHAR(max)
SET #st = CONVERT(NVARCHAR(MAX),#tranXml)
SET #st = REPLACE(#st,'</tran>',#rtidXml + '</tran>')
SET #tranXml.modify('delete /SCPReplyMessage/ReaderTypeID')
RETURN CONVERT(xml, #st)
END
Input #ReplyMessageXml:
<SCPReplyMessage>
<ContDeviceID>5974</ContDeviceID>
<LocalTime>2019-08-29T12:35:43</LocalTime>
<Priority>false</Priority>
<ReaderTypeID>5</ReaderTypeID>
<Deferred>false</Deferred>
<tran>
<ser_num>147</ser_num>
<time>1567096543</time>
<source_type>9</source_type>
<source_number>0</source_number>
<tran_type>6</tran_type>
<tran_code>13</tran_code>
<sys>
<error_code>4</error_code>
</sys>
<sys_comm>
<current_primary_comm>123</current_primary_comm>
<current_alternate_comm>4</current_alternate_comm>
</sys_comm>
<c_id>
<format_number>4</format_number>
<cardholder_id>123</cardholder_id>
<floor_number>4</floor_number>
</c_id>
<oal>
<nData>AAAAAA==</nData>
</oal>
</tran>
<SCPId>99</SCPId>
<ReplyType>7</ReplyType>
<ChannelNo>-1</ChannelNo>
</SCPReplyMessage>
output (which is correct):
<tran>
<source_type>9</source_type>
<source_number>0</source_number>
<tran_type>6</tran_type>
<tran_code>13</tran_code>
<c_id>
<format_number>4</format_number>
<cardholder_id>123</cardholder_id>
<floor_number>4</floor_number>
</c_id>
<oal>
<nData>AAAAAA==</nData>
</oal>
<ReaderTypeID>5</ReaderTypeID>
</tran>
FINAL RESULT:
Thanks to #PeterHe
CREATE FUNCTION dbo.udf_mTranAddl (#ReplyMessageXml XML)
returns XML
AS
BEGIN
DECLARE #tranXml XML
SELECT #tranXml = #ReplyMessageXml.query('/SCPReplyMessage/tran')
-- Discard extraneous tran elements
SET #tranXml.modify('delete /tran/ser_num')
SET #tranXml.modify('delete /tran/time')
SET #tranXml.modify('delete /tran/sys')
SET #tranXml.modify('delete /tran/sys_comm')
-- move ReaderTypeID from outside <tran> to be inside <tran>
DECLARE #x1 xml;
SELECT #x1=#ReplyMessageXml.query('SCPReplyMessage/ReaderTypeID');
SET #tranXml.modify('insert sql:variable("#x1") into (/tran)[1]')
SET #tranXml.modify('delete /SCPReplyMessage/ReaderTypeID')
RETURN #tranXml
END
GO
YOu can do it using xquery:
DECLARE #x xml = '<SCPReplyMessage>
<ContDeviceID>5974</ContDeviceID>
<LocalTime>2019-08-29T12:35:43</LocalTime>
<Priority>false</Priority>
<ReaderTypeID>5</ReaderTypeID>
<Deferred>false</Deferred>
<tran>
<ser_num>147</ser_num>
<time>1567096543</time>
<source_type>9</source_type>
<source_number>0</source_number>
<tran_type>6</tran_type>
<tran_code>13</tran_code>
<sys>
<error_code>4</error_code>
</sys>
<sys_comm>
<current_primary_comm>123</current_primary_comm>
<current_alternate_comm>4</current_alternate_comm>
</sys_comm>
<c_id>
<format_number>4</format_number>
<cardholder_id>123</cardholder_id>
<floor_number>4</floor_number>
</c_id>
<oal>
<nData>AAAAAA==</nData>
</oal>
</tran>
<SCPId>99</SCPId>
<ReplyType>7</ReplyType>
<ChannelNo>-1</ChannelNo>
</SCPReplyMessage>'
DECLARE #output xml;
SELECT #output = #x.query('/SCPReplyMessage/tran');
SET #Output.modify('delete(/tran/ser_num)');
SET #Output.modify('delete(/tran/time)');
SET #Output.modify('delete(/tran/sys)');
SET #Output.modify('delete(/tran/sys_comm)');
DECLARE #x1 xml;
SELECT #x1=#x.query('SCPReplyMessage/ReaderTypeID');
SET #output.modify('insert sql:variable("#x1") into (/tran)[1]')
SELECT #output;
Here is a much easier way by using XQuery FLWOR expression. The main idea is to construct what you need in one single statement instead of moving, deleting, inserting, etc.
SQL
DECLARE #xml XML =
N'<SCPReplyMessage>
<ContDeviceID>5974</ContDeviceID>
<LocalTime>2019-08-29T12:35:43</LocalTime>
<Priority>false</Priority>
<ReaderTypeID>5</ReaderTypeID>
<Deferred>false</Deferred>
<tran>
<ser_num>147</ser_num>
<time>1567096543</time>
<source_type>9</source_type>
<source_number>0</source_number>
<tran_type>6</tran_type>
<tran_code>13</tran_code>
<sys>
<error_code>4</error_code>
</sys>
<sys_comm>
<current_primary_comm>123</current_primary_comm>
<current_alternate_comm>4</current_alternate_comm>
</sys_comm>
<c_id>
<format_number>4</format_number>
<cardholder_id>123</cardholder_id>
<floor_number>4</floor_number>
</c_id>
<oal>
<nData>AAAAAA==</nData>
</oal>
</tran>
<SCPId>99</SCPId>
<ReplyType>7</ReplyType>
<ChannelNo>-1</ChannelNo>
</SCPReplyMessage>';
SELECT #xml.query('<tran>{
for $x in /SCPReplyMessage/tran
return ($x/source_type,
$x/source_number,
$x/tran_type,
$x/tran_code,
$x/c_id,
$x/oal,
$x/../ReaderTypeID)
}</tran>');

How to return value 1 stored procedure

I have written a stored procedure for inserting name and email address:
CREATE PROCEDURE [dbo].[usp_Referral_Email]
#user_key varchar(36),
#name nvarchar(100),
#email nvarchar(500),
#result int output
AS
BEGIN
DECLARE #username Nvarchar(500)
DECLARE #useremail Nvarchar(500)
DECLARE #CusrsorID CURSOR
SET #CusrsorID = CURSOR FOR
SELECT Value,Value1
FROM ufn_split_string(#name,#email)
OPEN #CusrsorID
FETCH NEXT FROM #CusrsorID INTO #username, #useremail
WHILE ##FETCH_STATUS = 0
BEGIN
declare #user nvarchar(36)
begin try
begin transaction trans_Referral_Email
IF NOT EXISTS (SELECT 1 FROM dbo.C_User_Credentials
WHERE email = #useremail)
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.Referral_Email
WHERE R_Email = #useremail)
BEGIN
INSERT INTO dbo.Referral_Email (CFK_C_UP_key, R_Name, R_Email)
VALUES (#user_key, #username, #useremail)
SET #result = 1
END
ELSE
BEGIN
SET #result = 0
END
END
ELSE
BEGIN
SET #result = 0
END
COMMIT transaction trans_Referral_Email
end try
begin catch
rollback transaction trans_Referral_Email
set #result=ERROR_MESSAGE()
end catch
FETCH NEXT FROM #CusrsorID INTO #username, #useremail
END
CLOSE #CusrsorID
DEALLOCATE #CusrsorID
END
As a example I will pass value
#name = ramesh,suresh,rahul
#email = ramesh#gmail.com,suresh#gmail.com,rahul#gmail.com
before inserting we checking condition email address are exists or not, suppose email address is exist it will not insert into the table.
Now my problem is i will explain through example. ramesh#gmail.com and suresh#gmail.com are new email address both email address are will insert into the table so return value is 1 rahul#gmail.com already exist in table so it will insert into the table so return value will be 0 and output #return value will be 0 but we have inserted 2 email address so i need #return value should be 1 as out put.
So my question is if at any place of email address is insert into the table if one email address also insert output should be #return=1
What you need is known as a "latch" (archaic) or as a flag variable, and is pretty common.
A flag variable (in this case, #result) should be initialized outside the loop and then set when a condition arises (in this case, a record is inserted). The variable should not be touched when any subsequent records are skipped. That way it acts as a sort of an OR gate.
Like this:
CREATE PROCEDURE [dbo].[usp_Referral_Email]
#user_key varchar(36),
#name nvarchar(100),
#email nvarchar(500),
#result int output
AS
BEGIN
DECLARE #username Nvarchar(500)
DECLARE #useremail Nvarchar(500)
DECLARE #CusrsorID CURSOR
SET #CusrsorID = CURSOR FOR
SELECT Value,Value1
FROM ufn_split_string(#name,#email)
OPEN #CusrsorID
FETCH NEXT FROM #CusrsorID INTO #username, #useremail
SET #result = 0 --<--- Will stay 0 until one or more rows are inserted
WHILE ##FETCH_STATUS = 0
BEGIN
declare #user nvarchar(36)
begin try
begin transaction trans_Referral_Email
IF NOT EXISTS (SELECT 1 FROM dbo.C_User_Credentials
WHERE email = #useremail)
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.Referral_Email
WHERE R_Email = #useremail)
BEGIN
INSERT INTO dbo.Referral_Email (CFK_C_UP_key, R_Name, R_Email)
VALUES (#user_key, #username, #useremail)
SET #result = 1 --<--- Will stay 1 for the rest of its lifespan, even if other rows are not inserted
END
END
COMMIT transaction trans_Referral_Email
end try
begin catch
rollback transaction trans_Referral_Email
set #result=ERROR_MESSAGE()
end catch
FETCH NEXT FROM #CusrsorID INTO #username, #useremail
END
CLOSE #CusrsorID
DEALLOCATE #CusrsorID
END
Notice I've removed a bunch of the ELSE conditions, since you don't need to do anything when a record is skipped.
you are trying to process/insert several users with 1 stored procedure call and you can't use a single output INT field to return back insert status for several users.
Better to split #name and #email parameters at application level and pass to your (modified) stored procedure only a SINGLE pair of name and email. You will then have to call the spT from application level several times for each name/email pair.
If you still want to use a single spT for batch user insert, you will have to record each insert status into a temp table or table variable and then at the spT end you will have to SELECT from that temp table or table variable.
This way at application level you will have a row with status returned for each name/email input pair.
But I personally suggest you actually change your spT to be called once per each name/email pair. It's a better and cleaner approach.
HTH

MSSQL nvarchar IF PROCEDURE exec with nvarchar input

I want know how to release IF with update and how to exec procedure with nvarchar input.
There are table Dictionary with 2 values ('Orig','Translated')
I need procedure that adds or replace "Trasnslated' depending on input. There must be 2 inputs, no more and no less. For example
CREATE PROCEDURE Translate_Orig (#Orig nvarchar(32),#Translated nvarchar(32))
AS
BEGIN
UPDATE Dictionary
IF EXISTS (SELECT * FROM Dictionary WHERE Dictionary.Orig=#Orig)
SET Dictionary.Translated=#Translated
ELSE INSERT INTO Dictionary VALUES (#Orig, #Translated);
END
GO
SET #Orig = N'Orig'
SET #Translated = N'traslated'
EXEC Translate_Orig (#Orig,#Translated);
CREATE PROCEDURE Translate_Orig (#Orig nvarchar(32),#Translated nvarchar(32))
AS
BEGIN
IF EXISTS (SELECT * FROM Dictionary WHERE Dictionary.Orig=#Orig)
BEGIN
UPDATE Dictionary
SET Dictionary.Translated=#Translated
END
ELSE INSERT INTO Dictionary VALUES (#Orig, #Translated);
END
GO
SET #Orig = N'Orig'
SET #Translated = N'traslated'
EXEC Translate_Orig (#Orig,#Translated);
Make sure you are just running the CREATE PROCEDURE through GO as a statement.
There EXEC that works fine. No brackets.
And "SET Dictionary.Translated=#Translated WHERE Dictionary.Orig=#Orig"
CREATE PROCEDURE Translate_Orig (#Orig nvarchar(32),#Translated nvarchar(32))
AS
BEGIN
IF EXISTS (SELECT * FROM Dictionary WHERE Dictionary.Orig=#Orig)
BEGIN
UPDATE Dictionary
SET Dictionary.Translated=#Translated WHERE Dictionary.Orig=#Orig
END
ELSE INSERT INTO Dictionary VALUES (#Orig, #Translated);
END
GO
DECLARE #Orig nvarchar(32);
DECLARE #Translated nvarchar(32);
SET #Orig = N'Name'
SET #Translated = N'Name_Translated'
EXEC Translate_Orig #Orig,#Translated;

Is a recursively called stored procedure possible in SQL Server?

Here is what I have as VBScript Subroutine:
sub buildChildAdminStringHierarchical(byval pAdminID, byref adminString)
set rsx = conn.execute ("select admin_id from administrator_owners where admin_id not in (" & adminString & ") and owner_id = " & pAdminID)
do while not rsx.eof
adminString = adminString & "," & rsx(0)
call buildChildAdminStringHierarchical(rsx(0),adminString)
rsx.movenext
loop
end sub
Is there anyway to turn this into a stored procedure since it's got the recursive call in the subroutine?
Here is what I've tried...
CREATE PROCEDURE usp_build_child_admin_string_hierarchically
#ID AS INT,
#ADMIN_STRING AS VARCHAR(8000),
#ID_STRING AS VARCHAR(8000) OUTPUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #index int;
DECLARE #length int;
DECLARE #admin_id int;
DECLARE #new_string varchar(8000);
SET #index = 1;
SET #length = 0;
SET #new_string = #ADMIN_STRING;
CREATE TABLE #Temp (ID int)
WHILE #index <= LEN(#new_string)
BEGIN
IF CHARINDEX(',', #new_string, #index) = 0
SELECT #length = (LEN(#new_string) + 1) - #index;
ELSE
SELECT #length = (CHARINDEX(',', #new_string, #index) - #index);
SELECT #admin_id = CONVERT(INT,SUBSTRING(#new_string, #index, #length));
SET #index = #index + #length + 1;
INSERT INTO #temp VALUES(#admin_id);
END
DECLARE TableCursor CURSOR FOR
SELECT Admin_ID FROM Administrator_Owners WHERE Admin_ID NOT IN (SELECT ID FROM #temp) AND Owner_ID = #ID;
OPEN TableCursor;
FETCH NEXT FROM TableCursor INTO #admin_id;
WHILE ##FETCH_STATUS = 0
BEGIN
IF LEN(#ID_STRING) > 0
SET #ID_STRING = #ID_STRING + ',' + CONVERT(VARCHAR, #admin_id);
ELSE
SET #ID_STRING = CONVERT(VARCHAR, #admin_id);
EXEC usp_build_child_admin_string_hierarchically #admin_id, #ID_STRING, #ID_STRING;
FETCH NEXT FROM TableCursor INTO #admin_id;
END
CLOSE TableCursor;
DEALLOCATE TableCursor;
DROP TABLE #temp;
END
GO
But I get the following error when that stored procedure is called...
A cursor with the same name 'TableCursor' already exists.
You can specify a LOCAL cursor, like this:
DECLARE TableCursor CURSOR LOCAL FOR
SELECT ...
At least in SQL Server 2008 R2 (my machine), this allows you to recursively call the sproc without running into "Cursor already exists" errors.
The problem is that while your cursor isn't global, it is a session cursor. Since you're doing recursion, even though each iteration is creating a cursor in a new proc scope, they're all being created in the same PID (connection) at the same time, thus the collision.
You'll need to generate unique cursor names in each iteration of the procedure based on some criteria that won't be reproduced during the recursion.
Or, preferably, find a way to do what you need using set logic, and handle any necessary recursion using a recursive CTE.
You can, but it's usually not a good idea. SQL is made for set-based operations. Also, in MS SQL Server at least, the recursion is limited to the number of recursive calls that it can make. You can only nest up to 32 levels deep.
The problem in your case is that the CURSOR lasts through each call, so you end up creating it more than once.

performance problem sql server 2005 update sentence

I have a table "OFICIAL3" with 500k rows. and 30 columns. and table INSIS with 150k rows and 20 columns.
OFICIAL3.NUMERO_TITULO has an index.
INSIS.NumeroDocumento has an index too.
update sentence take long time. this process will take 9 hours in my machine
my machine is a core 2 duo 2.GHZ and 2GB RAM
ALTER PROCEDURE [dbo].[CompletarDatos] AS
declare #cantidad int;
declare #CONTADOR int;
declare #NRO_TITULO VARCHAR(600);
declare #POYECTO VARCHAR(200);
DECLARE #I_PROYECTO VARCHAR(500);
DECLARE #I_AREA_INT VARCHAR(500);
SET NOCOUNT ON
BEGIN
SET #cantidad =(select count(*) from OFICIAL3)
SET #CONTADOR=1
declare CURSORITO cursor for
select NUMERO_TITULO from OFICIAL3
open CURSORITO
fetch next from CURSORITO
into #NRO_TITULO
while ##fetch_status = 0
begin
SET #CONTADOR=#CONTADOR+1
PRINT 'ROW='+CONVERT(NVARCHAR(30),#CONTADOR)+' NRO TITULO='+#NRO_TITULO
SET #I_PROYECTO = (SELECT PROYECTO FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
SET #I_AREA_INT = (SELECT I_AREA_INTERVENCION FROM INSIS WHERE NumeroDocumento=#NRO_TITULO)
UPDATE OFICIAL3 SET PROYECT=#I_PROYECTO , COD_AREA=#I_AREA_INT WHERE NUMERO_TITULO=#NRO_TITULO
fetch next from CURSORITO into #NRO_TITULO
end
-- cerramos el cursor
close CURSORITO
deallocate CURSORITO
END
Assuming OFICIAL4 is a typo, this should work as a single update:
UPDATE o
SET PROYECT = i.PROYECTO,
COD_AREA = i.I_AREA_INTERVENCION
FROM OFICIAL3 o
INNER JOIN
INSIS i
ON o.NUMERO_TITULO = i.NumeroDocumento
As others have commented, an approach that avoids the CURSOR is vastly preferable from a performance point of view. Another thought is that a covering index on `INSIS (NumeroDocumento, PROYECTO, I_AREA_INTERVENCION) would speed things up further for this query.
Is there any way you can do this without a cursor? Removing the iteration should help it considerably.

Resources