CREATE TABLE servererror_log (
error_datetime TIMESTAMP,
error_user VARCHAR2(30),
db_name VARCHAR2(9),
error_stack VARCHAR2(2000),
captured_sql VARCHAR2(1000));
/
CREATE OR REPLACE TRIGGER log_server_errors
AFTER SERVERERROR
ON DATABASE
DECLARE
captured_sql VARCHAR2(1000);
BEGIN
SELECT q.sql_text
INTO captured_sql
FROM gv$sql q, gv$sql_cursor c, gv$session s
WHERE s.audsid = audsid
AND s.prev_sql_addr = q.address
AND q.address = c.parent_handle;
INSERT INTO servererror_log
(error_datetime, error_user, db_name,
error_stack, captured_sql)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, captured_sql);
END log_server_errors;
sábado, 9 de septiembre de 2017
TRIIGER AFTER SERVERERROR
TRIGGER SERVICE
CREATE OR REPLACE TRIGGER
manage_service
after startup on database
DECLARE
role VARCHAR(30);
BEGIN
SELECT DATABASE_ROLE INTO role FROM V$DATABASE;
IF role = 'PRIMARY' THEN
DBMS_SERVICE.START_SERVICE('sales_rw');
ELSE
DBMS_SERVICE.START_SERVICE('sales_ro');
END IF;
END;
TRIGGER COMPOUND
CREATE TABLE compound_trigger_test (
id NUMBER,
description VARCHAR2(50)
);
CREATE OR REPLACE TRIGGER compound_trigger_test_trg
FOR INSERT OR UPDATE OR DELETE ON compound_trigger_test
COMPOUND TRIGGER
-- Global declaration.
TYPE t_tab IS TABLE OF VARCHAR2(50);
l_tab t_tab := t_tab();
BEFORE STATEMENT IS
BEGIN
l_tab.extend;
CASE
WHEN INSERTING THEN
l_tab(l_tab.last) := 'BEFORE STATEMENT - INSERT';
WHEN UPDATING THEN
l_tab(l_tab.last) := 'BEFORE STATEMENT - UPDATE';
WHEN DELETING THEN
l_tab(l_tab.last) := 'BEFORE STATEMENT - DELETE';
END CASE;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
l_tab.extend;
CASE
WHEN INSERTING THEN
l_tab(l_tab.last) := 'BEFORE EACH ROW - INSERT (new.id=' || :new.id || ')';
WHEN UPDATING THEN
l_tab(l_tab.last) := 'BEFORE EACH ROW - UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
WHEN DELETING THEN
l_tab(l_tab.last) := 'BEFORE EACH ROW - DELETE (old.id=' || :old.id || ')';
END CASE;
END BEFORE EACH ROW;
AFTER EACH ROW IS
BEGIN
l_tab.extend;
CASE
WHEN INSERTING THEN
l_tab(l_tab.last) := 'AFTER EACH ROW - INSERT (new.id=' || :new.id || ')';
WHEN UPDATING THEN
l_tab(l_tab.last) := 'AFTER EACH ROW - UPDATE (new.id=' || :new.id || ' old.id=' || :old.id || ')';
WHEN DELETING THEN
l_tab(l_tab.last) := 'AFTER EACH ROW - DELETE (old.id=' || :old.id || ')';
END CASE;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
l_tab.extend;
CASE
WHEN INSERTING THEN
l_tab(l_tab.last) := 'AFTER STATEMENT - INSERT';
WHEN UPDATING THEN
l_tab(l_tab.last) := 'AFTER STATEMENT - UPDATE';
WHEN DELETING THEN
l_tab(l_tab.last) := 'AFTER STATEMENT - DELETE';
END CASE;
FOR i IN l_tab.first .. l_tab.last LOOP
DBMS_OUTPUT.put_line(l_tab(i));
END LOOP;
l_tab.delete;
END AFTER STATEMENT;
END compound_trigger_test_trg;
/
martes, 5 de septiembre de 2017
TRIGGER INSTEAD OF
Example 9-2 INSTEAD OF Trigger
This example creates the view oe.order_info to display information about customers and their orders. The view is not inherently updatable (because the primary key of the orders table, order_id, is not unique in the result set of the join view). The example creates an INSTEAD OF trigger to process INSERT statements directed to the view. The trigger inserts rows into the base tables of the view, customers and orders.CREATE OR REPLACE VIEW order_info AS
SELECT c.customer_id, c.cust_last_name, c.cust_first_name,
o.order_id, o.order_date, o.order_status
FROM customers c, orders o
WHERE c.customer_id = o.customer_id;
CREATE OR REPLACE TRIGGER order_info_insert
INSTEAD OF INSERT ON order_info
DECLARE
duplicate_info EXCEPTION;
PRAGMA EXCEPTION_INIT (duplicate_info, -00001);
BEGIN
INSERT INTO customers
(customer_id, cust_last_name, cust_first_name)
VALUES (
:new.customer_id,
:new.cust_last_name,
:new.cust_first_name);
INSERT INTO orders (order_id, order_date, customer_id)
VALUES (
:new.order_id,
:new.order_date,
:new.customer_id);
EXCEPTION
WHEN duplicate_info THEN
RAISE_APPLICATION_ERROR (
num=> -20107,
msg=> 'Duplicate customer or order ID');
END order_info_insert;
/
jueves, 31 de agosto de 2017
RESULT CACHE PL/SQL
Supongamos que estoy en un equipo que está construyendo una aplicación de recursos humanos. La tabla de empleados es una de las estructuras clave, manteniendo todos los datos para todos los empleados. Cientos de usuarios ejecutan numerosos programas en la aplicación que se leen de esta tabla y leen con mucha frecuencia. Sin embargo, la tabla cambia con relativa poca frecuencia, tal vez una o dos veces por hora. Como resultado, el código de aplicación recupera repetidamente de la caché de memoria intermedia de bloque lo que es en su mayoría datos estáticos, soportando la sobrecarga de comprobar si la consulta particular ya se ha analizado, encontrando los datos en el búfer y devolviéndolos.
El equipo necesita mejorar el rendimiento de consultar los datos de la tabla de empleados. En la actualidad, utilizamos la siguiente función para devolver una fila de la tabla de empleados:
The team needs to improve the performance of querying data from the employees table. Currently,
we use the following function to return a row from the employees table:
FUNCTION one_employee (employee_id_in
IN employees.employee_id%TYPE)
RETURN employees%ROWTYPE
IS
l_employee employees%ROWTYPE;
BEGIN
SELECT *
INTO l_employee
FROM employees
WHERE employee_id = employee_id_in;
RETURN l_employee;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
/* Return an empty record. */
RETURN l_employee;
END one_employee;
In Oracle Database 11g, however, we can add a line to the header of this function as follows:
FUNCTION one_employee (employee_id_in
IN employees.employee_id%TYPE)
RETURN employees%ROWTYPE
RESULT_CACHE RELIES_ON (employees)
IS
l_employee employees%ROWTYPE;
BEGIN
Esta cláusula RESULT_CACHE le dice a Oracle Database que debería recordar (almacenar en una memoria caché de resultados en memoria especial) cada registro recuperado para un número de identificación de empleado específico. Y cuando una sesión ejecuta esta función y pasa un ID de empleado que fue almacenado previamente, el motor de tiempo de ejecución de PL / SQL no ejecutará el cuerpo de la función, que incluye esa consulta.En su lugar, simplemente recuperará el registro del caché y devolverá los datos inmediatamente. El resultado es una recuperación mucho más rápida.Además, especificando RELIES_ON (empleados), informamos a Oracle Database que si cualquier sesión confirma cambios en esa tabla, se invalidarán todos los datos de la caché de resultados extraídos de la tabla. La siguiente llamada a la función one_employee tendría entonces que ejecutar la consulta y recuperar los datos frescos de la tabla.Debido a que el caché es una parte del Área Global del Sistema (SGA), su contenido está disponible para todas las sesiones conectadas a la instancia. Además, Oracle Database aplicará su "algoritmo utilizado menos recientemente" a la caché, para garantizar que los datos más recientemente accedidos se conservarán en la caché.Antes de Oracle Database 11g, era posible un tipo similar de almacenamiento en caché con colecciones a nivel de paquete, pero esta caché era específica de la sesión y estaba ubicada en el Área de Proceso Global (PGA). Esto significa que si tengo 1.000 sesiones diferentes ejecutando la aplicación, podría utilizar una enorme cantidad de memoria además de la consumida por la SGA.La cache de resultados de la función PL / SQL minimiza la cantidad de memoria necesaria para almacenar en caché y compartir estos datos en todas las sesiones. Este perfil de memoria baja, además de la purga automática de los resultados almacenados en caché siempre que se cometen cambios, hace que esta característica de Oracle Database 11g sea muy práctica para optimizar el rendimiento en aplicaciones PL / SQL.
martes, 22 de agosto de 2017
Predefined PL/SQL Exceptions
Predefined PL/SQL Exceptions
An internal exception is raised implicitly whenever your PL/SQL program violates an Oracle rule or exceeds a system-dependent limit. Every Oracle error has a number, but exceptions must be handled by name. So, PL/SQL predefines some common Oracle errors as exceptions. For example, PL/SQL raises the predefined exception
NO_DATA_FOUND if a SELECT INTO statement returns no rows.
To handle other Oracle errors, you can use the
OTHERS handler. The functions SQLCODE and SQLERRM are especially useful in the OTHERS handler because they return the Oracle error code and message text. Alternatively, you can use the pragma EXCEPTION_INIT to associate exception names with Oracle error codes.
PL/SQL declares predefined exceptions globally in package
STANDARD, which defines the PL/SQL environment. So, you need not declare them yourself. You can write handlers for predefined exceptions using the names in the following list:
Brief descriptions of the predefined exceptions follow:
jueves, 17 de agosto de 2017
BULK COLLECT
http://www.oracle.com/technetwork/issue-archive/2008/08-mar/o28plsql-095155.html
When you are using BULK COLLECT and collections to fetch data from your cursor, you should never rely on the cursor attributes to decide whether to terminate your loop and data processing.
DEVELOPER: PL/SQL Practices
On BULK COLLECT
By Steven Feuerstein
Best practices for knowing your LIMIT and kicking %NOTFOUND
I have started using BULK COLLECT whenever I need to fetch large volumes of data. This has caused me some trouble with my DBA, however. He is complaining that although my programs might be running much faster, they are also consuming way too much memory. He refuses to approve them for a production rollout. What's a programmer to do?
The most important thing to remember when you learn about and start to take advantage of features such as BULK COLLECT is that there is no free lunch. There is almost always a trade-off to be made somewhere. The tradeoff with BULK COLLECT, like so many other performance-enhancing features, is "run faster but consume more memory."
Specifically, memory for collections is stored in the program global area (PGA), not the system global area (SGA). SGA memory is shared by all sessions connected to Oracle Database, but PGA memory is allocated for each session. Thus, if a program requires 5MB of memory to populate a collection and there are 100 simultaneous connections, that program causes the consumption of 500MB of PGA memory, in addition to the memory allocated to the SGA.
Fortunately, PL/SQL makes it easy for developers to control the amount of memory used in a BULK COLLECT operation by using the LIMIT clause.
Suppose I need to retrieve all the rows from the employees table and then perform some compensation analysis on each row. I can use BULK COLLECT as follows:
PROCEDURE process_all_rows
IS
TYPE employees_aat
IS TABLE OF employees%ROWTYPE
INDEX BY PLS_INTEGER;
l_employees employees_aat;
BEGIN
SELECT *
BULK COLLECT INTO l_employees
FROM employees;
FOR indx IN 1 .. l_employees.COUNT
LOOP
analyze_compensation
(l_employees(indx));
END LOOP;
END process_all_rows;
Very concise, elegant, and efficient code. If, however, my employees table contains tens of thousands of rows, each of which contains hundreds of columns, this program can cause excessive PGA memory consumption.
Consequently, you should avoid this sort of "unlimited" use of BULK COLLECT. Instead, move the SELECT statement into an explicit cursor declaration and then use a simple loop to fetch many, but not all, rows from the table with each execution of the loop body, as shown in Listing 1.
Code Listing 1: Using BULK COLLECT with LIMIT clause
PROCEDURE process_all_rows (limit_in IN PLS_INTEGER DEFAULT 100)
IS
CURSOR employees_cur
IS
SELECT * FROM employees;
TYPE employees_aat IS TABLE OF employees_cur%ROWTYPE
INDEX BY PLS_INTEGER;
l_employees employees_aat;
BEGIN
OPEN employees_cur;
LOOP
FETCH employees_cur
BULK COLLECT INTO l_employees LIMIT limit_in;
FOR indx IN 1 .. l_employees.COUNT
LOOP
analyze_compensation (l_employees(indx));
END LOOP;
EXIT WHEN l_employees.COUNT < limit_in;
END LOOP;
CLOSE employees_cur;
END process_all_rows;
The process_all_rows procedure in Listing 1 requests that up to the value of limit_in rows be fetched at a time. PL/SQL will reuse the same limit_in elements in the collection each time the data is fetched and thus also reuse the same memory. Even if my table grows in size, the PGA consumption will remain stable.
How do you decide what number to use in the LIMIT clause? Theoretically, you will want to figure out how much memory you can afford to consume in the PGA and then adjust the limit to be as close to that amount as possible.
From tests I (and others) have performed, however, it appears that you will see roughly the same performance no matter what value you choose for the limit, as long as it is at least 25. The test_diff_limits.sql script, included with the sample code for this column, demonstrates this behavior, using the ALL_SOURCE data dictionary view on an Oracle Database 11g instance. Here are the results I saw (in hundredths of seconds) when fetching all the rows (a total of 470,000):
Elapsed CPU time for limit of 1 = 1839 Elapsed CPU time for limit of 5 = 716 Elapsed CPU time for limit of 25 = 539 Elapsed CPU time for limit of 50 = 545 Elapsed CPU time for limit of 75 = 489 Elapsed CPU time for limit of 100 = 490 Elapsed CPU time for limit of 1000 = 501 Elapsed CPU time for limit of 10000 = 478 Elapsed CPU time for limit of 100000 = 527
Kicking the %NOTFOUND Habit
I was very happy to learn that Oracle Database 10g will automatically optimize my cursor FOR loops to perform at speeds comparable to BULK COLLECT. Unfortunately, my company is still running on Oracle9i Database, so I have started converting my cursor FOR loops to BULK COLLECTs. I have run into a problem: I am using a LIMIT of 100, and my query retrieves a total of 227 rows, but my program processes only 200 of them. [The query is shown in Listing 2.] What am I doing wrong?
Code Listing 2: BULK COLLECT, %NOTFOUND, and missing rows
PROCEDURE process_all_rows
IS
CURSOR table_with_227_rows_cur
IS
SELECT * FROM table_with_227_rows;
TYPE table_with_227_rows_aat IS
TABLE OF table_with_227_rows_cur%ROWTYPE
INDEX BY PLS_INTEGER;
l_table_with_227_rows table_with_227_rows_aat;
BEGIN
OPEN table_with_227_rows_cur;
LOOP
FETCH table_with_227_rows_cur
BULK COLLECT INTO l_table_with_227_rows LIMIT 100;
EXIT WHEN table_with_227_rows_cur%NOTFOUND; /* cause of missing rows */
FOR indx IN 1 .. l_table_with_227_rows.COUNT
LOOP
analyze_compensation (l_table_with_227_rows(indx));
END LOOP;
END LOOP;
CLOSE table_with_227_rows_cur;
END process_all_rows;
You came so close to a completely correct conversion from your cursor FOR loop to BULK COLLECT! Your only mistake was that you didn't give up the habit of using the %NOTFOUND cursor attribute in your EXIT WHEN clause.
The statement
EXIT WHEN table_with_227_rows_cur%NOTFOUND;
makes perfect sense when you are fetching your data one row at a time. With BULK COLLECT, however, that line of code can result in incomplete data processing, precisely as you described.
Let's examine what is happening when you run your program and why those last 27 rows are left out. After opening the cursor and entering the loop, here is what occurs:
1. The fetch statement retrieves rows 1 through 100.
2. table_with_227_rows_cur%NOTFOUND evaluates to FALSE, and the rows are processed.
3. The fetch statement retrieves rows 101 through 200.
4. table_with_227_rows_cur%NOTFOUND evaluates to FALSE, and the rows are processed.
5. The fetch statement retrieves rows 201 through 227.
6. table_with_227_rows_cur%NOTFOUND evaluates to TRUE , and the loop is terminatedwith 27 rows left to process!
2. table_with_227_rows_cur%NOTFOUND evaluates to FALSE, and the rows are processed.
3. The fetch statement retrieves rows 101 through 200.
4. table_with_227_rows_cur%NOTFOUND evaluates to FALSE, and the rows are processed.
5. The fetch statement retrieves rows 201 through 227.
6. table_with_227_rows_cur%NOTFOUND evaluates to TRUE , and the loop is terminatedwith 27 rows left to process!
|
So, to make sure that your query processes all 227 rows, replace this statement:
EXIT WHEN table_with_227_rows_cur%NOTFOUND; with EXIT WHEN l_table_with_227_rows.COUNT = 0;
Generally, you should keep all of the following in mind when working with BULK COLLECT:
- The collection is always filled sequentially, starting from index value 1.
- It is always safe (that is, you will never raise a NO_DATA_FOUND exception) to iterate through a collection from 1 to collection .COUNT when it has been filled with BULK COLLECT.
- The collection is empty when no rows are fetched.
- Always check the contents of the collection (with the COUNT method) to see if there are more rows to process.
- Ignore the values returned by the cursor attributes, especially %NOTFOUND.
Suscribirse a:
Entradas (Atom)
