PLSQL : Dynamic table record holder - dynamic

If I want to fetch records from a table (table name is dynamic from input), how to define the record holder or how to fetch the data from this defined table? p_table_name%rowtype will not complie because p_table_name is a parameter, not a table name.
PROCEDURE do_scan(p_table_name IN VARCHAR2
,p_min_num IN NUMBER
,p_time_range IN NUMBER
,p_problem_desc OUT
,p_result_code OUT)
IS
TYPE ObjCurTyp IS REF CURSOR;
v_obj_cursor ObjCurTyp;
v_obj_record ???????(p_table_name%rowtype)
BEGIN
v_stmt_str := 'Select * from :t where date_started > TRUNC(SYSDATE-3)';
OPEN v_obj_cursor FOR v_stmt_str USING p_table_name;
LOOP
FETCH v_obj_cursor INTO v_obj_record;
EXIT WHEN v_obj_cursor %NOTFOUND;
END LOOP;
END do_scan;

You can put that code as a dynamic PL/SQL block within an EXECUTE IMMEDIATE statement.
PROCEDURE do_scan
(
p_table_name IN VARCHAR2
p_min_num IN NUMBER
p_time_range IN NUMBER
p_problem_desc OUT
p_result_code OUT
)
IS
BEGIN
EXECUTE IMMEDIATE
'DECLARE ' ||
' TYPE ObjCurTyp IS REF CURSOR; ' ||
' v_obj_cursor ObjCurTyp; ' ||
' v_obj_record ' || p_table_name || '%rowtype; '||
'BEGIN ' ||
' v_stmt_str := ''Select * from :t where ' ||
' date_started > TRUNC(SYSDATE-3)''; ' ||
' OPEN v_obj_cursor ' ||
' FOR v_stmt_str USING ' ||
p_table_name || '; ' ||
' LOOP ' ||
' FETCH v_obj_cursor INTO v_obj_record; ' ||
' EXIT WHEN v_obj_cursor %NOTFOUND; ' ||
' END LOOP; ' ||
'END;';
END do_scan;
Regards,
Dariyoosh

Related

Print a json structure from a dynamic query

I want to create function that return a CLOB with a json content.
But first i want to print the result (see query code) to see how it looks like.
The result must be a json, that will contain the information for 1 record just, this record select query, must be created dynamically.
So I try the next query, to resolve this problem. but i get an error.
Query
declare
v_stmt VARCHAR2(1000);
l_sep varchar2(1) := chr(10);
l_tab varchar2(1) := chr(9);
up_column VARCHAR2(50);
low_column VARCHAR2(50);
cur_asc SYS_REFCURSOR;
Vcdogcur SYS_REFCURSOR;
id_ccd number;
first boolean := true;
begin
id_ccd := 174;
v_stmt := 'select ';
open cur_asc for
SELECT column_name as up_column
,lower(column_name) as low_column
FROM USER_TAB_COLUMNS
WHERE table_name = upper('my_table');
loop
FETCH cur_asc
INTO up_column, low_column;
EXIT WHEN cur_asc%NOTFOUND;
if not first then
v_stmt := v_stmt || ',';
end if;
first := false;
v_stmt := v_stmt || l_sep || l_tab ||up_column || ' as ' || low_column;
END LOOP;
--v_stmt := v_stmt || l_sep || 'from my_table' || l_sep ||'where id = ' || id_ccd || ';';
v_stmt := v_stmt || l_sep || 'from my_table' || l_sep ||'where id = ' || id_ccd;
DBMS_OUTPUT.PUT_LINE('salida: ' || v_stmt);
CLOSE cur_asc;
--v_stmt := v_stmt || l_sep || 'from my_table' || l_sep ||'where id = ' || id_ccd;
--DBMS_OUTPUT.PUT_LINE('salida: ' || v_stmt);
open Vcdogcur for v_stmt;
APEX_JSON.initialize_clob_output;
APEX_JSON.open_object;
APEX_JSON.write('my_table', Vcdogcur);
APEX_JSON.close_object;
DBMS_OUTPUT.put_line(APEX_JSON.get_clob_output);
CLOSE Vcdogcur;
end;
/
DBMS output
salida: select
CREATED as created,
CREATED_BY as created_by,
UPDATED as updated,
UPDATED_BY as updated_by,
ID as id,
TEXT as text,
PART as part,
CODE as code,
DATE as date,
PERCENTAGE as percentage
from my_table
where id = 174
{
"my_table":[
{
"CREATED":"2020-06-04T08:10:09.000000000Z"
,"CREATED_BY":"APEX_APP"
,"UPDATED":"2020-06-04T10:10:09.295632000Z"
,"UPDATED_BY":"APEX_APP"
,"ID":174
,"TEXT":"ALL"
,"PART":2770
,"CODE":2212
,"DATE":"2017-01-01T00:00:00.000000000Z"
,"PERCENTAGE":-4.98
}
]
}
Error
Informe de error -
ORA-01001: cursor no válido
ORA-06512: en línea 44
01001. 00000 - "invalid cursor"
*Cause:
*Action:
Can somebody help me to resolve this?
Best Regards

