Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3c9f270

Browse files
authored
Merge pull request #74 from stevenfeuerstein/master
dynamic SQL files
2 parents 2291771 + 6800b3c commit 3c9f270

10 files changed

+1498
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
When you are writing a program with dynamic SQL in it (that is, you construct
3+
your statement at runtime and execute it with EXECUTE IMMEDIATE or DBMS_SQL -
4+
most likely and preferably the former), you should make sure to bind all variable
5+
values into that statement, and not concatenate.
6+
*/
7+
8+
-- Update Any Column In Table - Concatenation
9+
/*
10+
Here's a great example of a too-generic subprogram: update any numeric
11+
(well, I suppose it would work for strings, too) column in the employees
12+
table whose hire date falls within the specified range. I use concatenation
13+
to put it all together. That leads to lots of typing, lots of code to look out
14+
and lots of single quotes. It is unlikely that a program as generic as this will ever truly be needed!
15+
*/
16+
CREATE OR REPLACE PROCEDURE updnumval (
17+
col_in IN VARCHAR2
18+
, start_in IN DATE
19+
, end_in IN DATE
20+
, val_in IN NUMBER
21+
)
22+
IS
23+
c_format CONSTANT VARCHAR2 ( 100 ) := 'YYYYMMDDHH24MISS';
24+
BEGIN
25+
EXECUTE IMMEDIATE 'UPDATE employees SET '
26+
|| col_in
27+
|| ' = '
28+
|| val_in
29+
|| ' WHERE hire_date BETWEEN TO_DATE ('''
30+
|| TO_CHAR ( start_in, c_format )
31+
|| ''', '''
32+
|| c_format
33+
|| ''') AND TO_DATE ('''
34+
|| TO_CHAR ( end_in, c_format )
35+
|| ''', '''
36+
|| c_format
37+
|| ''')';
38+
END;
39+
/
40+
41+
BEGIN
42+
updnumval ('salary',
43+
DATE '2002年01月01日',
44+
DATE '2002年12月31日',
45+
20000);
46+
END;
47+
/
48+
49+
SELECT *
50+
FROM employees
51+
WHERE salary = 20000 ;
52+
53+
-- Well You Don't Need All Those Single Quotes
54+
/*
55+
A rewrite of the original, still full of concatenation, but now using the Q literal
56+
terminator feature to specify another character as the terminator of the literal,
57+
so I can avoid having code like ''','''. I'm not convinced this is all that much better -
58+
the benefit is more obvious when you are a large dynamic SQL statement as a single
59+
string - full of doubled-up quotes.
60+
*/
61+
CREATE OR REPLACE PROCEDURE updnumval (
62+
col_in IN VARCHAR2
63+
, start_in IN DATE
64+
, end_in IN DATE
65+
, val_in IN NUMBER
66+
)
67+
IS
68+
c_format CONSTANT VARCHAR2 ( 100 ) := 'YYYYMMDDHH24MISS';
69+
BEGIN
70+
EXECUTE IMMEDIATE 'UPDATE employees SET '
71+
|| col_in
72+
|| ' = '
73+
|| val_in
74+
|| q'[ WHERE hire_date BETWEEN TO_DATE (']'
75+
|| TO_CHAR ( start_in, c_format )
76+
|| q'[', ']'
77+
|| c_format
78+
|| q'[') AND TO_DATE (']'
79+
|| TO_CHAR ( end_in, c_format )
80+
|| q'[', ']'
81+
|| c_format
82+
|| q'[')]';
83+
END;
84+
/
85+
86+
BEGIN
87+
updnumval ('salary',
88+
DATE '2002年01月01日',
89+
DATE '2002年12月31日',
90+
30000);
91+
END;
92+
/
93+
94+
SELECT *
95+
FROM employees
96+
WHERE salary = 30000 ;
97+
98+
-- Switch to Binding with USING Clause
99+
/*
100+
Now I rewrite the procedure to bind everything I can possibly bind:
101+
the column value, low and high dates. My code is much simpler, easier to read,
102+
and is performance-optimized (more likely to avoid unnecessary parsing).
103+
Note that I cannot bind in the column NAME. That information is needed in
104+
order to parse the SQL statement (which comes before binding).
105+
*/
106+
CREATE OR REPLACE PROCEDURE updnumval (col_in IN VARCHAR2,
107+
start_in IN DATE,
108+
end_in IN DATE,
109+
val_in IN NUMBER)
110+
IS
111+
BEGIN
112+
EXECUTE IMMEDIATE
113+
'UPDATE employees SET '
114+
|| col_in
115+
|| ' = :val
116+
WHERE hire_date BETWEEN :lodate AND :hidate'
117+
USING val_in, start_in, end_in;
118+
END;
119+
/
120+
121+
BEGIN
122+
updnumval ('salary',
123+
DATE '2002年01月01日',
124+
DATE '2002年12月31日',
125+
40000);
126+
END;
127+
/
128+
129+
SELECT *
130+
FROM employees
131+
WHERE salary = 40000 ;
132+

‎plsql/dynamic-sql/dbms-assert.sql‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Use DBMS_ASSERT to help guard against SQL injection.
3+
4+
The DBMS_ASSERT package was introduced in Oracle 10g Release 2. It contains functions
5+
that help developers sanitize user input and reduce the likelihood of SQL injection
6+
in applications that concatenate text (they do NOT use bind variables).
7+
*/
8+
9+
BEGIN
10+
sys.DBMS_OUTPUT.put_line (DBMS_ASSERT.schema_name ('HR'));
11+
END;
12+
/
13+
14+
BEGIN
15+
sys.DBMS_OUTPUT.put_line (DBMS_ASSERT.sql_object_name ('EMPLOYEES'));
16+
END;
17+
/
18+
19+
BEGIN
20+
sys.DBMS_OUTPUT.put_line (DBMS_ASSERT.qualified_sql_name ('HR.EMPLOYEES'));
21+
END;
22+
/
23+
24+
BEGIN
25+
sys.DBMS_OUTPUT.put_line (DBMS_ASSERT.schema_name ('HR'));
26+
END;
27+
/
28+
29+
BEGIN
30+
sys.DBMS_OUTPUT.put_line (DBMS_ASSERT.schema_name ('WHO_ME'));
31+
END;
32+
/
33+
34+
BEGIN
35+
DBMS_OUTPUT.put_line (
36+
DBMS_ASSERT.sql_object_name (
37+
'EMPLOYEES, (SELECT * FROM ALL_USERS) u'));
38+
END;
39+
/
40+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
Method 3 Dynamic SQL: a SELECT statement whose select list (number of elements
3+
returned by the query) and bind variables are fixed at compile-time. For more
4+
information on dynamic SQL in PL/SQL:
5+
6+
https://stevenfeuersteinonplsql.blogspot.com/2016/07/a-quick-guide-to-writing-dynamic-sql-in.html
7+
*/
8+
9+
CREATE TABLE employees
10+
AS
11+
SELECT * FROM hr.employees ;
12+
13+
-- Concatenate Table Name, Bind One Variable, Return Two Values
14+
-- Yes, all of that! Notice that I don't trust the table name provided.
15+
-- I use DBMS_ASSERT to make sure it's a valid table name.
16+
DECLARE
17+
l_last_name employees.last_name%TYPE;
18+
l_salary employees.salary%TYPE;
19+
20+
PROCEDURE show_value (table_in IN VARCHAR2, id_in IN INTEGER)
21+
IS
22+
BEGIN
23+
EXECUTE IMMEDIATE
24+
'SELECT last_name, salary FROM '
25+
|| sys.DBMS_ASSERT.sql_object_name (table_in)
26+
|| ' WHERE employee_id = :id_value'
27+
INTO l_last_name, l_salary
28+
USING id_in;
29+
30+
DBMS_OUTPUT.put_line (l_last_name || ' Earning ' || l_salary);
31+
END;
32+
BEGIN
33+
show_value ('EMPLOYEES', 138);
34+
END;
35+
/
36+
37+
DECLARE
38+
l_employee employees%ROWTYPE;
39+
BEGIN
40+
EXECUTE IMMEDIATE 'SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = :empid'
41+
INTO l_employee
42+
USING 138;
43+
44+
DBMS_OUTPUT.put_line (l_employee.last_name);
45+
END allrows_by;
46+
/
47+
48+
-- Use BULK COLLECT with EXECUTE IMMEDIATE
49+
-- Sure, why not? Get all or specified set of rows and load a collection!
50+
DECLARE
51+
TYPE employee_ntt IS TABLE OF employees%ROWTYPE;
52+
53+
l_employees employee_ntt;
54+
BEGIN
55+
EXECUTE IMMEDIATE 'SELECT * FROM employees' BULK COLLECT INTO l_employees;
56+
57+
DBMS_OUTPUT.put_line (l_employees.COUNT);
58+
END allrows_by;
59+
/
60+
61+
-- Use OPEN FOR to Fetch From Dynamic SELECT
62+
-- In Oracle8, when native dynamic SQL was first introduced, you could not
63+
-- use BULK COLLECT with EXECUTE IMMEDIATE. So, instead, you could use OPEN
64+
-- FOR to assign a cursor variable to the dynamic SELECT, and then FETCH BULK COLLECT.
65+
66+
DECLARE
67+
l_cursor SYS_REFCURSOR;
68+
69+
TYPE employee_ntt IS TABLE OF employees%ROWTYPE;
70+
71+
l_employees employee_ntt;
72+
BEGIN
73+
OPEN l_cursor FOR 'SELECT * FROM employees';
74+
75+
FETCH l_cursor BULK COLLECT INTO l_employees;
76+
77+
DBMS_OUTPUT.put_line (l_employees.COUNT);
78+
79+
CLOSE l_cursor;
80+
END allrows_by;
81+
/
82+
83+
-- And With a LIMIT Clause
84+
DECLARE
85+
l_cursor SYS_REFCURSOR;
86+
87+
TYPE employee_ntt IS TABLE OF employees%ROWTYPE;
88+
89+
l_employees employee_ntt;
90+
BEGIN
91+
OPEN l_cursor FOR 'SELECT * FROM employees';
92+
93+
LOOP
94+
FETCH l_cursor BULK COLLECT INTO l_employees LIMIT 100;
95+
96+
EXIT WHEN l_employees.COUNT = 0;
97+
98+
DBMS_OUTPUT.put_line (l_employees.COUNT);
99+
END LOOP;
100+
101+
CLOSE l_cursor;
102+
END allrows_by;
103+
/
104+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /