opening a cursor after some code - sql

I have some Stored Procedure in DB2. Some lines of code (INSERT, UPDATE etc). After some operations I have IF condition that opening/not opening CURSOR. For some reason the CURSOR is not opened even if IF condition is TRUE. All the code before the CURSOR works fine. If I'll remove it, so the CURSOR will works fine. If I'll put the CURSOR in separate SP, it works fine (calling for another SP from this SP). But together for some reason it not works. I can't understand why. I need this code for the IF condition.
That's how it looks (only 1 row with INSERT left outside the CURSOR):
DECLARE C1 CURSOR WITH HOLD FOR
SELECT s.KEY, s.CODE, s.PRODUCT, s.AMOUNT
FROM DB2ADMIN.SALES s, DB2ADMIN.PRODUCTS p
WHERE s.DATE_KEY = CDC AND s.PRODUCT_KEY = p.PRODUCT_KEY;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET EOF = 1;
INSERT INTO DB2ADMIN.IA_BASE_SALES_TMP (SOME_FIELD) VALUES (SOME_VALUE);
IF true THEN
OPEN C1;
WHILE EOF = 0 DO
FETCH FROM C1 INTO SP_KEY, SP_CODE, SP_KEY, SP_PRODUCT, SP_AMOUNT;
MERGE INTO DB2ADMIN.IA_BASE_SALES_TMP t
USING (
SELECT POS, p.KEY, s.TELLER, s.TYPE, s.AMOUNT, s.CDC
FROM DB2ADMIN.COMMISSIONS s, DB2ADMIN.PRODUCTS p
WHERE TELLER = SP_TELLER AND TYPE = SP_TYPE
) e ON t.TELLER_KEY = e.TELLER_ID
WHEN matched
THEN UPDATE SET t.KEY = e.KEY, t.UPD = 0;
END WHILE;
CLOSE C1;
END IF;

Thanks to Jean.
Replacing the cursor with FOR loop and everything works OK.

Related

Firedac multiparametric query with macro