error when executing a varchar2 in a procedure

I have a varchar2 with an INSERT and I want to execute it in a procedure I try to do it with an execute but this happens:
EXECUTE IMMEDIATE sql_str;
Error:
ERROR at line 1:
ORA-00911: invalid character
ORA-06512: at "SYS.INSERT_MOVIMIENTOS", line 47
ORA-06512: at line 1
Varchar2 carries an insert that is this and works if I paste it but when executing it in the procedure something of the procedure fails.
INSERT INTO MOVIMIENTOS (COD_BANCO, COD_SUCUR, NUM_CTA, FECHA_MOV, TIPO_MOV, IMPORTE) VALUES (2000, 2000, 0, '11/11/08', 'I', 500);
My procedure
CREATE OR REPLACE PROCEDURE INSERT_MOVIMIENTOS (
INSERTMOV_COD_BANCO IN NUMBER,
INSERTMOV_COD_SUCUR IN NUMBER,
INSERTMOV_NUM_CTA IN NUMBER,
INSERTMOV_FECHA_MOV IN DATE,
INSERTMOV_TIPO_MOV IN CHAR,
INSERTMOV_IMPORTE IN NUMBER
)
IS
sql_str VARCHAR2(500) := 'INSERT INTO MOVIMIENTOS (';
movimiento movimientos_typ;
BEGIN
movimiento := movimientos_typ(
INSERTMOV_COD_BANCO,
INSERTMOV_COD_SUCUR,
INSERTMOV_NUM_CTA,
INSERTMOV_FECHA_MOV,
INSERTMOV_TIPO_MOV,
INSERTMOV_IMPORTE
);
IF movimiento.getCOD_BANCO() != 0 THEN
sql_str := sql_str || 'COD_BANCO, COD_SUCUR, NUM_CTA, FECHA_MOV, TIPO_MOV, IMPORTE) VALUES (' ||
movimiento.getCOD_BANCO() || ', ' ||
movimiento.getCOD_SUCUR() || ', ' ||
movimiento.getNUM_CTA() || ', ''' ||
movimiento.getFECHA_MOV() || ''', ''' ||
movimiento.getTIPO_MOV() || ''', ' ||
movimiento.getIMPORTE() || ');';
ELSE
sql_str := sql_str || 'COD_SUCUR, NUM_CTA, FECHA_MOV, TIPO_MOV, IMPORTE) VALUES (' ||
movimiento.getCOD_SUCUR() || ', ' ||
movimiento.getNUM_CTA() || ', ''' ||
movimiento.getFECHA_MOV() || ''', ''' ||
movimiento.getTIPO_MOV() || ''', ' ||
movimiento.getIMPORTE() || ');';
END IF;
DBMS_OUTPUT.PUT_LINE('////////////////////////////////////////');
DBMS_OUTPUT.PUT_LINE('CONSULTA: ' || sql_str);
DBMS_OUTPUT.PUT_LINE('////////////////////////////////////////');
DBMS_OUTPUT.PUT_LINE('DATOS INTRODUCIDOS: ');
movimiento.display;
DBMS_OUTPUT.PUT_LINE('////////////////////////////////////////');
EXECUTE IMMEDIATE sql_str;
DBMS_OUTPUT.PUT_LINE('FUNCION REALIZADA CON EXITO');
END;
/
Donot end with the semicolon ; in your query string.
movimiento.getIMPORTE() || ')';
The error is just because of it.
by the way, you should be using bind variables. Dynamically constructing the values this way is vulnerable to SQL Injection.

continuing loops a thing in oracle

I am carrying out another exercise to learn various things about Oracle SQL's syntax today's lesson I've chosen are loops. Please see below code where I am trying to have two fields (temp variables) having values being decreased until it's less than zero depending on which of the variables hit's zero depends on what text I would like to output into the script output. Hopefully it makes sense. If I have made any faux pas's please do shout at me.
DECLARE
"Character" VARCHAR2(255);
"Enemy" VARCHAR2(255);
"Character_Health" NUMBER;
"Enemy_Health" NUMBER;
"Character_Attack" NUMBER;
"Enemy_Attack" NUMBER;
BEGIN -- Generates Base Stats
SELECT 'Hero' INTO "Character" FROM dual;
SELECT 'Villain' INTO "Enemy" FROM dual;
SELECT 100 INTO "Character_Health" FROM dual;
SELECT 25 INTO "Enemy_Health" FROM dual;
SELECT 10 INTO "Character_Attack" FROM dual;
SELECT 5 INTO "Enemy_Attack" FROM dual;
dbms_output.put_line ("Character" || ' (' || "Character_Health" || ') VS ' ||' '|| "Enemy" || ' (' || "Enemy_Health" || ')') ;
dbms_output.put_line ('');
dbms_output.put_line ('FIGHT!');
BEGIN -- Round 1
LOOP
dbms_output.put_line ('');
SELECT "Enemy_Health" - "Character_Attack" INTO "Enemy_Health" FROM dual; -- Hero hits Villain
dbms_output.put_line ("Character" || ' Hits ' || "Enemy" || ' for ' || "Character_Attack" ||' Damage' );
dbms_output.put_line ("Enemy" || ' Has ' || "Enemy_Health" || ' Health Remaining ');
CASE WHEN "Enemy_Health" < 0 THEN dbms_output.put_line ("Enemy" || 'Has Fainted' || "Character" || 'Wins!');
ELSE CONTINUE;
EXIT WHEN "Enemy_Health" < 0;
SELECT "Character_Health" - "Enemy_Attack" INTO "Character_Health" FROM dual; -- Villain hits Hero
dbms_output.put_line ('');
dbms_output.put_line ("Enemy" || ' Hits ' || "Character" || ' for ' || "Enemy_Attack" ||' Damage' );
dbms_output.put_line ("Character" || ' Has ' || "Character_Health" || ' Health Remaining ');
CASE WHEN "Character_Health" < 0 THEN dbms_output.put_line ("Character" || 'Has Fainted' || "Enemy" || 'Wins!');
ELSE CONTINUE;
EXIT WHEN "Character_Health" < 0;
END LOOP;
END;
END;
I found some errors
1. Syntax for case is:
CASE [ expression ]
WHEN condition_1 THEN result_1
WHEN condition_2 THEN result_2
...
WHEN condition_n THEN result_n
[ELSE result]
END CASE
Learn more:
- simple case statement
- searched case statement
CONTINUE stops processing loop and starts it from the begining, so your logic is corrupt - only Hero is attacking.
You need space after "Fainted"
It's better to use IF in this case:
IF "Character_Health" < 0
THEN
dbms_output.put_line ("Character" || 'Has Fainted. ' || "Enemy" || 'Wins!');
EXIT;
END IF;
It's not mistake, but you don't need select into from dual if you want to change value of variable.
Last but not least- i don't like how you use variables. "Character_Health" is similar to 'Character_Health' or '''Character_Health'''. Better use names like characterHealth or character_health.
My version would be something like that:
DECLARE
v_Character VARCHAR2(255) := 'Hero' ;
v_Enemy VARCHAR2(255) := 'Villain';
v_Character_Health NUMBER := 100;
v_Enemy_Health NUMBER := 25;
v_Character_Attack NUMBER := 10;
v_Enemy_Attack NUMBER := 5;
v_nl VARCHAR2(1) := chr(10);
BEGIN
dbms_output.put_line (v_Character || ' (' || v_Character_Health || ') VS ' ||' '|| v_Enemy || ' (' || v_Enemy_Health || ')' || v_nl) ;
dbms_output.put_line ('FIGHT!' || v_nl);
LOOP
v_Enemy_Health := v_Enemy_Health - v_Character_Attack;
dbms_output.put_line (v_Character || ' Hits ' || v_Enemy || ' for ' || v_Character_Attack ||' Damage' );
dbms_output.put_line (v_Enemy || ' Has ' || v_Enemy_Health || ' Health Remaining' || v_nl);
IF v_Enemy_Health < 0 THEN dbms_output.put_line (v_Enemy || 'Has Fainted. ' || v_Character || 'Wins!'); EXIT;
END IF;
v_Character_Health := v_Character_Health - v_Enemy_Attack;
dbms_output.put_line (v_Enemy || ' Hits ' || v_Character || ' for ' || v_Enemy_Attack || ' Damage' );
dbms_output.put_line (v_Character || ' Has ' || v_Character_Health || ' Health Remaining' || v_nl);
IF v_Character_Health < 0 THEN dbms_output.put_line (v_Character || 'Has Fainted. ' || v_Enemy || 'Wins!'); EXIT;
END IF;
END LOOP;
END;
/

PL/SQL - Create tables based on cursor using execute immediate?

I've written the following code that selects some student test data and using a cursor, inserts it into a table.
What id like to be able to do is create one table for each student and insert their relative data. This could be one row or multiple rows.
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE run_student_scores
IS
CURSOR c_pass_fail_cursor
IS
SELECT students.firstname,
test_history.score,
test_id.test_name,
test_id.passing_grade
FROM students
INNER JOIN test_history
ON students.student_id = test_history.student_id
INNER JOIN test_id
ON test_id.test_id = test_history.test_id
WHERE test_history.start_time BETWEEN to_timestamp(sysdate) + INTERVAL '8' HOUR
AND to_timestamp(sysdate) + INTERVAL '21' HOUR;
v_name students.firstname%TYPE;
v_score test_history.score%TYPE;
v_test varchar2(40);
v_passing test_id.passing_grade%TYPE;
v_result varchar2(4);
BEGIN
EXECUTE IMMEDIATE ('create table student_tests_' || (to_char(sysdate, 'yyyymmdd')) || '(student_name VARCHAR2(20), test_name varchar2(40), test_score NUMBER(3), pass_rate NUMBER(3), pass_fail VARCHAR2(4))');
OPEN c_pass_fail_cursor;
LOOP
FETCH c_pass_fail_cursor INTO v_name, v_score, v_test, v_passing;
EXIT WHEN c_pass_fail_cursor%NOTFOUND;
If v_score < v_passing
THEN v_result := 'Fail';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
ELSE
v_result := 'Pass';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
END IF;
EXECUTE IMMEDIATE 'INSERT INTO student_tests_' || (to_char(sysdate, 'yyyymmdd')) || ' ' || ' values(:1, :2, :3, :4, :5)' using v_name, v_test, v_score, v_passing, v_result;
END LOOP;
CLOSE c_pass_fail_cursor;
END;
/
I've played around with it for the last couple of days and cant get it to work. The closest i can get is to create the tables and insert the first row only, generating an error when the loop tries to create a table that already exists.
Any help would be awesome
Thanks folks
Ben
Outcome: 1 table for each student (unique by name).
The create table ... is now inside the loop surrounded by an exception block. If the table already exists, the exception (ORA-00955) is handled.
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE run_student_scores
IS
CURSOR c_pass_fail_cursor
IS
SELECT students.firstname,
test_history.score,
test_id.test_name,
test_id.passing_grade
FROM students
INNER JOIN test_history
ON students.student_id = test_history.student_id
INNER JOIN test_id
ON test_id.test_id = test_history.test_id
WHERE test_history.start_time BETWEEN to_timestamp(sysdate) + INTERVAL '8' HOUR
AND to_timestamp(sysdate) + INTERVAL '21' HOUR;
v_name students.firstname%TYPE;
v_score test_history.score%TYPE;
v_test varchar2(40);
v_passing test_id.passing_grade%TYPE;
v_result varchar2(4);
--New variables
table_already_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(table_already_exists, -955);
BEGIN
OPEN c_pass_fail_cursor;
LOOP
FETCH c_pass_fail_cursor INTO v_name, v_score, v_test, v_passing;
EXIT WHEN c_pass_fail_cursor%NOTFOUND;
begin
EXECUTE IMMEDIATE ('create table student_tests_' || v_name || ' (student_name VARCHAR2(20), test_name varchar2(40), test_score NUMBER(3), pass_rate NUMBER(3), pass_fail VARCHAR2(4))');
exception when table_already_exists then
null;
end;
If v_score < v_passing
THEN v_result := 'Fail';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
ELSE
v_result := 'Pass';
DBMS_OUTPUT.PUT_LINE(v_name || ' ' || v_score || ' ' || v_test || ' ' || V_passing || ' ' || 'Result =' || v_result);
END IF;
EXECUTE IMMEDIATE 'INSERT INTO student_tests_' || v_name || ' ' || ' values(:1, :2, :3, :4, :5)' using v_name, v_test, v_score, v_passing, v_result;
END LOOP;
CLOSE c_pass_fail_cursor;
END;
/
Grafros answer looks good, but i strongly recommend to take a look at DBMS_ASSERT with DBMS_ASSERT.ENQUOTE_NAME or DBMS_ASSERT.ENQUOTE_LITERAL whenever you have take abitrary strings and use them in execute immidiate.

SQL Server 2008 - Implementing DECODE

First, I know there are plenty of posts etc relating to the use of CASE in place of DECODE, but they don't appear to fit with my concern.
I am trying to convert an Oracle PL/SQL procedure to SQL Server. The procedure builds a SQL statement dynamically, and uses the DECODE function to create a x-tab. The procedure is as so:
PROCEDURE GET_XFORM_DATALOGS (
fLOTCODE IN VARCHAR2,
THEDATA OUT SYS_REFCURSOR) IS
-- VARIABLE DECLARATIONS
TYPE loc_array_type IS TABLE OF VARCHAR2(40); -- array type
loc_array loc_array_type; -- array for test names
prod VARCHAR2(20); -- product ID
step VARCHAR2(20); -- step ID
sql_str VARCHAR2(32000); -- SQL statement
-- EXECUTABLE CODE
BEGIN -- executable part starts here
-- get the test names for the given lot code
SELECT
PT_TESTNAME BULK COLLECT INTO loc_array
FROM
(
SELECT DISTINCT
TESTPARMS.PT_TESTNAME,
TESTPARMS.PT_TESTNUM
FROM
"PRETEST".PRETEST_LOT#PRS_DBLINK LOT,
"PRETEST".PRETEST_MEASURE#PRS_DBLINK MEASURE,
"PRETEST".PRETEST_TEST_PARMS#PRS_DBLINK TESTPARMS
WHERE
LOT.PT_LOTSQ = MEASURE.PT_LOTSQ AND
MEASURE.PT_LOTSQ = TESTPARMS.PT_LOTSQ AND
MEASURE.PT_TESTNUM = TESTPARMS.PT_TESTNUM AND
LOT.PT_LOTID = fLOTCODE
ORDER BY
PT_TESTNUM
);
-- build the SQL string
sql_str := '';
sql_str := sql_str || 'SELECT ';
sql_str := sql_str || ' PRETEST_LOT.PT_LOTID, ';
sql_str := sql_str || ' PRETEST_LOT.PT_LOCTYPE, ' ;
sql_str := sql_str || ' PRETEST_LOT.PT_TESTDATE, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_WAFERID, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_XCOORD, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_YCOORD, ';
-- add the decodes for column headings
FOR i IN loc_array.first..loc_array.last LOOP
sql_str := sql_str || 'max ( decode ( PRETEST_TEST_PARMS.PT_TESTNAME, '''
|| loc_array(i) || ''', PRETEST_MEASURE.PT_MEAS_VALUE, null ) ) '
|| loc_array(i);
IF (i < loc_array.last) THEN
sql_str := sql_str || ', ';
END IF;
END LOOP;
-- build the remainder of the SQL
sql_str := sql_str || ' FROM ';
sql_str := sql_str || ' "PRETEST".PRETEST_LOT#PRS_DBLINK PRETEST_LOT, ';
sql_str := sql_str || ' "PRETEST".PRETEST_MEASURE#PRS_DBLINK PRETEST_MEASURE, ';
sql_str := sql_str || ' "PRETEST".PRETEST_TEST_PARMS#PRS_DBLINK PRETEST_TEST_PARMS ';
sql_str := sql_str || ' WHERE ';
sql_str := sql_str || ' PRETEST_LOT.PT_LOTSQ = PRETEST_MEASURE.PT_LOTSQ AND ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_LOTSQ = PRETEST_TEST_PARMS.PT_LOTSQ AND ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_TESTNUM = PRETEST_TEST_PARMS.PT_TESTNUM AND ';
sql_str := sql_str || ' (PRETEST_LOT.PT_LOTID = :fFLOTCODE) ';
sql_str := sql_str || ' GROUP BY ';
sql_str := sql_str || ' PRETEST_LOT.PT_LOTID, ';
sql_str := sql_str || ' PRETEST_LOT.PT_LOCTYPE, ';
sql_str := sql_str || ' PRETEST_LOT.PT_TESTDATE, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_WAFERID, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_XCOORD, ';
sql_str := sql_str || ' PRETEST_MEASURE.PT_YCOORD ';
sql_str := sql_str || ' ORDER BY ';
sql_str := sql_str || ' PRETEST_LOT.PT_TESTDATE ';
-- run the query
OPEN THEDATA FOR sql_str USING fLOTCODE;
END GET_XFORM_DATALOGS;
The question I have is, is this going to be possible to implement in SQL Server? I can't seem to work out how to use CASE WHEN THEN etc to create column headings based on the dynamic data.
Sorry if what I've asked is unclear. Thanks for any help you can offer.
BBz
Well the Oracle equivalent using CASE would be:
sql_str := sql_str || 'max ( CASE WHEN PRETEST_TEST_PARMS.PT_TESTNAME = '''
|| loc_array(i) || ''' THEN PRETEST_MEASURE.PT_MEAS_VALUE END ) '
|| loc_array(i);
So try converting that to SQL Server instead.