I created the main query that returns values for a whole, with 2 secondary conditions to restrict choice to be taken in the side combo box.
Everything works with the set parameters. I wish I could turn off or turn on these conditions with side combo box, how should I proceed?
my code is in Delphi:
procedure TForm1.Button3Click(Sender: TObject);
begin
FDQuery3.Close;
FDquery3.Params[0].Value := Datetimepicker1.Date;
FDquery3.Params[1].Value := Datetimepicker2.Date;
FDQuery3.Params[2].Value := Combobox3.Items [Combobox3.Itemindex];
FDQuery3.Params[3].Value := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.Open;
end;
SQL text is:
select
G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE,
(select DESKEY from ANAFORN where CODKEY=T.CODICE ) as Cliente,
O.NOMINATIVO, T.TERMINALE,T.INCASSO
from LG_RIGHE G
inner join LG_TESTA T on G.NUM_PROG =T.NUM_PROG
inner join OPERATORI O on T.OPERATORE = O.CODICE
inner join LG_CAUSA C on T.CAUSALE = C.CODICE
where T.DATA >= :data1
and T.DATA <= :data2
and T.INCASSO = :pagamento
and T.TERMINALE = :terminale
order by G.NUM_PROG
i want turn on/off only Params[2][ and Params[3] (name: pagamento, terminale)
1) The typical way to optionally ignore a condition is to add one more "toggle" parameter. Consider this Delphi code
Result := True; // or False. Actually - some previous part of formula
If Need_Check_Option_A then
Result := Result and ( Option_A > 20 );
If Need_Check_Option_B then
Result := Result and ( Option_B < 10 );
Got the idea?
But very long that is, is there a more concise way to write it ?
Result := .....some other parts....
and (Ignore_Option_A or (Option_A > 20 ))
and (Ignore_Option_B or (Option_A < 10 ))
and ....
Now let's re-phrase it from Delphi to SQL WHERE clause
WHERE (.......) and (......)
AND ( ( :Use_pagamento = 0 ) or ( T.INCASSO = :pagamento ) )
AND ( ( :Use_terminale = 0 ) or ( T.TERMINALE = :terminale ) )
Whether you set that USE_xxxx parameter to zero (similar to false) then the second check would be shortcut out, ignored.
And the calling code would be something like
FDquery3.ParamByName('data1').AsDate := Datetimepicker1.Date;
FDquery3.ParamByName('data2').AsDate := Datetimepicker2.Date;
FDQuery3.ParamByName('pagamento').AsString := Combobox3.Items [Combobox3.Itemindex];
FDQuery3.ParamByName('terminale').AsString := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.ParamByName('Use_pagamento').AsSmallInt := Ord( CheckBox3.Checked );
FDQuery3.ParamByName('Use_terminale').AsSmallInt := Ord( CheckBox5.Checked );
Some more suggestions follow:
2) using names like ComboBox3 are bad. You would not understand what they mean, what was they intended to be for. Look at your SQL - you give names there! You do not make it like
SELECT FIELD1, FIELD2 FROM TABLE1 WHERE FIELD3 < :PARAM1
And you have to give reasonable names to your Delphi objects too!
That FDQuery3, that Checkbox3 that Combobox5 - rename them all, give them some meaningful names!
3) you have a nested select there as the Cliente column. Unless very special circumstances that is slow and inefficient - change it to JOIN too (maybe to LEFT JOIN, if sometimes there is no matching value)
select
G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE,
-- (select DESKEY from ANAFORN where CODKEY=T.CODICE ) as Cliente,
A.DESKEY as Cliente,
O.NOMINATIVO, T.TERMINALE,T.INCASSO
from LG_RIGHE G
inner join LG_TESTA T on G.NUM_PROG =T.NUM_PROG
inner join OPERATORI O on T.OPERATORE = O.CODICE
inner join LG_CAUSA C on T.CAUSALE = C.CODICE
/* left */ join ANAFORN A on A.CODKEY=T.CODICE
where T.DATA >= :data1
and T.DATA <= :data2
AND ( ( :Use_pagamento = 0 ) or ( T.INCASSO = :pagamento ) )
AND ( ( :Use_terminale = 0 ) or ( T.TERMINALE = :terminale ) )
order by G.NUM_PROG
4) Depending on the circumstances you may just want to alter the SQL text.
If the parameter would be ignored - then simply remove it!
This option is not universal, it has good and bad sides though.
But in your case it would rather do good or nothing - because you have human to re-open the query and human would not be able to do it more often than once per second.
Good: then the server gets your SQL text it prepares the QUERY PLAN. The internal program of how to fetch your data. And it does not know yet what your parameters would be, so it prepares the PLAN to always check those parameters. Even if you later would ignore them. Sometimes it might make server choose slow PLAN where it could choose faster one if it knew the parameter would be not used. Sometimes it would make no difference. Game of luck.
Bad: if you keep the SQL text the same, then you can PREPARE the query once and the server would not build different PLAN when you re-open the query with different parameters. But if you do change the SQL text, then server would have to parse that new query and PREPARE the PLAN again before it would give you data. Sometimes it would take considerable time when you open-close queries, say, 1000 times per second. OF course, when you use a human to set those checkboxes, comboboxes and then press buttons, he would not do it that frequently, so in this case that risk is moot.
So in your case you might do something like this instead of introducing those toggle-parameters:
var qt: TStrings; // SQL query text
.....
qt := FDQuery3.SQL;
qt.Clear; // changing SQL Text would auto-close the query
qt.Add('select G.NUM_PROG,T.DATA,T.ORA,C.DESCRIZIONE, ');
qt.Add(' A.DESKEY as Cliente, O.NOMINATIVO, T.TERMINALE,T.INCASSO ');
qt.Add('from LG_RIGHE G ');
qt.Add(' join LG_TESTA T on G.NUM_PROG = T.NUM_PROG ');
qt.Add(' left join ANAFORN A on A.CODKEY=T.CODICE');
qt.Add(' join OPERATORI O on T.OPERATORE = O.CODICE ');
qt.Add(' join LG_CAUSA C on T.CAUSALE = C.CODICE ');
qt.Add('where T.DATA >= :data1 and T.DATA <= :data2 ');
if CheckBox3.Checked then
qt.Add(' and T.INCASSO = :pagamento ');
if CheckBox5.Checked then
qt.Add(' and T.TERMINALE = :terminale ');
qt.Add('order by G.NUM_PROG');
FDquery3.ParamByName('data1').AsDate := Datetimepicker1.Date;
FDquery3.ParamByName('data2').AsDate := Datetimepicker2.Date;
if CheckBox3.Checked then
FDQuery3.ParamByName('pagamento').AsString := Combobox3.Items [Combobox3.Itemindex];
if CheckBox3.Checked then
FDQuery3.ParamByName('terminale').AsString := Combobox5.Items [Combobox5.Itemindex];
FDQuery3.Open;
In this option you do not introduce extra toggle-parameters, but instead you only add value-parameters when user checked to use them. If user unchecked them - then you do not include them into your SQL text and consequently you do not assign them any values (they would not be found anyway).
5) you may use BETWEEN - it may be easier to read.
...
where ( T.DATA BETWEEN :data1 AND :data2 )
and T.INCASSO = :pagamento
....

Write ref cursor with loop

Can I write ref cursor in a loop? Is it possible? Otherwise give suggestion?
FOR bd_id_rec IN (SELECT BD_ID FROM DEAL D WHERE D.BD_NR=BD_NR_REC.BD_NR AND BD_VERSION_NR IN (SELECT MAX(BD_VERSION_NR) FROM DEAL D WHERE D.BD_NR=BD_NR_REC.BD_NR AND QUOTE_DIST_CD='A'))
LOOp
open v_refcursor for(SELECT D.BD_NR,D.BD_ID, D.BD_VERSION_NR, L.PROD_GNRC_NR_TX, L.PROD_OPT_CD, LD.AUTH_BD_NET_PRC_AM, D.W_L_STAT_CD, L.LINE_NR FROM DEAL D, LINE_ITEM L, LINE_DISC_SCALE LD, EU_CUST E
WHERE
D.BD_ID=bd_id_rec.bd_id
AND D.CONTROL_CNTRY_CD=l_control_cntry_cd
AND D.BUS_MODEL_CD=l_bus_model_cd
AND D.MC_CHARGE=l_mc_charge
AND D.QUOTE_DIST_CD=l_quote_dist_cd
AND D.QUOTE_DIST_DT_GMT>SYSDATE-60
AND LD.SCALE_ID IN (SELECT MAX(SCALE_ID) FROM LINE_DISC_SCALE WHERE bd_id=d.bd_id)
AND LD.BD_ID=D.BD_ID
AND LD.LINE_NR=L.LINE_NR
AND E.DFT_ENDCUST_OTRPRTYSITE_INSTID=l_dft_otrprtysite
AND E.DFT_ENDCUST_OTRPRTYSITE_ID=l_dft_endcust_otrprt_id
AND L.PROD_GNRC_NR_TX=prod_rec.PROD_GNRC_NR_TX
AND L.LINE_TYPE_CD='PN'
AND L.BD_ID=D.BD_ID
AND D.EU_CUST_NR=l_eu_cust_nr)
LOOP
end loop;
Is it possible or not? Give sample code as well. Give me suggestion how can I use Ref cursor with in loop?
Of course you can use nested loops for iterate through cursors. E.g.
-- iterate through first cursor
FOR rec_cursor1 IN (SELECT column1, column2 FROM my_table1) LOOP
-- iterate through second cursor
FOR rec_cursor2 IN (SELECT * FROM my_table2 t2 WHERE t2.column1 = rec_cursor1.column2 ) LOOP
-- some actions or another nested loop
END LOOP;
END LOOP;

How to display a sys_refcursor data in TOAD's DataGrid

Please i need help.
(I SEARCHED A lot and get more confused . )
I use Toad 9.7.25 and i made this procedure (in a package)
PROCEDURE ReportaCC(pfcorte IN DATE, lcursor IN OUT SYS_REFCURSOR)
IS
BEGIN
OPEN lcursor FOR
select c1, c3, c3 from table1 where hdate = pfcorte;
close lcursor;
END;
In toad's sql editor i´d like execute that procedure and show the cursor results in toad's datagrid:
--- I WANT THIS CODE CAN EXECUTE IN TOAD'S SQL EDITOR.
DECLARE
PFCORTE DATE;
LCURSOR SYS_REFCURSOR;
BEGIN
PFCORTE := '31/08/2012';
-- LCURSOR := NULL; -- Modify the code to initialize this parameter
mypaq.REPORTACC( TO_DATE(PFCORTE,'DD/MM/YYYY') , LCURSOR );
:to_grid := LCURSOR;
COMMIT;
END;
When i execute the script (F9), and set the variable :to_grid type cursor,
i get the next error:
"ORA-24338: statement handle not executed"
What can be the problem
Thanks in advance.
Thanks four your posts... worked fine!
But now have another question...
If i replace the simple query (select c1, c2, c3 from table...) for a mor complex like this:
PROCEDURE ReportaCC(pfcorte IN DATE, lcursor OUT SYS_REFCURSOR)
IS
BEGIN
OPEN lcursor FOR
SELECT ENC.CVEOTORGANTE, ENC.NOMBREOTORGANTE, ENC.IDENDINTIFICADORDEMEDIO, TO_CHAR(SYSDATE, 'YYYYMMDD') AS FECHAEXT, ENC.NOTAOTORGANTE,
CIRCRED.valida_cc.QUITASIGNOS(VCL.APELLIDOPATERNO) AS VAL_APELLIDOPATERNO,
CIRCRED.valida_cc.QUITASIGNOS(VCL.APELLIDOMATERNO) AS VAL_APMATERNO,
CIRCRED.valida_cc.QUITASIGNOS(VCL.APELLIDOADICIONAL) AS APELLIDOADICIONAL ,
CIRCRED.valida_cc.QUITASIGNOS(VCL.NOMBRES) AS NOMBRES,
VCL.FECHANACIMIENTO,
circred.valida_cc.valida_rfc(Vcl.rfc,'CORRIGE') AS VALRFC,
circred.valida_cc.valida_curp(VCL.CURP,'CORRIGE') AS VALCURP, VCL.NACIONALIDAD,
circred.valida_cc.valida_RESIDENCIA('ESIACOM', SC.TIPOVIV ) AS VAL_RESIDENCIA, VCL.NUMEROLICENCIACONDUCIR,
circred.valida_cc.valida_EDOCIVIL('ESIACOM', VCL.ESTADOCIVIL) AS VAL_ESTADOCIVIL, VCL.SEXO,
circred.valida_cc.valida_IFE(VCL.CLAVEELECTORIFE,'CORRIGE') AS CLAVEELECTORIFE,
VCL.NUMERODEPENDIENTES,
VCL.FECHADEFUNCION, VCL.INDICADORDEFUNCION, VCL.TIPOPERSONA,
CIRCRED.valida_cc.QUITASIGNOS(VCL.DIRECCION) AS DIRECCION,
CIRCRED.valida_cc.QUITASIGNOS(VCL.COLONIAPOBLACION) AS COLONIAPOBLACION,
CIRCRED.valida_cc.QUITASIGNOS(VCL.DELEGACIONMUNICIPIO) AS DELEGACIONMUNICIPIO,
CIRCRED.valida_cc.QUITASIGNOS(VCL.CIUDAD) AS CIUDAD,
VCL.ESTADO, circred.valida_cc.valida_cp(VCL.CP, VCL.CDGEF) AS VAL_CP, VCL.FECHARESIDENCIA,
circred.valida_cc.valida_TEL(VCL.NUMEROTELEFONO,'CORRIGE') AS VAL_TEL, circred.valida_cc.valida_TIPODOMICILIO('ESIACOM', 'C') AS VAL_TIPODOMICILIO, VCL.TIPOASENTAMIENTO,
EMP.*,
ENC.CVEOTORGANTE CVEACTUAL, ENC.NOMBREOTORGANTE, SAL.CUENTAACTUAL, SAL.TIPORESPONSABILIDAD, SAL.TIPOCUENTA, SAL.TIPOCONTRA, SAL.CLAVEUNIDADMONETARIA, SAL.VALORACTIVOVALUACION,
SAL.NUMPAGOS, SAL.FREQPAGOS,SAL.PAGOPACCL, SAL.FECHAAPERTURACUENTA,
TO_CHAR(circred.valida_cc.FUN_FULTDEPCL(sal.CLNS, sal.CDGNS, sal.CDGNS, sal.CDGCL, sal.CICLO, SAL.INICICLO, SAL.FREQPAGOS, pfcorte ), 'YYYYMMDD') AS FULTPAGO,
SAL.FECHAULTIMACOMPRA, SAL.FECHACIERRECUENTA, SAL.FECHACORTE, SAL.GARANTIA, SAL.CREDITOMAXIMO,
SAL.SALDOCL, SAL.limitecredito, SAL.SDOVENCL, SAL.NUMPAGVEN, SAL.pagoactual, SAL.HISTORICOPAG, SAL.CLAVEPREVENCION, SAL.TOTPAGREP, SAL.CLAVEANTERIOROTORGANTE,
SAL.NOMBREANTERIOROTORGANTE, SAL.NUMEROCUENTAANTERIOR,
SAL.SUMSALDO, SAL.sumsdoven, SAL.numcred, SAL.numdirecc, SAL.numempleo, SAL.numctas, ENC.NOMBREOTORGANTE, NULL AS DOMDEVOL
FROM
CIRCRED.VW_ENCABEZADO ENC,
circred.VW_DATOSPERDOM VCL,
ICARO.VW_PROYINVE SC,
CIRCRED.EMPLEO EMP,
CIRCRED.VW_SALDOINCOB SAL
WHERE SAL.FUENTEBD = 'ESIACOM'
AND SAL.CDGCL = VCL.CDGCL
AND SAL.CDGCL = SC.CDGCL(+) AND SAL.CICLO = SC.CICLO(+) and SAL.INICICLO = SC.INICIO(+)
AND SAL.FCORTE = pfcorte
AND SAL.STATUSCC IN ('INCOB', 'CIERR', 'CEROS') ;
END ReportaCC;
Why cant display the results?
(The query works fine if i execute it directly in a TOAD SQL editor)
Thanks again....!!!
After you hit F9 the "Variables" dialog appears and you select Type=Cursor from the dropdown list then press OK:
The reason you are getting the "ORA-24338: statement handle not executed" error is because you are closing your cursor before it is accessed.
This is the process that is happening:
Execute procedure
OPEN statement returns a pointer to the result set in memory (but does not return any data)
CLOSE statement discards the results before they are accessed
Procedure call ends
The client caller (in this case TOAD) attempts to access the result stream, but the pointer is invalid, so nothing can be read and the error is thrown
Solution: Remove the close lcursor; statement.
As your procedure is doing only a select statement better use a function like
CREATE or REPLACE function ReportaCC(pfcorte IN DATE)
RETURN SYS_REFCURSOR
AS
lcursor SYS_REFCURSOR;
BEGIN
OPEN lcursor FOR
select c1, c3, c3 from table1 where hdate = pfcorte;
RETURN lcursor ;
END;
/
Do not close lcursor here, close from your calling statement because if you close lcursor then you wouldn't be able to see any results.
And execute as
select ReportaCC(<>) from dual
from toad, double click cursor in datagrid to see the results.

ORA-38101: Invalid column in the INSERT VALUES Clause:

So i'm in the middle of testing the accuracy of my normalized data by creating a tool to de-normalize the data for comparison. While doing this i was looking into learning new techniques for this tool over what i normally would do(which was using cursors and looping through an insert/update) So i came across two items i wanted to try which were bulk collections and the merge statement. My problem is i'm having some trouble finding the best way to utilize the bulk collection.
EDIT:
Okay so i found my problem/solution when it came to the bulk collection. It was in fact the way i was fetching it. Instead of using the forall statement i changed it to a for and added a loop underneath it. Which lead to the discoveries of more bugs. The way i was trying to call the values stored in the indx was being done wrong so i've rectified that. Now the only problem i seem to be having is with the error noted in the title. In my merge for some reason the first value i try to use in the insert throws the following error:
PL/SQL: ORA-38101: Invalid column in the INSERT VALUES Clause: "TMI"."MACHINE_INTERVAL_ID"
ORA-06550: line 92, column 7:
So what i would like to know now is why exactly i'm getting this error. I understand the concept that my insert value is invalid. but i do not fully understand why this is so.
This is the merge statement in question:
MERGE INTO TEST_MACHINE_INTERVAL TMI
USING (SELECT * FROM TEST_MACHINE_INTERVAL) OTMI
ON (TMI.MACHINE_INTERVAL_ID = OTMI.MACHINE_INTERVAL_ID)
WHEN MATCHED THEN
UPDATE SET TMI.MACHINE_INTERVAL_ID = OTMI.MACHINE_INTERVAL_ID, TMI.START_DATE_TIME = OTMI.START_DATE_TIME,
TMI.INTERVAL_DURATION = OTMI.INTERVAL_DURATION, TMI.CALC_END_TIME = OTMI.CALC_END_TIME,
TMI.MACHINE_NAME = OTMI.MACHINE_NAME, TMI.SITE_NAME = OTMI.SITE_NAME,
TMI.OPERATOR_INSTANCE = OTMI.OPERATOR_INSTANCE, TMI.OPERATOR_INSTANCE2 = OTMI.OPERATOR_INSTANCE2,
TMI.OPERATOR_INSTANCE3 = OTMI.OPERATOR_INSTANCE3, TMI.SHIFT_NAME = OTMI.SHIFT_NAME,
TMI.INTERVAL_CATEGORY = OTMI.INTERVAL_CATEGORY, TMI.NTP_CATEGORY_NAME = OTMI.NTP_CATEGORY_NAME,
TMI.MACHINE_MODE = OTMI.MACHINE_MODE, TMI.JOB_LOAD_STATE_NAME = OTMI.JOB_LOAD_STATE_NAME,
TMI.RAW_SOURCE_MSG_TYPE = OTMI.RAW_SOURCE_MSG_TYPE
WHEN NOT MATCHED THEN
INSERT (TMI.MACHINE_INTERVAL_ID, TMI.START_DATE_TIME, TMI.INTERVAL_DURATION, TMI.CALC_END_TIME,
TMI.MACHINE_NAME, TMI.SITE_NAME, TMI.OPERATOR_INSTANCE, TMI.OPERATOR_INSTANCE2,
TMI.OPERATOR_INSTANCE3, TMI.SHIFT_NAME, TMI.INTERVAL_CATEGORY, TMI.NTP_CATEGORY_NAME,
TMI.MACHINE_MODE, TMI.JOB_LOAD_STATE_NAME, TMI.RAW_SOURCE_MSG_TYPE )
VALUES (MACHINE_INTERVAL_ID, START_DATE_TIME, INTERVAL_DURATION, CALC_END_TIME,
MACHINE_NAME, SITE_NAME, OPERATOR_INSTANCE, OPERATOR_INSTANCE2, OPERATOR_INSTANCE3,
SHIFT_NAME, INTERVAL_CATEGORY, NTP_CATEGORY_NAME, MACHINE_MODE,
JOB_LOAD_STATE_NAME, RAW_SOURCE_MSG_TYPE);
below is the full version of my newly modified code:
-- Denormaliztion of machine_interval Table.
-- Is used to take all intervals from interval_table and convert it from
-- foreign keys to corresponding names.
DECLARE
START_DATE_TIME TIMESTAMP(6) WITH TIME ZONE;
CALC_END_TIME TIMESTAMP(6) WITH TIME ZONE;
MACHINE_NAME VARCHAR2(256);
SITE_NAME VARCHAR2(256);
OPERATOR_INSTANCE VARCHAR2(256);
OPERATOR_INSTANCE2 VARCHAR2(256);
OPERATOR_INSTANCE3 VARCHAR2(256);
SHIFT_NAME VARCHAR2(256);
INTERVAL_CATEGORY VARCHAR2(256);
NPT_CATEGORY_NAME VARCHAR2(256);
MACHINE_MODE VARCHAR2(256);
JOB_LOAD_STATE_NAME VARCHAR2(256);
RAW_SOURCE_MSG_TYPE VARCHAR2(256);
INTERVAL_DURATION NUMBER;
MACHINE_INTERVAL_ID NUMBER;
--step one: Get all the intervals and store them into a cursor
CURSOR INTERVAL_CUR IS
SELECT *
FROM MACHINE_INTERVAL
ORDER BY START_DATE_TIME ASC;
TYPE TOTAL_MACHINE_INTERVALS IS
TABLE OF interval_cur%rowtype
INDEX BY PLS_INTEGER;
MACHINE_INTERVAL_ROW TOTAL_MACHINE_INTERVALS;
BEGIN
--step two: Make sure Test_Machine_interval is empty.
DELETE FROM TEST_MACHINE_INTERVAL;
OPEN INTERVAL_CUR;
LOOP
FETCH INTERVAL_CUR BULK COLLECT INTO MACHINE_INTERVAL_ROW LIMIT 100;
--step three: Loop through all the intervals.
FOR INDX IN 1..MACHINE_INTERVAL_ROW.COUNT
LOOP
--step four: Gather all datavalues needed to populate test_machine_interval.
MACHINE_INTERVAL_ID := MACHINE_INTERVAL_ROW(indx).MACHINE_INTERVAL_ID;
START_DATE_TIME := MACHINE_INTERVAL_ROW(indx).START_DATE_TIME;
CALC_END_TIME := MACHINE_INTERVAL_ROW(indx).CALC_END_TIME;
INTERVAL_DURATION := MACHINE_INTERVAL_ROW(indx).INTERVAL_DURATION;
INTERVAL_CATEGORY := MACHINE_INTERVAL_ROW(indx).INTERVAL_CATEGORY;
RAW_SOURCE_MSG_TYPE := MACHINE_INTERVAL_ROW(indx).RAW_SOURCE_MSG_TYPE;
SELECT M.MACHINE_NAME INTO MACHINE_NAME
FROM MACHINE M
WHERE MACHINE_ID = MACHINE_INTERVAL_ROW(indx).MACHINE_ID;
SELECT S.SITE_NAME INTO SITE_NAME
FROM SITE S
LEFT OUTER JOIN MACHINE M ON M.SITE_ID = S.SITE_ID
WHERE M.MACHINE_ID = MACHINE_INTERVAL_ROW(indx).MACHINE_ID;
SELECT O.OPERATOR_NAME INTO OPERATOR_INSTANCE
FROM OPERATOR_INSTANCE OI
LEFT OUTER JOIN OPERATOR O ON OI.OPERATOR_ID = O.OPERATOR_ID
WHERE OI.OPERATOR_INSTANCE_ID = MACHINE_INTERVAL_ROW(indx).OPERATOR_INSTANCE_ID;
SELECT O.OPERATOR_NAME INTO OPERATOR_INSTANCE2
FROM OPERATOR_INSTANCE OI
LEFT OUTER JOIN OPERATOR O ON OI.OPERATOR_ID = O.OPERATOR_ID
WHERE OI.OPERATOR_INSTANCE_ID = MACHINE_INTERVAL_ROW(indx).OPERATOR_INSTANCE_ID_2;
SELECT O.OPERATOR_NAME INTO OPERATOR_INSTANCE3
FROM OPERATOR_INSTANCE OI
LEFT OUTER JOIN OPERATOR O ON OI.OPERATOR_ID = O.OPERATOR_ID
WHERE OI.OPERATOR_INSTANCE_ID = MACHINE_INTERVAL_ROW(indx).OPERATOR_INSTANCE_ID_3;
SELECT NPT_CATEGORY_NAME INTO NPT_CATEGORY_NAME
FROM NPT_CATEGORY
WHERE NPT_CATEGORY_ID = MACHINE_INTERVAL_ROW(indx).NPT_CATEGORY_ID;
SELECT S.SHIFT_NAME INTO SHIFT_NAME
FROM SHIFTS S
LEFT OUTER JOIN SHIFT_TBL STBL ON S.SHIFT_ID = STBL.SHIFT_NAME_FK
WHERE STBL.SHIFT_ID_PK = MACHINE_INTERVAL_ROW(indx).SHIFT_ID;
SELECT MACHINE_MODE_NAME INTO MACHINE_MODE
FROM MACHINE_MODE MM
WHERE MM.MACHINE_MODE_ID = MACHINE_INTERVAL_ROW(indx).MACHINE_MODE_ID;
SELECT JLS.JOB_LOAD_STATE_NAME INTO JOB_LOAD_STATE_NAME
FROM JOB_LOAD_STATE JLS
WHERE JLS.JOB_LOAD_STATE_ID = MACHINE_INTERVAL_ROW(indx).JOB_LOAD_STATE_ID;
--step five: merge record into test_machine_interval.
MERGE INTO TEST_MACHINE_INTERVAL TMI
USING (SELECT * FROM TEST_MACHINE_INTERVAL) OTMI
ON (TMI.MACHINE_INTERVAL_ID = OTMI.MACHINE_INTERVAL_ID)
WHEN MATCHED THEN
UPDATE SET TMI.MACHINE_INTERVAL_ID = OTMI.MACHINE_INTERVAL_ID, TMI.START_DATE_TIME = OTMI.START_DATE_TIME,
TMI.INTERVAL_DURATION = OTMI.INTERVAL_DURATION, TMI.CALC_END_TIME = OTMI.CALC_END_TIME,
TMI.MACHINE_NAME = OTMI.MACHINE_NAME, TMI.SITE_NAME = OTMI.SITE_NAME,
TMI.OPERATOR_INSTANCE = OTMI.OPERATOR_INSTANCE, TMI.OPERATOR_INSTANCE2 = OTMI.OPERATOR_INSTANCE2,
TMI.OPERATOR_INSTANCE3 = OTMI.OPERATOR_INSTANCE3, TMI.SHIFT_NAME = OTMI.SHIFT_NAME,
TMI.INTERVAL_CATEGORY = OTMI.INTERVAL_CATEGORY, TMI.NTP_CATEGORY_NAME = OTMI.NTP_CATEGORY_NAME,
TMI.MACHINE_MODE = OTMI.MACHINE_MODE, TMI.JOB_LOAD_STATE_NAME = OTMI.JOB_LOAD_STATE_NAME,
TMI.RAW_SOURCE_MSG_TYPE = OTMI.RAW_SOURCE_MSG_TYPE
WHEN NOT MATCHED THEN
INSERT (TMI.MACHINE_INTERVAL_ID, TMI.START_DATE_TIME, TMI.INTERVAL_DURATION, TMI.CALC_END_TIME,
TMI.MACHINE_NAME, TMI.SITE_NAME, TMI.OPERATOR_INSTANCE, TMI.OPERATOR_INSTANCE2,
TMI.OPERATOR_INSTANCE3, TMI.SHIFT_NAME, TMI.INTERVAL_CATEGORY, TMI.NTP_CATEGORY_NAME,
TMI.MACHINE_MODE, TMI.JOB_LOAD_STATE_NAME, TMI.RAW_SOURCE_MSG_TYPE )
VALUES (MACHINE_INTERVAL_ID, START_DATE_TIME, INTERVAL_DURATION, CALC_END_TIME,
MACHINE_NAME, SITE_NAME, OPERATOR_INSTANCE, OPERATOR_INSTANCE2, OPERATOR_INSTANCE3,
SHIFT_NAME, INTERVAL_CATEGORY, NTP_CATEGORY_NAME, MACHINE_MODE,
JOB_LOAD_STATE_NAME, RAW_SOURCE_MSG_TYPE);
/*
EXECUTE IMMEDIATE 'INSERT INTO TEST_MACHINE_INTERVAL
(MACHINE_INTERVAL_ID, START_DATE_TIME, INTERVAL_DURATION, CALC_END_TIME,
MACHINE_NAME, SITE_NAME, OPERATOR_INSTANCE, OPERATOR_INSTANCE2,
OPERATOR_INSTANCE3, SHIFT_NAME, INTERVAL_CATEGORY, NTP_CATEGORY_NAME,
MACHINE_MODE,JOB_LOAD_STATE_NAME,RAW_SOURCE_MSG_TYPE )
VALUES(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, :14, :15)'
USING MACHINE_INTERVAL_ID, START_DATE_TIME, INTERVAL_DURATION,
CALC_END_TIME, MACHINE_NAME, SITE_NAME,
OPERATOR_INSTANCE, OPERATOR_INSTANCE2, OPERATOR_INSTANCE3,
SHIFT_NAME, INTERVAL_CATEGORY, NTP_CATEGORY_NAME,
MACHINE_MODE,JOB_LOAD_STATE_NAME,RAW_SOURCE_MSG_TYPE;
*/
END LOOP;
EXIT WHEN MACHINE_INTERVAL_ROW.COUNT = 0;
END LOOP;
END;
I'm 75% sure that my problem lies in how i'm trying to fetch the bulk collection as displayed in the code above. So my question is: How exactly should i be fetching the value from a bulk collection to utilize with the merging of data?
And suggestions or comments are greatly appreciated. Thank you.
If you use FORALL, what needs to follow is a single SQL statement that you will pass the entire collection to. If you simply want to iterate over the elements in the collection, you'd use a FOR loop.
The syntax for referring to the n-th element of a collection is collection_name(index).column_name.
So, if you want to iterate over the elements in the collection one by one, you'd want something like
FOR indx IN MACHINE_INTERVAL_ROW.FIRST..MACHINE_INTERVAL_ROW.COUNT
LOOP
MACHINE_INTERVAL_ID := machine_interval_row(indx).MACHINE_INTERVAL_ID;
START_DATE_TIME := machine_interval_row(indx).START_DATE_TIME;
<<more code>>
END LOOP;
If you're going to refactor your code, though, I'm not sure what benefit you get from having a local variable MACHINE_INTERVAL_ID rather than just using machine_interval_row(indx).MACHINE_INTERVAL_ID. I'm also not sure why you're executing half a dozen separate SELECT statements each of which return a single row rather than writing one SELECT statement that joins together all these tables and populates whatever local variables you want.
Your MERGE is also going to be problematic-- it doesn't make sense for both the source and the destination of a MERGE to be the same table-- I would expect you to get an error that Oracle couldn't generate a stable set of rows if it tried to execute that statement. You could change the source of your query to be a query against DUAL that selected all the local variables you've populated, I guess, i.e.
MERGE INTO TEST_MACHINE_INTERVAL TMI
USING (SELECT machine_interval_row(indx).MACHINE_INTERVAL_ID,
machine_interval_row(indx).START_DATE_TIME
FROM dual) OTMI
ON (TMI.MACHIN_INTERVAL_ID = OTMI.MACHINE_INTERVAL_ID)
If TEST_MACHINE_INTERVAL is going to start off empty, though, it sounds like you'd be better off not using a MERGE, not using a BULK COLLECT and just writing an INSERT ... SELECT that pulled all the data you want to pull. Something like
INSERT INTO test_machine_interval( machine_interval_id,
start_date_time,
<<more columns>> )
SELECT machine_interval_id,
last_value(start_date_time) over (partition by machine_interval_id
order by start_date_time asc
rows between unbounded preceding
and unbounded following ) last_start_date_time,
<<more columns>>
FROM machine_interval

How to organize infinite while loop in SQL Server?

I want to use infinite WHILE loop in SQL Server 2005 and use BREAK keyword to exit from it on certain condition.
while true does not work, so I have to use while 1=1.
Is there a better way to organize infinite loop ?
I know that I can use goto, but while 1=1 begin ... end looks better structurally.
In addition to the WHILE 1 = 1 as the other answers suggest, I often add a "timeout" to my SQL "infintie" loops, as in the following example:
DECLARE #startTime datetime2(0) = GETDATE();
-- This will loop until BREAK is called, or until a timeout of 45 seconds.
WHILE (GETDATE() < DATEADD(SECOND, 45, #startTime))
BEGIN
-- Logic goes here: The loop can be broken with the BREAK command.
-- Throttle the loop for 2 seconds.
WAITFOR DELAY '00:00:02';
END
I found the above technique useful within a stored procedure that gets called from a long polling AJAX backend. Having the loop on the database-side frees the application from having to constantly hit the database to check for fresh data.
Using While 1 = 1 with a Break statement is the way to do it. There is no constant in T-SQL for TRUE or FALSE.
If you really have to use an infinite loop than using while 1=1 is the way I'd do it.
The question here is, isn't there some other way to avoid an infinite loop? These things just tend to go wrong ;)
you could use the snippet below to kick a sp after soem condition are rised. I assume that you ahev some sort of CurrentJobStatus table where all the jobs/sp keeps their status...
-- *** reload data on N Support.usp_OverrideMode with checks on Status
/* run
Support.usp_OverrideMode.Number1.sql
and
Support.usp_OverrideMode.Number2.sql
*/
DECLARE #FileNameSet TABLE (FileName VARCHAR(255));
INSERT INTO #FileNameSet
VALUES ('%SomeID1%');
INSERT INTO #FileNameSet
VALUES ('%SomeID2%');
DECLARE #BatchRunID INT;
DECLARE #CounterSuccess INT = 0;
DECLARE #CounterError INT = 0;
-- Loop
WHILE WHILE (#CounterError = 0 AND #CounterSuccess < (select COUNT(1) c from #FileNameSet) )
BEGIN
DECLARE #CurrenstStatus VARCHAR(255)
SELECT #CurrenstStatus = CAST(GETDATE() AS VARCHAR)
-- Logic goes here: The loop can be broken with the BREAK command.
SELECT #CounterSuccess = COUNT(1)
FROM dbo.CurrentJobStatus t
INNER JOIN #FileNameSet fns
ON (t.FileName LIKE fns.FileName)
WHERE LoadStatus = 'Completed Successfully'
SELECT #CounterError = COUNT(1)
FROM dbo.CurrentJobStatus t
INNER JOIN #FileNameSet fns
ON (t.FileName LIKE fns.FileName)
WHERE LoadStatus = 'Completed with Error(s)'
-- Throttle the loop for 3 seconds.
WAITFOR DELAY '00:00:03';
select #CurrenstStatus = #CurrenstStatus +char(9)+ '#CounterSuccess ' + CAST(#CounterSuccess AS VARCHAR(11))
+ char(9)+ 'CounterError ' + CAST(#CounterError AS VARCHAR(11))
RAISERROR (
'Looping... # %s'
,0
,1
,#CurrenstStatus
)
WITH NOWAIT;
END
-- TODO add some codition on #CounterError value
/* run
Support.usp_OverrideMode.WhenAllSuceed.sql
*/
Note the code is flexibile you can add as many condition checks on the #FileNameSet table var
Mario