<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4063906708258638821</id><updated>2012-02-01T01:34:17.699-03:00</updated><title type='text'>El Blog de Leonardo Horikian</title><subtitle type='html'>I'm an "Oracle" of Oracle .-</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>65</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-4648910066394330510</id><published>2010-07-06T13:41:00.007-03:00</published><updated>2010-07-06T17:34:33.665-03:00</updated><title type='text'>Eliminar una consulta de la Shared Pool</title><content type='html'>A partir de Oracle 10.2.0.4 en adelante, el paquete DBMS_SHARED_POOL contiene nuevos procedimientos. Uno de esos procedimientos se llama PURGE y es el encargado de eliminar determinados objetos de la Shared Pool. Estos objetos pueden ser: cursores (consultas SQL), paquetes, procedimientos, funciones, triggers, secuencias y tipos.&lt;br /&gt;En versiones anterior, esto no podíamos hacerlo y lo que debíamos hacer era eliminar todas las consultas de la Shared Pool mediante la sentencia "ALTER SYSTEM FLUSH SHARED_POOL".&lt;br /&gt;&lt;br /&gt;La creación del paquete DBMS_SHARED_POOL podemos encontrarla en $ORACLE_HOME/rdbms/admin/dbmspool.sql&lt;br /&gt;&lt;br /&gt;En Oracle 10.2.0.4 existe el bug 5614566 que provoca el mal funcionamiento del procedimiento PURGE. Pero, tenemos un manera de evitar este bug en esa versión.&lt;br /&gt;&lt;br /&gt;Comencemos viendo un ejemplo en 11gR1:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; desc DBMS_SHARED_POOL&lt;br /&gt;&lt;br /&gt;PROCEDURE ABORTED_REQUEST_THRESHOLD&lt;br /&gt; Argument Name                  Type                    In/Out Default?&lt;br /&gt; ------------------------------ ----------------------- ------ --------&lt;br /&gt; THRESHOLD_SIZE                 NUMBER                  IN&lt;br /&gt;PROCEDURE KEEP&lt;br /&gt; Argument Name                  Type                    In/Out Default?&lt;br /&gt; ------------------------------ ----------------------- ------ --------&lt;br /&gt; NAME                           VARCHAR2                IN&lt;br /&gt; FLAG                           CHAR                    IN     DEFAULT&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;PROCEDURE PURGE&lt;br /&gt; Argument Name                  Type                    In/Out Default?&lt;br /&gt; ------------------------------ ----------------------- ------ --------&lt;br /&gt; NAME                           VARCHAR2                IN&lt;br /&gt; FLAG                           CHAR                    IN     DEFAULT&lt;br /&gt; HEAPS                          NUMBER                  IN     DEFAULT&lt;/span&gt;&lt;br /&gt;PROCEDURE SIZES&lt;br /&gt; Argument Name                  Type                    In/Out Default?&lt;br /&gt; ------------------------------ ----------------------- ------ --------&lt;br /&gt; MINSIZE                        NUMBER                  IN&lt;br /&gt;PROCEDURE UNKEEP&lt;br /&gt; Argument Name                  Type                    In/Out Default?&lt;br /&gt; ------------------------------ ----------------------- ------ --------&lt;br /&gt; NAME                           VARCHAR2                IN&lt;br /&gt; FLAG                           CHAR                    IN     DEFAULT&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para ejecutar el procedimiento PURGE, debemos pasar como valor en el parámetro NAME el ADDRESS junto con el HASH_VALUE separados por una coma(,).&lt;br /&gt;Estos valores podemos encontrarlos en la vista V$SQLAREA.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; SELECT /* prueba */ 1 FROM DUAL;&lt;br /&gt;&lt;br /&gt;         1&lt;br /&gt;----------&lt;br /&gt;         1&lt;br /&gt;&lt;br /&gt;SQL&gt; SELECT address||','||hash_value name &lt;br /&gt;     FROM v$sqlarea &lt;br /&gt;     WHERE sql_text = 'SELECT /* prueba */ 1 FROM DUAL';&lt;br /&gt;&lt;br /&gt;NAME&lt;br /&gt;-------------------&lt;br /&gt;2E10E868,4198164882&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, lo que hicimos fue ejecutar una consulta para que sea parseada por primera vez y almacenada en la Shared Pool. Luego buscamos en la vista V$SQLAREA los valores que debo pasar en el parámetro NAME del procedimiento PURGE.&lt;br /&gt;&lt;br /&gt;Ahora vamos a eliminar la consulta de la Shared Pool y a verificar que se haya eliminado.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; &lt;span style="font-weight:bold;"&gt;exec dbms_shared_pool.purge('2E10E868,4198164882','C',1);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL&gt; SELECT address||','||hash_value name &lt;br /&gt;     FROM v$sqlarea &lt;br /&gt;     WHERE sql_text = 'SELECT /* prueba */ 1 FROM DUAL';&lt;br /&gt;&lt;br /&gt;no rows selected&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La consulta fue eliminada con éxito!&lt;br /&gt;&lt;br /&gt;Para hacer funcionar el procedimiento en 10.2.0.4, debemos realizar el siguiente alter en la sesión antes de ejecutar el procedimiento PURGE:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; alter session set events '5614566 trace name context forever';&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Pueden utilizar el siguiente script para hacer el purge de un determinado cursor de la Shared Pool:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DECLARE&lt;br /&gt; name    varchar2(50);&lt;br /&gt; version varchar2(3);&lt;br /&gt;BEGIN&lt;br /&gt; select regexp_replace(version,'\..*') &lt;br /&gt; into version &lt;br /&gt; from v$instance;&lt;br /&gt;&lt;br /&gt; if version = '10' then&lt;br /&gt;   execute immediate&lt;br /&gt;   q'[alter session set events '5614566 trace name context forever']'; -- bug 5614566&lt;br /&gt; end if;&lt;br /&gt;&lt;br /&gt; select address||','||hash_value &lt;br /&gt; into name&lt;br /&gt; from v$sqlarea&lt;br /&gt; where sql_id like '&amp;sql_id';&lt;br /&gt;&lt;br /&gt; dbms_shared_pool.purge(name,'C',1);&lt;br /&gt;END;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-4648910066394330510?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/4648910066394330510/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=4648910066394330510' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4648910066394330510'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4648910066394330510'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2010/07/eliminar-una-consulta-de-la-shared-pool.html' title='Eliminar una consulta de la Shared Pool'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-4419795490719036757</id><published>2010-07-06T02:04:00.013-03:00</published><updated>2010-07-06T13:39:44.121-03:00</updated><title type='text'>Diferencias entre  BETWEEN  y  &gt;= &lt;=</title><content type='html'>Muchas personas prefieren utilizar el BETWEEN en vez de &gt;= &lt;= y otras personas prefieren usar lo contrario porque suponen que hay alguna diferencia a nivel de performance. Esto es un mito muy antiguo. La realidad es que no existen diferencias.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;BETWEEN es una "sinónimo" de  &gt;= &lt;=&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;Voy a demostrar esto con una sencilla prueba:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT level id, 'nombre_'||level nombre&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST', estimate_percent =&gt; 100, method_opt =&gt; 'for all columns size 1');&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL&gt; DESC test&lt;br /&gt;&lt;br /&gt; Name          Null?    Type&lt;br /&gt; ------------- -------- --------------&lt;br /&gt; ID                     NUMBER&lt;br /&gt; NOMBRE                 VARCHAR2(47)&lt;br /&gt;&lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id BETWEEN 20 AND 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;---------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1357081020&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  TABLE ACCESS FULL| TEST |      1 |      7 |      6 |00:00:00.01 |       4 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - filter(("ID"&lt;=25 AND "ID"&gt;=20))&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver en la sección "Predicate Information", el optimizador reemplazó la sentencia BETWEEN por "ID"&lt;=25 AND "ID"&gt;=20.&lt;br /&gt;También podemos ver, que leyó 4 bloques de datos para recuperar los 6 registros mediante un acceso FULL SCAN.&lt;br /&gt;&lt;br /&gt;Ahora, probemos colocando el &gt;= &lt;= en la consulta ...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id &gt;= 20 AND id &lt;= 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;---------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1357081020&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  TABLE ACCESS FULL| TEST |      1 |      7 |      6 |00:00:00.01 |       4 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - filter(("ID"&lt;=25 AND "ID"&gt;=20)) &lt;/span&gt;  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Acá volvemos a ver en la a sección "Predicate Information" que el optimizador no realizó ningún reemplazo.&lt;br /&gt;Seguimos leyendo 4 bloques de datos para recuperar los 6 registros mediante un acceso FULL SCAN.&lt;br /&gt;&lt;br /&gt;Una cosa muy importante a destacar entre estas 2 consultas es que el hash value de ambas es el mismo (1357081020). Esto nos indica que la utilización de BETWEEN o &gt;= &lt;= es transparente para el optimizador ya que siempre va a utilizar &gt;= &lt;= para resolver la consulta.&lt;br /&gt;&lt;br /&gt;Qué sucede si creamos un índice en la tabla? Obtendremos alguna diferencia?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; CREATE UNIQUE INDEX test_uq ON test(id, nombre);&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST', estimate_percent =&gt; 100, method_opt =&gt; 'for all columns size 1', cascade =&gt; true);&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id BETWEEN 20 AND 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;---------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1087767317&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation        | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  INDEX RANGE SCAN| TEST_UQ |      1 |      7 |      6 |00:00:00.01 |       2 |&lt;/span&gt;&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - access("ID"&gt;=20 AND "ID"&lt;=25)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que se volvió a reemplazar el BETWEEN por &gt;= &lt;= y ahora como estamos accediendo por índice, leemos solamente 2 bloques de datos.&lt;br /&gt;&lt;br /&gt;Qué sucederá si modificamos nuevamente la consulta?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id &gt;= 20 AND id &lt;= 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;---------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;sys@orcl&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1087767317&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation        | Name    | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  INDEX RANGE SCAN| TEST_UQ |      1 |      7 |      6 |00:00:00.01 |       2 |&lt;/span&gt;&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - access("ID"&gt;=20 AND "ID"&lt;=25)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Obtuvimos exactamente el mismo resultado que con el BETWEEN.&lt;br /&gt;El hash value entre las 2 consultas sigue siendo el mismo (1087767317).&lt;br /&gt;&lt;br /&gt;En esta prueba, colocamos el campo numérico ID en el WHERE pero qué sucedería si utilizamos un campo caracter?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; DROP TABLE test;&lt;br /&gt;&lt;br /&gt;Table dropped.&lt;br /&gt;&lt;br /&gt;SQL&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT to_char(level) id, 'nombre_'||level nombre&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL&gt; DESC test&lt;br /&gt;&lt;br /&gt; Name          Null?    Type&lt;br /&gt; ------------- -------- --------------&lt;br /&gt; ID                     VARCHAR2(40)&lt;br /&gt; NOMBRE                 VARCHAR2(47)&lt;br /&gt;&lt;br /&gt;SQL&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST', estimate_percent =&gt; 100, method_opt =&gt; 'for all columns size 1');&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id BETWEEN 20 AND 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;---------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1357081020&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  TABLE ACCESS FULL| TEST |      1 |      1 |      6 |00:00:00.01 |       4 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - filter((TO_NUMBER("ID")&gt;=20 AND TO_NUMBER("ID")&lt;=25))&lt;/span&gt;&lt;br /&gt;   &lt;br /&gt;SQL&gt; SELECT nombre&lt;br /&gt;  2  FROM test&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  3  WHERE id &gt;= 20 AND id &lt;= 25;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;NOMBRE&lt;br /&gt;-----------------------------------------------&lt;br /&gt;nombre_20&lt;br /&gt;nombre_21&lt;br /&gt;nombre_22&lt;br /&gt;nombre_23&lt;br /&gt;nombre_24&lt;br /&gt;nombre_25&lt;br /&gt;&lt;br /&gt;6 rows selected.&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;Plan hash value: 1357081020&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|*  1 |  TABLE ACCESS FULL| TEST |      1 |      1 |      6 |00:00:00.01 |       4 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;   1 - filter((TO_NUMBER("ID")&gt;=20 AND TO_NUMBER("ID")&lt;=25))&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como observamos, ambas consultas siguen siendo idénticas.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estas pruebas nos indican que no existe diferencia alguna entre la utilización del BETWEEN y el &gt;= &lt;=&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-4419795490719036757?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/4419795490719036757/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=4419795490719036757' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4419795490719036757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4419795490719036757'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2010/07/diferencias-entre-between-y.html' title='Diferencias entre  BETWEEN  y  &gt;= &lt;='/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-4022916383463348388</id><published>2010-06-30T19:49:00.006-03:00</published><updated>2010-07-01T10:32:13.339-03:00</updated><title type='text'>Crear una base de datos de prueba en minutos!</title><content type='html'>La tarea de crear una base de datos puede parecer bastante complicada para algunos o bastante agotadora para otros... pero la realidad es que podemos crear una base de datos de prueba en tan solo minutos sin utilizar entorno gráfico como el X Windows o la VNC. Tan solo ejecutando DBCA podemos crear por linea de comando y de manera no interactica una base de datos!!!&lt;br /&gt;&lt;br /&gt;En este sencillo ejemplo creé el archivo testdb.dbt copiando uno de los archivos template que se encuentran en el directorio $ORACLE_HOME/assistants/dbca/templates/. Luego modifiqué este archivo colocando algunas configuraciones para la nueva base de datos que quiero crear. Por último, ejecuté el siguiente comando ...&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ dbca -Silent -CreateDatabase -gdbName testdb -sid testdb -templateName $ORACLE_HOME/assistants/dbca/templates/testdb.dbt -sysPassword oracle -systemPassword oracle -dbsnmpPassword oracle -sysmanPassword oracle&lt;br /&gt;&lt;br /&gt;4% complete&lt;br /&gt;Creating and starting Oracle instance&lt;br /&gt;5% complete&lt;br /&gt;6% complete&lt;br /&gt;7% complete&lt;br /&gt;12% complete&lt;br /&gt;Creating database files&lt;br /&gt;20% complete&lt;br /&gt;Creating data dictionary views&lt;br /&gt;22% complete&lt;br /&gt;24% complete&lt;br /&gt;27% complete&lt;br /&gt;28% complete&lt;br /&gt;29% complete&lt;br /&gt;30% complete&lt;br /&gt;31% complete&lt;br /&gt;32% complete&lt;br /&gt;33% complete&lt;br /&gt;34% complete&lt;br /&gt;39% complete&lt;br /&gt;41% complete&lt;br /&gt;44% complete&lt;br /&gt;46% complete&lt;br /&gt;Adding Oracle Application Express&lt;br /&gt;47% complete&lt;br /&gt;48% complete&lt;br /&gt;49% complete&lt;br /&gt;50% complete&lt;br /&gt;51% complete&lt;br /&gt;52% complete&lt;br /&gt;53% complete&lt;br /&gt;54% complete&lt;br /&gt;55% complete&lt;br /&gt;56% complete&lt;br /&gt;57% complete&lt;br /&gt;58% complete&lt;br /&gt;59% complete&lt;br /&gt;60% complete&lt;br /&gt;62% complete&lt;br /&gt;Adding Oracle Warehouse Builder&lt;br /&gt;63% complete&lt;br /&gt;64% complete&lt;br /&gt;65% complete&lt;br /&gt;66% complete&lt;br /&gt;67% complete&lt;br /&gt;75% complete&lt;br /&gt;78% complete&lt;br /&gt;Completing Database Creation&lt;br /&gt;80% complete&lt;br /&gt;83% complete&lt;br /&gt;85% complete&lt;br /&gt;92% complete&lt;br /&gt;100% complete&lt;br /&gt;&lt;br /&gt;Look at the log file "/u01/app/oracle/cfgtoollogs/dbca/soldb/testdb.log" for further details.&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Existen muchos parámetros opcionales con los que podemos ejecutar el DBCA para crear una base de datos. También, podemos ejecutar DBCA para realizar otras operaciones por linea de comando; como borrar una base de datos, crear scripts, etc.&lt;br /&gt;&lt;br /&gt;Podemos ver todas las operaciones que se pueden ejecutar con el comando DBCA -H ...&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;$ dbca -h&lt;br /&gt;&lt;br /&gt;dbca  [-silent | -progressOnly | -customCreate] {&lt;command&gt; &lt;options&gt; }  | { [&lt;command&gt; [options] ] -responseFile  &lt;response file &gt; } [-continueOnNonFatalErrors &lt;true | false&gt;]&lt;br /&gt;Please refer to the manual for details.&lt;br /&gt;You can enter one of the following command:&lt;br /&gt;&lt;br /&gt;Create a database by specifying the following parameters:&lt;br /&gt;        -createDatabase&lt;br /&gt;                -templateName &lt;name of an existing  template&gt;&lt;br /&gt;                [-cloneTemplate]&lt;br /&gt;                -gdbName &lt;global database name&gt;&lt;br /&gt;                [-policyManaged | -adminManaged &lt;Policy managed or Admin managed Database, default is Admin managed database&gt;]&lt;br /&gt;                        [-createServerPool &lt;To create ServerPool which will be used by the database to be created&gt;]&lt;br /&gt;                        [-force &lt;To create serverpool by force when adequate free servers are not available. This may affect already running database&gt;]&lt;br /&gt;                        -serverPoolName &lt;One serverPool Name in case of create server pool and comma separated list of serverPool name in case of use serverpool&gt;&lt;br /&gt;                        -[cardinality &lt;Specify cardinality for new serverPool to be created, default is the number of qualified nodes&gt;]&lt;br /&gt;                [-sid &lt;database system identifier prefix&gt;]&lt;br /&gt;                [-sysPassword &lt;SYS user password&gt;]&lt;br /&gt;                [-systemPassword &lt;SYSTEM user password&gt;]&lt;br /&gt;                [-emConfiguration &lt;CENTRAL|LOCAL|ALL|NOBACKUP|NOEMAIL|NONE&gt;&lt;br /&gt;                        -dbsnmpPassword &lt;DBSNMP user password&gt;&lt;br /&gt;                        -sysmanPassword &lt;SYSMAN user password&gt;&lt;br /&gt;                        [-hostUserName &lt;Host user name for EM backup job&gt;&lt;br /&gt;                         -hostUserPassword &lt;Host user password for EM backup job&gt;&lt;br /&gt;                         -backupSchedule &lt;Daily backup schedule in the form of hh:mm&gt;]&lt;br /&gt;                        [-smtpServer &lt;Outgoing mail (SMTP) server for email notifications&gt;&lt;br /&gt;                         -emailAddress &lt;Email address for email notifications&gt;]&lt;br /&gt;                        [-centralAgent &lt;Enterprise Manager central agent home&gt;]]&lt;br /&gt;                [-disableSecurityConfiguration &lt;ALL|AUDIT|PASSWORD_PROFILE|NONE&gt;&lt;br /&gt;                [-datafileDestination &lt;destination directory for all database files&gt; |  -datafileNames &lt;a text file containing database objects such as controlfiles, tablespaces, redo log files and spfile to their corresponding raw device file names mappings in name=value format.&gt;]&lt;br /&gt;                [-redoLogFileSize &lt;size of each redo log file in megabytes&gt;]&lt;br /&gt;                [-recoveryAreaDestination &lt;destination directory for all recovery files&gt;]&lt;br /&gt;                [-datafileJarLocation  &lt;location of the data file jar, used only for clone database creation&gt;]&lt;br /&gt;                [-storageType &lt; CFS | ASM &gt;&lt;br /&gt;                        [-asmsnmpPassword     &lt;ASMSNMP password for ASM monitoring&gt;]&lt;br /&gt;                         -diskGroupName   &lt;database area disk group name&gt;&lt;br /&gt;                         -recoveryGroupName       &lt;recovery area disk group name&gt;&lt;br /&gt;                [-nodelist &lt;node names separated by comma for the database&gt;]&lt;br /&gt;                [-characterSet &lt;character set for the database&gt;]&lt;br /&gt;                [-nationalCharacterSet  &lt;national character set for the database&gt;]&lt;br /&gt;                [-registerWithDirService &lt;true | false&gt;&lt;br /&gt;                        -dirServiceUserName    &lt;user name for directory service&gt;&lt;br /&gt;                        -dirServicePassword    &lt;password for directory service &gt;&lt;br /&gt;                        -walletPassword    &lt;password for database wallet &gt;]&lt;br /&gt;                [-listeners  &lt;list of listeners to configure the database with&gt;]&lt;br /&gt;                [-variablesFile   &lt;file name for the variable-value pair for variables in the template&gt;]]&lt;br /&gt;                [-variables  &lt;comma seperated list of name=value pairs&gt;]&lt;br /&gt;                [-initParams &lt;comma seperated list of name=value pairs&gt;]&lt;br /&gt;                [-memoryPercentage &lt;percentage of physical memory for Oracle&gt;]&lt;br /&gt;                [-automaticMemoryManagement ]&lt;br /&gt;                [-totalMemory &lt;memory allocated for Oracle in MB&gt;]&lt;br /&gt;                [-databaseType &lt;MULTIPURPOSE|DATA_WAREHOUSING|OLTP&gt;]]&lt;br /&gt;&lt;br /&gt;Configure a database by specifying the following parameters:&lt;br /&gt;        -configureDatabase&lt;br /&gt;                -sourceDB    &lt;local instance_name of source database&gt;&lt;br /&gt;                [-sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;&lt;br /&gt;                 -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;]&lt;br /&gt;                [-registerWithDirService|-unregisterWithDirService|-regenerateDBPassword &lt;true | false&gt;&lt;br /&gt;                        -dirServiceUserName    &lt;user name for directory service&gt;&lt;br /&gt;                        -dirServicePassword    &lt;password for directory service &gt;&lt;br /&gt;                        -walletPassword    &lt;password for database wallet &gt;]&lt;br /&gt;                [-disableSecurityConfiguration &lt;ALL|AUDIT|PASSWORD_PROFILE|NONE&gt;&lt;br /&gt;                [-enableSecurityConfiguration &lt;true|false&gt;&lt;br /&gt;                [-emConfiguration &lt;CENTRAL|LOCAL|ALL|NOBACKUP|NOEMAIL|NONE&gt;&lt;br /&gt;                        -dbsnmpPassword &lt;DBSNMP user password&gt;&lt;br /&gt;                        -symanPassword &lt;SYSMAN user password&gt;&lt;br /&gt;                        [-hostUserName &lt;Host user name for EM backup job&gt;&lt;br /&gt;                         -hostUserPassword &lt;Host user password for EM backup job&gt;&lt;br /&gt;                         -backupSchedule &lt;Daily backup schedule in the form of hh:mm&gt;]&lt;br /&gt;                        [-smtpServer &lt;Outgoing mail (SMTP) server for email notifications&gt;&lt;br /&gt;                         -emailAddress &lt;Email address for email notifications&gt;]&lt;br /&gt;                        [-centralAgent &lt;Enterprise Manager central agent home&gt;]]&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Create a template from an existing database by specifying the following parameters:&lt;br /&gt;        -createTemplateFromDB&lt;br /&gt;                -sourceDB    &lt;service in the form of &lt;host&gt;:&lt;port&gt;:&lt;sid&gt;&gt;&lt;br /&gt;                -templateName      &lt;new template name&gt;&lt;br /&gt;                -sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;&lt;br /&gt;                -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;&lt;br /&gt;                [-maintainFileLocations &lt;true | false&gt;]&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Create a clone template from an existing database by specifying the following parameters:&lt;br /&gt;        -createCloneTemplate&lt;br /&gt;                -sourceSID    &lt;local instance_name of source database&gt;&lt;br /&gt;                -templateName      &lt;new template name&gt;&lt;br /&gt;                [-sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;&lt;br /&gt;                 -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;]&lt;br /&gt;                [-maintainFileLocations &lt;true | false&gt;]&lt;br /&gt;                [-datafileJarLocation       &lt;directory to place the datafiles in a compressed format&gt;]&lt;br /&gt;&lt;br /&gt;Generate scripts to create database by specifying the following parameters:&lt;br /&gt;        -generateScripts&lt;br /&gt;                -templateName &lt;name of an existing  template&gt;&lt;br /&gt;                -gdbName &lt;global database name&gt;&lt;br /&gt;                [-scriptDest       &lt;destination for all the scriptfiles&gt;]&lt;br /&gt;&lt;br /&gt;Delete a database by specifying the following parameters:&lt;br /&gt;        -deleteDatabase&lt;br /&gt;                -sourceDB    &lt;source database global database name&gt;&lt;br /&gt;                [-sid    &lt;local instance_name of source database&gt;]&lt;br /&gt;                [-sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;&lt;br /&gt;                 -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;]&lt;br /&gt;&lt;br /&gt;Add an instance to a cluster database by specifying the following parameters:&lt;br /&gt;        -addInstance&lt;br /&gt;                -gdbName &lt;global database name&gt;&lt;br /&gt;                -nodelist &lt;node name for the new instance to add&gt;&lt;br /&gt;                [-instanceName &lt;instance name for the new instance to add&gt;]&lt;br /&gt;                [-sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;]&lt;br /&gt;                 -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;&lt;br /&gt;                [-updateDirService &lt;true | false&gt;&lt;br /&gt;                        -dirServiceUserName    &lt;user name for directory service&gt;&lt;br /&gt;                        -dirServicePassword    &lt;password for directory service &gt;]&lt;br /&gt;&lt;br /&gt;Delete an instance from a cluster database by specifying the following parameters:&lt;br /&gt;        -deleteInstance&lt;br /&gt;                -gdbName &lt;global database name&gt;&lt;br /&gt;                -instanceName &lt;instance name for the instance to be removed&gt;&lt;br /&gt;                [-nodelist &lt;node name for the instance to be removed&gt;]&lt;br /&gt;                [-sysDBAUserName     &lt;user name  with SYSDBA privileges&gt;]&lt;br /&gt;                 -sysDBAPassword     &lt;password for sysDBAUserName user name&gt;&lt;br /&gt;                [-updateDirService &lt;true | false&gt;&lt;br /&gt;                        -dirServiceUserName    &lt;user name for directory service&gt;&lt;br /&gt;                        -dirServicePassword    &lt;password for directory service &gt;]&lt;br /&gt;&lt;br /&gt;Query for help by specifying the following options: -h | -help&lt;br /&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-4022916383463348388?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/4022916383463348388/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=4022916383463348388' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4022916383463348388'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4022916383463348388'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2010/06/crear-una-base-de-datos-de-prueba-en.html' title='Crear una base de datos de prueba en minutos!'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7640126126413582192</id><published>2010-02-17T19:02:00.004-03:00</published><updated>2010-02-17T19:16:32.166-03:00</updated><title type='text'>Simposio HOTSOS 2010</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.hotsos.com/img/sym_logo.gif"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 201px; height: 111px;" src="http://www.hotsos.com/img/sym_logo.gif" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Todos los años, se realiza un evento llamado Simposio HOTSOS. Este evento esta exclusivamente dedicado a Oracle Performance Tuning.&lt;br /&gt;&lt;br /&gt;El Simposio se realiza todos los años en el mes de Marzo en la ciudad de Dallas, Texas (USA). Esta conferencia es única a nivel mundial! Reune a los mejores expertos de Oracle del mundo!&lt;br /&gt;&lt;br /&gt;El año pasado, tuve la oportunidad de poder viajar y presenciar el Simposio. Definitivamente se lo recomiendo a todas las personas que utilizan Oracle y que se especializan o que se encuentran interesadas en lo que respecta a Performance Tuning. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Este año (días 7 al 11 de Marzo) volveré a presenciar el Simposio! Nos vemos ahí!!!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Para más información: &lt;a href="http://www.hotsos.com/sym10.html"&gt;AQUI&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7640126126413582192?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7640126126413582192/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7640126126413582192' title='7 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7640126126413582192'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7640126126413582192'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2010/02/simposio-hotsos-2010.html' title='Simposio HOTSOS 2010'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7698628241114973502</id><published>2009-10-22T01:39:00.006-03:00</published><updated>2009-10-22T02:32:46.561-03:00</updated><title type='text'>Necesitás ayuda sobre un tema en específico? Postealo AQUI !!!</title><content type='html'>&lt;span style="font-weight:bold;"&gt;A pedido de mucha gente que visita el blog y que utiliza Oracle...&lt;br /&gt;&lt;br /&gt;Este post es para que ustedes puedan sugerir los temas que tienen dudas y que les gustaría que se les explicara con más detalle en éste blog. Los temas pueden estar relacionados con Performance Tuning, Administración, Desarrollo, etc.&lt;br /&gt;&lt;br /&gt;Los invito a todos a sugerir temas técnicos relacionados con Oracle que requieran ser explicados en detalle.&lt;br /&gt;&lt;br /&gt;Saludos a todos!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7698628241114973502?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7698628241114973502/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7698628241114973502' title='43 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7698628241114973502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7698628241114973502'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/10/necesitas-ayuda-sobre-un-tema-en.html' title='Necesitás ayuda sobre un tema en específico? Postealo AQUI !!!'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>43</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-5409580349480811478</id><published>2009-10-20T23:16:00.007-03:00</published><updated>2009-10-21T00:01:51.688-03:00</updated><title type='text'>Añadiendo columnas con valores por defecto y constraint NOT NULL en Oracle 11g</title><content type='html'>Antes de la versión 11g, cuando agregabamos una columna a una tabla con valores por defecto y una constraint del tipo NOT NULL, Oracle realizaba un loqueo exclusivo en la tabla para insertar los valores en cada uno de los registros existentes en la misma.&lt;br /&gt;&lt;br /&gt;A partir de la versión 11g release 1, ésta operación se optimizó para mejorar en gran medida la utilización de los recursos del sistema y el espacio de almacenamiento de los nuevos valores. Oracle logra ésta optimización, guardando el valor por defecto en el diccionario de datos en vez de modificar todos los registros de la tabla, haciendo la ejecución de ésta operacion de manera instantánea. Gracias a ésto, obtenemos un beneficio enorme a la hora de modificar tablas con millones de registros.&lt;br /&gt;&lt;br /&gt;Por otro lado, las siguientes operaciones ADD COLUMN ahora pueden ejecutarse de manera concurrente junto con las operaciones DML:&lt;br /&gt;- Agregar una columna NOT NULL con un valor por defecto.&lt;br /&gt;- Agregar una columna NULL sin un valor por defecto.&lt;br /&gt;- Agregar una columna virtual.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;- versión 9i release 2&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test&lt;br /&gt;  2  NOLOGGING&lt;br /&gt;  3  AS&lt;br /&gt;  4  SELECT level id&lt;br /&gt;  5  FROM dual&lt;br /&gt;  6  CONNECT BY level &lt;= 1000000;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test ADD (nombre VARCHAR2(100) &lt;span style="font-weight:bold;"&gt;DEFAULT 'LEONARDO_HORIKIAN' NOT NULL&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:01:17.62&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; DESC test&lt;br /&gt;&lt;br /&gt; Name                                      Null?    Type&lt;br /&gt; ----------------------------------------- -------- ----------------------------&lt;br /&gt; ID                                                 NUMBER&lt;br /&gt;&lt;span style="font-weight:bold;"&gt; NOMBRE                                    NOT NULL VARCHAR2(100)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; exec dbms_stats.gather_table_stats(USER, 'TEST');&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  WHERE nombre &lt;span style="font-weight:bold;"&gt;IS NULL&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;         0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;- versión 11g release 1&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_11gR1&gt; CREATE TABLE test&lt;br /&gt;  2  NOLOGGING&lt;br /&gt;  3  AS&lt;br /&gt;  4  SELECT level id&lt;br /&gt;  5  FROM dual&lt;br /&gt;  6  CONNECT BY level &lt;= 1000000;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_11gR1&gt; ALTER TABLE test ADD (nombre VARCHAR2(100) &lt;span style="font-weight:bold;"&gt;DEFAULT 'LEONARDO_HORIKIAN' NOT NULL&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:00:00.17&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_11gR1&gt; DESC test&lt;br /&gt;&lt;br /&gt; Name                                      Null?    Type&lt;br /&gt; ----------------------------------------- -------- ----------------------------&lt;br /&gt; ID                                                 NUMBER&lt;br /&gt;&lt;span style="font-weight:bold;"&gt; NOMBRE                                    NOT NULL VARCHAR2(100)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_11gR1&gt; exec dbms_stats.gather_table_stats(USER, 'TEST');&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_11gR1&gt; SELECT count(*)&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  WHERE nombre &lt;span style="font-weight:bold;"&gt;IS NULL&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;         0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, creamos una tabla con 1 millón de registros y luego agregamos una columna con valores por defecto. En 9iR2, esa operación se ejecutó en 1 minuto 17 segundos; en cambio en 11gR1, se ejecutó de manera instantánea en tan solo 17 milisegundos!!!&lt;br /&gt;&lt;br /&gt;IMPORTANTE: Tengan en cuenta que existen bugs en 11g (leer nota Metalink 602327.1) relacionados con éste tipo de operación.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-5409580349480811478?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/5409580349480811478/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=5409580349480811478' title='4 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5409580349480811478'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5409580349480811478'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/10/anadiendo-columnas-con-valores-por.html' title='Añadiendo columnas con valores por defecto y constraint NOT NULL en Oracle 11g'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6038572716708263726</id><published>2009-10-15T12:18:00.006-03:00</published><updated>2009-11-11T08:34:48.544-03:00</updated><title type='text'>TKPROF y el parámetro SYS=Y</title><content type='html'>Conozco muchas personas que al ejecutar la herramienta TKPROF, lo hacen con el parámetros SYS=N para que no se incluyan en el archivo de salida las consultas recursivas que realiza internamente la base de datos. Si bien en algunos casos se hace ésto para que el archivo de salida no contenga información en exceso, muchas veces cuando buscamos la causa de un determinado problema, si colocamos SYS=N lo único que estaremos logrando será "ocultar" el causante de ese problema.&lt;br /&gt;&lt;br /&gt;Hace unos días, investigando un problema de performance en un proceso de un cliente, ejecuté la herramienta TKPROF con el parámetro SYS=Y (que es la opción por defecto) y noté que había una consulta recursiva que estaba leyendo millones de bloques de datos!&lt;br /&gt;&lt;br /&gt;La consulta es la siguiente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select min(bitmapped)&lt;br /&gt;from&lt;br /&gt; ts$ where dflmaxext =:1 and bitand(flags,1024) =1024&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse     7915      0.08       0.07          0          0          0           0&lt;br /&gt;Execute   7915      0.17       0.14          0          0          0           0&lt;br /&gt;Fetch    15830      4.24       4.23          0    3245150          0        7915&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total    31660      4.50       4.45          0    3245150          0        7915&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;Optimizer mode: CHOOSE&lt;br /&gt;Parsing user id: SYS   (recursive depth: 2)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  SORT AGGREGATE (cr=410 pr=0 pw=0 time=691 us)&lt;br /&gt;      2   TABLE ACCESS FULL TS$ (cr=410 pr=0 pw=0 time=659 us)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Esta consulta es interna de la base de datos. Debido a que el proceso se ejecuta con el usuario APPS y debido a que a éste usuario durante su creación se lo asignó dentro de un grupo de tablespaces temporales (new feature en 10g) llamado TEMP (que contienen los tablespaces TEMP1 y TEMP2), Oracle necesita ejecutar ésta consulta para determinar cuál de los dos tablespaces es el mejor para realizar la operación de sort que está requiriendo el proceso. Como pueden ver, esta consulta es ALTAMENTE costosa y consume muchos recursos del sistema ya que la consulta lee 3,245,150 bloques (410 * 7915) y el proceso en total lee 4,452,813 bloques! Por lo cual, ésta consulta está leyendo más del 70% del total de todos los bloques de datos del proceso!!!&lt;br /&gt;&lt;br /&gt;Para solucionar éste problema, lo que hice fue modificar el usuario APPS, sacarlo del  grupo de tablespaces temporales TEMP y asignarle directamente el tablespace TEMP2 que tiene un tamaño de 39 GB a comparación del TEMP1 que tiene sólo 6 GB. Al hacer éste cambio, lo que logramos fue que Oracle deje de ejecutar esa consulta debido a que ya no tiene necesidad de determinar cual de los dos tablespaces es el mejor para ejecutar una determinada operación de sort.&lt;br /&gt;&lt;br /&gt;Al ejecutar nuevamente el proceso, podemos ver el resultado de la solución implementada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select min(bitmapped)&lt;br /&gt;from&lt;br /&gt; ts$ where dflmaxext =:1 and bitand(flags,1024) =1024&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.00       0.00          0        410          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        4      0.00       0.00          0        410          0           1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;Optimizer mode: CHOOSE&lt;br /&gt;Parsing user id: SYS   (recursive depth: 3)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  SORT AGGREGATE (cr=410 pr=0 pw=0 time=3045 us)&lt;br /&gt;      2   TABLE ACCESS FULL TS$ (cr=410 pr=0 pw=0 time=2952 us)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Luego que se realizó éste cambio, se notó una mejora muy importante en cuanto a la performance y se notó una disminución drástica de la utilización de los recursos del sistema.&lt;br /&gt;&lt;br /&gt;Tengan siempre presente la importancia del parámetro SYS=Y al momento de ejecutar la herramienta TKPROF.&lt;br /&gt;&lt;br /&gt;Por cierto, esta consulta es un Bug! Hay que aplicar el Parche 5455880 para solucionar éste grave problema de performance.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6038572716708263726?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6038572716708263726/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6038572716708263726' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6038572716708263726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6038572716708263726'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/10/tkprof-y-el-parametro-sysy.html' title='TKPROF y el parámetro SYS=Y'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-48743975517302275</id><published>2009-09-02T23:28:00.003-03:00</published><updated>2010-07-08T01:26:58.415-03:00</updated><title type='text'>Oracle OpenWorld</title><content type='html'>&lt;a href="http://www.oracle.com/us/openworld" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.oracle.com/ocom/groups/public/documents/digitalasset/register_banner.gif"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 250px; height: 151px;" src="http://www.oracle.com/ocom/groups/public/documents/digitalasset/register_banner.gif" border="0" alt="" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-48743975517302275?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/48743975517302275/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=48743975517302275' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/48743975517302275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/48743975517302275'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/09/oracle-openworld-2009.html' title='Oracle OpenWorld'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-9013133876867972161</id><published>2009-07-06T16:18:00.010-03:00</published><updated>2009-07-13T16:16:24.161-03:00</updated><title type='text'>adiós a los RAW-devices en 12g...</title><content type='html'>Según la nota de Metalink 578455.1 en 12g NO se seguirán soportando RAW-devices para los archivos físicos de la base de datos (data files, redo logs, control files), OCR (Oracle Cluster Registry) o discos Voting.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;IMPORTANTE!!! Este anuncio NO afecta a ASM.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;En 12g, ya NO será posible ejecutar la siguiente sentencia porque intenta utilizar RAW-devices directamente...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; create tablespace ABC DATAFILE '/dev/raw/ABC1.dbf' size 2GB;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lo que tendremos que hacer, es ejecutar alguna de las siguientes sentencias para crear un diskgroup...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; alter diskgroup MYDG add disk '/dev/raw/ABC1.dbf';&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;o&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; create diskgroup MYDB EXTERNAL REDUNDANCY disk 'dev/raw/ABC1.dbf'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Luego de crear el diskgroup, podemos ejecutar la sentencia que acostumbramos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; create tablespace ABC DATAFILE '+MYDG' size 2GB;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para los que tengan que migrar desde RAW-devices, las opciones existentes incluyen ASM (Automatic Storage Management), OCFS (Oracle Cluster File System) y otros sistemas de archivos clúster.&lt;br /&gt;&lt;br /&gt;Muchos estarán leyendo ésta nota y diciendo... OUCHHHH!!!!!! Y bueno... tendremos que irnos aconstumbrando a ésta clase de cambios ya que éste es sólo uno de los grandes cambios que veremos en 12g...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-9013133876867972161?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/9013133876867972161/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=9013133876867972161' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/9013133876867972161'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/9013133876867972161'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/07/adios-los-volumenes-raw-en-12g.html' title='adiós a los RAW-devices en 12g...'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3142161754434203279</id><published>2009-06-25T10:03:00.009-03:00</published><updated>2009-06-25T12:02:27.607-03:00</updated><title type='text'>Stress Test</title><content type='html'>El "Stress Test" es una prueba que mide el comportamiento de nuestro sistema bajo una cierta demanda concurrente de conexiones. Esta es una de las pruebas claves que se DEBEN realizar durante el ciclo de vida del software para garantizar que nuestro sistema va a cumplir con las expectativas previstas cuando sea implementado en producción.&lt;br /&gt;&lt;br /&gt;Quiero mostrarles una simple prueba de Stress que hice para que vean qué tan importante es realizarla y qué tan mortal puede ser evitarla.&lt;br /&gt;&lt;br /&gt;Ejemplo:&lt;br /&gt;&lt;br /&gt;Vamos a crear una tabla llamada TEST que vamos a utilizar en el Stress para cargar datos.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test (id NUMBER);&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, yo tengo creados unos Shell Scripts propios para realizar el Stress Test. Los más importantes son los siguientes:&lt;br /&gt; - iniciar_stress.sh --&gt; Iniciamos el Stress Test.&lt;br /&gt; - reporte.sh --&gt; Genera el reporte con el detalle sobre el Stress Test.&lt;br /&gt;&lt;br /&gt;Lo que vamos a realizar es un Stress Test simulando una concurrencia de 100 conexiones simultáneas ejecutando el siguiente bloque PL/SQL anónimo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;BEGIN&lt;br /&gt;  FOR x IN 1 .. 100 LOOP&lt;br /&gt;    INSERT INTO test VALUES (x);&lt;br /&gt;  END LOOP;&lt;br /&gt;END;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pueden observar, el INSERT que vamos a realizar es muy sencillo y debería ejecutarse rápido, cierto? Veamos cuánto demora si lo ejecuto sin concurrencia:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; BEGIN&lt;br /&gt;  2    FOR x IN 1 .. 100 LOOP&lt;br /&gt;  3      INSERT INTO test VALUES (x);&lt;br /&gt;  4    END LOOP;&lt;br /&gt;  5  END;&lt;br /&gt;  6  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Transcurrido: 00:00:01.25&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, vemos que demoró 1.25 segundos. El bloque se ejecutó bastante rápido y esperamos que se comporte de la misma manera cuando realicemos el Stress Test... asi que hagamos la prueba para comprobarlo!&lt;br /&gt;&lt;br /&gt;Para comenzar el Stress Test, vamos a ejecutar el Shell Script llamado iniciar_stress.sh&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ./iniciar_stress.sh&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;====================== STRESS TEST ==============================&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;Autor: Leonardo Horikian - Oracle Argentina&lt;br /&gt;&lt;br /&gt;Descripción: Programa que inicia un StressTest&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Apretar ENTER para continuar...&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;Ingresar la password del usuario STRESS_TEST:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;TNS alias (para local dejar en blanco):&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Esta instancia se encuentra configurada para soportar 769 conexiones concurrentes!&lt;br /&gt;Cuántas conexiones concurrentes querés ejecutar [1-769]?&lt;br /&gt;&lt;br /&gt;100&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;StressTest listo para ser ejecutado.&lt;br /&gt;&lt;br /&gt;Para comenzar, apretar ENTER&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;nohup: appending output to `nohup.out'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pueden ver, se iniciaron 100 conexiones a la base de datos. Las mismas se ejecutan con el comando nohup realizando la conexión a la instancia vía SQL*PLUS.&lt;br /&gt;Ahora vamos a ejecutar el Shell Script llamado reporte.sh para generar el reporte con la información resultante de la prueba.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ./reporte.sh&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;====================== STRESS TEST ==============================&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;Autor: Leonardo Horikian - Oracle Argentina&lt;br /&gt;&lt;br /&gt;Descripción: Programa que genera el reporte con la información&lt;br /&gt;    sobre la ejecución del StressTest&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;Apretar ENTER para continuar...&lt;br /&gt;&lt;br /&gt;=================================================================&lt;br /&gt;&lt;br /&gt;Ingresar la password del usuario STRESS_TEST:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;TNS alias (para local dejar en blanco):&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;NOTA: Este script generará un archivo con la información del reporte en el directorio /reportes/.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ingresar el nombre del archivo de salida:&lt;br /&gt;&lt;br /&gt;stress_test.txt&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elegir el TEST_ID con el cual se quiere generar el reporte...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   TEST_ID TEST_DATE                      COMMENTS&lt;br /&gt;********** ****************************** ****************************************&lt;br /&gt;        37 06/25/2009 08:46               100 conexiones concurrentes.&lt;br /&gt;&lt;br /&gt;Ingresá el TEST_ID: 37&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;El archivo fue generado satisfactoriamente!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Abrimos el reporte y vemos lo siguiente...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------- INFORMACION SOBRE EL STRESS_TEST -------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;ID: 37 - FECHA: 06/25/2009 08:46 - COMENTARIOS: 100 conexiones concurrentes.&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------- RESULTADO INDIVIDUAL DEL STRESS_TEST (para cada una de las sesiones) -------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 248 - SPID: 11065&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 1.51&lt;/span&gt; - TOTAL_TIME_IN_MIN: .03 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 47  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 1.3&lt;br /&gt;EVENT: library cache pin                        --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .52&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 8   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .31&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 6   - TIMEOUTS: 2   - TIME_WAITED_IN_SEC: .03&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 11  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .03&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 808 - SPID: 11143&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 1.5&lt;/span&gt; - TOTAL_TIME_IN_MIN: .03 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 47  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 1.3&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 7   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .3 &lt;br /&gt;EVENT: library cache pin                        --&gt; WAITS: 3   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .16&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 1   - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 714 - SPID: 11116&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 1.48&lt;/span&gt; - TOTAL_TIME_IN_MIN: .02 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 35  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 1.3&lt;br /&gt;EVENT: library cache pin                        --&gt; WAITS: 3   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .31&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 2   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .24&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 2   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 204 - SPID: 11110&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 1.48&lt;/span&gt; - TOTAL_TIME_IN_MIN: .02 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 38  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 1.2&lt;br /&gt;EVENT: library cache pin                        --&gt; WAITS: 3   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .36&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .3 &lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .09&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 7   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .03&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .02&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 2   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;CONTINUA ...&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_M2xLBs8np6Q/SkORIOMf8rI/AAAAAAAAAB4/zzZc6scw0FI/s1600-h/stress_test_100.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 159px;" src="http://1.bp.blogspot.com/_M2xLBs8np6Q/SkORIOMf8rI/AAAAAAAAAB4/zzZc6scw0FI/s320/stress_test_100.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5351280352681783986" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Como podemos observar, el comportamiento para cada sesión fue el mismo que cuando ejecutamos la prueba sin concurrencia. El bloque se ejecutó en un promedio de 1.5 segundos por sesión.&lt;br /&gt;&lt;br /&gt;Qué sucede si repetimos la prueba con 300 usuarios concurrentes? El resultado seguirá siendo el mismo? Veamos el reporte de la ejecución...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------- INFORMACION SOBRE EL STRESS_TEST -------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;ID: 38 - FECHA: 06/25/2009 09:07 - COMENTARIOS: 300 conexiones concurrentes.&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------- RESULTADO INDIVIDUAL DEL STRESS_TEST (para cada una de las sesiones) -------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 607 - SPID: 15310&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 5.47&lt;/span&gt; - TOTAL_TIME_IN_MIN: .09 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 12  - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 4.8&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 12  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 2.9&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 15  - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 1.2&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 11  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .77&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .06&lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 3   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .04&lt;br /&gt;EVENT: log file sync                            --&gt; WAITS: 1   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .03&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 286 - SPID: 15362&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 5.19&lt;/span&gt; - TOTAL_TIME_IN_MIN: .09 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 13  - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 4.9&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 11  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 2.9&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 1.1&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 5   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .45&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .12&lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 3   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .02&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 435 - SPID: 15329&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 4.97&lt;/span&gt; - TOTAL_TIME_IN_MIN: .08 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 9   - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 3.5&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 8   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 2.7&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 37  - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 2.5&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 7   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .58&lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 17  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .18&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .07&lt;br /&gt;EVENT: library cache pin                        --&gt; WAITS: 1   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;SID: 202 - SPID: 15746&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;TOTAL_TIME_IN_SEC: 4.63&lt;/span&gt; - TOTAL_TIME_IN_MIN: .08 - TOTAL_TIME_IN_HO: 0&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;- EVENTOS ---------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;EVENT: enqueue                                  --&gt; WAITS: 76  - TIMEOUTS: 1   - TIME_WAITED_IN_SEC: 5.5&lt;br /&gt;EVENT: row cache lock                           --&gt; WAITS: 181 - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 2.1&lt;br /&gt;EVENT: latch free                               --&gt; WAITS: 47  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 2.0&lt;br /&gt;EVENT: buffer busy waits                        --&gt; WAITS: 5   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .16&lt;br /&gt;EVENT: SQL*Net message from client              --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .07&lt;br /&gt;EVENT: wait list latch free                     --&gt; WAITS: 1   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: .01&lt;br /&gt;EVENT: control file sequential read             --&gt; WAITS: 4   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: SQL*Net message to client                --&gt; WAITS: 19  - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;EVENT: db file sequential read                  --&gt; WAITS: 2   - TIMEOUTS: 0   - TIME_WAITED_IN_SEC: 0  &lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;-------------------------------------------------------------------------------------------------------------&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;CONTINUA ...&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_M2xLBs8np6Q/SkORdV3xXpI/AAAAAAAAACA/vZATfGLsVYU/s1600-h/stress_test_300.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 159px;" src="http://4.bp.blogspot.com/_M2xLBs8np6Q/SkORdV3xXpI/AAAAAAAAACA/vZATfGLsVYU/s320/stress_test_300.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5351280715519581842" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Como observamos, cuando incrementamos la cantidad de conexiones concurrentes, aumentó casi un 400 porciento el tiempo de ejecución de cada conexión!!! Este es un ejemplo muy claro del porqué es TAN importante hacer ésta clase de pruebas de Stress para evitar problemas futuros cuando pasemos nuestro sistema a producción!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3142161754434203279?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3142161754434203279/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3142161754434203279' title='18 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3142161754434203279'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3142161754434203279'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/06/stress-test.html' title='Stress Test'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_M2xLBs8np6Q/SkORIOMf8rI/AAAAAAAAAB4/zzZc6scw0FI/s72-c/stress_test_100.jpg' height='72' width='72'/><thr:total>18</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-503331525881455237</id><published>2009-05-19T17:20:00.011-03:00</published><updated>2009-05-19T18:09:48.524-03:00</updated><title type='text'>Costo super alto!</title><content type='html'>En Febrero del 2008, publiqué un post llamado "&lt;a href="http://lhorikian.blogspot.com/2008/02/tunear-en-base-al-costo-del-plan-de.html"&gt;¿Tunear en base al COSTO del plan de ejecución?&lt;/a&gt;". En ese post, les decía que NO hay que tunear en base al costo del plan de ejecución, sino al trabajo que realiza la consulta en la base de datos.&lt;br /&gt;&lt;br /&gt;Voy a mostrarles una consulta en el que el costo es super alto pero el trabajo que realiza en la base de datos no lo es:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;2  SELECT (t5.column_value).getstringval() t5&lt;br /&gt;3  FROM TABLE(xmlsequence(extract(xmltype('&lt;x&gt;'),'/x'))) t1,&lt;br /&gt;4       TABLE(xmlsequence(t1.column_value))t2,&lt;br /&gt;5       TABLE(xmlsequence(t2.column_value))t3,&lt;br /&gt;6       TABLE(xmlsequence(t3.column_value))t4,&lt;br /&gt;7       TABLE(xmlsequence(t4.column_value))t5;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM table(dbms_xplan.display('plan_table',null,'ALL'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;-------------------------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 4104774429&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                             | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|   0 | SELECT STATEMENT                      |                        |    18E|    15E|    98P  (2)|999:59:59 |&lt;/span&gt;&lt;br /&gt;|   1 |  NESTED LOOPS                         |                        |    18E|    15E|    98P  (2)|999:59:59 |&lt;br /&gt;|   2 |   NESTED LOOPS                        |                        |  4451T|    31P|    12T  (2)|999:59:59 |&lt;br /&gt;|   3 |    NESTED LOOPS                       |                        |   544G|  3045G|  1481M  (2)|999:59:59 |&lt;br /&gt;|   4 |     NESTED LOOPS                      |                        |    66M|   254M|   181K  (2)| 00:36:18 |&lt;br /&gt;|   5 |      COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   6 |      COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   7 |     COLLECTION ITERATOR PICKLER FETCH | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   8 |    COLLECTION ITERATOR PICKLER FETCH  | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   9 |   COLLECTION ITERATOR PICKLER FETCH   | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Query Block Name / Object Alias (identified by operation id):&lt;br /&gt;-------------------------------------------------------------&lt;br /&gt;1 - SEL$E270DE78&lt;br /&gt;&lt;br /&gt;Column Projection Information (identified by operation id):&lt;br /&gt;-----------------------------------------------------------&lt;br /&gt;1 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;2 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;3 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;4 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;5 - VALUE(A0)[40]&lt;br /&gt;6 - VALUE(A0)[40]&lt;br /&gt;7 - VALUE(A0)[40]&lt;br /&gt;8 - VALUE(A0)[40]&lt;br /&gt;9 - VALUE(A0)[40]&lt;br /&gt;&lt;/x&gt;&lt;/pre&gt;&lt;br /&gt;Wow!!! Y esos números??? Extremadamente altos no??? Bueno, según el plan de ejecución, esa consulta retorna 18 cuatrillones (18E) de registros, 15 exabytes (15E), tiene un costo de 1.8E19 (98P) y el tiempo de ejecución es de aproximadamente 1 mes (999:59:59)!!!&lt;br /&gt;&lt;br /&gt;Veamos qué sucede cuando ejecutamos la consulta y obtenemos las estadísticas de la misma:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT (t5.column_value).getstringval() t5&lt;br /&gt;2  FROM TABLE(xmlsequence(extract(xmltype('&lt;x&gt;'),'/x'))) t1,&lt;br /&gt;3       TABLE(xmlsequence(t1.column_value))t2,&lt;br /&gt;4       TABLE(xmlsequence(t2.column_value))t3,&lt;br /&gt;5       TABLE(xmlsequence(t3.column_value))t4,&lt;br /&gt;6       TABLE(xmlsequence(t4.column_value))t5;&lt;br /&gt;&lt;br /&gt;T5&lt;br /&gt;----------------------------------------------------------------&lt;br /&gt;&lt; x/ &gt;&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:00.04&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;    0  recursive calls&lt;br /&gt;    0  db block gets&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;     3  consistent gets&lt;/span&gt;&lt;br /&gt;    0  physical reads&lt;br /&gt;    0  redo size&lt;br /&gt;  425  bytes sent via SQL*Net to client&lt;br /&gt;  381  bytes received via SQL*Net from client&lt;br /&gt;    2  SQL*Net roundtrips to/from client&lt;br /&gt;    0  sorts (memory)&lt;br /&gt;    0  sorts (disk)&lt;br /&gt;    1  rows processed&lt;br /&gt;&lt;/x&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, la consulta retorna un sólo registro y se ejecuta en tan solo 4 milisegundos, consumiendo sólo 3 bloques de datos.&lt;br /&gt;&lt;br /&gt;Este ejemplo es otra prueba de cómo el optimizador puede calcular valores totalmente erróneos en el plan de ejecución. Es por eso, que hay que tener &lt;span style="font-style: italic;"&gt;especial cuidado&lt;/span&gt; al ver esos números cuando obtenemos un plan de ejecución y al tratar de optimizar la consulta en base a esos números.&lt;br /&gt;&lt;br /&gt;Para ésta consulta en particular, el problema es que el optimizador desconoce las estadísticas de las tablas (que en éste caso no son tablas, son llamadas a funciones). Fácilmente, podemos "mejorar" el plan de ejecución, agregando el hint CARDINALITY para decirle al optimizador cuántos registros va a retornar esa función... que en éste caso, es un sólo registro:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;2  SELECT /*+ &lt;span style="font-weight: bold;"&gt;cardinality(t1 1) cardinality(t2 1) cardinality(t3 1) cardinality(t4 1) cardinality(t5 1)&lt;/span&gt; */&lt;br /&gt;3        (t5.column_value).getstringval() t5&lt;br /&gt;4  FROM TABLE(xmlsequence(extract(xmltype('&lt;x&gt;'),'/x'))) t1,&lt;br /&gt;5       TABLE(xmlsequence(t1.column_value))t2,&lt;br /&gt;6       TABLE(xmlsequence(t2.column_value))t3,&lt;br /&gt;7       TABLE(xmlsequence(t3.column_value))t4,&lt;br /&gt;8       TABLE(xmlsequence(t4.column_value))t5;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM table(dbms_xplan.display('plan_table',null,'ALL'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;------------------------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 4104774429&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                             | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|   0 | SELECT STATEMENT                      |                        |     1 |    10 |   122   (2)| 00:00:02 |&lt;/span&gt;&lt;br /&gt;|   1 |  NESTED LOOPS                         |                        |     1 |    10 |   122   (2)| 00:00:02 |&lt;br /&gt;|   2 |   NESTED LOOPS                        |                        |     1 |     8 |    97   (2)| 00:00:02 |&lt;br /&gt;|   3 |    NESTED LOOPS                       |                        |     1 |     6 |    73   (2)| 00:00:01 |&lt;br /&gt;|   4 |     NESTED LOOPS                      |                        |     1 |     4 |    49   (3)| 00:00:01 |&lt;br /&gt;|   5 |      COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   6 |      COLLECTION ITERATOR PICKLER FETCH| XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   7 |     COLLECTION ITERATOR PICKLER FETCH | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   8 |    COLLECTION ITERATOR PICKLER FETCH  | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;|   9 |   COLLECTION ITERATOR PICKLER FETCH   | XMLSEQUENCEFROMXMLTYPE |       |       |            |          |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Query Block Name / Object Alias (identified by operation id):&lt;br /&gt;-------------------------------------------------------------&lt;br /&gt;1 - SEL$E270DE78&lt;br /&gt;&lt;br /&gt;Column Projection Information (identified by operation id):&lt;br /&gt;-----------------------------------------------------------&lt;br /&gt;1 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;2 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;3 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;4 - (#keys=0) VALUE(A0)[40], VALUE(A0)[40]&lt;br /&gt;5 - VALUE(A0)[40]&lt;br /&gt;6 - VALUE(A0)[40]&lt;br /&gt;7 - VALUE(A0)[40]&lt;br /&gt;8 - VALUE(A0)[40]&lt;br /&gt;9 - VALUE(A0)[40]&lt;br /&gt;&lt;/x&gt;&lt;/pre&gt;&lt;br /&gt;Recuerden siempre el acrónimo GIGO: Garbage In ... Garbage Out   :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-503331525881455237?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/503331525881455237/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=503331525881455237' title='4 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/503331525881455237'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/503331525881455237'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/05/costo-super-alto.html' title='Costo super alto!'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2435431516148546481</id><published>2009-05-13T13:58:00.007-03:00</published><updated>2009-05-13T16:04:04.388-03:00</updated><title type='text'>DML Error Logging</title><content type='html'>Alguna vez trataron de actualizar 10 millones de registros? Que sucedía cuando uno de los registros fallaba? Oracle realizaba un rollback automático de los cambios... y qué sucede si el registro que falló era uno de los últimos en actualizarse? Y bueno... seguramente desperdiciamos todos el tiempo de ejecución de ese proceso ya que los cambios se perdieron tan solo porque un registro falló! A muchos les habrá pasado lo mismo al ejecutar una sentencia del tipo INSERT AS SELECT cierto? En donde uno de los registros no pudo insertarse por X motivo y por consecuencia toda la sentencia falló!&lt;br /&gt;&lt;br /&gt;Generalmente, sabemos que la manera más rápida de realizar un DML es en una sola sentencia. Cuando tenemos una sentencia en donde pueden haber registros que terminen en error, se solía armar un procedimiento que recorra los datos que se quieren insertar, updatear, eliminar, etc. e ir ejecutando la senetencia de a un/o varios registros a la vez, y los registros que fallaban, se insertaban en una tabla de errores. Este procedimiento suele ser muy lento, ya que no estamos realizando un DML en una sola sentencia y en una sola vez para todos los registros; sino que estamos recorriendo los datos proceduralmente y realizando el DML de a X cantidad de registros a la vez con el fin de loguear los registros que terminaron en error e ir comiteando, a medida que se va ejecutando la sentencia DML, los registros que terminan satisfactoriamente.&lt;br /&gt;&lt;br /&gt;En Oracle 10g Release 2, existe una nueva funcionalidad llamada "DML Error Logging" que nos permite ejecutar una sentencia DML de "principio a fin"... y si en el transcurso de ejecución de esa sentencia, uno o más registros fallaran, esos registros se loguean en una tabla de errores para que luego podramos corregirlos, sin necesidad, de volver a insertar todos los registros nuevamente ya que sólo tendremos que volver a insertar los que terminaron en error.&lt;br /&gt;Por ejemplo: Si intentamos insertar 1 millón de registros, y sólo un registro falla, se insertarán en la tabla final 999,999 de registros, y el registro que terminó en error, se loguea en otra tabla para que luego podamos corregirlo y volverlo a insertar. Acaso no es sensacional ésto???&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo muy simple:&lt;br /&gt;&lt;br /&gt;Creamos una tabla llamada TEST, en donde vamos a insertar 1 millón de registros.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test&lt;br /&gt;2  (&lt;br /&gt;3  ID     NUMBER,&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4  NOMBRE VARCHAR2(7)&lt;/span&gt;&lt;br /&gt;5  );&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Creamos una tabla llamada ERROR_LOG_TEST que apunta a la tabla TEST. Esta tabla va a contener todos los registros que terminen en error cuando queramos realizar un DML en la tabla TEST.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC dbms_errlog.create_error_log('TEST','&lt;span style="font-weight: bold;"&gt;ERROR_LOG_TEST&lt;/span&gt;') ;&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; DESC error_log_test&lt;br /&gt;&lt;br /&gt;Nombre                                                Nulo?    Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;ORA_ERR_NUMBER$                                                NUMBER&lt;br /&gt;ORA_ERR_MESG$                                                  VARCHAR2(2000)&lt;br /&gt;ORA_ERR_ROWID$                                                 ROWID&lt;br /&gt;ORA_ERR_OPTYP$                                                 VARCHAR2(2)&lt;br /&gt;ORA_ERR_TAG$                                                   VARCHAR2(2000)&lt;br /&gt;ID                                                             VARCHAR2(4000)&lt;br /&gt;NOMBRE                                                         VARCHAR2(4000)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ahora vamos a insertar los registros en la tabla TEST.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test&lt;br /&gt;2  SELECT level, 'nom_'||level&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 1000000   &lt;span style="color: rgb(255, 0, 0); font-weight: bold;"&gt;&lt;br /&gt;5  LOG ERRORS INTO ERROR_LOG_TEST REJECT LIMIT UNLIMITED;  &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;999 filas creadas.&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;WAW! De 1 millón de registros, sólo 999 registros fueron insertados satisfactoriamente, el resto de los registros se insertaron en la tabla de logueo de errores.&lt;br /&gt;&lt;br /&gt;Como notarán, use la cláusula LOG ERRORS INTO ... REJECT LIMIT UNLIMITED. Esta cláusula nos permite decirle a Oracle, que queremos utilizar "DML Error Logging" para nuestra sentencia. Fijense que coloqué UNLIMITED como parámetro de REJECT LIMIT. Esto le dice a Oracle, que no se fije en la cantidad de registros que terminan en error, que simplemente me inserte todos esos registros en la tabla de logueo de errores y que continúe con la ejecución de la sentencia hasta que termine. En vez de UNLIMITED, podría haber puesto 100, 1000, etc... que denota el máximo número de registros que quiero que Oracle loguee. Si hay más registros que terminan en error, Oracle simplemente aborta la ejecución de la consulta y devuelve un error por pantalla.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*) FROM error_log_test;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;999001&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como verán, no perdimos ningún registros. Los registros que se insertaron satisfactoriamente están en la tabla final TEST y el resto en la tabla de logueo de errores. Tener los registros en la tabla de logueo, nos permite corregirlos y tratar de insertarlos nuevamente en la tabla TEST.&lt;br /&gt;&lt;br /&gt;Veamos porqué falló la inserción de la mayoría de los registros...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*), ora_err_mesg$ FROM error_log_test GROUP BY ora_err_mesg$;&lt;br /&gt;&lt;br /&gt;COUNT(*) ORA_ERR_MESG$&lt;br /&gt;---------- ----------------------------------------------------------------------------------------------------&lt;br /&gt;90000 &lt;span style="font-weight: bold;"&gt;ORA-12899: el valor es demasiado grande para la columna "TEST"."TEST"."NOMBRE"&lt;/span&gt; (real: 9, máximo: 7)&lt;br /&gt; 9000 ORA-12899: el valor es demasiado grande para la columna "TEST"."TEST"."NOMBRE" (real: 8, máximo: 7)&lt;br /&gt;    1 ORA-12899: el valor es demasiado grande para la columna "TEST"."TEST"."NOMBRE" (real: 11, máximo: 7)&lt;br /&gt;900000 ORA-12899: el valor es demasiado grande para la columna "TEST"."TEST"."NOMBRE" (real: 10, máximo: 7)&lt;br /&gt;&lt;br /&gt;4 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, los registros fallaron porque el campo NOMBRE tiene un tamaño de 7 caracteres, y nosotros estamos intentando insertar valores más grandes. Solucionar éste problema es muy sencillo, simplemente, tenemos que agrandar el campo NOMBRE.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test MODIFY &lt;span style="font-weight: bold;"&gt;nombre VARCHAR2(100)&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;Tabla modificada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a tratar de insertar nuevamente los registros en la tabla TEST desde la tabla de logueo de errores.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test&lt;br /&gt;2  SELECT id, nombre&lt;br /&gt;3  FROM error_log_test;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;999001 filas creadas.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*) FROM test;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;1000000&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Excelenete!!! Ya tenemos todos los registros en la tabla TEST! Ahora sólo resta truncar la tabla de logueo de errores o simplemente borrarla en caso de que no la vayamos a utilizar nunca más.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; TRUNCATE TABLE error_log_test;&lt;br /&gt;&lt;br /&gt;Tabla truncada.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;CONCLUSIÓN:&lt;br /&gt;&lt;br /&gt;Esta nueva funcionalidad está acotada para ser utilizada sólo para algunos casos en particulares, pero espero que en lo posible, todos puedan comenzar a hacer uso de ésta funcionalidad ya que es muy simple de utilizar y muy eficiente a la hora de realizar carga masiva de datos y de logueo de errores de manera simultánea en una sola sentencia DML sin necesidad de recurrir a procedimientos costosos en cuanto a performance, desarrollo, mantenimiento y debuging.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2435431516148546481?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2435431516148546481/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2435431516148546481' title='6 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2435431516148546481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2435431516148546481'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/05/dml-error-logging.html' title='DML Error Logging'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6067478786535677790</id><published>2009-05-08T19:54:00.005-03:00</published><updated>2009-05-08T20:04:22.239-03:00</updated><title type='text'>Oracle adquiere Sun Microsystems</title><content type='html'>En momentos de crisis... pueden pasar cosas increíbles...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_M2xLBs8np6Q/SgS4jOW2pII/AAAAAAAAABw/kfovFwo8GkE/s1600-h/oracle-sun.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 216px;" src="http://1.bp.blogspot.com/_M2xLBs8np6Q/SgS4jOW2pII/AAAAAAAAABw/kfovFwo8GkE/s320/oracle-sun.jpg" alt="" id="BLOGGER_PHOTO_ID_5333590774002394242" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;unbreakable Linux... y ahora? unbreakable Solaris???   =)&lt;br /&gt;&lt;br /&gt;Para más información... &lt;a href="http://www.oracle.com/sun/index.html"&gt;AQUI&lt;/a&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6067478786535677790?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6067478786535677790/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6067478786535677790' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6067478786535677790'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6067478786535677790'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/05/oracle-adquiere-sun-microsystems.html' title='Oracle adquiere Sun Microsystems'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_M2xLBs8np6Q/SgS4jOW2pII/AAAAAAAAABw/kfovFwo8GkE/s72-c/oracle-sun.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1500366452033578809</id><published>2009-05-03T00:12:00.010-03:00</published><updated>2009-05-03T19:31:09.262-03:00</updated><title type='text'>Cuántos registros hay en cada bloque de mi tabla?</title><content type='html'>En el día de hoy, me llegó una e-mail de una persona preguntándome lo siguiente: "Me podrías decir cómo hago para saber cuántos registros hay en cada bloque de mi tabla?". Bueno, la verdad es que es muy fácil ver cuántos registros caben en cada bloque y también es muy fácil comprobarlo.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;En la base de datos de prueba en la que estoy actualmente, tengo bloques de 8 KB.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; show parameter db_block_size&lt;br /&gt;&lt;br /&gt;NAME                                 TYPE        VALUE&lt;br /&gt;------------------------------------ ----------- ------------------------------&lt;br /&gt;db_block_size                        integer     8192&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vamos a crear una tabla llamada TEST con 1.000 registros.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test AS&lt;br /&gt;2  SELECT level id, 'nom_'||level nombre&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 1000;      &lt;br /&gt;&lt;br /&gt;Tabla creada. &lt;/pre&gt;&lt;br /&gt;Para ver la cantidad de bloques que necesité para almacenar los 1.000 registros y la cantidad de registros que hay en cada uno de esos bloques, podemos ejecutar la siguiente consulta.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT dbms_rowid.rowid_block_number(rowid) "Número de Bloque", count(*)&lt;br /&gt;2  FROM test&lt;br /&gt;3  GROUP BY dbms_rowid.rowid_block_number(rowid)&lt;br /&gt;4  ORDER BY dbms_rowid.rowid_block_number(rowid) ASC;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Número de Bloque   COUNT(*)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;---------------- ----------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;           46196        438&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;           46197        425&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;           46198        137&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;3 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Por lo que podemos observar, en el bloque 46196 tengo 438 registros, en el bloque 46197 tengo 425 registros y en el bloque 46198 tengo 137 registros. Pero cómo hacemos para comprobar que realmente es cierto? Cómo hacemos para verificar que el resultado de la consulta es verdadero? Bueno, lo que vamos a hacer, es realizar un Dump de los 3 bloques y ver la información del Trace que se genera automáticamente. Para ejecutar un Dump, primero necesitamos obtener el número del DataFile donde se encuentra almacenada nuestra tabla (segmento). Para ésto, primero vamos a obtener ésta información y luego a realizar el Dump de los bloques. Veamos...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_10gR2&gt; SELECT header_file FROM dba_segments WHERE segment_name = 'TEST';&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;HEADER_FILE&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;-----------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;          4&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt;  &lt;span style="font-weight: bold;"&gt;alter system dump &lt;span style="color: rgb(255, 0, 0);"&gt;datafile 4 block min 46196 block max 46198&lt;/span&gt;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;Sistema modificado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; select spid&lt;br /&gt;2  from v$session s, v$process p&lt;br /&gt;3  where p.addr = s.paddr&lt;br /&gt;4  and s.audsid = sys_context('userenv','sessionid')&lt;br /&gt;5  /&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;SPID&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;------------&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4360&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya se generó el Trace en el directorio especificado en el parámetro user_dump_dest. El nombre con el que se generó es test_ora_4360.trc (el número es el SPID... "System Process Identifier" que obtuvimos). Veamos las partes que más nos interesan del archivo de Trace...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;data_block_dump,data header at 0x4f06a7c&lt;br /&gt;===============&lt;br /&gt;tsiz: 0x1f80&lt;br /&gt;hsiz: 0x37e&lt;br /&gt;pbl: 0x04f06a7c&lt;br /&gt;bdba: 0x0100b474&lt;br /&gt;76543210&lt;br /&gt;flag=--------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ntab=1  &lt;-- número de tablas en el bloque 46196.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;nrow=438  &lt;-- número de registros contenidos dentro del bloque 46196. &lt;/span&gt;&lt;br /&gt;frre=-1&lt;br /&gt;fsbo=0x37e  &lt;-- comienzo del espacio libre del bloque 46196.&lt;br /&gt;fseo=0x6a9  &lt;-- fin del espacio libre del bloque 46196.&lt;br /&gt;avsp=0x32b  &lt;-- espacio disponible del bloque 46196.&lt;br /&gt;tosp=0x32b  &lt;-- espacio total del bloque 46196. &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;0xe:pti[0]    nrow=438    offs=0  &lt;-- hay 438 registros en el bloque 46196 comenzando desde el registro número 0.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;data_block_dump,data header at 0xa206a7c&lt;br /&gt;===============&lt;br /&gt;tsiz: 0x1f80&lt;br /&gt;hsiz: 0x364&lt;br /&gt;pbl: 0x0a206a7c&lt;br /&gt;bdba: 0x0100b475&lt;br /&gt; 76543210&lt;br /&gt;flag=--------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ntab=1&lt;/span&gt;  &lt;span style="font-weight: bold;"&gt;&lt;-- número de tablas en el bloque 46197.&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;nrow=425&lt;/span&gt;  &lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;&lt;-- número de registros contenidos dentro del bloque 46197.&lt;br /&gt;&lt;/span&gt;frre=-1&lt;br /&gt;fsbo=0x364&lt;br /&gt;fseo=0x69d&lt;br /&gt;avsp=0x339&lt;br /&gt;tosp=0x339&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;0xe:pti[0] nrow=425 offs=0&lt;/span&gt;  &lt;span style="font-weight: bold;"&gt;&lt;-- hay 425 registros en el bloque &lt;/span&gt;&lt;span&gt;&lt;span style="font-weight: bold;"&gt;46197 &lt;/span&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;comenzando desde el registro número 0.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;data_block_dump,data header at 0xa206a7c&lt;br /&gt;===============&lt;br /&gt;tsiz: 0x1f80&lt;br /&gt;hsiz: 0x124&lt;br /&gt;pbl: 0x0a206a7c&lt;br /&gt;bdba: 0x0100b476&lt;br /&gt; 76543210&lt;br /&gt;flag=--------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ntab=1&lt;/span&gt;  &lt;span style="font-weight: bold;"&gt;&lt;-- número de tablas en el bloque 46198. &lt;/span&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;&lt;br /&gt;nrow=137&lt;/span&gt;  &lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;&lt;-- número de registros contenidos dentro del bloque 46198. &lt;/span&gt;&lt;br /&gt;frre=-1&lt;br /&gt;fsbo=0x124&lt;br /&gt;fseo=0x177a&lt;br /&gt;avsp=0x1656&lt;br /&gt;tosp=0x1656&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;0xe:pti[0] nrow=137 offs=0&lt;/span&gt;  &lt;span style="font-weight: bold;"&gt;&lt;-- hay 137 registros en el bloque &lt;/span&gt;&lt;span&gt;&lt;span style="font-weight: bold;"&gt;46198&lt;/span&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt; comenzando desde el registro número 0.&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;Bien, con éste ejemplo pudimos comprobar y verificar el resultado de nuestra primer consulta.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1500366452033578809?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1500366452033578809/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1500366452033578809' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1500366452033578809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1500366452033578809'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/05/cuantos-registros-hay-en-cada-bloque-de.html' title='Cuántos registros hay en cada bloque de mi tabla?'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3736087926348268718</id><published>2009-05-02T01:13:00.019-03:00</published><updated>2009-05-02T03:34:47.021-03:00</updated><title type='text'>Encriptando columnas con TDE (Transparent Data Encryption) en tablas con millones de registros</title><content type='html'>Para los que no conocen TDE, es una nueva funcionalidad de 10g R2 que permite proteger datos sensibles de las columnas de nuestras tablas encriptando los datos al almacenarlos en los respectivos Data Files en el sistema operativo. Para protegernos de personas malintencionadas que quieran desencriptar los datos sin autorización, guarda las claves de encriptamiento en un módulo seguro externo a la base de datos.&lt;br /&gt;&lt;br /&gt;Mi intención en éste post no es mostrar el paso a paso de cómo implementar TDE, sino mostrarles cómo encriptar columnas con TDE en tablas con millones de datos de la manera más eficiente y con el menor impacto posible en cuanto a la performance.&lt;br /&gt;&lt;br /&gt;En el momento en el que encriptamos columnas en una tabla, sólo podemos acceder a la tabla en modo lectura... NO están permitidas las operaciones DML hasta que el encriptamiento termine. En tablas chicas, con muy pocos datos, ésto no suele perjudicarnos demasiado. Pero qué sucede en tablas con millones de registros? En este caso, el encriptamiento puede durar varias horas!!!&lt;br /&gt;&lt;br /&gt;La estrategia que vamos a utilizar para encriptar columnas en tablas con millones de registros, es utilizando el paquete DBMS_REDEFINITION.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;Tengo una tabla llamada TEST_TDE con 1 millón de registros. Esta tabla tiene 3 columnas: ID (primary key), NOMBRE, NUM_TARJETA. La columna que vamos a encriptar es NUM_TARJETA ya que tiene los números de tarjetas, y como para mi es información sensible, quiero encriptarla y protegerla con TDE.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*) FROM test_tde;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;1000000&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; desc TEST_TDE&lt;br /&gt;&lt;br /&gt;Nombre                                     Nulo?   Tipo&lt;br /&gt;----------------------------------------- -------- ----------------------------&lt;br /&gt;ID                                        NOT NULL NUMBER&lt;br /&gt;NOMBRE                                             VARCHAR2(44)&lt;br /&gt;NUM_TARJETA                                        NUMBER&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT index_name, column_name FROM dba_ind_columns WHERE table_name = 'TEST_TDE';&lt;br /&gt;&lt;br /&gt;INDEX_NAME                               COLUMN_NAME&lt;br /&gt;---------------------------------------- ----------------------------------------&lt;br /&gt;TEST_TDE_ID_PK                           ID&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT constraint_name, constraint_type FROM dba_constraints WHERE table_name = 'TEST_TDE';&lt;br /&gt;&lt;br /&gt;CONSTRAINT_NAME                C&lt;br /&gt;------------------------------ -&lt;br /&gt;TEST_TDE_ID_PK                 P&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ahora vamos a encriptar la columna NUM_TARJETA de la manera tradicional y sin utilizar ninguna técnica. En la SESIÓN 1, voy a encriptar la columna, en la SESIÓN 2, voy a modificar el valor de uno de los registros de la tabla mientras se está realizando el procedimiento de encriptado... veamos qué sucede!&lt;br /&gt;&lt;br /&gt;SESIÓN 1:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test_tde MODIFY (num_tarjeta &lt;span style="font-weight: bold;"&gt;ENCRYPT&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;Tabla modificada.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;Transcurrido: 00:01:20.11&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;SESIÓN 2:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_10gR2&gt; UPDATE test_tde SET num_tarjeta = 123456789 WHERE id = 1000000;&lt;br /&gt;&lt;br /&gt;1 fila actualizada.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;Transcurrido: 00:01:17.64&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, el encriptamiento de 1 millón de registros demoró 1 minuto 20 segundos. Mientras que se encriptaban los datos, ejecuté un UPDATE en otra sesión y demoró 1 minuto 17 segundos. Esto es debido a que la tabla quedó loqueda en la SESIÓN 1 por el proceso de encriptamiento y la SESIÓN 2 tuvo que esperar que termine de encriptar los datos para poder modificarlos. Imaginen los problemas de performance que tendrían en sus aplicaciones si tienen varios usuarios concurrentes realizando operaciones DML sobre esa tabla mientras se está ejecutando el procedimiento de encriptación!!!&lt;br /&gt;&lt;br /&gt;Si ejecutamos el mismo UPDATE luego de que los datos ya se encuentran encriptados, podemos observar que no demoró nada en ejecutarse la operación DML.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; UPDATE test_tde SET num_tarjeta = 123456789 WHERE id = 1000000;&lt;br /&gt;&lt;br /&gt;1 fila actualizada.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:00.00&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a ver la técnica que les mencionaba con el paquete DBMS_REDEFINITION. Lo primero que vamos a hacer, es ejecutar el procedimiento CAN_REDEF_TABLE de éste paquete para corroborar que la tabla puede redefinirse online.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; exec DBMS_REDEFINITION.CAN_REDEF_TABLE('TEST', 'TEST_TDE', dbms_redefinition.cons_use_pk);&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:00.31&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como el procedimiento terminó sin errores, quiere decir que la tabla puede ser redefinida online.&lt;br /&gt;Ahora vamos a comenzar el proceso de redefinir la tabla. Para ésto, primero tenemos que crear una tabla intermedia (TEST_TDE_TEMP) con la estructura de la tabla TEST_TDE en donde Oracle va a colocar los datos de la tabla original de manera temporal.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test_tde MODIFY (num_tarjeta DECRYPT);&lt;br /&gt;&lt;br /&gt;Tabla modificada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:01:04.53&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SET long 400000000&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT dbms_metadata.get_ddl('TABLE', 'TEST_TDE', 'TEST') FROM dual;&lt;br /&gt;&lt;br /&gt;DBMS_METADATA.GET_DDL('TABLE','TEST_TDE','TEST')&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;CREATE TABLE "TEST"."TEST_TDE"&lt;br /&gt;(    "ID" NUMBER,&lt;br /&gt;"NOMBRE" VARCHAR2(44),&lt;br /&gt;"NUM_TARJETA" NUMBER,&lt;br /&gt;CONSTRAINT "TEST_TDE_ID_PK" PRIMARY KEY ("ID")&lt;br /&gt;USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS&lt;br /&gt;STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645&lt;br /&gt;PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)&lt;br /&gt;TABLESPACE "USERS"  ENABLE&lt;br /&gt;) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING&lt;br /&gt;STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645&lt;br /&gt;PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)&lt;br /&gt;TABLESPACE "USERS"&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE TEST_TDE_TEMP&lt;br /&gt;2  (&lt;br /&gt;3  ID          NUMBER,&lt;br /&gt;4  NOMBRE      VARCHAR2(44),&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5  NUM_TARJETA NUMBER ENCRYPT,&lt;/span&gt;&lt;br /&gt;6  CONSTRAINT TEST_TDE_TEMP_ID_PK PRIMARY KEY (ID)&lt;br /&gt;7  );&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pueden notar, lo primero que hice fue desencriptar la columna NUM_TARJETA para poder volver a encriptarla con ésta técnica. Luego utilicé el paquete DBMS_METADATA para ver la estructura de la tabla TEST_TDE y poder crear una similar con el nombre TEST_TDE_TEMP.&lt;br /&gt;&lt;br /&gt;Ahora vamos a redefinir la tabla con el procedimiento START_REDEF_TABLE.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; exec dbms_redefinition.start_redef_table('TEST', 'TEST_TDE', 'TEST_TDE_TEMP', 'id, nombre, num_tarjeta', DBMS_REDEFINITION.CONS_USE_PK);&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:41.18&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*) FROM test_tde_temp;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;1000000&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; desc TEST_TDE_TEMP&lt;br /&gt;&lt;br /&gt;Nombre                                                 Nulo?   Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;ID                                                    NOT NULL NUMBER&lt;br /&gt;NOMBRE                                                         VARCHAR2(44)&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;NUM_TARJETA                                                    NUMBER ENCRYPT&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT master, log_table FROM dba_mview_logs WHERE log_table LIKE '%TEST%';&lt;br /&gt;&lt;br /&gt;MASTER                         LOG_TABLE&lt;br /&gt;------------------------------ ------------------------------&lt;br /&gt;TEST_TDE                       MLOG$_TEST_TDE&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense, que al redefinir la tabla, Oracle copió los datos de la tabla TEST_TDE y los insertó en la tabla transitoria TEST_TDE_TEMP con la columna NUM_TARJETA encriptada. A su vez, Oracle creó una vista materializada para registrar cualquier cambio que se realice sobre la tabla TEST_TDE para luego, en el momento de hacer la sincronización de datos entre las 2 tablas, pueda impactar cualquier cambio realizado.&lt;br /&gt;&lt;br /&gt;Antes de realizar el proceso de sincronización entre las 2 tablas, voy a agregar algunos registros a la tabla TEST_TDE para ver si luego de la sincronización, los últimos cambios que realicé fueron impactados. Recuerden, que todos éstos pasos los estamos haciendo online, y que por el momento, los usuarios siguen trabajando sobre la tabla TEST_TDE sin ningún tipo de impacto en cuanto a la performance.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test_tde&lt;br /&gt;2  SELECT * FROM&lt;br /&gt;3  (&lt;br /&gt;4  SELECT level id, 'nom_'||level, round(dbms_random.value(100000000000,900000000000))&lt;br /&gt;5  FROM dual&lt;br /&gt;6  CONNECT BY level &lt;= 1000100  &lt;br /&gt;7  )  &lt;br /&gt;8  WHERE id &gt; 1000000;&lt;br /&gt;&lt;br /&gt;100 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:06.93&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; COMMIT;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a sincronizar con el procedimiento SYNC_INTERIM_TABLE, las 2 tablas para aplicar cualquier cambio realizado en la tabla TEST_TDE a la tabla TEST_TDE_TEMP.&lt;br /&gt;En éste paso en adelante, necesitamos una ventana de tiempo muy chica para ejecutar los 2 procedimientos que restan. En esta ventana de tiempo, los usuarios no deben estar modificando la tabla TEST_TDE.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; exec DBMS_REDEFINITION.SYNC_INTERIM_TABLE('TEST', 'TEST_TDE', 'TEST_TDE_TEMP');&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:00.29&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*) FROM test_tde_temp;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;1000100&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:06.92&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; desc TEST_TDE&lt;br /&gt;&lt;br /&gt;Nombre                                                 Nulo?   Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;ID                                                    NOT NULL NUMBER&lt;br /&gt;NOMBRE                                                         VARCHAR2(44)&lt;br /&gt;NUM_TARJETA                                                    NUMBER&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los 100 registros que habiamos agregado a la tabla TEST_TDE fueron impactados con éxito en la tabla TEST_TDE_TEMP! Para finalizar, lo único que resta es ejecutar el procedimiento FINISH_REDEF_TABLE para aplicar en la tabla TEST_TDE todos los cambios de la tabla TEST_TDE_TEMP (incluyendo la columna encriptada).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_10gR2&gt; exec DBMS_REDEFINITION.FINISH_REDEF_TABLE('TEST', 'TEST_TDE', 'TEST_TDE_TEMP');&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:00.37&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; desc TEST_TDE&lt;br /&gt;&lt;br /&gt;Nombre                                                 Nulo?   Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;ID                                                    NOT NULL NUMBER&lt;br /&gt;NOMBRE                                                         VARCHAR2(44)&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;NUM_TARJETA                                                    NUMBER ENCRYPT&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; DROP TABLE test_tde_temp;&lt;br /&gt;&lt;br /&gt;Tabla borrada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con el mas mínimo impacto en la performance, pudimos encriptar la columna NUM_TARJETA de la tabla TEST_TDE (que contiene 1 millón de registros) y no afectar las operaciones de los usuarios online sobre esa tabla! Sólo tuvimos que necesitar una ventana de tiempo de 1 minuto para colocar la tabla TEST_TDE nuevamente disponible para todos los usuarios.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3736087926348268718?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3736087926348268718/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3736087926348268718' title='13 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3736087926348268718'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3736087926348268718'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/05/encriptando-columnas-con-tde-en-tablas.html' title='Encriptando columnas con TDE (Transparent Data Encryption) en tablas con millones de registros'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-8680256895345343728</id><published>2009-04-30T15:11:00.006-03:00</published><updated>2009-04-30T16:09:05.983-03:00</updated><title type='text'>En lo posible, evitá utilizar el comando "EXPLAIN PLAN FOR" !!!</title><content type='html'>"EXPLAIN PLAN FOR" tiene problemas...&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;1er. PROBLEMA: Trata todas las Bind Variables como VARCHAR2.&lt;/li&gt;&lt;/ul&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DESC emp&lt;br /&gt;&lt;br /&gt;Nombre                  Nulo?    Tipo&lt;br /&gt;----------------------- -------- -------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt; EMPNO                   NOT NULL NUMBER(4)&lt;/span&gt;&lt;br /&gt;ENAME                            VARCHAR2(10)&lt;br /&gt;JOB                              VARCHAR2(9)&lt;br /&gt;GENDER                           VARCHAR2(1)&lt;br /&gt;MGR                              NUMBER(4)&lt;br /&gt;HIREDATE                         DATE&lt;br /&gt;SAL                              NUMBER(7,2)&lt;br /&gt;COMM                             NUMBER(7,2)&lt;br /&gt;DEPTNO                           NUMBER(2)&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; VAR &lt;span style="font-weight: bold;"&gt;bind NUMBER&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXECUTE :bind := 7900&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;2  SELECT * FROM emp WHERE empno = :bind;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM table(dbms_xplan.display('plan_table',null,'typical'));&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;--------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |              |     1 |    39 |     1   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| EMP          |     1 |    39 |     1   (0)| 00:00:01 |&lt;br /&gt;|*  2 |   INDEX UNIQUE SCAN         | EMP_EMPNO_PK |     1 |       |     0   (0)| 00:00:01 |&lt;br /&gt;--------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;2 - access("EMPNO"=&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;TO_NUMBER(:BIND)&lt;/span&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, creé una variable del tipo NUMBER pero al ejecutar el comando "EXPLAIN PLAN FOR" utilizando esa variable, Oracle la convirtió en una variable del tipo VARCHAR2 para luego aplicarle la función de conversión TO_NUMBER y volver a convertirla en tipo NUMBER.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;2do. PROBLEMA: Puede NO mostrarte el plan de ejecución real que será utilizado en el ejecución de tu consulta.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DESC dept&lt;br /&gt;Nombre                  Nulo?    Tipo&lt;br /&gt;----------------------- -------- ----------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt; DEPTNO                  NOT NULL VARCHAR2(10)&lt;/span&gt;&lt;br /&gt;DNAME                            VARCHAR2(14)&lt;br /&gt;LOC                              VARCHAR2(13)&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM dept;&lt;br /&gt;&lt;br /&gt;DEPTNO     DNAME          LOC&lt;br /&gt;---------- -------------- -------------&lt;br /&gt;10         ACCOUNTING     NEW YORK&lt;br /&gt;20         RESEARCH       DALLAS&lt;br /&gt;30         SALES          CHICAGO&lt;br /&gt;40         OPERATIONS     BOSTON&lt;br /&gt;&lt;br /&gt;4 filas seleccionadas.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; VAR &lt;span style="font-weight: bold;"&gt;bind NUMBER;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXECUTE :bind := 20&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;2  SELECT * FROM dept WHERE deptno = :bind;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM table(&lt;span style="font-weight: bold;"&gt;dbms_xplan.display&lt;/span&gt;('plan_table',null,'typical'));&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |                |     1 |    20 |     1   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT           |     1 |    20 |     1   (0)| 00:00:01 |&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|*  2 |   INDEX UNIQUE SCAN         | DEPT_DEPTNO_PK |     1 |       |     0   (0)| 00:00:01 |&lt;/span&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;2 - access("DEPTNO"=:BIND)&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM dept WHERE deptno = :bind;&lt;br /&gt;&lt;br /&gt;DEPTNO     DNAME          LOC&lt;br /&gt;---------- -------------- -------------&lt;br /&gt;20         RESEARCH       DALLAS&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM table(&lt;span style="font-weight: bold;"&gt;dbms_xplan.display_cursor&lt;/span&gt;(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|*  1 |  TABLE ACCESS FULL| DEPT |      1 |      1 |      1 |00:00:00.01 |       8 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;1 - filter(&lt;span style="font-weight: bold; color: rgb(255, 0, 0);"&gt;TO_NUMBER("DEPTNO")=:BIND&lt;/span&gt;)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pueden observar, la primera vez que obtuve el plan de ejecución de la consulta, lo hice con el comando "EXPLAIN PLAN FOR" y vemos que estamos utilizando un índice para acceder a los datos. La segunda vez, optamos por ejecutar la consulta y luego obtener el plan de ejecución REAL (utilizando dbms_xplan.display_cursor). Qué observamos? Oracle está realizando un acceso a datos através de un FULL SCAN de la tabla. Esto sucedió porque el tipo de dato VARCHAR2 es siempre elegido para ser convertido en una comparación de tipos de datos distintos. En este caso, como la columna DEPTNO es del tipo VARCHAR2, y como estoy comparando esa columna con una variable del tipo NUMBER, Oracle tuvo que aplicar la función TO_NUMBER a la columna DEPTNO y como ya sabemos, si aplicamos una función a una columna indexada, Oracle no puede utilizar el índice en esa columna ya que la función lo deshabilita. Cuando obtuve el plan de ejecución de la primer consulta no hubo ningún tipo de conversión de datos (es por eso que accedimos por índice) ya que estoy comparando una columna del tipo VARCHAR2 (deptno) con una variable NUMBER... pero que en realidad esa variable NUMBER es un VARCHAR2 (ya que como dijimos anteriormente, si ejecutamos el comando "EXPLAIN PLAN FOR", Oracle trata todas las Bind Variables como VARCHAR2).&lt;br /&gt;&lt;br /&gt;Siempre es bueno tener en mente cuál es la diferencia entre lo IDEAL y lo REAL. Lo IDEAL para éste caso sería acceder por índice a los datos, lo REAL es que Oracle está realizando lo contrario.&lt;br /&gt;&lt;p:colorscheme colors="#FFFFFF,#000000,#777777,#000000,#FD0000,#C0C0C0,#4D4D4D,#667263"&gt;  &lt;/p:colorscheme&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-8680256895345343728?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/8680256895345343728/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=8680256895345343728' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8680256895345343728'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8680256895345343728'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/04/en-lo-posible-evita-utilizar-el-comando.html' title='En lo posible, evitá utilizar el comando &quot;EXPLAIN PLAN FOR&quot; !!!'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7379671309246560814</id><published>2009-04-29T18:03:00.005-03:00</published><updated>2009-04-30T19:10:46.930-03:00</updated><title type='text'>Diferencias entre COUNT(1) y COUNT(*) - Parte 2</title><content type='html'>Hace unas semanas, se me acercaron y me hicieron la siguiente pregunta:&lt;br /&gt;&lt;br /&gt;"Me dijeron que si en una consulta se coloca el COUNT(*), por cada registro que leamos en un acceso por índice, vamos a tener que acceder también a la tabla ya que el símbolo * significa que estoy colocando todas las columnas de la tabla en la consulta, y si no tengo todas las columnas de la tabla en el índice, entonces Oracle tiene que acceder a la tabla a buscar el resto de las columnas. Es cierto?".&lt;br /&gt;&lt;br /&gt;Bueno, no... no es cierto. Para validar el porque digo ésto, primero veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test AS&lt;br /&gt;2  SELECT level id, 'texto_'||level texto&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 100000 ; &lt;br /&gt;&lt;br /&gt;Tabla creada. &lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE UNIQUE INDEX test_id_uq ON test(id) ;&lt;br /&gt;&lt;br /&gt;Índice creado.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST',CASCADE=&gt;TRUE) ;&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, veamos el plan de ejecución de la siguiente consulta:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_10gR2&gt; SELECT COUNT(*)&lt;br /&gt;2  FROM test&lt;br /&gt;3  WHERE id = 100;&lt;br /&gt;&lt;br /&gt;COUNT(*)&lt;br /&gt;----------&lt;br /&gt;      1&lt;br /&gt;&lt;br /&gt;1 fila seleccionada.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID  d6urw3zfuxz32, child number 0&lt;br /&gt;-------------------------------------&lt;br /&gt;SELECT COUNT(*) FROM test WHERE id = 100&lt;br /&gt;&lt;br /&gt;Plan hash value: 4041652814&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name       | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;-------------------------------------------------------------------------------------------&lt;br /&gt;|   1 |  SORT AGGREGATE    |            |      1 |      1 |      1 |00:00:00.01 |       2 |&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|*  2 |   INDEX UNIQUE SCAN| TEST_ID_UQ |      1 |      1 |      1 |00:00:00.01 |       2 |&lt;/span&gt;&lt;br /&gt;-------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;2 - access("ID"=100)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, coloqué el COUNT con el símbolo * y accedí al índice con el ID 100; pero luego de acceder al índice NO accedí a la tabla, simplemente accedí al índice (ya que tiene la columna que estoy utilizando en el predicado). Como anteriormente dije en el post "Diferencias entre COUNT(1) y COUNT(*) - Parte 1", no existe ninguna diferencia entre el COUNT(1) y COUNT(*), pero si hay diferencia si ejecutamos COUNT(*) y COUNT(nonbre_de_columna) ya que si colocamos una columna de la tabla en el COUNT, Oracle hace un conteo sólo de los valores de esa columna que NO tengan valores nulos.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7379671309246560814?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7379671309246560814/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7379671309246560814' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7379671309246560814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7379671309246560814'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2009/04/diferencias-entre-count1-y-count-parte.html' title='Diferencias entre COUNT(1) y COUNT(*) - Parte 2'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-8105145605436188367</id><published>2008-09-29T11:19:00.003-03:00</published><updated>2008-09-29T11:25:27.172-03:00</updated><title type='text'>Explain Plan Vs. Bind Variables</title><content type='html'>&lt;div&gt;Obtener el plan de ejecución de una consulta que contiene Bind Variables sin haberlas reemplazado??? NO!!! NO!!! NO!!!!!!!!!!&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ya hablamos en otras ocasiones del beneficio que obtenemos al utilizar Bind Variables y también explicamos qué son. Cuando ejecutamos una consulta con Bind Variables (sin haberlas reemplazado) para obtener el plan de ejecución, el optimizador de costos (CBO) no sabe el valor de la Bind Variable; y por lo tanto, calcula la selectividad del filtro utilizando reglas definidas por defecto. Que quiere decir ésto? Que el plan de ejecución que obtenemos puede ser MUY distinto al plan de ejecución real!!! Porqué muy distinto? Porque todo depende del valor con el que se reemplazará la Bind Variable y el tipo de dato de la misma.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Veamos un ejemplo:&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;&lt;div&gt;SQL_10gR&gt; CREATE TABLE test AS&lt;/div&gt;&lt;div&gt;  2 SELECT TO_CHAR(level) id, 'test'||level descripcion&lt;/div&gt;&lt;div&gt;  3 FROM dual&lt;/div&gt;&lt;div&gt;  4 CONNECT BY level &lt;= 100000;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Table created.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SQL_10gR&gt; DESC test&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Name                    Null?    Type&lt;/div&gt;&lt;div&gt;----------------------- -------- ----------------&lt;/div&gt;&lt;div&gt;ID                               VARCHAR2(40)&lt;/div&gt;&lt;div&gt;DESCRIPCION                      VARCHAR2(44)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SQL_10gR&gt; CREATE UNIQUE INDEX test_uq ON test(id, descripcion);&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Index created.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SQL_10gR&gt; EXEC dbms_stats.gather_table_stats(user, 'TEST', cascade=&gt;true) ;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;PL/SQL procedure successfully completed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/pre&gt;&lt;div&gt;Supongamos que detectamos un problema grave en la performance de una de nuestras aplicaciones. Al identificar la consulta que nos está causando problemas, obtenemos el plan de ejecución de la misma para ver si está accediendo correctamente...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ejecutamos la consulta &lt;span class="Apple-style-span" style="font-style: italic;"&gt;con &lt;/span&gt;Bind Variable:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;div&gt;SQL_10gR&gt; EXPLAIN PLAN FOR&lt;/div&gt;&lt;div&gt;  2 SELECT descripcion&lt;/div&gt;&lt;div&gt;  3 FROM test&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;  4 WHERE id = :b1;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Explained.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SQL_10gR&gt; @explains&lt;/div&gt;&lt;div&gt;Plan hash value: 1087767317&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;----------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;| Id | Operation        | Name    | Rows  | Bytes | Cost (%CPU)| Time      |&lt;/div&gt;&lt;div&gt;----------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;|  0 | SELECT STATEMENT |         |     1 |    15 |       2 (0)|  00:00:01 |&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;|* 1 | INDEX RANGE SCAN | TEST_UQ |     1 |    15 |       2 (0)|  00:00:01 |&lt;/span&gt;&lt;/div&gt;&lt;div&gt;----------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Predicate Information (identified by operation id):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;---------------------------------------------------&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;1 - access("ID"=:B1)&lt;/span&gt;&lt;/div&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Por lo que vemos en el plan de ejecución, si filtramos la columna ID con un valor del mismo tipo de dato, el optimizador eligirá acceder por índice en vez de realizar un full scan de la tabla. Esto suena lógico sabiendo que los valores de la columna ID son únicos y que por cada valor con el que filtremos, a lo sumo obtendremos una ocurrencia del mismo valor en la tabla.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Ejecutamos una consulta &lt;span class="Apple-style-span" style="font-style: italic;"&gt;sin &lt;/span&gt;Bind Variable:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;div&gt;SQL_10gR&gt; EXPLAIN PLAN FOR&lt;/div&gt;&lt;div&gt;  2 SELECT descripcion&lt;/div&gt;&lt;div&gt;  3 FROM test&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;  4 WHERE id = 10000;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Explained.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SQL_10gR&gt; @explains&lt;/div&gt;&lt;div&gt;Plan hash value: 1357081020&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;--------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;| Id | Operation        | Name | Rows | Bytes | Cost (%CPU)| Time        |&lt;/div&gt;&lt;div&gt;--------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;|  0 | SELECT STATEMENT |      |    1 |    15 |      54 (4)|    00:00:01 |&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;|* 1 | TABLE ACCESS FULL| TEST |    1 |    15 |      54 (4)|    00:00:01 |&lt;/span&gt;&lt;/div&gt;&lt;div&gt;--------------------------------------------------------------------------&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Predicate Information (identified by operation id):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;---------------------------------------------------&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;1 - filter(TO_NUMBER("ID")=10000)&lt;/span&gt;&lt;/div&gt;&lt;/pre&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Pero qué sucede si filtramos la columna ID con un valor de distinto tipo de dato? Como en éste caso estamos realizando una conversión implícita, el optimizador no puede utilizar el índice que tenemos creado en la tabla y por lo tanto se ve forzado a realizar un full scan de la misma.&lt;/div&gt;&lt;div&gt;Si en nuestra aplicación el problema es justamente éste (que estamos realizando una conversión implícita), si no reemplazamos las Bind Variables con valores reales, estaremos pensando que el optimizador está accediendo de la manera correcta... cuando en realidad ésto no es cierto.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Recuerden lo siguiente:&lt;/span&gt; Siempre que obtengan el plan de ejecución de una consulta... reemplacen las Bind Variables con valores reales!!! En caso contrario... no deberíamos fiarnos demasiado con el plan de ejecución obtenido.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-8105145605436188367?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/8105145605436188367/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=8105145605436188367' title='12 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8105145605436188367'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8105145605436188367'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2008/09/explain-plan-vs-bind-variables_29.html' title='Explain Plan Vs. Bind Variables'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7596419103284392758</id><published>2008-02-26T15:39:00.008-02:00</published><updated>2008-05-13T09:12:14.470-03:00</updated><title type='text'>¿Tunear en base al COSTO del plan de ejecución?</title><content type='html'>Todos los días observo que alguien está queriendo tunear una consulta en base al costo del plan de ejecución. Pero... ¿Qué es el COSTO? ¿Qué representa? La respuesta es simple: El costo representa unidades de trabajo o recursos utilizados. El optimizador usa I/O a disco, CPU y memoria como unidades de trabajo. Entonces, el costo para una determinada consulta representa una estimación de la cantidad de I/O a disco, de CPU y memoria que se utilizará para la ejecución de la consulta.&lt;br /&gt;&lt;br /&gt;Bien, con ésto ya dicho, porqué hay personas que tratan de tunear una consulta en base al costo??? El costo es simplemente un número que le asigna el optimizador de costos (CBO) a la consulta para saber qué plan de ejecución elegir entre todos los planes que genera en el momento de la optimización (el plan de ejecución que se genera con el menor costo es el que Oracle utiliza para ejecutar nuestra consulta), pero no existe un "mejor número" que debemos tener en mente para deducir si una consulta es óptima o no.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;NO&lt;/span&gt; debemos tunear en base al costo. &lt;span style="font-weight: bold;"&gt;SI&lt;/span&gt; debemos tunear en base a los I/O lógicos (LIO's).&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;2  SELECT level id, 'nombre_'||level nom&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 100000 ; &lt;br /&gt;&lt;br /&gt;Table created. &lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.gather_table_stats(user,'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT nom&lt;br /&gt;2  FROM test&lt;br /&gt;3  WHERE id = 50000 ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;0      SELECT STATEMENT Optimizer=CHOOSE (&lt;span style="font-weight: bold;"&gt;Cost=49&lt;/span&gt; Card=1 Bytes=17)&lt;br /&gt;1    0   TABLE ACCESS (FULL) OF 'TEST' (Cost=49 Card=1 Bytes=17)&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;     5  recursive calls&lt;br /&gt;     0  db block gets&lt;br /&gt;  &lt;span style="font-weight: bold;"&gt; 323  consistent gets&lt;/span&gt;&lt;br /&gt;     0  physical reads&lt;br /&gt;     0  redo size&lt;br /&gt;   335  bytes sent via SQL*Net to client&lt;br /&gt;   495  bytes received via SQL*Net from client&lt;br /&gt;     2  SQL*Net roundtrips to/from client&lt;br /&gt;     2  sorts (memory)&lt;br /&gt;     0  sorts (disk)&lt;br /&gt;     1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, el costo de la consulta es de 49. Si yo tuneo en base al costo no puedo saber si el plan de ejecución que se está utilizando para ésta consulta es óptimo o no porque no sé que costo sería el ideal para saberlo. Es por eso que NO debemos tunear en base al costo. Por otro lado, podríamos tunear en base a la cantidad de I/O lógicos que se estén realizando. En éste ejemplo, se realizan 323 LIO's. Si observamos la consulta, estoy seleccionando la columna NOM que corresponde al ID 50000 . Como creé la tabla de forma tal que todos los ID's sean únicos, ésta consulta me debería traer un solo registro. Si nos ponemos a pensar, hacer 323 LIO's para traer sólo un registro es demasiado. En éste momento es en donde nos damos cuenta que tenemos un problema de performance porque estamos haciendo un Full Scan de una tabla de 100.000 registros para buscar solamente el ID 50000 que nos devuelve un sólo registro.&lt;br /&gt;&lt;br /&gt;Veamos qué sucede si creamos un índice único por la columna ID,NOM...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX test_id_nom_uq ON test(id,nom) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.gather_index_stats(user,'TEST_ID_NOM_UQ') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT nom&lt;br /&gt;2  FROM test&lt;br /&gt;3  WHERE id = 50000 ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;0      SELECT STATEMENT Optimizer=CHOOSE (&lt;span style="font-weight: bold;"&gt;Cost=2&lt;/span&gt; Card=1 Bytes=17)&lt;br /&gt;1    0   INDEX (RANGE SCAN) OF 'TEST_ID_NOM_UQ' (UNIQUE) (Cost=2 Card=1 Bytes=17)&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;     0  recursive calls&lt;br /&gt;     0  db block gets&lt;br /&gt;   &lt;span style="font-weight: bold;"&gt;  3  consistent gets&lt;/span&gt;&lt;br /&gt;     0  physical reads&lt;br /&gt;     0  redo size&lt;br /&gt;   335  bytes sent via SQL*Net to client&lt;br /&gt;   495  bytes received via SQL*Net from client&lt;br /&gt;     2  SQL*Net roundtrips to/from client&lt;br /&gt;     0  sorts (memory)&lt;br /&gt;     0  sorts (disk)&lt;br /&gt;     1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Luego de crear un índice único por la columna ID,NOM, ejecutando nuevamente la consulta, notamos que ahora sólo estamos realizando 3 LIO's y, por consiguiente, el costo se decrementó ya que estamos utilizando menos recursos que el caso anterior.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7596419103284392758?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7596419103284392758/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7596419103284392758' title='8 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7596419103284392758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7596419103284392758'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2008/02/tunear-en-base-al-costo-del-plan-de.html' title='¿Tunear en base al COSTO del plan de ejecución?'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6058203315160826</id><published>2008-02-26T09:45:00.005-02:00</published><updated>2010-12-23T22:24:45.397-03:00</updated><title type='text'>¿Cuándo utilizar HINTS?</title><content type='html'>Aunque el optimizador es increiblemente exacto a la hora de elegir el correcto plan de ejecución para las consultas, no es perfecto! Oracle nos provee de Hints que podemos utilizar en una determinada consulta para influenciar al optimizador a que elija otro determinado plan de ejecución, con la esperanza de que obtengamos mejor performance para la consulta.&lt;br /&gt;&lt;br /&gt;Una pregunta que me hacen a menudo es: ¿Cuándo utilizar HINTS? Mi respuesta es...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Los Hints se utilizan como ULTIMO recurso. Nunca como el primero ni tampoco como un estándar a seguir.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Son útiles si estamos utilizando el optimizador por reglas (RBO), pero con el optimizador por costos (CBO) deberíamos dejar que el optimizador tome las decisiones sobre qué plan de ejecución sería el más conveniente para nuestra consulta. Si utilizamos el CBO, DEBEMOS obtener estadísticas para los objetos que utilizamos para lograr que el CBO pueda decidir correctamente el plan de ejecución que se utilizará.&lt;br /&gt;&lt;br /&gt;Suele suceder que muchas personas utilizan los Hints porque analizan un plan de ejecución y piensan que el CBO está tomando una mala decisión al hacer un Full Scan de una tabla, y entonces, colocan un Hint para que acceda por índice. Otras personas utilizan los Hints porque piensan que el CBO tomó una mala decisión a la hora de elegir el plan de ejecución correcto para nuestra consulta. Si ésto es verdad, porque el CBO eligió mal? Obtuvimos correctamente las estadísticas para nuestras tablas e índices? Sería conveniente obtener Hitogramas también? Nos está faltando crear un índice? Todas estas cuestiones deberíamos investigarlas en detalle cuando vemos que el CBO no se comporta como nosotros esperamos. &lt;br /&gt;&lt;br /&gt;El CBO es sólo un software, no tiene una mente propia. Toma sus decisiones en base a la información (estadísticas) que nosotros le damos. Por tal motivo, si le damos información correcta y actual, podremos obtener un plan de ejecución óptimo para la ejecución de nuestra consulta.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6058203315160826?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6058203315160826/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6058203315160826' title='7 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6058203315160826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6058203315160826'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2008/02/cundo-utilizar-hints.html' title='¿Cuándo utilizar HINTS?'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1703366817046458341</id><published>2007-10-16T16:19:00.000-03:00</published><updated>2007-10-16T17:11:46.035-03:00</updated><title type='text'>DB_FILE_MULTIBLOCK_READ_COUNT (MBRC)</title><content type='html'>El parámetro DB_FILE_MULTIBLOCK_READ_COUNT especifica la cantidad de bloques que van a ser leídos en cada I/O a través de un Full Scan.&lt;br /&gt;&lt;br /&gt;En Oracle 10g Release 2, el valor por default de éste parámetro, es el valor que corresponde a la cantidad máxima de I/O que se puede realizar de forma más eficiente. &lt;br /&gt;&lt;br /&gt;En ambientes OLTP o Batch, éste parámetro suele setearse en valores entre 4 y 16 bloques. En ambientes DSS o Data Warehouse, éste parámetro suele setearse en un valor mayor.&lt;br /&gt;&lt;br /&gt;Este parámetro afecta al Optimizador de Costos (CBO) ya que si seteamos un valor alto, el CBO puede ser influenciado en elegir realizar un Full Scan en vez de acceder por índice. Como en los ambientes DSS o Data Warehouse suelen utilizarse planes de ejecución que incluyen Full Scan, aumentar el valor de éste parámetro puede ser beneficioso.&lt;br /&gt;&lt;br /&gt;El máximo valor que podemos setear depende del sistema operativo.&lt;br /&gt;La fórmula que se utiliza para buscar el valor de éste parámetro es:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DB_FILE_MULTIBLOCK_READ_COUNT = ( (MAX I/O SIZE) / DB_BLOCK_SIZE )&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Si elegimos un valor mayor al máximo soportado, Oracle simplemente elige el valor máximo que puede utilizar.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo de cómo buscar el valor máximo del parámetro DB_FILE_MULTIBLOCK_READ_COUNT sin utilizar la fórmula:&lt;br /&gt;&lt;br /&gt;Primero, veamos el valor actual del MBRC:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; show parameter multiblock&lt;br /&gt;&lt;br /&gt;NAME_COL_PLUS_SHOW_PARAM       TYPE        VALUE_COL_PLUS_SHOW_PARAM&lt;br /&gt;------------------------------ ----------- ------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;db_file_multiblock_read_count  integer     16&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observemos el Explain Plan de una de las tablas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT /*+ full(test) noparallel(test) nocache(test) */ count(*)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;Plan hash value: 3740828345&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation             | Name                  | Rows  | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|   0 | SELECT STATEMENT      |                       |     1 | 46709   (1)| 00:10:55 |&lt;/span&gt;&lt;br /&gt;|   1 |  SORT AGGREGATE       |                       |     1 |            |          |&lt;br /&gt;|   2 |   TEST ACCESS FULL    | TEST                  |    20M| 46709   (1)| 00:10:55 |&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El costo de la consulta es 46709. &lt;br /&gt;Veamos qué sucede si seteo el valor del parámetro muy alto:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET DB_FILE_MULTIBLOCK_READ_COUNT = 50000 ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; show parameter multiblock&lt;br /&gt;&lt;br /&gt;NAME_COL_PLUS_SHOW_PARAM       TYPE        VALUE_COL_PLUS_SHOW_PARAM&lt;br /&gt;------------------------------ ----------- ------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;db_file_multiblock_read_count  integer     64&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, como podemos observar, seteamos el parámetro en 50.000 bloques; pero como excedemos el máximo permitido, Oracle setea el máximo valor que puede alcanzar.&lt;br /&gt;&lt;br /&gt;Ahora veamos si Oracle realmente está utilizando el nuevo MBRC que acabamos de setear:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET EVENTS '10046 trace name context forever, level 8' ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT /*+ full(test) noparallel(test) nocache(test) */ count(*)&lt;br /&gt;  2  FROM test ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;  52673028&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET EVENTS '10046 trace name context off' ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @trace_file_name&lt;br /&gt;&lt;br /&gt;TRACE_FILE_NAME&lt;br /&gt;-----------------------------&lt;br /&gt;testdb_ora_11379.trc&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;[test@linux_test udump]$ cat testdb_ora_11379.trc | grep "scattered read" | awk '{ split ($11,listado,"="); print listado[2];}' | sort -n | tail -1&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;64&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;[test@linux_test udump]$ more testdb_ora_11379.trc | grep scattered&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;WAIT #1: nam='db file scattered read' ela= 1411 file#=115 block#=241927 &lt;span style="font-weight:bold;"&gt;blocks=64&lt;/span&gt; obj#=70390 tim=1164040832168738&lt;br /&gt;WAIT #1: nam='db file scattered read' ela= 1394 file#=115 block#=241991 &lt;span style="font-weight:bold;"&gt;blocks=64&lt;/span&gt; obj#=70390 tim=1164040832171544&lt;br /&gt;WAIT #1: nam='db file scattered read' ela= 1426 file#=115 block#=242055 &lt;span style="font-weight:bold;"&gt;blocks=64&lt;/span&gt; obj#=70390 tim=1164040832174390&lt;br /&gt;WAIT #1: nam='db file scattered read' ela= 1399 file#=115 block#=242119 &lt;span style="font-weight:bold;"&gt;blocks=64&lt;/span&gt; obj#=70390 tim=1164040832177182&lt;br /&gt;WAIT #1: nam='db file scattered read' ela= 1391 file#=115 block#=242183 &lt;span style="font-weight:bold;"&gt;blocks=64&lt;/span&gt; obj#=70390 tim=1164040832179966&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien. Vemos que efectivamente estamos leyendo 64 bloques en cada I/O que realizamos.&lt;br /&gt;&lt;br /&gt;Ahora ejecutamos nuevamente nuestra consulta y observamos el Explain Plan:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT /*+ full(test) noparallel(test) nocache(test) */ count(*)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;Plan hash value: 3740828345&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation             | Name                  | Rows  | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;|   0 | SELECT STATEMENT      |                       |     1 | 41994   (1)| 00:09:49 |&lt;/span&gt;&lt;br /&gt;|   1 |  SORT AGGREGATE       |                       |     1 |            |          |&lt;br /&gt;|   2 |   TEST ACCESS FULL    | TEST                  |    20M| 41994   (1)| 00:09:49 |&lt;br /&gt;---------------------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora el costo de la consulta pasa a ser 41994. &lt;br /&gt;&lt;br /&gt;Hay que tener mucho cuidado si decidimos modificar éste parámetro ya que no siempre obtenemos un beneficio. Recuerden que modificando el MBRC por default puede producir que nuestro sistema funcione peor, igual o mejor. Por lo general, el MBRC por default suele ser el correcto.&lt;br /&gt;&lt;br /&gt;Mi consejo es que no modifiquen el MBRC si no es necesario.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1703366817046458341?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1703366817046458341/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1703366817046458341' title='6 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1703366817046458341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1703366817046458341'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/10/dbfilemultiblockreadcount-mbrc.html' title='DB_FILE_MULTIBLOCK_READ_COUNT (MBRC)'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-967170335870270678</id><published>2007-09-29T17:14:00.000-03:00</published><updated>2007-09-30T11:22:32.971-03:00</updated><title type='text'>Problemas utilizando Analytic Functions junto con PL/SQL  en 8i</title><content type='html'>La idea de éste post no es explicar el funcionamiento de las Analytic Functions, sino el error PLS-00103 al utilizar Analytic Functions.&lt;br /&gt;La mejor manera de explicar éste problema, es realizando un ejemplo.&lt;br /&gt;&lt;br /&gt;La tabla de prueba que vamos a utilizar en nuestro ejemplo, contiene los siguientes registros:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; SELECT * FROM test ;&lt;br /&gt;&lt;br /&gt;        ID      NIVEL    SALARIO&lt;br /&gt;---------- ---------- ----------&lt;br /&gt;        10          1       2500&lt;br /&gt;        20          2       3000&lt;br /&gt;        30          1       3500&lt;br /&gt;        40          2       4000&lt;br /&gt;        50          1       4500&lt;br /&gt;        60          2       5000&lt;br /&gt;        70          1       5500&lt;br /&gt;        80          2       6000&lt;br /&gt;        90          1       6500&lt;br /&gt;       100          2       7000&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Primero vamos a realizar el ejemplo en una base de datos 8.1.7.4.0 &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_8i&gt; SELECT id, nivel, salario, DENSE_RANK() OVER (PARTITION BY nivel ORDER BY salario DESC) AS rank&lt;br /&gt;  2      FROM test ;&lt;br /&gt;&lt;br /&gt;        ID      NIVEL    SALARIO       RANK&lt;br /&gt;---------- ---------- ---------- ----------&lt;br /&gt;        90          1       6500          1&lt;br /&gt;        70          1       5500          2&lt;br /&gt;        50          1       4500          3&lt;br /&gt;        30          1       3500          4&lt;br /&gt;        10          1       2500          5&lt;br /&gt;       100          2       7000          1&lt;br /&gt;        80          2       6000          2&lt;br /&gt;        60          2       5000          3&lt;br /&gt;        40          2       4000          4&lt;br /&gt;        20          2       3000          5&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, si ejecutamos esa consulta con Analytic Functions en SQL*Plus, funciona a la perfección. Veamos qué sucede si intentamos ejecutar la consulta dentro de PL/SQL:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_8i&gt; CREATE PROCEDURE pr_test&lt;br /&gt;  2  IS&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      --&lt;br /&gt;  5      FOR cur IN ( SELECT id, nivel, salario, &lt;span style="font-weight:bold;"&gt;DENSE_RANK() OVER (PARTITION BY&lt;br /&gt; nivel ORDER BY salario DESC&lt;/span&gt;) AS rank&lt;br /&gt;  6                 FROM test ) LOOP&lt;br /&gt;  7          --&lt;br /&gt;  8          dbms_output.put_line('ID: '||cur.id||&lt;br /&gt;  9                               ' - NIVEL: '||cur.nivel||&lt;br /&gt; 10                               ' - SALARIO: '||cur.salario||&lt;br /&gt; 11                               ' - RANK: '||cur.rank) ;&lt;br /&gt; 12          --&lt;br /&gt; 13      END LOOP ;&lt;br /&gt; 14      --&lt;br /&gt; 15  END pr_test ;&lt;br /&gt; 16  /&lt;br /&gt;&lt;br /&gt;Warning: Procedure created with compilation errors.&lt;br /&gt;&lt;br /&gt;SQL_8i&gt; show errors&lt;br /&gt;&lt;br /&gt;Errors for PROCEDURE PR_TEST:&lt;br /&gt;&lt;br /&gt;LINE/COL     ERROR&lt;br /&gt;-----------  ------------------------------------------------------------------------&lt;br /&gt;5/63         &lt;span style="font-weight:bold;"&gt;PLS-00103&lt;/span&gt;: Encountered the symbol "(" when expecting one of the following:&lt;br /&gt;             , from&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Este error se debe a que según la Nota 147808.1, no podemos utilizar Analytic Functions dentro de PL/SQL en versiones anteriores a 9i. Podemos utilizar éste tipo de funciones en SQL, pero no en PL/SQL. Desde las versiones de la 9i en adelante, podemos utilizar Analytic Functions tanto en SQL como en PL/SQL.&lt;br /&gt;&lt;br /&gt;Hay 2 formas de solucionar éste problema:&lt;br /&gt;- Crear vistas utilizando Analytic Function y luego hacer referencia a esas vistas dentro de PL/SQL. &lt;br /&gt;- Usar Dynamic SQL.&lt;br /&gt;&lt;br /&gt;Veamos la implementación de esas 2 soluciones:&lt;br /&gt;&lt;br /&gt;Utilizando una vista...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_8i&gt; CREATE VIEW pr_test_view AS&lt;br /&gt;  2  SELECT id, nivel, salario, DENSE_RANK() OVER (PARTITION BY nivel ORDER BY s&lt;br /&gt;alario DESC) AS rank&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;View created.&lt;br /&gt;&lt;br /&gt;SQL_8i&gt; CREATE OR REPLACE PROCEDURE pr_test&lt;br /&gt;  2  IS&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      --&lt;br /&gt;  5      FOR cur IN ( SELECT id, nivel, salario, rank&lt;br /&gt;  6                   FROM &lt;span style="font-weight:bold;"&gt;pr_test_view&lt;/span&gt; ) LOOP&lt;br /&gt;  7          --&lt;br /&gt;  8          dbms_output.put_line('ID: '||cur.id||&lt;br /&gt;  9                               ' - NIVEL: '||cur.nivel||&lt;br /&gt; 10                               ' - SALARIO: '||cur.salario||&lt;br /&gt; 11                               ' - RANK: '||cur.rank) ;&lt;br /&gt; 12          --&lt;br /&gt; 13      END LOOP ;&lt;br /&gt; 14      --&lt;br /&gt; 15  END pr_test ;&lt;br /&gt; 16  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_8i&gt; EXEC pr_test&lt;br /&gt;&lt;br /&gt;ID: 90 - NIVEL: 1 - SALARIO: 6500 - RANK: 1&lt;br /&gt;ID: 70 - NIVEL: 1 - SALARIO: 5500 - RANK: 2&lt;br /&gt;ID: 50 - NIVEL: 1 - SALARIO: 4500 - RANK: 3&lt;br /&gt;ID: 30 - NIVEL: 1 - SALARIO: 3500 - RANK: 4&lt;br /&gt;ID: 10 - NIVEL: 1 - SALARIO: 2500 - RANK: 5&lt;br /&gt;ID: 100 - NIVEL: 2 - SALARIO: 7000 - RANK: 1&lt;br /&gt;ID: 80 - NIVEL: 2 - SALARIO: 6000 - RANK: 2&lt;br /&gt;ID: 60 - NIVEL: 2 - SALARIO: 5000 - RANK: 3&lt;br /&gt;ID: 40 - NIVEL: 2 - SALARIO: 4000 - RANK: 4&lt;br /&gt;ID: 20 - NIVEL: 2 - SALARIO: 3000 - RANK: 5&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Utilizando Dynamic SQL...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_8i&gt; CREATE OR REPLACE PROCEDURE pr_test&lt;br /&gt;  2  IS&lt;br /&gt;  3  --&lt;br /&gt;  4  TYPE   mi_cursor IS REF CURSOR ;&lt;br /&gt;  5  cur    mi_cursor ;&lt;br /&gt;  6  --&lt;br /&gt;  7  l_consulta  VARCHAR2(1000) ;&lt;br /&gt;  8  l_id        test.id%TYPE ;&lt;br /&gt;  9  l_nivel     test.nivel%TYPE ;&lt;br /&gt; 10  l_salario   test.salario%TYPE ;&lt;br /&gt; 11  l_rank      NUMBER ;&lt;br /&gt; 12  --&lt;br /&gt; 13  BEGIN&lt;br /&gt; 14      --&lt;br /&gt; 15      l_consulta := 'SELECT id, nivel, salario, DENSE_RANK() OVER (PARTITION&lt;br /&gt;BY nivel ORDER BY salario DESC) AS rank FROM test' ;&lt;br /&gt; 16      --&lt;br /&gt; 17      &lt;span style="font-weight:bold;"&gt;OPEN cur FOR l_consulta ;&lt;/span&gt;&lt;br /&gt; 18      --&lt;br /&gt; 19      LOOP&lt;br /&gt; 20          --&lt;br /&gt; 21          FETCH cur INTO l_id, l_nivel, l_salario, l_rank ;&lt;br /&gt; 22          --&lt;br /&gt; 23          EXIT WHEN cur%NOTFOUND ;&lt;br /&gt; 24          --&lt;br /&gt; 25          dbms_output.put_line('ID: '||l_id||&lt;br /&gt; 26                               ' - NIVEL: '||l_nivel||&lt;br /&gt; 27                               ' - SALARIO: '||l_salario||&lt;br /&gt; 28                               ' - RANK: '||l_rank) ;&lt;br /&gt; 29          --&lt;br /&gt; 30      END LOOP ;&lt;br /&gt; 31      --&lt;br /&gt; 32      CLOSE cur ;&lt;br /&gt; 33      --&lt;br /&gt; 34  END pr_test ;&lt;br /&gt; 35  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_8i&gt; EXEC pr_test&lt;br /&gt;&lt;br /&gt;ID: 90 - NIVEL: 1 - SALARIO: 6500 - RANK: 1&lt;br /&gt;ID: 70 - NIVEL: 1 - SALARIO: 5500 - RANK: 2&lt;br /&gt;ID: 50 - NIVEL: 1 - SALARIO: 4500 - RANK: 3&lt;br /&gt;ID: 30 - NIVEL: 1 - SALARIO: 3500 - RANK: 4&lt;br /&gt;ID: 10 - NIVEL: 1 - SALARIO: 2500 - RANK: 5&lt;br /&gt;ID: 100 - NIVEL: 2 - SALARIO: 7000 - RANK: 1&lt;br /&gt;ID: 80 - NIVEL: 2 - SALARIO: 6000 - RANK: 2&lt;br /&gt;ID: 60 - NIVEL: 2 - SALARIO: 5000 - RANK: 3&lt;br /&gt;ID: 40 - NIVEL: 2 - SALARIO: 4000 - RANK: 4&lt;br /&gt;ID: 20 - NIVEL: 2 - SALARIO: 3000 - RANK: 5&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a realizar el ejemplo anterior pero en una base de datos 9.2.0.8.0&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9i&gt; CREATE PROCEDURE pr_test&lt;br /&gt;  2  IS&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      --&lt;br /&gt;  5      FOR cur IN ( SELECT id, nivel, salario, DENSE_RANK() OVER (PARTITION BY&lt;br /&gt; nivel ORDER BY salario DESC) AS rank&lt;br /&gt;  6               FROM test ) LOOP&lt;br /&gt;  7          --&lt;br /&gt;  8          dbms_output.put_line('ID: '||cur.id||&lt;br /&gt;  9                               ' - NIVEL: '||cur.nivel||&lt;br /&gt; 10                               ' - SALARIO: '||cur.salario||&lt;br /&gt; 11                               ' - RANK: '||cur.rank) ;&lt;br /&gt; 12          --&lt;br /&gt; 13      END LOOP ;&lt;br /&gt; 14      --&lt;br /&gt; 15  END pr_test ;&lt;br /&gt; 16  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_9i&gt; EXEC pr_test&lt;br /&gt;&lt;br /&gt;ID: 90 - NIVEL: 1 - SALARIO: 6500 - RANK: 1&lt;br /&gt;ID: 70 - NIVEL: 1 - SALARIO: 5500 - RANK: 2&lt;br /&gt;ID: 50 - NIVEL: 1 - SALARIO: 4500 - RANK: 3&lt;br /&gt;ID: 30 - NIVEL: 1 - SALARIO: 3500 - RANK: 4&lt;br /&gt;ID: 10 - NIVEL: 1 - SALARIO: 2500 - RANK: 5&lt;br /&gt;ID: 100 - NIVEL: 2 - SALARIO: 7000 - RANK: 1&lt;br /&gt;ID: 80 - NIVEL: 2 - SALARIO: 6000 - RANK: 2&lt;br /&gt;ID: 60 - NIVEL: 2 - SALARIO: 5000 - RANK: 3&lt;br /&gt;ID: 40 - NIVEL: 2 - SALARIO: 4000 - RANK: 4&lt;br /&gt;ID: 20 - NIVEL: 2 - SALARIO: 3000 - RANK: 5&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Realizamos el ejemplo anterior en una base de datos 10.1.0.2.0, veremos el mismo resultado que en 9i...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10g&gt; CREATE PROCEDURE pr_test&lt;br /&gt;  2  IS&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      --&lt;br /&gt;  5      FOR cur IN ( SELECT id, nivel, salario, DENSE_RANK() OVER (PARTITION BY&lt;br /&gt; nivel ORDER BY salario DESC) AS rank&lt;br /&gt;  6                   FROM test ) LOOP&lt;br /&gt;  7          --&lt;br /&gt;  8          dbms_output.put_line('ID: '||cur.id||&lt;br /&gt;  9                               ' - NIVEL: '||cur.nivel||&lt;br /&gt; 10                               ' - SALARIO: '||cur.salario||&lt;br /&gt; 11                               ' - RANK: '||cur.rank) ;&lt;br /&gt; 12          --&lt;br /&gt; 13      END LOOP ;&lt;br /&gt; 14      --&lt;br /&gt; 15  END pr_test ;&lt;br /&gt; 16  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_10g&gt; EXEC pr_test&lt;br /&gt;&lt;br /&gt;ID: 90 - NIVEL: 1 - SALARIO: 6500 - RANK: 1&lt;br /&gt;ID: 70 - NIVEL: 1 - SALARIO: 5500 - RANK: 2&lt;br /&gt;ID: 50 - NIVEL: 1 - SALARIO: 4500 - RANK: 3&lt;br /&gt;ID: 30 - NIVEL: 1 - SALARIO: 3500 - RANK: 4&lt;br /&gt;ID: 10 - NIVEL: 1 - SALARIO: 2500 - RANK: 5&lt;br /&gt;ID: 100 - NIVEL: 2 - SALARIO: 7000 - RANK: 1&lt;br /&gt;ID: 80 - NIVEL: 2 - SALARIO: 6000 - RANK: 2&lt;br /&gt;ID: 60 - NIVEL: 2 - SALARIO: 5000 - RANK: 3&lt;br /&gt;ID: 40 - NIVEL: 2 - SALARIO: 4000 - RANK: 4&lt;br /&gt;ID: 20 - NIVEL: 2 - SALARIO: 3000 - RANK: 5&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pudimos observar, de la versión 9i en adelante, podemos utilizar Analytic Functions tanto en SQL como en PL/SQL.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-967170335870270678?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/967170335870270678/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=967170335870270678' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/967170335870270678'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/967170335870270678'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/problemas-utilizando-analytic-functions.html' title='Problemas utilizando Analytic Functions junto con PL/SQL  en 8i'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3273184379334305455</id><published>2007-09-24T11:31:00.000-03:00</published><updated>2007-09-24T12:53:58.725-03:00</updated><title type='text'>Problemas en Vistas Materializadas - Parte 2</title><content type='html'>Como pudimos ver en el post "Problemas en Vistas Materializadas - Parte 1", podemos identificar con el paquete DBMS_MVIEW (procedimiento EXPLAIN_MVIEW), los problemas que pueden haber en nuestra Vista Materializada e implementar las soluciones necesarias. La contra de ese procedimiento es que nos muestra los errores que tenemos en la Vista Materializada, pero no nos dice cómo solucionarlos.&lt;br /&gt;&lt;br /&gt;En Oracle 10g se introduce el paquete DBMS_ADVISOR (procedimiento TUNE_MVIEW) que nos dice qué cambios debemos implementar en nuestra Vista Materializada para soportar las capacidades que necesitamos.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test1 AS&lt;br /&gt;  2  SELECT level id, level*level total&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test2 AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nom&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 20 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; COMMIT ;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test2 ADD CONSTRAINT id_2_pk PRIMARY KEY (id) ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'TEST1') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'TEST2') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT test2.id,&lt;br /&gt;  2         test2.nom,&lt;br /&gt;  3         SUM(test1.total) sum_total&lt;br /&gt;  4  FROM test1,&lt;br /&gt;  5       test2&lt;br /&gt;  6  WHERE test1.id = test2.id(+)&lt;br /&gt;  7  GROUP BY test2.id,&lt;br /&gt;  8           test2.nom&lt;br /&gt;  9  ORDER BY id ;&lt;br /&gt;&lt;br /&gt;        ID NOM         SUM_TOTAL&lt;br /&gt;---------- ---------- ----------&lt;br /&gt;         1 nom_1               1&lt;br /&gt;         2 nom_2               4&lt;br /&gt;         3 nom_3               9&lt;br /&gt;         4 nom_4              16&lt;br /&gt;         5 nom_5              25&lt;br /&gt;         6 nom_6              36&lt;br /&gt;         7 nom_7              49&lt;br /&gt;         8 nom_8              64&lt;br /&gt;         9 nom_9              81&lt;br /&gt;        10 nom_10            100&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ya creé mi ambiente de prueba. Ahora voy a crear las tablas de Log necesarias para la capacidad de REFRESH FAST. Pero las voy a crear de forma errónea para demostrar el poder que nos brinda el paquete DBMS_ADVISOR.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW LOG ON test1&lt;br /&gt;  2  WITH ROWID, SEQUENCE (id) ;&lt;br /&gt;&lt;br /&gt;Materialized view log created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW LOG ON test2&lt;br /&gt;  2  WITH ROWID, SEQUENCE (id,nom) ;&lt;br /&gt;&lt;br /&gt;Materialized view log created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Procedemos a crear la Vista Materializada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  2  NOCOMPRESS&lt;br /&gt;  3  LOGGING&lt;br /&gt;  4  BUILD IMMEDIATE&lt;br /&gt;  5  USING INDEX&lt;br /&gt;  6  REFRESH FAST ON DEMAND&lt;br /&gt;  7  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt;  8  DISABLE QUERY REWRITE&lt;br /&gt;  9  AS&lt;br /&gt; 10  SELECT test2.id,&lt;br /&gt; 11         test2.nom,&lt;br /&gt; 12         SUM(test1.total) sum_total,&lt;br /&gt; 13         COUNT(*) cnt,&lt;br /&gt; 14         COUNT(test1.total) cnt_sum&lt;br /&gt; 15  FROM test1,&lt;br /&gt; 16       test2&lt;br /&gt; 17  WHERE test1.id = test2.id(+)&lt;br /&gt; 18  GROUP BY test2.id,&lt;br /&gt; 19           test2.nom&lt;br /&gt; 20  ORDER BY id ;&lt;br /&gt;FROM test1,&lt;br /&gt;     *&lt;br /&gt;ERROR at line 15:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-32401: materialized view log on "CDW"."TEST2" does not have new values&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Efectivamente como supusimos, tenemos errores en las tablas de Log que creamos.&lt;br /&gt;Ahora vamos a ejecutar DBMS_ADVISOR.TUNE_MVIEW y ver los resultados en la tabla USER_TUNE_MVIEW...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DECLARE&lt;br /&gt;  2  l_nombre  VARCHAR2(100) := 'test_1' ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4  &lt;span style="font-weight:bold;"&gt;DBMS_ADVISOR.TUNE_MVIEW&lt;/span&gt;(l_nombre, 'CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  5  NOCOMPRESS&lt;br /&gt;  6  LOGGING&lt;br /&gt;  7  BUILD IMMEDIATE&lt;br /&gt;  8  USING INDEX&lt;br /&gt;  9  REFRESH FAST ON DEMAND&lt;br /&gt; 10  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt; 11  DISABLE QUERY REWRITE&lt;br /&gt; 12  AS&lt;br /&gt; 13  SELECT test2.id,&lt;br /&gt; 14         test2.nom,&lt;br /&gt; 15         SUM(test1.total) sum_total,&lt;br /&gt; 16         COUNT(*) cnt,&lt;br /&gt; 17         COUNT(test1.total) cnt_sum&lt;br /&gt; 18  FROM test1,&lt;br /&gt; 19       test2&lt;br /&gt; 20  WHERE test1.id = test2.id(+)&lt;br /&gt; 21  GROUP BY test2.id,&lt;br /&gt; 22           test2.nom&lt;br /&gt; 23  ORDER BY id') ;&lt;br /&gt; 24  END ;&lt;br /&gt; 25  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT *&lt;br /&gt;  2  FROM user_tune_mview&lt;br /&gt;  3  WHERE task_name = 'test_1' ;&lt;br /&gt;&lt;br /&gt;TASK_NAME        ACTION_ID SCRIPT_TYPE    STATEMENT&lt;br /&gt;--------------- ---------- -------------- -----------------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;test_1                   1 IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE O&lt;br /&gt;                                          N "CDW"."TEST2" ADD ROWID, SEQUENCE&lt;br /&gt;                                           ("ID","NOM")  INCLUDING NEW VALUES&lt;br /&gt;&lt;br /&gt;test_1                   2 IMPLEMENTATION ALTER MATERIALIZED VIEW LOG FORCE O&lt;br /&gt;                                          N "CDW"."TEST1" ADD ROWID, SEQUENCE&lt;br /&gt;                                           ("ID","TOTAL")  INCLUDING NEW VALU&lt;br /&gt;                                          ES&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;test_1                   3 IMPLEMENTATION CREATE MATERIALIZED VIEW CDW.TEST_M&lt;br /&gt;                                          V NOCOMPRESS&lt;br /&gt;                                          LOGGING&lt;br /&gt;                                          BUILD IMMEDIATE&lt;br /&gt;                                          USING INDEX&lt;br /&gt;                                           REFRESH FAST WITH ROWID DISABLE QU&lt;br /&gt;                                          ERY REWRITE AS SELECT test2.id,&lt;br /&gt;                                                 test2.nom,&lt;br /&gt;                                                 SUM(test1.total) sum_total,&lt;br /&gt;                                                 COUNT(*) cnt,&lt;br /&gt;                                                 COUNT(test1.total) cnt_sum&lt;br /&gt;                                          FROM test1,&lt;br /&gt;                                               test2&lt;br /&gt;                                          WHERE test1.id = test2.id(+)&lt;br /&gt;                                          GROUP BY test2.id,&lt;br /&gt;                                                   test2.nom&lt;br /&gt;                                          ORDER BY id&lt;br /&gt;&lt;br /&gt;test_1                   4 UNDO           DROP MATERIALIZED VIEW CDW.TEST_MV&lt;br /&gt;&lt;br /&gt;4 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La columna SCRIPT_TYPE nos muestra el tipo de recomendación. Deberíamos ver las columnas con el valor 'IMPLEMENTED' (la recomendación ya está implementada); pero en cambio, estamos viendo que aparecen como 'IMPLEMENTATION' (la recomendación NO está implementada).&lt;br /&gt;El próximo paso es ejecutar los script recomendados...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER MATERIALIZED VIEW LOG FORCE ON "CDW"."TEST2"&lt;br /&gt;  2  ADD ROWID, SEQUENCE("ID","NOM")  INCLUDING NEW VALUES ;&lt;br /&gt;&lt;br /&gt;Materialized view log altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER MATERIALIZED VIEW LOG FORCE ON "CDW"."TEST1"&lt;br /&gt;  2  ADD ROWID, SEQUENCE("ID","TOTAL")  INCLUDING NEW VALUES ;&lt;br /&gt;&lt;br /&gt;Materialized view log altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  2  NOCOMPRESS&lt;br /&gt;  3  LOGGING&lt;br /&gt;  4  BUILD IMMEDIATE&lt;br /&gt;  5  USING INDEX&lt;br /&gt;  6  REFRESH FAST ON DEMAND&lt;br /&gt;  7  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt;  8  DISABLE QUERY REWRITE&lt;br /&gt;  9  AS&lt;br /&gt; 10  SELECT test2.id,&lt;br /&gt; 11         test2.nom,&lt;br /&gt; 12         SUM(test1.total) sum_total,&lt;br /&gt; 13         COUNT(*) cnt,&lt;br /&gt; 14         COUNT(test1.total) cnt_sum&lt;br /&gt; 15  FROM test1,&lt;br /&gt; 16       test2&lt;br /&gt; 17  WHERE test1.id = test2.id(+)&lt;br /&gt; 18  GROUP BY test2.id,&lt;br /&gt; 19           test2.nom&lt;br /&gt; 20  ORDER BY id ;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Materialized view created.&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejecutando las recomendaciones de la tabla, pudimos solucionar rápidamente el problema que teníamos en la Vista Materializada.&lt;br /&gt;&lt;br /&gt;Qué sucede si ejecutamos el DBMS_ADVISOR.TUNE_MVIEW nuevamente?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DECLARE&lt;br /&gt;  2  l_nombre  VARCHAR2(100) := 'test_1' ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4  DBMS_ADVISOR.TUNE_MVIEW(l_nombre, 'CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  5  NOCOMPRESS&lt;br /&gt;  6  LOGGING&lt;br /&gt;  7  BUILD IMMEDIATE&lt;br /&gt;  8  USING INDEX&lt;br /&gt;  9  REFRESH FAST ON DEMAND&lt;br /&gt; 10  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt; 11  DISABLE QUERY REWRITE&lt;br /&gt; 12  AS&lt;br /&gt; 13  SELECT test2.id,&lt;br /&gt; 14         test2.nom,&lt;br /&gt; 15         SUM(test1.total) sum_total,&lt;br /&gt; 16         COUNT(*) cnt,&lt;br /&gt; 17         COUNT(test1.total) cnt_sum&lt;br /&gt; 18  FROM test1,&lt;br /&gt; 19       test2&lt;br /&gt; 20  WHERE test1.id = test2.id(+)&lt;br /&gt; 21  GROUP BY test2.id,&lt;br /&gt; 22           test2.nom&lt;br /&gt; 23  ORDER BY id') ;&lt;br /&gt; 24  END ;&lt;br /&gt; 25  /&lt;br /&gt;DECLARE&lt;br /&gt;*&lt;br /&gt;ERROR at line 1:&lt;br /&gt;ORA-13600: error encountered in Advisor&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;QSM-03116: The materialized view is already optimal and cannot be tuned any further&lt;/span&gt;&lt;br /&gt;ORA-06512: at "SYS.DBMS_SYS_ERROR", line 86&lt;br /&gt;ORA-06512: at "SYS.PRVT_ACCESS_ADVISOR", line 202&lt;br /&gt;ORA-06512: at "SYS.PRVT_TUNE_MVIEW", line 1042&lt;br /&gt;ORA-06512: at "SYS.DBMS_ADVISOR", line 754&lt;br /&gt;ORA-06512: at line 4&lt;br /&gt;&lt;br /&gt;[TEST@TEST_10GR2 ~]$ oerr qsm 03116&lt;br /&gt;03116, 00000, "The materialized view is already optimal and cannot be tuned any further"&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;// *Cause:   The materialized view has the capabilities that are specified in&lt;br /&gt;//           the statement.&lt;br /&gt;// *Action:  none&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Oracle nos está diciendo que el script de creación de la Vista Materializada que nosotros ejecutamos, ya cuenta con todas las capacidades necesarias para su creación, y por lo tanto, ya se encuentra óptima y no se puede seguir tuneando.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3273184379334305455?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3273184379334305455/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3273184379334305455' title='12 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3273184379334305455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3273184379334305455'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/problemas-en-vistas-materializadas.html' title='Problemas en Vistas Materializadas - Parte 2'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-9146087600416776061</id><published>2007-09-21T15:56:00.000-03:00</published><updated>2007-09-24T11:08:50.033-03:00</updated><title type='text'>Exchange Partition</title><content type='html'>Exchange Partition permite cargar en tablas particionadas datos en forma rápida y con muy poco impacto para los usuarios que se encuentran activos.&lt;br /&gt;En resumen, lo que hace la sentencia Exchange Partition es modificar el diccionario de datos y simular que los datos que ya tenemos cargados en una tabla, corresponden a una partición determinada de otra tabla.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo muy sencillo para entender mejor éste tema:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE datos_1 AS&lt;br /&gt;  2  SELECT level id, timestamp'2000-11-02 09:00:00' fecha&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:01.06&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE datos_2 AS&lt;br /&gt;  2  SELECT level id, timestamp'2001-09-10 13:00:00' fecha&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lo que hicimos fue crear 2 tablas con distintas fechas en cada una de ellas.&lt;br /&gt;&lt;br /&gt;Ahora creamos solamente la estructura de la tabla particionada en donde vamos a cargar los datos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test&lt;br /&gt;  2  ( id, fecha )&lt;br /&gt;  3  PARTITION BY RANGE ( fecha )&lt;br /&gt;  4  (&lt;br /&gt;  5      PARTITION year_2000 VALUES LESS THAN ( timestamp'2000-12-02 00:00:00' ),&lt;br /&gt;  6      PARTITION year_2001 VALUES LESS THAN ( timestamp'2001-10-10 00:00:00' )&lt;br /&gt;  7  )&lt;br /&gt;  8  AS&lt;br /&gt;  9  SELECT 1, timestamp'2000-11-02 09:00:00'&lt;br /&gt; 10  FROM dual&lt;br /&gt; 11  WHERE 1 = 0 ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vamos a realizar un alter para modificar el diccionario de datos y relacionar cada  una de las 2 tablas que creamos con la respectiva partición de la tabla TEST...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test&lt;br /&gt;  2  &lt;span style="font-weight:bold;"&gt;EXCHANGE PARTITION&lt;/span&gt; year_2000&lt;br /&gt;  3  WITH table datos_1&lt;br /&gt;  4  &lt;span style="font-weight:bold;"&gt;WITHOUT VALIDATION&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:00:00.03&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test&lt;br /&gt;  2  &lt;span style="font-weight:bold;"&gt;EXCHANGE PARTITION&lt;/span&gt; year_2001&lt;br /&gt;  3  WITH table datos_2&lt;br /&gt;  4  &lt;span style="font-weight:bold;"&gt;WITHOUT VALIDATION&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:00:00.02&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM test ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;    200000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM datos_1 ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;         0&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM datos_2 ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;         0&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, con el Exchange Partition no tardamos casi nada en cargar los datos en la tabla particionada ya que en realidad no estamos cargando los datos, simplemente se modifica el diccionario de datos.&lt;br /&gt;&lt;br /&gt;Pueden notar que agregué la sentencia WITHOUT VALIDATION. Que es ésto? WITHOUT VALIDATION suele ser una operación rápida porque sólo realiza modificaciones en el diccionario de datos. Si la tabla o tabla particionada que colocamos en el Exchange Partition tiene una primary key o unique constraint habilitado, entonces el Exchange Partition se realiza como WITH VALIDATION para mantener la integridad de las constraints.&lt;br /&gt; &lt;br /&gt;Vamos a ejecutar nuevamente los 2 alter anteriores sin la sentencia WITHOUT VALIDATION...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test&lt;br /&gt;  2  &lt;span style="font-weight:bold;"&gt;EXCHANGE PARTITION&lt;/span&gt; year_2000&lt;br /&gt;  3  WITH table datos_1 ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:00:01.00&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test&lt;br /&gt;  2  &lt;span style="font-weight:bold;"&gt;EXCHANGE PARTITION&lt;/span&gt; year_2001&lt;br /&gt;  3  WITH table datos_2 ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:00:01.05&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Si hubiera ejecutado esos alter con un Trace, el reporte del Trace me mostraría, entre otras sentencias, las siguientes...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select 1&lt;br /&gt;from&lt;br /&gt; "DATOS_1" where TBL$OR$IDX$PART$NUM("TEST", 0, 3,1048576,"FECHA") != :1&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          1          0           0&lt;br /&gt;Fetch        1      0.04       0.04          0         65          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        3      0.04       0.04          0         66          0           0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Misses in library cache during execute: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 81     (recursive depth: 1)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      0  TABLE ACCESS FULL DATOS_1 (cr=65 pr=0 pw=0 time=44582 us)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select 1&lt;br /&gt;from&lt;br /&gt; "DATOS_2" where TBL$OR$IDX$PART$NUM("TEST", 0, 3,1048576,"FECHA") != :1&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          1          0           0&lt;br /&gt;Fetch        1      0.04       0.04          0         65          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        3      0.04       0.04          0         66          0           0&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Misses in library cache during execute: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 81     (recursive depth: 1)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      0  TABLE ACCESS FULL DATOS_2 (cr=65 pr=0 pw=0 time=46957 us)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Antes que nada, notamos que los alter se ejecutaron en mayor tiempo, cierto? De las consultas que observamos del Trace, vemos que se está realizando un FULL SCAN de las tablas y que se está ejecutando una función en el WHERE de cada consulta. Imagínense si tenemos que realizar ésta clase de procesos en ambientes con gran volumen de datos y en donde el sistema se encuentra saturado por el I/O a disco.&lt;br /&gt;&lt;br /&gt;Qué sucede si no tenemos los datos separados por año en distintas tablas, y en cambio, tenemos todos los datos en una misma tabla? Bueno, tomando como ejemplo la tabla TEST que acabamos de cargar, podríamos realizar lo siguiente...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test_2&lt;br /&gt;  2  ( id, fecha )&lt;br /&gt;  3  PARTITION BY RANGE ( fecha )&lt;br /&gt;  4  (&lt;br /&gt;  5      PARTITION year_2000 VALUES LESS THAN ( timestamp'2000-12-02 00:00:00' ),&lt;br /&gt;  6      PARTITION year_2001 VALUES LESS THAN ( timestamp'2001-10-10 00:00:00' )&lt;br /&gt;  7  )&lt;br /&gt;  8  AS&lt;br /&gt;  9  SELECT *&lt;br /&gt; 10  FROM test ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:05.04&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; DROP TABLE test ;&lt;br /&gt;&lt;br /&gt;Table dropped.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test_2 RENAME TO test ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM test ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;    200000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-9146087600416776061?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/9146087600416776061/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=9146087600416776061' title='15 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/9146087600416776061'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/9146087600416776061'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/exchange-partition.html' title='Exchange Partition'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-8314856157262862105</id><published>2007-09-20T10:56:00.000-03:00</published><updated>2007-09-20T16:07:19.946-03:00</updated><title type='text'>Buscar valores NULL en forma eficiente</title><content type='html'>Un problema que veo seguidamente, se relaciona con la escritura de consultas que buscan valores NULL en una tabla. Este es un problema muy común. &lt;br /&gt;A continuación vamos a ver algunas soluciones que podemos implementar para buscar los valores NULL en forma eficiente y evitar lecturas innecesarias de bloques de datos. &lt;br /&gt;&lt;br /&gt;Veamos algunos ejemplos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT decode(mod(rownum,100),0,null,level) id, 'nom_'||level nom&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 1000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos la cantidad de valores NULL que tiene la tabla que creamos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  WHERE id IS NULL ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;     10000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos cómo se ejecuta internamente esa consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT count(*)&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  WHERE id IS NULL ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     7 |   304 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     7 |       |&lt;br /&gt;|*  2 |  &lt;span style="font-weight:bold;"&gt; TABLE ACCESS FULL  | TEST        | 10000 | 70000 |   304&lt;/span&gt; |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("TEST"."ID" IS NULL)&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.16       0.16       3144       3150          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        4      0.17       0.16       3144       3150          0           1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que obviamente está realizando un Full Scan. Qué sucede si creamos un índice B*Tree?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE INDEX test_id_idx ON test(id) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST',cascade=&gt;true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT count(*)&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  WHERE id IS NULL ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     7 |   304 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     7 |       |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;TABLE ACCESS FULL  | TEST        | 10000 | 70000 |   304&lt;/span&gt; |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("TEST"."ID" IS NULL)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Porqué nuestra consulta no está accediendo a través del índice? por la simple razón de que los índices B*Tree no indexan los valores NULL. Como estamos realizando un COUNT buscando la cantidad de valores NULL de la tabla, el CBO sabe que si accede por índice, la consulta nos va a retornar un resultado erróneo ya que no existen valores NULL en el índice. Es por eso, que en vez de acceder por índice, realiza un Full Scan de la tabla. Esto tiene como consecuencia una lectura innecesaria de bloques por no haber accedido a través de un índice.&lt;br /&gt;&lt;br /&gt;Una solución para éste problema sería crear un FBI (Function Based-Index) para indexar sólo los valores NULL de la columna:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE INDEX test_id_nulo_idx ON test(NVL(id,'nulo')) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST',cascade=&gt;true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT count(*)&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  WHERE &lt;span style="font-weight:bold;"&gt;NVL(id,'nulo') = 'nulo'&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name             | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |                   |     1 |     7 |    27 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |                   |     1 |     7 |       |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN   | TEST_ID_NULO_IDX  | 10001 | 70007 |    27&lt;/span&gt; |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access(NVL("TEST"."ID",'nulo')='nulo')&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.00       0.00          1         26          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        4      0.00       0.00          1         26          0           1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Veamos qué sucede si creamos un índice Bitmap:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DROP INDEX test_id_idx ;&lt;br /&gt;&lt;br /&gt;Index dropped.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE BITMAP INDEX test_id_bitmap ON test(id) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST',cascade=&gt;true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT count(*)&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  WHERE id IS NULL ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   |  Name            | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |                  |     1 |     7 |    &lt;span style="font-weight:bold;"&gt;44&lt;/span&gt; |&lt;br /&gt;|   1 |  SORT AGGREGATE             |                  |     1 |     7 |       |&lt;br /&gt;|   2 |   BITMAP CONVERSION COUNT   |                  |       |       |       |&lt;br /&gt;|*  3 |    &lt;span style="font-weight:bold;"&gt;BITMAP INDEX SINGLE VALUE| TEST_ID_BITMAP&lt;/span&gt;   |       |       |       |&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - access("TEST"."ID" IS NULL)&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.00       0.00          1          5          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total        4      0.00       0.00          1          5          0           1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que también podemos utilizar índices Bitmap para buscar valores NULL ya que ésta clase de índices SI indexan éstos valores. &lt;br /&gt;Lo importante a tener en cuenta si utilizamos éste índice es que cada modificación que se realiza sobre el índice, requiere un gran trabajo del sistema que si utilizamos índices B*tree. Por otro lado, si a ésto le sumamos las modificaciones concurrentes que se realizan sobre la columna indexada, puede llegar a ser mortal para el sistema.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-8314856157262862105?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/8314856157262862105/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=8314856157262862105' title='15 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8314856157262862105'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8314856157262862105'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/buscar-valores-null-en-forma-eficiente.html' title='Buscar valores NULL en forma eficiente'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3674735486078040070</id><published>2007-09-19T10:19:00.000-03:00</published><updated>2007-09-24T11:56:06.915-03:00</updated><title type='text'>Problemas en Vistas Materializadas - Parte 1</title><content type='html'>Suele pasarnos que cuando queremos crear Vistas Materializadas, suelen haber errores y no logramos crearlas. O bien, logramos crearlas pero no suelen estar optimizadas para las capacidades que nosotros queremos. Para analizar y optimizar nuestras vistas materializadas, vamos a utilizar el paquete DBMS_MVIEW.EXPLAIN_MVIEW y la tabla MV_CAPABILITIES_TABLE.  &lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;Primero vamos a crear las tablas con los datos que vamos a utilizar para crear nuestra vista materializada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test1 AS&lt;br /&gt;  2  SELECT level id, level*level total&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test2 AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nom&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 19 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test2 VALUES(19,'nom_19') ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; COMMIT ;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'TEST1') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'TEST2') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los datos quedan de la siguiente manera en las tablas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT *&lt;br /&gt;  2  FROM test1 ;&lt;br /&gt;&lt;br /&gt;        ID      TOTAL&lt;br /&gt;---------- ----------&lt;br /&gt;         1          1&lt;br /&gt;         2          4&lt;br /&gt;         3          9&lt;br /&gt;         4         16&lt;br /&gt;         5         25&lt;br /&gt;         6         36&lt;br /&gt;         7         49&lt;br /&gt;         8         64&lt;br /&gt;         9         81&lt;br /&gt;        10        100&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT *&lt;br /&gt;  2  FROM test2 ;&lt;br /&gt;&lt;br /&gt;        ID NOM&lt;br /&gt;---------- ----------------&lt;br /&gt;         1 nom_1&lt;br /&gt;         2 nom_2&lt;br /&gt;         3 nom_3&lt;br /&gt;         4 nom_4&lt;br /&gt;         5 nom_5&lt;br /&gt;         6 nom_6&lt;br /&gt;         7 nom_7&lt;br /&gt;         8 nom_8&lt;br /&gt;         9 nom_9&lt;br /&gt;        10 nom_10&lt;br /&gt;        11 nom_11&lt;br /&gt;        12 nom_12&lt;br /&gt;        13 nom_13&lt;br /&gt;        14 nom_14&lt;br /&gt;        15 nom_15&lt;br /&gt;        16 nom_16&lt;br /&gt;        17 nom_17&lt;br /&gt;        18 nom_18&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;        19 nom_19&lt;br /&gt;        19 nom_19&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;20 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, supongamos que queremos crear una vista materializada con la opción de REFRESH FAST ON DEMAND sobre la siguiente consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT test2.id,&lt;br /&gt;  2         test2.nom,&lt;br /&gt;  3         SUM(test1.total) sum_total&lt;br /&gt;  4  FROM test1,&lt;br /&gt;  5       test2&lt;br /&gt;  6  WHERE test1.id = test2.id(+)&lt;br /&gt;  7  GROUP BY test2.id,&lt;br /&gt;  8           test2.nom&lt;br /&gt;  9  ORDER BY id ;&lt;br /&gt;&lt;br /&gt;        ID NOM         SUM_TOTAL&lt;br /&gt;---------- ---------- ----------&lt;br /&gt;         1 nom_1               1&lt;br /&gt;         2 nom_2               4&lt;br /&gt;         3 nom_3               9&lt;br /&gt;         4 nom_4              16&lt;br /&gt;         5 nom_5              25&lt;br /&gt;         6 nom_6              36&lt;br /&gt;         7 nom_7              49&lt;br /&gt;         8 nom_8              64&lt;br /&gt;         9 nom_9              81&lt;br /&gt;        10 nom_10            100&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;Antes de crear la vista materializada, vamos a crear las tablas de Log para el refresh fast:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW LOG ON test1&lt;br /&gt;  2  WITH ROWID, SEQUENCE (total)&lt;br /&gt;  3  INCLUDING NEW VALUES ;&lt;br /&gt;&lt;br /&gt;Materialized view log created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW LOG ON test2&lt;br /&gt;  2  WITH ROWID, SEQUENCE (id,nom)&lt;br /&gt;  3  INCLUDING NEW VALUES ;&lt;br /&gt;&lt;br /&gt;Materialized view log created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora creamos la vista materializada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  2  NOCOMPRESS&lt;br /&gt;  3  LOGGING&lt;br /&gt;  4  BUILD IMMEDIATE&lt;br /&gt;  5  USING INDEX&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  6  REFRESH FAST ON DEMAND&lt;/span&gt;&lt;br /&gt;  7  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt;  8  DISABLE QUERY REWRITE&lt;br /&gt;  9  AS&lt;br /&gt; 10  SELECT test2.id,&lt;br /&gt; 11         test2.nom,&lt;br /&gt; 12         SUM(test1.total) sum_total&lt;br /&gt; 13  FROM test1,&lt;br /&gt; 14       test2&lt;br /&gt; 15  WHERE test1.id = test2.id(+)&lt;br /&gt; 16  GROUP BY test2.id,&lt;br /&gt; 17           test2.nom&lt;br /&gt; 18  ORDER BY id ;&lt;br /&gt;FROM test1,&lt;br /&gt;     *&lt;br /&gt;ERROR at line 13:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-12015: cannot create a fast refresh materialized view from a complex query&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que no podemos crear la vista materializada usando fast refresh sobre nuestra consulta. Pero cómo identificamos cuales son los errores por los cuales no podemos crear la vista materializada?&lt;br /&gt;Lo que vamos a utilizar es el procedimiento EXPLAIN_MVIEW del paquete DBMS_MVIEW. Lo ejecutamos colocando como parámetro del procedimiento la consulta que vamos a utilizar en la vista materializada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total&lt;br /&gt;  8  FROM test1,&lt;br /&gt;  9       test2&lt;br /&gt; 10  WHERE test1.id = test2.id(+)&lt;br /&gt; 11  GROUP BY test2.id,&lt;br /&gt; 12           test2.nom&lt;br /&gt; 13  ORDER BY id '&lt;br /&gt; 14  ) ;&lt;br /&gt; 15&lt;br /&gt; 16  END ;&lt;br /&gt; 17  /&lt;br /&gt;BEGIN&lt;br /&gt;*&lt;br /&gt;ERROR at line 1:&lt;br /&gt;ORA-30377: table SQL_10GR2.MV_CAPABILITIES_TABLE not found&lt;br /&gt;ORA-00942: table or view does not exist&lt;br /&gt;ORA-06512: at "SYS.DBMS_XRWMV", line 23&lt;br /&gt;ORA-06512: at "SYS.DBMS_XRWMV", line 43&lt;br /&gt;ORA-06512: at "SYS.DBMS_SNAPSHOT", line 3007&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El error que estamos viendo se debe a que no tenemos que creada en nuestro esquema la tabla MV_CAPABILITIES_TABLE que se encuentra en $ORACLE_HOME/rdbms/admin (el script se llama utlxmv.sql) que utiliza el procedimiento para guardar la información resultante de la ejecución.&lt;br /&gt;&lt;br /&gt;Creamos la tabla MV_CAPABILITIES_TABLE:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; @$ORACLE_HOME/rdbms/admin/utlxmv.sql&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejecutamos nuevamente el procedimiento:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total&lt;br /&gt;  8  FROM test1,&lt;br /&gt;  9       test2&lt;br /&gt; 10  WHERE test1.id = test2.id(+)&lt;br /&gt; 11  GROUP BY test2.id,&lt;br /&gt; 12           test2.nom&lt;br /&gt; 13  ORDER BY id '&lt;br /&gt; 14  ) ;&lt;br /&gt; 15&lt;br /&gt; 16  END ;&lt;br /&gt; 17  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora consultamos la tabla:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT capability_name, possible, related_text, msgtxt&lt;br /&gt;  2  FROM mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;CAPABILITY_NAME                P RELATED_TE MSGTXT&lt;br /&gt;------------------------------ - ---------- ------------------------------&lt;br /&gt;PCT                            N&lt;br /&gt;REFRESH_COMPLETE               Y&lt;br /&gt;REFRESH_FAST                   N&lt;br /&gt;REWRITE                        Y&lt;br /&gt;PCT_TABLE                      N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE                      N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;REFRESH_FAST_AFTER_INSERT      N            outer join in mv&lt;/span&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_INSERT is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ANY_DML     N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_ONETAB_DML is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_PCT               N            PCT is not possible on any of&lt;br /&gt;                                            the detail tables in the mater&lt;br /&gt;                                            ialized view&lt;br /&gt;&lt;br /&gt;REWRITE_FULL_TEXT_MATCH        Y&lt;br /&gt;REWRITE_PARTIAL_TEXT_MATCH     Y&lt;br /&gt;REWRITE_GENERAL                Y&lt;br /&gt;REWRITE_PCT                    N            general rewrite is not possibl&lt;br /&gt;                                            e or PCT is not possible on an&lt;br /&gt;                                            y of the detail tables&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;16 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lo que nos muestra esta consulta es lo siguiente:&lt;br /&gt;- Un listado de todas las capacidades de la vista materializada (columna CAPABILITY_NAME).&lt;br /&gt;- Por cada capacidad, nos muestra si es posible o no (columna POSSIBLE con valores 'Y' o 'N').&lt;br /&gt;- Si la capacidad no es posible, nos muestra porque no es posible (columna MSGTXT).&lt;br /&gt;&lt;br /&gt;Como nosotros queremos crear la vista materializada con la opción de REFRESH FAST ON DEMAND, vemos la columna CAPABILITY_NAME y observamos los valores de la columna MSGTXT para lo que tiene que ver con REFRESH_FAST_%.&lt;br /&gt;Podemos ver que en REFRESH_FAST_AFTER_INSERT vemos que dice "outer join in mv". Como nosotros tenemos un outer join en la vista materializada, no podemos realizar refresh fast porque existen ciertas restricciones que debemos cumplir y que en éste ejemplo no estamos cumpliendo. Una de esas condiciones dice que las columnas que contienen el outer join en la condición de join de la consulta, tienen que tener creadas constraints primary key o índices unique key. &lt;br /&gt;En la TABLA2 de nuestro ejemplo, tenemos un valor duplicado (el ID 19). Veamos qué sucede si eliminamos uno de los ID 19 y creamos una primary key en la tabla TEST2 por el campo ID:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DELETE FROM test2&lt;br /&gt;  2  WHERE id = 19&lt;br /&gt;  3    AND rownum = 1 ;&lt;br /&gt;&lt;br /&gt;1 row deleted.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; COMMIT ;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER TABLE test2 ADD CONSTRAINT id_2_pk PRIMARY KEY (id) ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'TEST2', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT *&lt;br /&gt;  2  FROM test2 ;&lt;br /&gt;&lt;br /&gt;        ID NOM&lt;br /&gt;---------- ----------&lt;br /&gt;         1 nom_1&lt;br /&gt;         2 nom_2&lt;br /&gt;         3 nom_3&lt;br /&gt;         4 nom_4&lt;br /&gt;         5 nom_5&lt;br /&gt;         6 nom_6&lt;br /&gt;         7 nom_7&lt;br /&gt;         8 nom_8&lt;br /&gt;         9 nom_9&lt;br /&gt;        10 nom_10&lt;br /&gt;        11 nom_11&lt;br /&gt;        12 nom_12&lt;br /&gt;        13 nom_13&lt;br /&gt;        14 nom_14&lt;br /&gt;        15 nom_15&lt;br /&gt;        16 nom_16&lt;br /&gt;        17 nom_17&lt;br /&gt;        18 nom_18&lt;br /&gt;        19 nom_19&lt;br /&gt;&lt;br /&gt;19 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ejecutamos nuevamente el procedimiento EXPLAIN_MVIEW:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; TRUNCATE TABLE mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;Table truncated.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total&lt;br /&gt;  8  FROM test1,&lt;br /&gt;  9       test2&lt;br /&gt; 10  WHERE test1.id = test2.id(+)&lt;br /&gt; 11  GROUP BY test2.id,&lt;br /&gt; 12           test2.nom&lt;br /&gt; 13  ORDER BY id '&lt;br /&gt; 14  ) ;&lt;br /&gt; 15&lt;br /&gt; 16  END ;&lt;br /&gt; 17  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Consultamos nuevamente la tabla:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT capability_name, possible, related_text, msgtxt&lt;br /&gt;  2  FROM mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;CAPABILITY_NAME                P RELATED_TE MSGTXT&lt;br /&gt;------------------------------ - ---------- ------------------------------&lt;br /&gt;PCT                            N&lt;br /&gt;REFRESH_COMPLETE               Y&lt;br /&gt;REFRESH_FAST                   N&lt;br /&gt;REWRITE                        Y&lt;br /&gt;PCT_TABLE                      N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE                      N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_INSERT      N SQL_10GR2. mv log does not have all neces&lt;br /&gt;                                 TEST1      sary columns&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N SUM_TOTAL  SUM(expr) without COUNT(expr)&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_INSERT is disabled&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            COUNT(*) is not present in the&lt;br /&gt;                                             select list&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            SUM(expr) without COUNT(expr)&lt;br /&gt;REFRESH_FAST_AFTER_ANY_DML     N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_ONETAB_DML is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_PCT               N            PCT is not possible on any of&lt;br /&gt;                                            the detail tables in the mater&lt;br /&gt;                                            ialized view&lt;br /&gt;&lt;br /&gt;REWRITE_FULL_TEXT_MATCH        Y&lt;br /&gt;REWRITE_PARTIAL_TEXT_MATCH     Y&lt;br /&gt;REWRITE_GENERAL                Y&lt;br /&gt;REWRITE_PCT                    N            general rewrite is not possibl&lt;br /&gt;                                            e or PCT is not possible on an&lt;br /&gt;                                            y of the detail tables&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;19 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Podemos observar que el error anterior ya no aparece más. En REFRESH_FAST_AFTER_ONETAB_DML vemos el mensaje "COUNT(*) is not present in the select list". Esto nos está diciendo que debemos agregar a la consulta un COUNT(*).&lt;br /&gt;Vamos a agregar el COUNT(*) en la consulta y realizar el mismo procedimiento de antes:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; TRUNCATE TABLE mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;Table truncated.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total,&lt;br /&gt;  8         COUNT(*) cnt&lt;br /&gt;  9  FROM test1,&lt;br /&gt; 10       test2&lt;br /&gt; 11  WHERE test1.id = test2.id(+)&lt;br /&gt; 12  GROUP BY test2.id,&lt;br /&gt; 13           test2.nom&lt;br /&gt; 14  ORDER BY id '&lt;br /&gt; 15  ) ;&lt;br /&gt; 16&lt;br /&gt; 17  END ;&lt;br /&gt; 18  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT capability_name, possible, related_text, msgtxt&lt;br /&gt;  2  FROM mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;CAPABILITY_NAME                P RELATED_TE MSGTXT&lt;br /&gt;------------------------------ - ---------- ------------------------------&lt;br /&gt;PCT                            N&lt;br /&gt;REFRESH_COMPLETE               Y&lt;br /&gt;REFRESH_FAST                   N&lt;br /&gt;REWRITE                        Y&lt;br /&gt;PCT_TABLE                      N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE                      N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_INSERT      N SQL_10GR2. mv log does not have all neces&lt;br /&gt;                                 TEST1      sary columns&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;REFRESH_FAST_AFTER_ONETAB_DML  N SUM_TOTAL  SUM(expr) without COUNT(expr)&lt;/span&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_INSERT is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            SUM(expr) without COUNT(expr)&lt;br /&gt;REFRESH_FAST_AFTER_ANY_DML     N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_ONETAB_DML is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_PCT               N            PCT is not possible on any of&lt;br /&gt;                                            the detail tables in the mater&lt;br /&gt;                                            ialized view&lt;br /&gt;&lt;br /&gt;REWRITE_FULL_TEXT_MATCH        Y&lt;br /&gt;REWRITE_PARTIAL_TEXT_MATCH     Y&lt;br /&gt;REWRITE_GENERAL                Y&lt;br /&gt;REWRITE_PCT                    N            general rewrite is not possibl&lt;br /&gt;                                            e or PCT is not possible on an&lt;br /&gt;                                            y of the detail tables&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;18 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Podemos observar que el error anterior ya no aparece más. Ahora en REFRESH_FAST_AFTER_ONETAB_DML vemos "SUM(expr) without COUNT(expr)". Como estamos utilizando la función SUM, debemos agregar un COUNT(expr) a la consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; TRUNCATE TABLE mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;Table truncated.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total,&lt;br /&gt;  8         COUNT(*) cnt,&lt;br /&gt;  9         COUNT(test1.total) cnt_sum&lt;br /&gt; 10  FROM test1,&lt;br /&gt; 11       test2&lt;br /&gt; 12  WHERE test1.id = test2.id(+)&lt;br /&gt; 13  GROUP BY test2.id,&lt;br /&gt; 14           test2.nom&lt;br /&gt; 15  ORDER BY id'&lt;br /&gt; 16  ) ;&lt;br /&gt; 17&lt;br /&gt; 18  END ;&lt;br /&gt; 19  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT capability_name, possible, related_text, msgtxt&lt;br /&gt;  2  FROM mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;CAPABILITY_NAME                P RELATED_TE MSGTXT&lt;br /&gt;------------------------------ - ---------- ------------------------------&lt;br /&gt;PCT                            N&lt;br /&gt;REFRESH_COMPLETE               Y&lt;br /&gt;REFRESH_FAST                   N&lt;br /&gt;REWRITE                        Y&lt;br /&gt;PCT_TABLE                      N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE                      N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;REFRESH_FAST_AFTER_INSERT      N SQL_10GR2. mv log does not have all neces&lt;br /&gt;                                 TEST1      sary columns&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_INSERT is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_ANY_DML     N            see the reason why REFRESH_FAS&lt;br /&gt;                                            T_AFTER_ONETAB_DML is disabled&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_PCT               N            PCT is not possible on any of&lt;br /&gt;                                            the detail tables in the mater&lt;br /&gt;                                            ialized view&lt;br /&gt;&lt;br /&gt;REWRITE_FULL_TEXT_MATCH        Y&lt;br /&gt;REWRITE_PARTIAL_TEXT_MATCH     Y&lt;br /&gt;REWRITE_GENERAL                Y&lt;br /&gt;REWRITE_PCT                    N            general rewrite is not possibl&lt;br /&gt;                                            e or PCT is not possible on an&lt;br /&gt;                                            y of the detail tables&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;16 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Podemos observar que el error anterior ya no aparece más. El último error que nos falta solucionar para poder crear la vista materializada con refresh fast es el que aparece en REFRESH_FAST_AFTER_INSERT con el mensaje "mv log does not have all necessary columns". Al parecer creamos de forma incorrecta la tabla de Logs de la tabla TEST1 y nos está diciendo que tenemos que agregar a la tabla de Log todas las columnas que aparecen en nuestra consulta.&lt;br /&gt;Lo que vamos a hacer es dropear la tabla de Log que habíamos creado y recrearla agregando una columna:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; DROP MATERIALIZED VIEW LOG on test1 ;&lt;br /&gt;&lt;br /&gt;Materialized view log dropped.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE MATERIALIZED VIEW LOG ON test1&lt;br /&gt;  2  WITH ROWID, SEQUENCE (id,total)&lt;br /&gt;  3  INCLUDING NEW VALUES ;&lt;br /&gt;&lt;br /&gt;Materialized view log created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; TRUNCATE TABLE mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;Table truncated.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; BEGIN&lt;br /&gt;  2&lt;br /&gt;  3  DBMS_MVIEW.EXPLAIN_MVIEW&lt;br /&gt;  4  (&lt;br /&gt;  5  'SELECT test2.id,&lt;br /&gt;  6         test2.nom,&lt;br /&gt;  7         SUM(test1.total) sum_total,&lt;br /&gt;  8         COUNT(*) cnt,&lt;br /&gt;  9         COUNT(test1.total) cnt_sum&lt;br /&gt; 10  FROM test1,&lt;br /&gt; 11       test2&lt;br /&gt; 12  WHERE test1.id = test2.id(+)&lt;br /&gt; 13  GROUP BY test2.id,&lt;br /&gt; 14           test2.nom&lt;br /&gt; 15  ORDER BY id'&lt;br /&gt; 16  ) ;&lt;br /&gt; 17&lt;br /&gt; 18  END ;&lt;br /&gt; 19  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT capability_name, possible, related_text, msgtxt&lt;br /&gt;  2  FROM mv_capabilities_table ;&lt;br /&gt;&lt;br /&gt;CAPABILITY_NAME                P RELATED_TE MSGTXT&lt;br /&gt;------------------------------ - ---------- ------------------------------&lt;br /&gt;PCT                            N&lt;br /&gt;REFRESH_COMPLETE               Y&lt;br /&gt;REFRESH_FAST                   Y&lt;br /&gt;REWRITE                        Y&lt;br /&gt;PCT_TABLE                      N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE                      N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;REFRESH_FAST_AFTER_INSERT      Y&lt;br /&gt;REFRESH_FAST_AFTER_ONETAB_DML  Y&lt;br /&gt;REFRESH_FAST_AFTER_ANY_DML     Y&lt;br /&gt;REFRESH_FAST_PCT               N            PCT is not possible on any of&lt;br /&gt;                                            the detail tables in the mater&lt;br /&gt;                                            ialized view&lt;br /&gt;&lt;br /&gt;REWRITE_FULL_TEXT_MATCH        Y&lt;br /&gt;REWRITE_PARTIAL_TEXT_MATCH     Y&lt;br /&gt;REWRITE_GENERAL                Y&lt;br /&gt;REWRITE_PCT                    N            general rewrite is not possibl&lt;br /&gt;                                            e or PCT is not possible on an&lt;br /&gt;                                            y of the detail tables&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST1      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;PCT_TABLE_REWRITE              N TEST2      relation is not a partitioned&lt;br /&gt;                                            table&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;16 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Buenísimo! Al parecer ya estamos en condiciones de crear la vista materializada utilizando la opción de REFRESH FAST ON DEMAND. Veamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;ihubowner@ihub1t&gt; CREATE MATERIALIZED VIEW TEST_MV&lt;br /&gt;  2  NOCOMPRESS&lt;br /&gt;  3  LOGGING&lt;br /&gt;  4  BUILD IMMEDIATE&lt;br /&gt;  5  USING INDEX&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;  6  REFRESH FAST ON DEMAND&lt;/span&gt;&lt;br /&gt;  7  USING DEFAULT LOCAL ROLLBACK SEGMENT&lt;br /&gt;  8  DISABLE QUERY REWRITE&lt;br /&gt;  9  AS&lt;br /&gt; 10  SELECT test2.id,&lt;br /&gt; 11         test2.nom,&lt;br /&gt; 12         SUM(test1.total) sum_total,&lt;br /&gt; 13         COUNT(*) cnt,&lt;br /&gt; 14         COUNT(test1.total) cnt_sum&lt;br /&gt; 15  FROM test1,&lt;br /&gt; 16       test2&lt;br /&gt; 17  WHERE test1.id = test2.id(+)&lt;br /&gt; 18  GROUP BY test2.id,&lt;br /&gt; 19           test2.nom&lt;br /&gt; 20  ORDER BY id ;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;&lt;br /&gt;Materialized view created.&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3674735486078040070?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3674735486078040070/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3674735486078040070' title='9 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3674735486078040070'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3674735486078040070'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/problemas-en-la-creacin-de-vistas.html' title='Problemas en Vistas Materializadas - Parte 1'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2877649888849944868</id><published>2007-09-17T17:13:00.000-03:00</published><updated>2007-09-17T17:21:23.060-03:00</updated><title type='text'>Oracle DBA Toolbar</title><content type='html'>Pueden ingresar &lt;a href="http://www.oracle.com/technology/toolbar/install/index.html?msgid=5191908" target="_blank"&gt;AQUI&lt;/a&gt; e instalar la "Oracle DBA Toolbar".&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.oracle.com/technology/toolbar/install/toolbar.jpg" border="0"/&gt;&lt;br /&gt;&lt;br /&gt;Disponible para Internet Explorer y Mozilla Firefox.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2877649888849944868?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2877649888849944868/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2877649888849944868' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2877649888849944868'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2877649888849944868'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/oracle-dba-toolbar.html' title='Oracle DBA Toolbar'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2134402564306621565</id><published>2007-09-17T10:58:00.000-03:00</published><updated>2007-09-17T14:23:04.660-03:00</updated><title type='text'>Estadísticas en tablas temporales</title><content type='html'>Lo ideal es tener estadísticas "representativas" en las tablas temporales. Suele suceder que muchas veces no sabemos la cantidad de registros que se van a cargar en las tablas temporales, pero podemos tener estadísticas que representen la cantidad de registros que en general suelen cargarse en las tablas. Por otro lado, hay ocasiones en que no sabemos ni eso. No sabemos que cantidad de registros promedio se van a cargar en las tablas. Cuando sucede ésto podemos pensar en algunas alternativas. &lt;br /&gt;&lt;br /&gt;Por default, Oracle asume que las tablas temporales van a contener X cantidad de registros (donde X representa a 8,168 de registros en las bases de datos que contiene bloques de 8K).&lt;br /&gt;Como éste valor por default suele ser incierto en las ejecuciones de nuestros procesos utilizando tablas temporales, tenemos que ayudar al CBO en recolectar estadísticas reales de las tablas temporales.&lt;br /&gt;&lt;br /&gt;Antes que nada, veamos las estadísticas por default que utiliza el CBO:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SHOW PARAMETER DB_BLOCK_SIZE&lt;br /&gt;&lt;br /&gt;db_block_size          integer          8192&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE GLOBAL TEMPORARY TABLE test_temp ( id NUMBER ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT /*+ ALL_ROWS */ id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |  8168 |   103K|    11 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL   | TEST_TEMP   |  &lt;span style="font-weight:bold;"&gt;8168&lt;/span&gt; |   103K|    11 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos lo que explicamos anteriormente. El CBO estima las estadísticas en base al parámetro DB_BLOCK_SIZE seteado en la base de datos.&lt;br /&gt;&lt;br /&gt;Las 3 soluciones disponibles en lo que concierne a las estadísticas en las tablas temporales son:&lt;br /&gt;1.- Usar el hint DYNAMIC_SAMPLING&lt;br /&gt;2.- Usar el hint CARDINALITY&lt;br /&gt;3.- Usar DBMS_STATS.SET_TABLE_STATS&lt;br /&gt;&lt;br /&gt;Veamos cada una de éstas soluciones:&lt;br /&gt;&lt;br /&gt;1.- Usar el hint DYNAMIC_SAMPLING&lt;br /&gt;&lt;br /&gt;Dynamic Sampling le dice a Oracle que realice rápidamente un "escaneo completo" de la tabla para obtener estadísticas que representen la realidad.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT /*+ DYNAMIC_SAMPLING(2) */ id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |    13 |    11 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL   | TEST_TEMP   |     &lt;span style="font-weight:bold;"&gt;1&lt;/span&gt; |    13 |    11 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Utilizando Dynamic Sampling (nivel 2), podemos ver que las estadísticas son más representativas de la realidad.&lt;br /&gt;&lt;br /&gt;Veamos qué sucede si insertamos 15,000 registros a la tabla temporal:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO test_temp&lt;br /&gt;  2  SELECT level&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 15000 ;&lt;br /&gt;&lt;br /&gt;15000 rows created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT /*+ DYNAMIC_SAMPLING(2) */ id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             | 15000 |   190K|    11 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL   | TEST_TEMP   | &lt;span style="font-weight:bold;"&gt;15000 &lt;/span&gt;|   190K|    11 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En 10g, el CBO obtiene por default, estadísticas utilizando Dynamic Sampling (nivel 2) sobre las tablas temporales.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE GLOBAL TEMPORARY TABLE test_temp ( id NUMBER ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; @explains&lt;br /&gt;Plan hash value: 1559088631&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |           |     1 |    13 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL| TEST_TEMP |     &lt;span style="font-weight:bold;"&gt;1&lt;/span&gt; |    13 |     2   (0)| 00:00:01 |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;2.- Usar el hint CARDINALITY&lt;br /&gt;&lt;br /&gt;Otra solución es utilizar el hint CARDINALITY, que le dice a Oracle la cantidad de registros que tiene la tabla. Pero éste valor debemos colocarlos nosotros. No se obtiene dinámicamente.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT /*+ CARDINALITY(test_temp 10000) */ id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             | 10000 |   126K|    11 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL   | TEST_TEMP   | &lt;span style="font-weight:bold;"&gt;10000 &lt;/span&gt;|   126K|    11 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;3.- Usar DBMS_STATS.SET_TABLE_STATS&lt;br /&gt;&lt;br /&gt;Por último, podemos utilizar el paquete DBMS_STATS, para cargar estadísticas representativas de la tabla temporal. El procedimiento SET_TABLE_STATS no analiza la tabla en cuestión, sino que setea las estadísticas de la tabla como nosotros queramos. &lt;br /&gt;Podemos realizar ésto cuando creamos la tabla o luego de cargar datos en la tabla. &lt;br /&gt;Si sabemos la cantidad de registros que puede llegar a tener la tabla temporal, podemos ejecutar éste procedimiento y setear las estadísticas a un valor representativo en base a los registros de la tabla.&lt;br /&gt;Algo muy importante a tener en cuenta es que la ejecución de éste procedimiento implica un COMMIT implícito; por lo que tenemos que tener cuidado en el momento en el cual lo ejecutemos. Podrían ejecutarlo dentro de un procedimiento utilizando AUTONOMOUS_TRANSACTION para que no cree ningún tipo de conflictos.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.set_table_stats( USER, 'TEST_TEMP', numrows =&gt; 10000 ) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXPLAIN PLAN FOR&lt;br /&gt;  2  SELECT id&lt;br /&gt;  3  FROM test_temp ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             | 10000 |   126K|    11 |&lt;br /&gt;|   1 |  TABLE ACCESS FULL   | TEST_TEMP   | &lt;span style="font-weight:bold;"&gt;10000 &lt;/span&gt;|   126K|    11 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2134402564306621565?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2134402564306621565/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2134402564306621565' title='12 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2134402564306621565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2134402564306621565'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/estadsticas-en-tablas-temporales.html' title='Estadísticas en tablas temporales'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-720200264231930937</id><published>2007-09-14T21:01:00.000-03:00</published><updated>2007-09-17T14:38:23.508-03:00</updated><title type='text'>Las columnas más selectivas deberían ir al comienzo del índice?</title><content type='html'>El mito dice lo siguiente: Cuando creamos un índice, las columnas más selectivas (las columnas con la mayor cantidad de valores distintos) deberían ir al comienzo del índice. &lt;br /&gt;Nuestro sentido común nos dice que es lógico crear los índices de esa forma; pero lo que no nos dice nuestro sentido común, es si en realidad éste mito es verdadero o falso. &lt;br /&gt;&lt;br /&gt;La mejor manera de comprobar la veracidad de éste mito, es realizando un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test&lt;br /&gt;  2  (nombre, edad, sexo)&lt;br /&gt;  3  AS&lt;br /&gt;  4  SELECT 'nom_'||level,&lt;br /&gt;  5         to_number(round(dbms_random.value(1,5))||round(dbms_random.value(1,5))),&lt;br /&gt;  6         decode(mod(level,2),1,'M','F')&lt;br /&gt;  7  FROM dual&lt;br /&gt;  8  CONNECT BY level &lt;= 10000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos la cantidad de valores distintos de cada columna:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT COUNT(DISTINCT nombre) nombre,&lt;br /&gt;  2              COUNT(DISTINCT edad) edad,&lt;br /&gt;  3              COUNT(DISTINCT sexo) sexo&lt;br /&gt;  4  FROM test ;&lt;br /&gt;&lt;br /&gt;    NOMBRE       EDAD       SEXO&lt;br /&gt;---------- ---------- ----------&lt;br /&gt;     10000         25          2&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con los valores que estamos viendo, nuestro sentido común nos diría que si tenemos que crear un índice sobre esas 3 columnas, las coloquemos en orden de selectividad (de las columnas con mayor cantidad de valores distintos a las de menor cantidad).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE INDEX test_selectivo_idx ON test(nombre,edad,sexo) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El índice "test_selectivo_idx" que acabamos de crear es el que la gente está más acostumbrada a crear. Colocando las columnas con mayor cantidad de valores distintos en la cabecera del índice hasta llegar a la columna con menor cantidad de valores distintos.&lt;br /&gt;Pero no sólo vamos a crear ese índice. También vamos a crear otro índice invirtiendo el orden de las columnas del índice anterior:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE INDEX test_no_selectivo_idx ON test(sexo,edad,nombre) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a analizar la estructura de los 2 índices para ver si el espacio que ocupa cada uno es el mismo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ANALYZE INDEX test_selectivo_idx VALIDATE STRUCTURE ;&lt;br /&gt;&lt;br /&gt;Index analyzed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; exec print_table('SELECT * FROM index_stats') ;&lt;br /&gt;-----------------&lt;br /&gt;HEIGHT                        : 2&lt;br /&gt;BLOCKS                        : 40&lt;br /&gt;NAME                          : TEST_SELECTIVO_IDX&lt;br /&gt;PARTITION_NAME                :&lt;br /&gt;LF_ROWS                       : 10000&lt;br /&gt;LF_BLKS                       : 35&lt;br /&gt;LF_ROWS_LEN                   : 248894&lt;br /&gt;LF_BLK_LEN                    : 7996&lt;br /&gt;BR_ROWS                       : 34&lt;br /&gt;BR_BLKS                       : 1&lt;br /&gt;BR_ROWS_LEN                   : 543&lt;br /&gt;BR_BLK_LEN                    : 8028&lt;br /&gt;DEL_LF_ROWS                   : 0&lt;br /&gt;DEL_LF_ROWS_LEN               : 0&lt;br /&gt;DISTINCT_KEYS                 : 10000&lt;br /&gt;MOST_REPEATED_KEY             : 1&lt;br /&gt;BTREE_SPACE                   : 287888&lt;br /&gt;USED_SPACE                    : 249437&lt;br /&gt;PCT_USED                      : 87&lt;br /&gt;ROWS_PER_KEY                  : 1&lt;br /&gt;BLKS_GETS_PER_ACCESS          : 3&lt;br /&gt;PRE_ROWS                      : 0&lt;br /&gt;PRE_ROWS_LEN                  : 0&lt;br /&gt;OPT_CMPR_COUNT                : 0&lt;br /&gt;OPT_CMPR_PCTSAVE              : 0&lt;br /&gt;-----------------&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ANALYZE INDEX test_no_selectivo_idx VALIDATE STRUCTURE ;&lt;br /&gt;&lt;br /&gt;Index analyzed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; exec print_table('SELECT * FROM index_stats') ;&lt;br /&gt;-----------------&lt;br /&gt;HEIGHT                        : 2&lt;br /&gt;BLOCKS                        : 40&lt;br /&gt;NAME                          : TEST_NO_SELECTIVO_IDX&lt;br /&gt;PARTITION_NAME                :&lt;br /&gt;LF_ROWS                       : 10000&lt;br /&gt;LF_BLKS                       : 35&lt;br /&gt;LF_ROWS_LEN                   : 248894&lt;br /&gt;LF_BLK_LEN                    : 7996&lt;br /&gt;BR_ROWS                       : 34&lt;br /&gt;BR_BLKS                       : 1&lt;br /&gt;BR_ROWS_LEN                   : 673&lt;br /&gt;BR_BLK_LEN                    : 8028&lt;br /&gt;DEL_LF_ROWS                   : 0&lt;br /&gt;DEL_LF_ROWS_LEN               : 0&lt;br /&gt;DISTINCT_KEYS                 : 10000&lt;br /&gt;MOST_REPEATED_KEY             : 1&lt;br /&gt;BTREE_SPACE                   : 287888&lt;br /&gt;USED_SPACE                    : 249567&lt;br /&gt;PCT_USED                      : 87&lt;br /&gt;ROWS_PER_KEY                  : 1&lt;br /&gt;BLKS_GETS_PER_ACCESS          : 3&lt;br /&gt;PRE_ROWS                      : 0&lt;br /&gt;PRE_ROWS_LEN                  : 0&lt;br /&gt;OPT_CMPR_COUNT                : 2&lt;br /&gt;OPT_CMPR_PCTSAVE              : 19&lt;br /&gt;-----------------&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que los índices usan la misma cantidad de espacio (hay alguna diferencia mínima en bytes, pero en general no hay diferencia en cuanto al espacio que ocupan). Por otro lado, el orden en el cual se encuentran las columnas en un índice, pueden ser o no, mejor candidatos para usar "Index Key Compression" (observando el valor OPT_CMPR_PCTSAVE de la vista index_stats).&lt;br /&gt;&lt;br /&gt;Lo que vamos a realizar ahora es un TKPROF ejecutando las 2 consultas dentro de un bloque PL/SQL para ver la diferencia en cuanto a la performance de utilizar cada uno de los índices:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST',cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET SQL_TRACE = TRUE ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  l_count  PLS_INTEGER ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      FOR i IN ( SELECT * FROM test ) LOOP&lt;br /&gt;  5&lt;br /&gt;  6          SELECT /*+ INDEX(test  test_selectivo_idx) */ COUNT(*)&lt;br /&gt;  7          INTO l_count&lt;br /&gt;  8          FROM test&lt;br /&gt;  9          WHERE nombre = i.nombre&lt;br /&gt; 10            AND edad = i.edad&lt;br /&gt; 11            AND sexo = i.sexo ;&lt;br /&gt; 12&lt;br /&gt; 13          SELECT /*+ INDEX(test  test_no_selectivo_idx) */ COUNT(*)&lt;br /&gt; 14          INTO l_count&lt;br /&gt; 15          FROM test&lt;br /&gt; 16          WHERE nombre = i.nombre&lt;br /&gt; 17            AND edad = i.edad&lt;br /&gt; 18            AND sexo = i.sexo ;&lt;br /&gt; 19&lt;br /&gt; 20      END LOOP ;&lt;br /&gt; 21  END ;&lt;br /&gt; 22  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:02.08&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET SQL_TRACE = FALSE ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos el reporte del TKPROF que generamos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT &lt;span style="font-weight:bold;"&gt;/*+ INDEX(test  test_selectivo_idx) */&lt;/span&gt; COUNT(*)&lt;br /&gt;FROM&lt;br /&gt; TEST WHERE NOMBRE = :B3 AND EDAD = :B2 AND SEXO = :B1&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute  10000      0.17       0.12          0          0          0           0&lt;br /&gt;Fetch    10000      0.13       0.09          0      20034          0       10000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total    20001      0.30       0.22          0      20034          0       10000&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer goal: CHOOSE&lt;br /&gt;Parsing user id: 137     (recursive depth: 1)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;  10000  SORT AGGREGATE&lt;br /&gt;  10000   INDEX RANGE SCAN TEST_SELECTIVO_IDX (object id 82961)&lt;br /&gt;&lt;br /&gt;SELECT &lt;span style="font-weight:bold;"&gt;/*+ INDEX(test  test_no_selectivo_idx) */&lt;/span&gt; COUNT(*)&lt;br /&gt;FROM&lt;br /&gt; TEST WHERE NOMBRE = :B3 AND EDAD = :B2 AND SEXO = :B1&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute  10000      0.19       0.12          0          0          0           0&lt;br /&gt;Fetch    10000      0.12       0.09          0      20034          0       10000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;total    20001      0.31       0.21          0      20034          0       10000&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer goal: CHOOSE&lt;br /&gt;Parsing user id: 137     (recursive depth: 1)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;  10000  SORT AGGREGATE&lt;br /&gt;  10000   INDEX RANGE SCAN TEST_NO_SELECTIVO_IDX (object id 82962)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver en el reporte, leen exactamente la misma cantidad de bloques de datos y se ejecutan aproximadamente en el mismo tiempo de CPU y elapsed. En conclusión, los 2 índices son exactamente iguales.&lt;br /&gt;&lt;br /&gt;Este ejemplo nos sirve como prueba de que el mito no es cierto. No hay diferencias en cuanto a performance en colocar las columnas menos selectivas en la cabecera del índice o las más selectivas.&lt;br /&gt;&lt;br /&gt;La realidad es que tenemos que decidir en qué orden colocar las columnas en el índice en base a cómo vamos a acceder a esas columnas en nuestras consultas. Si tenemos el índice compuesto por (sexo,edad,nombre) y accedemos sólo por la columna sexo, no tiene sentido crear un índice compuesto B*Tree y quizás nos convendría crear un índice Bitmap sólo por la columna sexo.&lt;br /&gt;&lt;br /&gt;Por ejemplo, si tenemos estas 2 consultas ...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT d FROM test WHERE a = :a AND b = :b ;&lt;br /&gt;&lt;br /&gt;SELECT d FROM test WHERE b = :b ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;... lo más razonable es crear un índice en el siguiente orden: (b,a). De ésta manera, ese índice puede ser usado en las 2 consultas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-720200264231930937?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/720200264231930937/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=720200264231930937' title='4 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/720200264231930937'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/720200264231930937'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/las-columnas-ms-selectivas-deberan-ir.html' title='Las columnas más selectivas deberían ir al comienzo del índice?'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-5411175212079010871</id><published>2007-09-14T17:51:00.001-03:00</published><updated>2009-04-29T18:49:49.122-03:00</updated><title type='text'>Diferencias entre COUNT(1) y COUNT(*) - Parte 1</title><content type='html'>Miles y miles y miles de veces veo que se arman consultas SQL que contiene el COUNT(1). Este es un viejo mito. Muchos piensan que colocar COUNT(1) en vez del COUNT(*) mejora la performance de la consulta.... pero en realidad mejora la performance?&lt;br /&gt;&lt;br /&gt;Veamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt; 2  SELECT level id, 'texto_'||level texto&lt;br /&gt; 3  FROM dual&lt;br /&gt; 4  CONNECT BY level &lt;= 100000 ;  Table created.  SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ahora ejecutemos las 2 consultas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET SQL_TRACE = TRUE ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT COUNT(1)&lt;br /&gt; 2  FROM test ;&lt;br /&gt;&lt;br /&gt; COUNT(1)&lt;br /&gt;----------&lt;br /&gt;   100000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT COUNT(*)&lt;br /&gt; 2  FROM test ;&lt;br /&gt;&lt;br /&gt; COUNT(*)&lt;br /&gt;----------&lt;br /&gt;   100000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER SESSION SET SQL_TRACE = FALSE ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos lo que nos muestra el TKPROF:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;select count(1)&lt;br /&gt;from&lt;br /&gt;test&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.01       0.01          0        306          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;total        4      0.01       0.01          0        306          0           1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;select count(*)&lt;br /&gt;from&lt;br /&gt;test&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.01       0.01          0        306          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;total        4      0.01       0.01          0        306          0           1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que en las 2 consultas tenemos la misma cantidad de lecturas de bloques (disk, query, current) y el mismo tiempo de elapsed y cpu.&lt;br /&gt;Las 2 consultas son idénticas y no hay un incremento en la performance por utilizar el COUNT(1) en vez del COUNT(*).&lt;br /&gt;&lt;br /&gt;Incluso podría poner cualquier cosa en el COUNT... obtendríamos los mismos resultados:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT COUNT(2222222)&lt;br /&gt;FROM&lt;br /&gt;TEST&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.01       0.01          0        306          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      0.01       0.01          0        306          0           1&lt;br /&gt;&lt;br /&gt;SELECT COUNT('EJEMPLO')&lt;br /&gt;FROM&lt;br /&gt;TEST&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.01       0.01          0        306          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      0.01       0.01          0        306          0           1&lt;br /&gt;&lt;br /&gt;SELECT COUNT(12345)&lt;br /&gt;FROM&lt;br /&gt;TEST&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.01       0.01          0        306          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      0.01       0.01          0        306          0           1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, por último veamos los siguiente. Qué sucede si realizo un COUNT de alguna de las columnas de la tabla? Veamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT COUNT(texto)&lt;br /&gt; 2  FROM test ;&lt;br /&gt;&lt;br /&gt;COUNT(TEXTO)&lt;br /&gt;------------&lt;br /&gt;     100000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La consulta nos devolvió la cantidad de registros totales de la tabla. Ahora veamos qué me devuelve la consulta si la columna TEXTO tiene valores nulos.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE test&lt;br /&gt; 2  SET texto = NULL&lt;br /&gt; 3  WHERE id &gt; 50000 ;&lt;br /&gt;&lt;br /&gt;50000 rows updated.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT COUNT(texto)&lt;br /&gt; 2  FROM test ;&lt;br /&gt;&lt;br /&gt;COUNT(TEXTO)&lt;br /&gt;------------&lt;br /&gt;      50000&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, el COUNT sobre una columna sólo cuenta la cantidad de valores que no son nulos. Por lo tanto, tenemos que tener cuidado a la hora de realizar un COUNT y tener en cuenta estos pequeños detalles.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-5411175212079010871?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/5411175212079010871/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=5411175212079010871' title='5 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5411175212079010871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5411175212079010871'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/diferencias-entre-count1-y-count.html' title='Diferencias entre COUNT(1) y COUNT(*) - Parte 1'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-5330851060867062364</id><published>2007-09-14T11:06:00.000-03:00</published><updated>2007-09-14T13:01:12.787-03:00</updated><title type='text'>BULK COLLECT con miles de registros y la cláusula LIMIT</title><content type='html'>Veo muchísimas veces, que cuando se utiliza BULK COLLECT en los códigos PL/SQL, no se coloca la cláusula LIMIT. Cuando no utilizamos ésta cláusula, lo único que ganamos es  arruinar la memoria del proceso.&lt;br /&gt;La cláusula LIMIT nos permite definir la cantidad de 'datos' que vamos a colocar en memoria. Cuando utilizamos LIMIT, lo ideal es definirlo en un valor entre 100 a 500. Personalmente, elijo el valor 100 porque en base a mi experiencia suele ser el mejor valor. Pero porque elijo un valor tan chico y no 1000 o 5000 por ejemplo? Bueno, por la simple razón que manejar gran cantidad de datos en memoria es más costoso que manejar poca cantidad.&lt;br /&gt;Si elegimos un valor alto en el LIMIT, puede darse 3 casos:&lt;br /&gt;- Que nuestro código se ejecute más rápidamente (improbable).&lt;br /&gt;- Que nuestro código se ejecute en igual tiempo (improbable).&lt;br /&gt;- Que nuestro código se ejecute más lentamente (probable).&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo para entender mejor las consecuencia de no utilizar la cláusula LIMIT.&lt;br /&gt;&lt;br /&gt;Primero creamos una tabla TEST con 10.000 registros (para ver la diferencia del uso de la cláusula LIMIT, no hace falta realizar el ejemplo con millones de registros. Con unos miles de registros nos alcanza para entender el tema) y una tabla TEST_2 con la estructura de la tabla TEST pero sin registros:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT level id , 'oracle_'||level texto&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test_2 AS&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  WHERE 1 = 2 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Antes de ejecutar el primer código, voy a liberar la memoria que ya no se está utilizando en mi sesión actual para poder ver claramente la diferencia de las estadísticas tomadas en cada ejecución.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; exec dbms_session.FREE_UNUSED_USER_MEMORY ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejecutamos un código PL/SQL con Bulk Collect pero SIN la cláusula LIMIT:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  TYPE t_array_number   IS TABLE OF NUMBER ;&lt;br /&gt;  3  TYPE t_array_varchar2 IS TABLE OF VARCHAR2(50) ;&lt;br /&gt;  4  t_array_id     t_array_number ;&lt;br /&gt;  5  t_array_texto  t_array_varchar2 ;&lt;br /&gt;  6  CURSOR cur IS&lt;br /&gt;  7      SELECT * FROM test ;&lt;br /&gt;  8  BEGIN&lt;br /&gt;  9      OPEN cur ;&lt;br /&gt; 10      LOOP&lt;br /&gt; 11&lt;br /&gt; 12      FETCH cur BULK COLLECT INTO t_array_id , t_array_texto ;&lt;br /&gt; 13&lt;br /&gt; 14      FORALL i IN 1 .. t_array_id.COUNT&lt;br /&gt; 15          INSERT INTO test_2&lt;br /&gt; 16          VALUES (t_array_id(i) , t_array_texto(i)) ;&lt;br /&gt; 17&lt;br /&gt; 18      EXIT WHEN cur%NOTFOUND ;&lt;br /&gt; 19&lt;br /&gt; 20      END LOOP ;&lt;br /&gt; 21      COMMIT ;&lt;br /&gt; 22      CLOSE cur ;&lt;br /&gt; 23  END ;&lt;br /&gt; 24  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.04&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Antes de ejecutar el segundo código, voy a liberar nuevamente la memoria que ya no se está utilizando en mi sesión actual.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; exec dbms_session.FREE_UNUSED_USER_MEMORY ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora ejecutamos el mismo código PL/SQL con Bulk Collect pero CON la cláusula LIMIT:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  TYPE t_array_number   IS TABLE OF NUMBER ;&lt;br /&gt;  3  TYPE t_array_varchar2 IS TABLE OF VARCHAR2(50) ;&lt;br /&gt;  4  t_array_id     t_array_number ;&lt;br /&gt;  5  t_array_texto  t_array_varchar2 ;&lt;br /&gt;  6  CURSOR cur IS&lt;br /&gt;  7      SELECT * FROM test ;&lt;br /&gt;  8  BEGIN&lt;br /&gt;  9      OPEN cur ;&lt;br /&gt; 10      LOOP&lt;br /&gt; 11&lt;br /&gt; 12      FETCH cur BULK COLLECT INTO t_array_id , t_array_texto &lt;span style="font-weight:bold;"&gt;LIMIT 100&lt;/span&gt; ;&lt;br /&gt; 13&lt;br /&gt; 14      FORALL i IN 1 .. t_array_id.COUNT&lt;br /&gt; 15          INSERT INTO test_2&lt;br /&gt; 16          VALUES (t_array_id(i) , t_array_texto(i)) ;&lt;br /&gt; 17&lt;br /&gt; 18      EXIT WHEN cur%NOTFOUND ;&lt;br /&gt; 19&lt;br /&gt; 20      END LOOP ;&lt;br /&gt; 21      COMMIT ;&lt;br /&gt; 22      CLOSE cur ;&lt;br /&gt; 23  END ;&lt;br /&gt; 24  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.04&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Podemos ver que en la segunda ejecución, estoy cargando en memoria 100 registros en cada Fetch que realizo. A diferencia de la primer ejecución, que carga todos los datos de una vez.&lt;br /&gt;&lt;br /&gt;Veamos las estadísticas obtenidas de las 2 ejecuciones anteriores:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Nombre                         Ejecución_1 Ejecución_2  Diferencia&lt;br /&gt;------------------------------ ----------- ----------- -----------&lt;br /&gt;LATCH.kwqit: protect wakeup ti           1           0          -1&lt;br /&gt;LATCH.simulator lru latch                1           0          -1&lt;br /&gt;LATCH.spilled msgs queues list           1           0          -1&lt;br /&gt;LATCH.transaction allocation             3           0          -3&lt;br /&gt;LATCH.session timer                      5           0          -5&lt;br /&gt;LATCH.multiblock read objects            8           0          -8&lt;br /&gt;LATCH.channel operations paren          11           0         -11&lt;br /&gt;LATCH.child cursor hash table           20           8         -12&lt;br /&gt;LATCH.Consistent RBA                    56           6         -50&lt;br /&gt;LATCH.lgwr LWN SCN                      56           6         -50&lt;br /&gt;LATCH.mostly latch-free SCN             56           6         -50&lt;br /&gt;LATCH.active checkpoint queue           63           7         -56&lt;br /&gt;LATCH.session idle bit                 183          45        -138&lt;br /&gt;LATCH.enqueues                         219          49        -170&lt;br /&gt;LATCH.redo writing                     241          25        -216&lt;br /&gt;LATCH.SQL memory manager worka         337           0        -337&lt;br /&gt;LATCH.messages                         427          42        -385&lt;br /&gt;LATCH.simulator hash latch             844           4        -840&lt;br /&gt;LATCH.dml lock allocation            1,428         154      -1,274&lt;br /&gt;LATCH.shared pool                    1,586         271      -1,315&lt;br /&gt;LATCH.row cache enqueue latch        1,648         194      -1,454&lt;br /&gt;LATCH.cache buffers lru chain        1,521           5      -1,516&lt;br /&gt;LATCH.library cache pin alloca       2,900         362      -2,538&lt;br /&gt;LATCH.row cache objects              4,438         502      -3,936&lt;br /&gt;LATCH.enqueue hash chains            4,841         508      -4,333&lt;br /&gt;LATCH.checkpoint queue latch         6,296         521      -5,775&lt;br /&gt;LATCH.session allocation            19,333       1,893     -17,440&lt;br /&gt;LATCH.undo global data              30,208       2,945     -27,263&lt;br /&gt;LATCH.redo allocation               30,716       3,231     -27,485&lt;br /&gt;LATCH.sequence cache                42,506       4,124     -38,382&lt;br /&gt;LATCH.library cache pin             60,580       6,241     -54,339&lt;br /&gt;LATCH.library cache                 76,555       7,911     -68,644&lt;br /&gt;&lt;br /&gt;LATCHES:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;&lt;br /&gt; Ejecución_1   Ejecución_2   Diferencia   Porcentaje&lt;br /&gt;     670,187        67,521     -602,666      992.56%&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una de las cosas que me interesa mostrarles acerca de éstas ejecuciones son los LATCHES (loqueos). Si bien la ejecución de los 2 códigos PL/SQL demoraron exactamente lo mismo en ejecutarse (en éste ejemplo procesando solamente 10.000 registros), las estadísticas nos muestran que estamos empleados muchísimos más loqueos en la primer ejecución que en la segunda. Pero porqué sucede ésto? Recordemos que en la segunda ejecución, lo único que modificamos en el código fue el agregado de la cláusula LIMIT. Bien, como dijimos en el comienzo, manejar gran cantidad de memoria es más costoso que manejar poca cantidad, por lo que Oracle tiene que emplear mayor cantidad de loqueos para manejar los 10.000 registros que subimos a memoria en la primer ejecución, que manejar solamente 100 registros en la segunda ejecución.&lt;br /&gt;Este ejemplo lo realizamos con un sólo usuario concurrente.... pero imagínese qué sucedería si tenemos muchos usuarios concurrentes realizando lo mismo que nosotros... y por lo tanto, generando gran cantidad de loqueos....      &lt;br /&gt;&lt;br /&gt;NOTA: Tenemos que evitar los loqueos a toda costa ya que los loqueos afectan la performance del sistema. Mientras mayor sea la cantidad de loqueos, nuestro sistema se vuelve cada vez menos escalable; y como consecuencia, cada vez soporta menor cantidad de usuarios concurrentes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-5330851060867062364?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/5330851060867062364/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=5330851060867062364' title='20 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5330851060867062364'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5330851060867062364'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/bulk-collect-con-miles-de-registros-y.html' title='BULK COLLECT con miles de registros y la cláusula LIMIT'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>20</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-258671487568356164</id><published>2007-09-13T12:45:00.000-03:00</published><updated>2007-09-13T15:13:28.262-03:00</updated><title type='text'>Modificar millones de registros de una tabla</title><content type='html'>Muchas veces me hacen la siguiente pregunta: ¿ Cómo puedo modificar de manera eficiente los datos de una tabla que contiene millones de registros sin tener impacto en la performance del sistema ?&lt;br /&gt;&lt;br /&gt;Suele ocurrir, que la solución que se utiliza es realizar un simple UPDATE. Si hablamos de performance, la ejecución de ésta sentencia puede tener un gran impacto en la base de datos. Porqué? porque principalmente, generamos muchísimos bytes de redo y undo.&lt;br /&gt;&lt;br /&gt;Si tengo que modificar millones de datos de una tabla, optaría por NO modificarlos.&lt;br /&gt;Implementaría la siguiente estrategia. &lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;Supongamos que tenemos una tabla llamada TEST que contiene 5 millones de registros, una primary key y un índice único:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test NOLOGGING PARALLEL 4 AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nom&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 5000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:16.06&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test ADD CONSTRAINT test_id_pk PRIMARY KEY (id) NOLOGGING PARALLEL 4 ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:14.03&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX test_id_nom_uq ON test(id,nom) NOLOGGING PARALLEL 4 ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  WHERE rownum &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;        ID NOM&lt;br /&gt;---------- --------------&lt;br /&gt;     37011 nom_37011&lt;br /&gt;     37012 nom_37012&lt;br /&gt;     37013 nom_37013&lt;br /&gt;     37014 nom_37014&lt;br /&gt;     37015 nom_37015&lt;br /&gt;     37016 nom_37016&lt;br /&gt;     37017 nom_37017&lt;br /&gt;     37018 nom_37018&lt;br /&gt;     37019 nom_37019&lt;br /&gt;     37020 nom_37020&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Si nosotros quisiéramos modificar los datos de la columna NOM, no vamos a utilizar la cláusula UPDATE, sino que vamos a realizar los siguientes pasos:&lt;br /&gt;&lt;br /&gt;1) Lo primero que vamos a hacer es crear una tabla con las modificaciones que queremos realizar. En nuestro caso dijimos que vamos a modificar la columna NOM. Fijense que cuando creé la tabla, esa columna está en minúsculas. El cambio que vamos a realizar es colocar el contenido en mayúsculas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test_nueva NOLOGGING PARALLEL 4 AS&lt;br /&gt;  2  SELECT id, &lt;span style="font-weight:bold;"&gt;UPPER(nom)&lt;/span&gt; nom&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:03.05&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, lo que hice fue colocar en el SELECT los cambios que quería realizar. En sólo 3 segundos tenemos nuestra tabla nueva creada y con los cambios realizados.&lt;br /&gt;&lt;br /&gt;2)Agregamos las constraints a la nueva tabla y sus respectivos índices:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test_nueva ADD CONSTRAINT test_id_pk_2 PRIMARY KEY(id) NOLOGGING PARALLEL 4 ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:22.05&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX test_id_nom_uq_2 ON test_nueva(id,nom) NOLOGGING PARALLEL 4 ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:20.03&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;3) Dropeamos la tabla TEST:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DROP TABLE test ;&lt;br /&gt;&lt;br /&gt;Table dropped.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.02&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;4) Modificamos el nombre de la tabla TEST_NUEVA, las constraints e índices:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test_nueva RENAME TO test ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.03&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER INDEX test_id_nom_uq_2 RENAME TO test_id_nom_uq ;&lt;br /&gt;&lt;br /&gt;Index altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.02&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test RENAME CONSTRAINT test_id_pk_2 TO test_id_pk ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.02&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;5) Por último, granteamos los permisos que teníamos en la tabla vieja a la nueva tabla.&lt;br /&gt;&lt;br /&gt;Para crear los objetos de éste ejemplo, utilicé las cláusulas NOLOGGING y PARALLEL. La cláusula NOLOGGING la utilicé para generar muy poco redo y nada de undo. &lt;br /&gt;La cláusula PARALLEL la utilicé para paralelizar (a través de 4 CPU's en nuestro ejemplo) las operaciones que ejecutamos sobre los objetos.&lt;br /&gt;En caso de que queramos volver a colocar la tabla en modo LOGGING y NOPARALLEL, ejecutaríamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE test LOGGING NOPARALLEL ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:00.01&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Cómo quedaron los datos de nuestra tabla? Veamos algunos registros...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  WHERE rownum &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;        ID NOM&lt;br /&gt;---------- --------------&lt;br /&gt;     37011 NOM_37011&lt;br /&gt;     37012 NOM_37012&lt;br /&gt;     37013 NOM_37013&lt;br /&gt;     37014 NOM_37014&lt;br /&gt;     37015 NOM_37015&lt;br /&gt;     37016 NOM_37016&lt;br /&gt;     37017 NOM_37017&lt;br /&gt;     37018 NOM_37018&lt;br /&gt;     37019 NOM_37019&lt;br /&gt;     37020 NOM_37020&lt;br /&gt;&lt;br /&gt;10 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos observar, realizamos las modificaciones que necesitábamos sin tener un impacto en la performance del sistema.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-258671487568356164?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/258671487568356164/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=258671487568356164' title='9 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/258671487568356164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/258671487568356164'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/modificar-millones-de-registros-de-una.html' title='Modificar millones de registros de una tabla'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-8678763734924813453</id><published>2007-09-11T12:57:00.000-03:00</published><updated>2007-09-11T14:03:57.357-03:00</updated><title type='text'>Clustering Factor</title><content type='html'>El Clustering Factor nos dice que tan ordenado se encuentran los registros de la tabla en base a los valores del índice.&lt;br /&gt;&lt;br /&gt;Respecto al valor que obtenemos del Clustering Factor, podemos hacer 2 observaciones:&lt;br /&gt;&lt;br /&gt;- Si el valor se acerca a la cantidad de bloques de la tabla, entonces se dice que la tabla está muy bien ordenada. En ese caso, el contendido del índice de un leaf block, tiende a apuntar a registros del mismo bloque de datos.&lt;br /&gt;- Si el valor se acerca a la cantidad de registros de la tabla, entonces se dice que la tabla está muy mal ordenada. En ese caso, es improbable que el contendido del índice de un leaf block, tienda a apuntar a registros del mismo bloque de datos.&lt;br /&gt;&lt;br /&gt;Bien, pero como afectaría el Clustering Factor en nuestra consulta?&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;Creamos una tabla llamada "ordenada":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE ordenada AS&lt;br /&gt;  2  SELECT level x, rpad(dbms_random.random,100,'*') y&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE ordenada&lt;br /&gt;  2  ADD CONSTRAINT ord_pk&lt;br /&gt;  3  PRIMARY KEY(x) ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'ORDENADA', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Creamos una tabla llamada "desordenada":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE desordenada AS&lt;br /&gt;  2  SELECT x, y&lt;br /&gt;  3  FROM ordenada&lt;br /&gt;  4  ORDER BY y ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE desordenada&lt;br /&gt;  2  ADD CONSTRAINT desord_pk&lt;br /&gt;  3  PRIMARY KEY(x) ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'DESORDENADA', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya tenemos nuestras 2 tablas creadas, analizadas y con sus respectivos índices.&lt;br /&gt;&lt;br /&gt;Veamos un poco los valores que hay en cada una de las tablas...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT x, y&lt;br /&gt;  2  FROM ordenada&lt;br /&gt;  3  WHERE rownum &lt;= 5 ;&lt;br /&gt;&lt;br /&gt;     X Y&lt;br /&gt;------ --------------------------------------------------&lt;br /&gt;     1 1546142627****************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;     2 -1607493826***************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;     3 -1823003438***************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;     4 -385107593****************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;     5 -478367392****************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;5 rows selected.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT x, y&lt;br /&gt;  2  FROM desordenada&lt;br /&gt;  3  WHERE rownum &lt;= 5 ;&lt;br /&gt;&lt;br /&gt;     X Y&lt;br /&gt;------ --------------------------------------------------&lt;br /&gt; 50230 -1000006552***************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt; 37976 1000011102****************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt; 71202 1000046989****************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt; 54512 -1000054026***************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt; 73252 -1000056784***************************************&lt;br /&gt;       **************************************************&lt;br /&gt;&lt;br /&gt;5 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Pueden notar que la columna X (donde creamos las 2 primary key) está ordenada en la tabla "ordenada" y desordenada en la tabla "desordenada".&lt;br /&gt;&lt;br /&gt;Veamos el Clustering Factor de los índices de cada tabla:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT a.index_name, b.num_rows, b.blocks, a.clustering_factor&lt;br /&gt;  2  FROM dba_indexes a, dba_tables b&lt;br /&gt;  3  WHERE a.index_name IN ('ORD_PK','DESORD_PK')&lt;br /&gt;  4    AND a.table_name = b.table_name ;&lt;br /&gt;&lt;br /&gt;INDEX_NAME                       NUM_ROWS     BLOCKS CLUSTERING_FACTOR&lt;br /&gt;------------------------------ ---------- ---------- -----------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;DESORD_PK                          100000       1539             99932&lt;br /&gt;ORD_PK                             100000       1539              1539&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;2 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para el índice DESORD_PK podemos ver que el valor del CLUSTERING_FACTOR se acerca al valor de NUM_ROWS, esto nos quiere decir que la tabla se encuentra muy mal ordenada y que es improbable que el contendido del leaf block de éste índice, tienda a apuntar a registros del mismo bloque de datos.&lt;br /&gt;&lt;br /&gt;Para el índice ORD_PK podemos ver que el valor del CLUSTERING_FACTOR es igual al valor de BLOCKS, esto nos quiere decir que la tabla se encuentra muy bien ordenada y que el contendido del leaf block de éste índice, tiende a apuntar a registros del mismo bloque de datos.&lt;br /&gt;&lt;br /&gt;Qué consecuencias puede tener el Clustering Factor en la ejecución de nuestra consulta?&lt;br /&gt;&lt;br /&gt;Supongamos la siguiente consulta en la tabla "ordenada":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM ordenada&lt;br /&gt;  4  WHERE x BETWEEN 55000 AND 60000 ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |             |  5002 |   512K|    89 |&lt;br /&gt;|   1 |  &lt;span style="font-weight:bold;"&gt;TABLE ACCESS BY INDEX ROWID| ORDENADA    |  5002 |   512K|    89&lt;/span&gt; |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN          | ORD_PK      |  5002 |       |    12&lt;/span&gt; |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("ORDENADA"."X"&gt;=55000 AND "ORDENADA"."X"&lt;=60000)&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;        757  consistent gets&lt;br /&gt;          0  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;     562801  bytes sent via SQL*Net to client&lt;br /&gt;       4159  bytes received via SQL*Net from client&lt;br /&gt;        335  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;       5001  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que el CBO optó por utilizar el índice ORD_PK para realizar un range scan. El CBO sabe, gracias al valor del Clustering Factor, que el índice de ésta tabla se encuentra ordenado; y como nosotros colocamos la condición "BETWEEN 55000 AND 60000", Oracle puede caminar en forma horizontal a través de los leaf blocks sin necesidad de realizar demasiadas lecturas de bloques ya que los valores se encuentran ordenados; y como se encuentran ordenados, el contenido de los leaf blocks tienden a apuntar a registros del mismo bloque de datos. &lt;br /&gt;&lt;br /&gt;Ahora veamos que sucede si ejecutamos la misma consulta pero en la tabla "desordenada":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM desordenada&lt;br /&gt;  4  WHERE x BETWEEN 55000 AND 60000 ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name        | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |              |  5002 |   512K|   150 |&lt;br /&gt;|*  1 |  &lt;span style="font-weight:bold;"&gt;TABLE ACCESS FULL   | DESORDENADA  |  5002 |   512K|   150&lt;/span&gt; |&lt;br /&gt;---------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - filter("DESORDENADA"."X"&gt;=55000 AND "DESORDENADA"."X"&lt;=60000)&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       1876  consistent gets&lt;br /&gt;       1539  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;     562801  bytes sent via SQL*Net to client&lt;br /&gt;       4159  bytes received via SQL*Net from client&lt;br /&gt;        335  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;       5001  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En éste caso, el CBO sabe, gracias al valor del Clustering Factor, que el índice de ésta tabla se encuentra desordenado. Entonces, opta por realizar un full scan ya que si accede por el índice DESORD_PK, va a tener que leer mayor cantidad de bloques que si realiza un barrido completo de la tabla.&lt;br /&gt;&lt;br /&gt;Hay personas fanáticas de los índices y tratan de evitar a toda costa el acceso por full scan. Bien, veamos que sucede si fuerzo al CBO a que utilice el índice de la tabla desordenada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT /*+ INDEX(desordenada desord_pk) */ *&lt;br /&gt;  3  FROM desordenada&lt;br /&gt;  4  WHERE x BETWEEN 55000 AND 60000 ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   |  Name        | Rows  | Bytes | Cost  |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |              |  5002 |   512K|  5011 |&lt;br /&gt;|   1 |  &lt;span style="font-weight:bold;"&gt;TABLE ACCESS BY INDEX ROWID| DESORDENADA  |  5002 |   512K|  5011&lt;/span&gt; |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN          | DESORD_PK    |  5002 |       |    12&lt;/span&gt; |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("DESORDENADA"."X"&gt;=55000 AND "DESORDENADA"."X"&lt;=60000)&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       5344  consistent gets&lt;br /&gt;       1495  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;     562801  bytes sent via SQL*Net to client&lt;br /&gt;       4159  bytes received via SQL*Net from client&lt;br /&gt;        335  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;       5001  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que si utilizamos el índice de la tabla desordenada, leemos muchos más bloques de datos que si dejamos al CBO hacer lo que mejor sabe hacer; que en éste caso para la tabla desordenada, es realizar un full scan de la tabla.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-8678763734924813453?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/8678763734924813453/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=8678763734924813453' title='11 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8678763734924813453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8678763734924813453'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/clustering-factor.html' title='Clustering Factor'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3803906083358412055</id><published>2007-09-10T16:39:00.000-03:00</published><updated>2007-09-10T17:10:42.807-03:00</updated><title type='text'>Cantidad de CPU utilizada en un procedimiento</title><content type='html'>Todos conocemos la función DBMS_UTILITY.GET_TIME() que se utiliza para tomar el tiempo entre dos puntos de un determinado proceso. Esta función es muy utilizada por los desarrolladores. La verdad es que no conozco un sólo desarrollador que jamás haya usado ésta función.&lt;br /&gt;En 10g, Oracle agrega una nueva función, DBMS_UTILITY.GET_CPU_TIME() que sirve para saber la cantidad de CPU que es utilizada entre dos puntos de un determinado proceso.&lt;br /&gt;&lt;br /&gt;En nuestro primer ejemplo vamos a ver la utilización de éstas 2 funciones sin acceder a disco, y luego, vamos a acceder a disco y vamos a ver la diferencia que se genera tanto en tiempo de CPU como de procesamiento:&lt;br /&gt;&lt;br /&gt;Ejemplo SIN I/0 a disco:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR1&gt; DECLARE&lt;br /&gt;  2&lt;br /&gt;  3  l_inicio_1  PLS_INTEGER ;&lt;br /&gt;  4  l_inicio_2  PLS_INTEGER ;&lt;br /&gt;  5  l_fin_1     PLS_INTEGER ;&lt;br /&gt;  6  l_fin_2     PLS_INTEGER ;&lt;br /&gt;  7&lt;br /&gt;  8  BEGIN&lt;br /&gt;  9&lt;br /&gt; 10  l_inicio_1 := DBMS_UTILITY.GET_TIME() ;&lt;br /&gt; 11  l_inicio_2 := DBMS_UTILITY.GET_CPU_TIME() ;&lt;br /&gt; 12&lt;br /&gt;&lt;span style="font-weight:bold;"&gt; 13      FOR i IN 1 .. 99999999 LOOP&lt;br /&gt; 14          NULL ;&lt;br /&gt; 15      END LOOP ;&lt;/span&gt;&lt;br /&gt; 16&lt;br /&gt; 17  l_fin_1 := DBMS_UTILITY.GET_TIME()     - l_inicio_1 ;&lt;br /&gt; 18  l_fin_2 := DBMS_UTILITY.GET_CPU_TIME() - l_inicio_2 ;&lt;br /&gt; 19&lt;br /&gt; 20  DBMS_OUTPUT.PUT_LINE( 'GET_TIME = '     || l_fin_1 || ' hsecs.' ) ;&lt;br /&gt; 21  DBMS_OUTPUT.PUT_LINE( 'GET_CPU_TIME = ' || l_fin_2 || ' hsecs.' ) ;&lt;br /&gt; 22&lt;br /&gt; 23  END ;&lt;br /&gt; 24  /&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;GET_TIME = 1820 hsecs.&lt;br /&gt;GET_CPU_TIME = 1692 hsecs.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejemplo CON I/0 a disco:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR1&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT level id&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR1&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR1&gt; DECLARE&lt;br /&gt;  2&lt;br /&gt;  3  l_inicio_1  PLS_INTEGER ;&lt;br /&gt;  4  l_inicio_2  PLS_INTEGER ;&lt;br /&gt;  5  l_fin_1     PLS_INTEGER ;&lt;br /&gt;  6  l_fin_2     PLS_INTEGER ;&lt;br /&gt;  7&lt;br /&gt;  8  BEGIN&lt;br /&gt;  9&lt;br /&gt; 10  l_inicio_1 := DBMS_UTILITY.GET_TIME() ;&lt;br /&gt; 11  l_inicio_2 := DBMS_UTILITY.GET_CPU_TIME() ;&lt;br /&gt; 12&lt;br /&gt;&lt;span style="font-weight:bold;"&gt; 13      FOR i IN ( SELECT * FROM test ) LOOP&lt;br /&gt; 14          NULL ;&lt;br /&gt; 15      END LOOP ;&lt;/span&gt;&lt;br /&gt; 16&lt;br /&gt; 17  l_fin_1 := DBMS_UTILITY.GET_TIME()     - l_inicio_1 ;&lt;br /&gt; 18  l_fin_2 := DBMS_UTILITY.GET_CPU_TIME() - l_inicio_2 ;&lt;br /&gt; 19&lt;br /&gt; 20  DBMS_OUTPUT.PUT_LINE( 'GET_TIME = '     || l_fin_1 || ' hsecs.' ) ;&lt;br /&gt; 21  DBMS_OUTPUT.PUT_LINE( 'GET_CPU_TIME = ' || l_fin_2 || ' hsecs.' ) ;&lt;br /&gt; 22&lt;br /&gt; 23  END ;&lt;br /&gt; 24  /&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;GET_TIME = 7884 hsecs.&lt;br /&gt;GET_CPU_TIME = 7741 hsecs.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3803906083358412055?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3803906083358412055/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3803906083358412055' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3803906083358412055'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3803906083358412055'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/cantidad-de-cpu-utilizada-en-un.html' title='Cantidad de CPU utilizada en un procedimiento'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1438872015976443191</id><published>2007-09-10T16:04:00.001-03:00</published><updated>2007-09-24T15:59:37.598-03:00</updated><title type='text'>Comillas simples dentro de cadenas de texto en 10g</title><content type='html'>En Oracle 10g se agrega una nueva funcionalidad que permite escribir literales en una cadena de texto con comillas simples, sin la necesidad de escribir los literales con dobles, triples, ... comillas. Esta funcionalidad es muy útil cuando los desarrolladores trabajan con sql dinámico, ya que muchas consultas escritas en sql dinámico pueden ser bastante complejas debido a las comillas.&lt;br /&gt;En PL/SQL llamamos a esta funcionalidad de ésta manera &lt;span style="font-weight:bold;"&gt;q'[.....]'&lt;/span&gt;. Podemos reemplazar los [] con cualquiera de éstos caracteres:&lt;br /&gt;[ ]&lt;br /&gt;{ }&lt;br /&gt;( )&lt;br /&gt;&lt; &gt;&lt;br /&gt;&lt;br /&gt;Creamos una tabla de ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE emp AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nom&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 100 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En Oracle 9iR2 escribiríamos éste código PL/SQL:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  l_query  VARCHAR2(100) ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      l_query := 'SELECT id FROM emp WHERE nom = ''nom_10''' ;&lt;br /&gt;  5      EXECUTE IMMEDIATE l_query ;&lt;br /&gt;  6      DBMS_OUTPUT.PUT_LINE(l_query) ;&lt;br /&gt;  7  END ;&lt;br /&gt;  8  /&lt;br /&gt;&lt;br /&gt;SELECT id FROM emp WHERE nom = 'nom_10'&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En 10gR1 podríamos transcribir ese mismo código de la siguiente manera:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR1&gt; DECLARE&lt;br /&gt;  2  l_query  VARCHAR2(100) ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      l_query := &lt;span style="font-weight:bold;"&gt;q'[SELECT id FROM emp WHERE nom = 'nom_10']'&lt;/span&gt; ;&lt;br /&gt;  5      EXECUTE IMMEDIATE l_query ;&lt;br /&gt;  6      DBMS_OUTPUT.PUT_LINE(l_query) ;&lt;br /&gt;  7  END ;&lt;br /&gt;  8  /&lt;br /&gt;&lt;br /&gt;SELECT id FROM emp WHERE nom = 'nom_10'&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR1&gt; DECLARE&lt;br /&gt;  2  l_query  VARCHAR2(100) ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      l_query := &lt;span style="font-weight:bold;"&gt;q'(SELECT id FROM emp WHERE nom = 'nom_10')'&lt;/span&gt; ;&lt;br /&gt;  5      EXECUTE IMMEDIATE l_query ;&lt;br /&gt;  6      DBMS_OUTPUT.PUT_LINE(l_query) ;&lt;br /&gt;  7  END ;&lt;br /&gt;  8  /&lt;br /&gt;&lt;br /&gt;SELECT id FROM emp WHERE nom = 'nom_10'&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_10gR1&gt; DECLARE&lt;br /&gt;  2  l_query  VARCHAR2(100) ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      l_query := &lt;span style="font-weight:bold;"&gt;q'{SELECT id FROM emp WHERE nom = 'nom_10'}'&lt;/span&gt; ;&lt;br /&gt;  5      EXECUTE IMMEDIATE l_query ;&lt;br /&gt;  6      DBMS_OUTPUT.PUT_LINE(l_query) ;&lt;br /&gt;  7  END ;&lt;br /&gt;  8  /&lt;br /&gt;&lt;br /&gt;SELECT id FROM emp WHERE nom = 'nom_10'&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Pueden observar, que con ésta nueva funcionalidad, ya no hace falta preocuparnos por las comillas en las cadenas de texto.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1438872015976443191?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1438872015976443191/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1438872015976443191' title='6 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1438872015976443191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1438872015976443191'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/comillas-simples-dentro-de-cadenas-de.html' title='Comillas simples dentro de cadenas de texto en 10g'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2112781684204690220</id><published>2007-09-10T12:14:00.000-03:00</published><updated>2007-09-10T14:15:40.443-03:00</updated><title type='text'>Buscar el valor MAX o MIN de un tabla de forma eficiente</title><content type='html'>Muchas veces tenemos que buscar el valor máximo o mínimo actual de cierta columna en una tabla, pero suele ser una consulta ineficiente. Generalmente se leen muchos bloques de datos para encontrar el valor MAX/MIN y ésto hace que cuando se ejecute la consulta en nuestro ambiente OLTP bajo gran cantidad de usuarios concurrentes, tengamos graves problemas de performance.&lt;br /&gt;Como es sabido, más del 90% del trabajo de tuning es sobre la aplicación y no necesariamente sobre la base de datos. Este caso es un problema de la aplicación, particularmente cómo se escribe la consulta para obtener el MAX/MIN.&lt;br /&gt;&lt;br /&gt;Supongamos el siguiente ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nombre&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 1000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Tenemos una tabla cargada con 1 millón de registros. Supongamos que queremos obtener el MAX de la columna ID de esta tabla. Escribiríamos la siguiente consulta...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MAX(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     5 |       |&lt;br /&gt;|   2 |   TABLE ACCESS FULL  | TEST        |  1000K|  4882K|   279 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       2889  consistent gets&lt;br /&gt;        773  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En el Explain Plan vemos que se está realizando un Full Scan de la tabla TEST y en las estadísticas del Autotrace observamos que se leen 2889 bloques de datos desde memoria y 773 bloques desde disco.&lt;br /&gt;Lo ideal es reducir lo mayor posible la cantidad de lecturas lógicas. Cómo podemos optimizar ésta consulta? Qué sucede si creamos un índice único sobre la columna ID?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX test_id_idx ON test(id) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MAX(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                  |  Name        | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT           |              |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE            |              |     1 |     5 |       |&lt;br /&gt;|   2 |   &lt;span style="font-weight:bold;"&gt;INDEX FULL SCAN (MIN/MAX)&lt;/span&gt;| TEST_ID_IDX  |  1000K|  4882K|   279 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt; &lt;span style="font-weight:bold;"&gt;         3  consistent gets&lt;br /&gt;          0  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MIN(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                  |  Name        | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT           |              |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE            |              |     1 |     5 |       |&lt;br /&gt;|   2 |   &lt;span style="font-weight:bold;"&gt;INDEX FULL SCAN (MIN/MAX)&lt;/span&gt;| TEST_ID_IDX  |  1000K|  4882K|   279 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt; &lt;span style="font-weight:bold;"&gt;         3  consistent gets&lt;br /&gt;          0  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que tanto para sacar el MAX o el MIN, se están leyendo solamente 3 bloques de datos desde memoria y ninguno desde disco! Esto no es ninguna magia. Se debe a que se está utilizando el método de acceso INDEX FULL SCAN (MIN/MAX).&lt;br /&gt;Cuando se utiliza el acceso INDEX FULL SCAN, Oracle no lee todos los bloques del índice (no lee todos los bloques que conforman la estructura del árbol del índice), sino que lee solamente los Leaf Blocks del índice (son los bloques del índice que contiene los datos. Son los bloques que se encuentran en la raíz del árbol del índice). Cuando se accede a través del INDEX FULL SCAN (MIN/MAX), Oracle solamente accede al primer Leaf Block en caso de que busquemos el MIN, o al último Leaf Block en caso de que busquemos el MAX. Es una manera muy eficiente de obtener el valor máximo o mínimo de un índice.&lt;br /&gt;&lt;br /&gt;Veamos lo siguiente. El ejemplo que acabos de realizar fue creando un índice de forma ascendente (es el default). Qué sucede si realizamos el mismo ejemplo creando un índice de forma descendente?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DROP INDEX test_id_idx ;&lt;br /&gt;&lt;br /&gt;Index dropped.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Eliminamos el índice que habíamos creado y ejecutamos la consulta buscando el MAX:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MAX(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     5 |       |&lt;br /&gt;|   2 |   TABLE ACCESS FULL  | TEST        |  1000K|  4882K|   279 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       2889  consistent gets&lt;br /&gt;        773  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora creamos un índice descendiente por la columna ID:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX test_id_idx ON test(id &lt;span style="font-weight:bold;"&gt;DESC&lt;/span&gt;) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TEST', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejecutamos la consulta buscando el MAX y luego el MIN:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MAX(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     5 |       |&lt;br /&gt;|   2 |   TABLE ACCESS FULL  | TEST        |  1000K|  4882K|   279 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       2889  consistent gets&lt;br /&gt;        773  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT MIN(id)&lt;br /&gt;  3  FROM test ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |     1 |     5 |   279 |&lt;br /&gt;|   1 |  SORT AGGREGATE      |             |     1 |     5 |       |&lt;br /&gt;|   2 |   TABLE ACCESS FULL  | TEST        |  1000K|  4882K|   279 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;       2889  consistent gets&lt;br /&gt;        773  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        329  bytes sent via SQL*Net to client&lt;br /&gt;        495  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ouch!!! Que pasó??? El CBO no está utilizando nuestro índice! Pero porqué? Bueno, ésto es debido al Bug 1389479. EL CBO no puede utilizar el tipo de acceso INDEX FULL SCAN (MIN/MAX) cuando creamos un índice descendiente.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2112781684204690220?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2112781684204690220/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2112781684204690220' title='8 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2112781684204690220'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2112781684204690220'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/buscar-el-valor-max-o-min-de-un-tabla.html' title='Buscar el valor MAX o MIN de un tabla de forma eficiente'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1162421082394704246</id><published>2007-09-10T09:14:00.000-03:00</published><updated>2007-09-10T10:14:01.781-03:00</updated><title type='text'>Utilizando Hint's sobre vistas</title><content type='html'>Hay 2 preguntas que me hacen muy a menudo: Cuando consulto una vista ¿Cómo puedo hacer para influenciar al CBO con un Hint sin modificar el contenido de la vista? ¿Porqué el CBO no toma los Hint's que le coloco cuando consulto la vista?&lt;br /&gt;&lt;br /&gt;Bien, generalmente veo que cuando se utilizan Hint's sobre las vistas... se hace de forma incorrecta. Lo importante es saber cómo escribir el Hint para que el CBO lo utilice. Para ésto, vamos a realizar un ejemplo. &lt;br /&gt;La primer parte del ejemplo va a consistir en crear una vista y colocar un Hint para que utilice un índice. En la segunda parte vamos a crear otra vista más sobre la vista anterior, y vamos a colocar un Hint para que utilice un índice de la primer vista.&lt;br /&gt;&lt;br /&gt;Comencemos creando las tablas, índice y estadísticas...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE a AS&lt;br /&gt;  2  SELECT level id, 'desc_a_'||level desc_a&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE b AS&lt;br /&gt;  2  SELECT level id, 'desc_b_'||level desc_b&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX a_id_idx ON a(id) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'A', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'B') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Creamos la primer vista:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE VIEW view_a_b AS&lt;br /&gt;  2  SELECT a.id, a.desc_a, b.desc_b&lt;br /&gt;  3  FROM a, b&lt;br /&gt;  4  WHERE a.id = b.id ;&lt;br /&gt;&lt;br /&gt;View created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos el Explain Plan de la vista...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM view_a_b ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             | 10000 |   292K|    12 |&lt;br /&gt;|*  1 |  HASH JOIN           |             | 10000 |   292K|    12 |&lt;br /&gt;|   2 |   TABLE ACCESS FULL  | A           | 10000 |   146K|     4 |&lt;br /&gt;|   3 |   TABLE ACCESS FULL  | B           | 10000 |   146K|     4 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - access("A"."ID"="B"."ID")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lo que notamos es que se está realizando un Full Scan de las tabla A y B. Que sucede si a modo de ejemplo queremos hacer que la tabla A acceda por el índice que creamos antes? El CBO eligió el explain plan que vemos como el más óptimo (de hecho lo es), pero supongamos que para nosotros es más conveniente acceder por índice... tendríamos que utilizar un Hint para decirle al CBO que acceda por índice en vez de realizar un Full Scan. Pero como hacemos ésto a través de una vista??? Veamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT /*+ INDEX(&lt;span style="font-weight:bold;"&gt;view_a_b.&lt;/span&gt;a a_id_idx) */ *&lt;br /&gt;  3  FROM view_a_b ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                    |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT             |             | 10000 |   292K|    58 |&lt;br /&gt;|*  1 |  HASH JOIN                   |             | 10000 |   292K|    58 |&lt;br /&gt;|   2 |   &lt;span style="font-weight:bold;"&gt;TABLE ACCESS BY INDEX ROWID&lt;/span&gt;| A           | 10000 |   146K|    50 |&lt;br /&gt;|   3 |    INDEX FULL SCAN           | A_ID_IDX    | 10000 |       |    21 |&lt;br /&gt;|   4 |   TABLE ACCESS FULL          | B           | 10000 |   146K|     4 |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - access("A"."ID"="B"."ID")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense que en el Hint estoy anteponiendo al nombre de la tabla A, el nombre de la vista. Porqué? Porque si coloco solamente en nombre de la tabla A en el Hint, el CBO no lo toma ya que la tabla A no existe en en nuestra consulta, si existe dentro de la vista VIEW_A_B, es por eso que tenemos que anteponer el nombre de la vista y luego hacer referencia a la tabla A.&lt;br /&gt;&lt;br /&gt;Qué suecede si tenemos una vista de una vista y queremos hacer lo mismo que hicimos antes?&lt;br /&gt;&lt;br /&gt;Creamos la segunda vista:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE VIEW view_a_b_2 AS&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM view_a_b&lt;br /&gt;  4  WHERE id BETWEEN 1 AND 500 ;&lt;br /&gt;&lt;br /&gt;View created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos el Explain Plan de la vista...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT *&lt;br /&gt;  3  FROM view_a_b_2 ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |             |   499 | 14970 |     9 |&lt;br /&gt;|*  1 |  HASH JOIN           |             |   499 | 14970 |     9 |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL  | A           |   500 |  7500 |     4 |&lt;br /&gt;|*  3 |   TABLE ACCESS FULL  | B           |   500 |  7500 |     4 |&lt;br /&gt;--------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - access("A"."ID"="B"."ID")&lt;br /&gt;   2 - filter("A"."ID"&gt;=1 AND "A"."ID"&lt;=500)&lt;br /&gt;   3 - filter("B"."ID"&gt;=1 AND "B"."ID"&lt;=500)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sucedió exactamente lo mismo que en el ejemplo anterior utilizando una única vista. El CBO está accediendo por Full Scan. Pero nosotros somos caprichosos y pensamos que con un índice la consulta sería más eficiente. Cómo le decimos al CBO a través de un Hint que acceda por índice?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT /*+ INDEX(&lt;span style="font-weight:bold;"&gt;view_a_b_2.view_a_b.&lt;/span&gt;a a_id_idx) */ *&lt;br /&gt;  3  FROM view_a_b_2 ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                    |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT             |             |   499 | 14970 |    10 |&lt;br /&gt;|*  1 |  HASH JOIN                   |             |   499 | 14970 |    10 |&lt;br /&gt;|   2 |   &lt;span style="font-weight:bold;"&gt;TABLE ACCESS BY INDEX ROWID&lt;/span&gt;| A           |   500 |  7500 |     5 |&lt;br /&gt;|*  3 |    INDEX RANGE SCAN          | A_ID_IDX    |   500 |       |     3 |&lt;br /&gt;|*  4 |   TABLE ACCESS FULL          | B           |   500 |  7500 |     4 |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - access("A"."ID"="B"."ID")&lt;br /&gt;   3 - access("A"."ID"&gt;=1 AND "A"."ID"&lt;=500)&lt;br /&gt;   4 - filter("B"."ID"&gt;=1 AND "B"."ID"&lt;=500)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora no sólo estoy anteponiendo en el Hint el nombre de la vista, sino que también estoy anteponiendo el nombre de la segunda vista que creamos. Porqué? Por la misma razón que en el ejemplo anterior. Si hacemos referencia en el Hint sólo a la tabla A, el CBO no utiliza el Hint ya que la tabla A no existe en nuestra consulta, tampoco existe en la vista VIEW_A_B_2, pero si existe dentro de la vista VIEW_A_B; es por eso que tenemos que anteponer el nombre de las 2 vistas y luego hacer referencia a la tabla A.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1162421082394704246?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1162421082394704246/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1162421082394704246' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1162421082394704246'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1162421082394704246'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/utilizando-hints-sobre-vistas.html' title='Utilizando Hint&apos;s sobre vistas'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2493775753538011742</id><published>2007-09-08T00:16:00.000-03:00</published><updated>2007-09-08T01:45:29.401-03:00</updated><title type='text'>Monitoreando el progreso de largas ejecuciones</title><content type='html'>Para monitorear las largas ejecuciones que se producen en la base de datos, contamos con la vista del diccionario de datos llamada V$SESSION_LONGOPS. Esta vista nos permite monitorear tanto las sentencias DML como así también las sentencias DDL.&lt;br /&gt;Pero cuáles son las sentencias que aparecen en esa vista?&lt;br /&gt;Una ejecución es considerada una operación 'larga' y aparece en la vista V$SESSION_LONGOPS si la ejecución demora más de 6 segundos. Este no es el único criterio por el cual una sentencia aparece en la vista. También aparecen las operaciones en la vista si Oracle tiene que leer más de 10.000 bloques de la tabla.&lt;br /&gt;Pero no todas las operaciones que cumplen estos criterios aparecen en esa vista.&lt;br /&gt;&lt;br /&gt;Cada versión de Oracle agrega más tipos de operaciones a la vista V$SESSION_LONGOPS.&lt;br /&gt;Algunas de esas operaciones son:&lt;br /&gt;- Table Scan&lt;br /&gt;- Index Fast Full Scan&lt;br /&gt;- Hash Join&lt;br /&gt;- Sort/Merge&lt;br /&gt;- Sort Output&lt;br /&gt;- Estadísticas&lt;br /&gt;- Rollback&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT level a, level b, level c&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 1000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(USER,'TEST') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ya tenemos nuestra tabla creada, cargada con 1 millón de registros y analizada.&lt;br /&gt;Lo que vamos a hacer ahora es ejecutar una consulta en una sesión, y en otra sesión vamos a consultar a la vista V$SESSION_LONGOPS mientras la consulta se ejecuta.&lt;br /&gt;&lt;br /&gt;En la SESION_1 ejecuto la consulta (Esta consulta realiza un Full Scan sobre la tabla):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT a, b, c&lt;br /&gt;  2  FROM test&lt;br /&gt;  3  ORDER BY a ASC, b DESC, c ASC ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION_2 ejecuto la consulta sobre la vista:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sid, serial#, username, opname, sql_hash_value hash_value,&lt;br /&gt;  2         TO_CHAR(start_time,'HH24:MI:SS') "INICIO",&lt;br /&gt;  3         (sofar/totalwork)*100 "%"&lt;br /&gt;  4  FROM v$session_longops&lt;br /&gt;  5  WHERE (sofar/totalwork)*100 &lt; 100 ;&lt;br /&gt;&lt;br /&gt;no rows selected&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense, que mientras estoy corriendo la consulta de la SESION_1, cuando ejecuto la consulta de la SESION_2, no me devuelve ningún registro. Esto sucede porque aún Oracle no leyó 10.000 bloques de datos.&lt;br /&gt;Qué sucede si intento ejecutar la consulta nuevamente?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sid, serial#, username, opname, sql_hash_value hash_value,&lt;br /&gt;  2         TO_CHAR(start_time,'HH24:MI:SS') "INICIO",&lt;br /&gt;  3         (sofar/totalwork)*100 "%"&lt;br /&gt;  4  FROM v$session_longops&lt;br /&gt;  5  WHERE (sofar/totalwork)*100 &lt; 100 ;&lt;br /&gt;&lt;br /&gt;       SID    SERIAL# USERNAME   OPNAME          HASH_VALUE INICIO            %&lt;br /&gt;---------- ---------- ---------- --------------- ---------- -------- ----------&lt;br /&gt;        87       6852 LEO        Sort Output     3570479103 22:27:58 &lt;span style="font-weight:bold;"&gt;4.24448217&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que el porcentaje está en 4.24 y tenemos que llegar al 100 porciento para que la consulta termine de ejecutarse.&lt;br /&gt;Ejecuto nuevamente la consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sid, serial#, username, opname, sql_hash_value hash_value,&lt;br /&gt;  2         TO_CHAR(start_time,'HH24:MI:SS') "INICIO",&lt;br /&gt;  3         (sofar/totalwork)*100 "%"&lt;br /&gt;  4  FROM v$session_longops&lt;br /&gt;  5  WHERE (sofar/totalwork)*100 &lt; 100 ;&lt;br /&gt;&lt;br /&gt;       SID    SERIAL# USERNAME   OPNAME          HASH_VALUE INICIO            %&lt;br /&gt;---------- ---------- ---------- --------------- ---------- -------- ----------&lt;br /&gt;        87       6852 LEO        Sort Output     3570479103 22:27:58 &lt;span style="font-weight:bold;"&gt;7.53820034&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El porcentaje se incrementó. Ahora se encuentra en 7.5 y aún falta mucho para que la consulta de la SESION_1 termine de ejecutarse.&lt;br /&gt;&lt;br /&gt;Algo interesante para ver de la vista, es que tenemos una columna llamada SQL_HASH_VALUE en donde nos muestra el valor hash (recordemos que cuando Oracle tiene que buscar una consulta en la Shared Pool, busca esa consulta mediante un valor hash. Cuando ejecutamos una consulta, Oracle aplica una función sobre la consulta obteniendo el valor hash que vemos en ésta tabla en la columna HASH_VALUE) de la consulta que estamos ejecutando. Si tomamos ese valor y buscamos la consulta en la V$SQLTEXT...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sql_text&lt;br /&gt;  2  FROM v$sqltext&lt;br /&gt;  3  WHERE hash_value = &lt;span style="font-weight:bold;"&gt;3570479103&lt;/span&gt;&lt;br /&gt;  4  ORDER BY piece ;&lt;br /&gt;&lt;br /&gt;SQL_TEXT&lt;br /&gt;------------------------------------------------------------&lt;br /&gt;SELECT a, b, c FROM test ORDER BY a ASC, b DESC, c ASC&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Bien, ahora veamos lo siguiente. El paquete DBMS_APPLICATION_INFO, contiene un procedimiento llamado SET_SESSION_LONGOPS, que se utiliza para popular la vista V$SESSION_LONGOPS desde una aplicación.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  l_rindex     BINARY_INTEGER := DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS_NOHINT ;&lt;br /&gt;  3  l_sofar      NUMBER := 0 ;&lt;br /&gt;  4  l_slno       BINARY_INTEGER ;&lt;br /&gt;  5  BEGIN&lt;br /&gt;  6      FOR i IN ( SELECT a, b, c&lt;br /&gt;  7                 FROM test&lt;br /&gt;  8                 WHERE rownum &lt;= 1000&lt;br /&gt;  9                 ORDER BY a ASC, b DESC, c ASC ) LOOP&lt;br /&gt; 10&lt;br /&gt; 11          l_sofar := l_sofar + 1 ;&lt;br /&gt; 12&lt;br /&gt; 13          DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS&lt;br /&gt; 14                      (rindex      =&gt; l_rindex ,&lt;br /&gt; 15                       slno        =&gt; l_slno ,&lt;br /&gt; 16                       op_name     =&gt; 'TEST LEO' ,&lt;br /&gt; 17                       target      =&gt; NULL ,&lt;br /&gt; 18                       context     =&gt; 0 ,&lt;br /&gt; 19                       sofar       =&gt; l_sofar ,&lt;br /&gt; 20                       totalwork   =&gt; 1000 ,&lt;br /&gt; 21                       target_desc =&gt; 'Completado') ;&lt;br /&gt; 22&lt;br /&gt; 23      END LOOP ;&lt;br /&gt; 24  END ;&lt;br /&gt; 25  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ese bloque PL/SQL anónimo, iteró 1000 veces y logueo la operación en la vista.&lt;br /&gt;Si ahora queremos ver esa información, lo único que tenemos que hacer es consultar la V$SESSION_LONGOPS.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sid, serial#, username, opname, sql_hash_value hash_value,&lt;br /&gt;  2         TO_CHAR(start_time,'HH24:MI:SS') "INICIO",&lt;br /&gt;  3         (sofar/totalwork)*100 "%"&lt;br /&gt;  4  FROM v$session_longops ;&lt;br /&gt;&lt;br /&gt;       SID    SERIAL# USERNAME   OPNAME          HASH_VALUE INICIO            %&lt;br /&gt;---------- ---------- ---------- --------------- ---------- -------- ----------&lt;br /&gt;        58      19110 LEO        &lt;span style="font-weight:bold;"&gt;TEST LEO&lt;/span&gt;        2061745150 23:13:00        100&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como pueden ver, este procedimiento es muy útil cuando queremos, por ejemplo, monitorear una consulta larga y poder identificarla en la vista de manera fácil como en éste último ejemplo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2493775753538011742?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2493775753538011742/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2493775753538011742' title='5 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2493775753538011742'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2493775753538011742'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/monitoreando-el-progreso-de-largas.html' title='Monitoreando el progreso de largas ejecuciones'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-5585064351141110046</id><published>2007-09-07T11:03:00.000-03:00</published><updated>2007-09-07T17:56:10.437-03:00</updated><title type='text'>Búsquedas Case-Insensitive</title><content type='html'>Los tipos de búsquedas Case-Insensitive (nueva funcionalidad en Oracle 10gR2) nos permiten tratar por igual las letras mayúsculas y las letras minúsculas cuando realizamos una búsqueda.&lt;br /&gt;&lt;br /&gt;Veamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;Oracle&lt;br /&gt;ORACLE&lt;br /&gt;oraCLE&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;4 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense que cuando busco las palabra 'oracle' en minúsculas, gracias a ésta nueva funcionalidad, la consulta me devuelve todas las palabras 'oracle' que encuentra.&lt;br /&gt;Pero cómo logramos realizar ésto? Necesitamos setear algún parámetro?&lt;br /&gt;&lt;br /&gt;Realicemos el ejemplo completo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE TABLE test ( texto  VARCHAR2(10) ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test VALUES ( 'Oracle' ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test VALUES ( 'ORACLE' ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test VALUES ( 'oraCLE' ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; INSERT INTO test VALUES ( 'oracle' ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya tenemos nuestra tabla creada con sus respectivos datos cargados.&lt;br /&gt;Ahora vamos a prestar atención a 2 parámetros: &lt;a href="http://download-east.oracle.com/docs/cd/B19306_01/server.102/b14237/initparams120.htm" target="_blank"&gt;NLS_COMP&lt;/a&gt; y &lt;a href="http://download-east.oracle.com/docs/cd/B19306_01/server.102/b14237/initparams130.htm" target="_blank"&gt;NLS_SORT&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;En versiones anteriores a la 10gR2, si teníamos la tabla TEST (la tabla de nuestro ejemplo) con estos mismos datos cargados y buscabamos la palabra 'oracle' (con el parámetro NLS_SORT = BINARY que es el valor por default), veríamos lo siguiente: &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;Qué sucede si nuestra aplicación necesita realizar búsquedas que devuelvan tanto los valores que se encontraron en mayúsculas como los valores encontrados en minúsculas?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER SESSION SET NLS_COMP = ANSI ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER SESSION SET &lt;span style="font-weight:bold;"&gt;NLS_SORT = BINARY_CI&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;Oracle&lt;br /&gt;ORACLE&lt;br /&gt;oraCLE&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;4 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Al setear el parámetro NLS_SORT = BINARY_CI, Oracle convierte nuestras búsquedas en case-insensitive.&lt;br /&gt;&lt;br /&gt;Pero qué sucede si nuestra aplicación necesita hacer búsquedas del tipo LIKE '%ora%' porque no conoce la palabra completa que quiere buscar?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto LIKE '%ora%' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;oraCLE&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;2 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Al parecer con los parámetros que seteamos anteriormente no fue suficiente para satisfacer el resultado de ésta consulta.&lt;br /&gt;Bien, es hora de setear correctamente el parámetro NLS_COMP. Si lo seteamos con el valor LINGUISTIC, le dice a Oracle que cada sort y comparación, use el orden lingüístico del parámetro NLS_SORT. Esto quiere decir, que si seteamos NLS_SORT = BINARY_CI, todos los sorts y comparaciones van a ser case-insensitive en esa sesión.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER SESSION SET &lt;span style="font-weight:bold;"&gt;NLS_COMP = LINGUISTIC&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; ALTER SESSION SET NLS_SORT = BINARY_CI ;&lt;br /&gt;&lt;br /&gt;Session altered.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto LIKE '%ora%' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;Oracle&lt;br /&gt;ORACLE&lt;br /&gt;oraCLE&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;4 rows selected.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;TEXTO&lt;br /&gt;----------&lt;br /&gt;Oracle&lt;br /&gt;ORACLE&lt;br /&gt;oraCLE&lt;br /&gt;oracle&lt;br /&gt;&lt;br /&gt;4 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense, que éste último seteo de parámetros me sirvió para satisfacer las 2 consultas que estuvimos realizando!&lt;br /&gt;&lt;br /&gt;Veamos el explain plan de ésta última consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;  2  SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; @explains&lt;br /&gt;Plan hash value: 1357081020&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |      |     1 |     7 |     3   (0)| 00:00:01 |&lt;br /&gt;|*  1 |  TABLE ACCESS FULL| TEST |     1 |     7 |     3   (0)| 00:00:01 |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - &lt;span style="font-weight:bold;"&gt;filter(NLSSORT("TEXTO",'nls_sort=''BINARY_CI''')=HEXTORAW('6F7261636C6500') )&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;   - star transformation used for this statement&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que el filter del explain plan nos muestra que se está utilizando case-insensitive.&lt;br /&gt;Bien, ahora vamos a crear un índice con el contenido del filter que acabamos de ver:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; CREATE INDEX test_texto_idx ON test (NLSSORT(texto,'NLS_SORT=BINARY_CI')) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejecutamos nuevamente la consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_10gR2&gt; explain plan for&lt;br /&gt;  2  SELECT * FROM test WHERE texto = 'oracle' ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_10gR2&gt; @explains&lt;br /&gt;Plan hash value: 2134909805&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |                |     4 |    28 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| TEST           |     4 |    28 |     2   (0)| 00:00:01 |&lt;br /&gt;|*  2 |   INDEX RANGE SCAN          | &lt;span style="font-weight:bold;"&gt;TEST_TEXTO_IDX &lt;/span&gt;|     4 |       |     1   (0)| 00:00:01 |&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - &lt;span style="font-weight:bold;"&gt;access(NLSSORT("TEXTO",'nls_sort=''BINARY_CI''')=HEXTORAW('6F7261636C6500') )&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;   - star transformation used for this statement&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-5585064351141110046?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/5585064351141110046/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=5585064351141110046' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5585064351141110046'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5585064351141110046'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/bsquedas-case-insensitive.html' title='Búsquedas Case-Insensitive'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3232878592967727620</id><published>2007-09-06T23:45:00.000-03:00</published><updated>2007-09-07T09:12:21.743-03:00</updated><title type='text'>ANYDATA</title><content type='html'>En Oracle 9i se agregó un nuevo tipo de dato: ANYDATA. Normalmente, creamos un campo DATE para guardar fechas, un campo VARCHAR2 para guardar texto, un campo NUMBER para guardar números... pero qué sucede si mi aplicación guarda datos genéricos? Es decir, qué sucede si mi aplicación guarda en un mismo campo valores de distinto tipo de dato? En éste tipo de casos nos convendría usar el tipo de dato ANYDATA.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test ( algo  sys.anyData ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO test VALUES ( sys.anyData.ConvertVarchar2('esto es&lt;br /&gt;un ejemplo') ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO test VALUES ( sys.anyData.ConvertDate(sysdate) ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO test VALUES ( sys.anyData.ConvertNumber(1000) ) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Si consultamos la tabla (como lo hariamos de forma tradicional), no vemos los datos... simplemente vemos el tipo ANYDATA...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;  2  FROM test ;&lt;br /&gt;&lt;br /&gt;ALGO()&lt;br /&gt;-----------------&lt;br /&gt;ANYDATA()&lt;br /&gt;ANYDATA()&lt;br /&gt;ANYDATA()&lt;br /&gt;&lt;br /&gt;3 rows selected.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; DESC test&lt;br /&gt; Name                      Null?    Type&lt;br /&gt; ------------------------- -------- --------------&lt;br /&gt; ALGO                               SYS.ANYDATA&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Antes de mostrarles cómo ver los datos reales de la tabla... Veamos los tipos de cada dato...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT &lt;span style="font-weight:bold;"&gt;anyData.gettypeName&lt;/span&gt;(algo) TipoDeDato&lt;br /&gt;  2  FROM test ;&lt;br /&gt;&lt;br /&gt;TIPODEDATO&lt;br /&gt;-----------------&lt;br /&gt;SYS.VARCHAR2&lt;br /&gt;SYS.DATE&lt;br /&gt;SYS.NUMBER&lt;br /&gt;&lt;br /&gt;3 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lamentablemente, no es tan fácil ver los datos de la tabla. Pero porqué no es fácil? Porque está pensando para ser utilizado dentro de un procedimiento en donde se hace el fetch de los datos y luego se los convierte y procesa. Entonces veamos... Para ver los datos reales de la tabla, vamos a construir un package con 3 funciones. Cada una de esas funciones se va a encargar de convertir un tipo de dato y devolvernos el valor de la conversión:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE pkg_convertir_anydata AS&lt;br /&gt;  2&lt;br /&gt;  3     FUNCTION convertir_varchar2 ( p_dato  IN  sys.anydata ) RETURN VARCHAR2 ;&lt;br /&gt;  4     FUNCTION convertir_date     ( p_dato  IN  sys.anydata ) RETURN DATE ;&lt;br /&gt;  5     FUNCTION convertir_number   ( p_dato  IN  sys.anydata ) RETURN NUMBER ;&lt;br /&gt;  6&lt;br /&gt;  7  END pkg_convertir_anydata ;&lt;br /&gt;  8  /&lt;br /&gt;&lt;br /&gt;Package created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE BODY pkg_convertir_anydata AS&lt;br /&gt;  2&lt;br /&gt;  3     FUNCTION convertir_varchar2 ( p_dato  IN  sys.anydata ) RETURN VARCHAR2 IS&lt;br /&gt;  4        nada       NUMBER ;&lt;br /&gt;  5        l_varchar2 VARCHAR2(4000) ;&lt;br /&gt;  6     BEGIN&lt;br /&gt;  7        nada := p_dato&lt;span style="font-weight:bold;"&gt;.GetVarchar2&lt;/span&gt;( l_varchar2 ) ;&lt;br /&gt;  8        RETURN ( l_varchar2 ) ;&lt;br /&gt;  9     END ;&lt;br /&gt; 10&lt;br /&gt; 11     FUNCTION convertir_date ( p_dato  IN  sys.anydata ) RETURN DATE IS&lt;br /&gt; 12        nada       NUMBER ;&lt;br /&gt; 13        l_date     DATE ;&lt;br /&gt; 14     BEGIN&lt;br /&gt; 15        nada := p_dato&lt;span style="font-weight:bold;"&gt;.GetDate&lt;/span&gt;( l_date ) ;&lt;br /&gt; 16        RETURN ( l_date ) ;&lt;br /&gt; 17     END ;&lt;br /&gt; 18&lt;br /&gt; 19     FUNCTION convertir_number ( p_dato  IN  sys.anydata ) RETURN NUMBER IS&lt;br /&gt; 20        nada       NUMBER ;&lt;br /&gt; 21        l_number   NUMBER ;&lt;br /&gt; 22     BEGIN&lt;br /&gt; 23        nada := p_dato&lt;span style="font-weight:bold;"&gt;.GetNumber&lt;/span&gt; ( l_number ) ;&lt;br /&gt; 24        RETURN ( l_number ) ;&lt;br /&gt; 25     END ;&lt;br /&gt; 26&lt;br /&gt; 27  END pkg_convertir_anydata ;&lt;br /&gt; 28  /&lt;br /&gt;&lt;br /&gt;Package body created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Lo único que nos resta por hacer es construir la consulta para convertir y mostrar nuestros datos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; WITH Datos AS&lt;br /&gt;  2  ( SELECT algo , sys.anydata.gettypename(algo) TipoDeDato FROM test )&lt;br /&gt;  3  SELECT CASE&lt;br /&gt;  4             WHEN TipoDeDato = 'SYS.VARCHAR2' THEN&lt;br /&gt;  5                  TO_CHAR(&lt;span style="font-weight:bold;"&gt;pkg_convertir_anydata.convertir_Varchar2&lt;/span&gt;(algo))&lt;br /&gt;  6             WHEN TipoDeDato = 'SYS.DATE' THEN&lt;br /&gt;  7                  TO_CHAR(&lt;span style="font-weight:bold;"&gt;pkg_convertir_anydata.convertir_Date&lt;/span&gt;(algo),'dd-mon-&lt;br /&gt;rrrr hh24:mi:ss')&lt;br /&gt;  8             WHEN TipoDeDato = 'SYS.NUMBER' THEN&lt;br /&gt;  9                  TO_CHAR(&lt;span style="font-weight:bold;"&gt;pkg_convertir_anydata.convertir_Number&lt;/span&gt;(algo))&lt;br /&gt; 10         END algo&lt;br /&gt; 11  FROM Datos ;&lt;br /&gt;&lt;br /&gt;ALGO&lt;br /&gt;--------------------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;esto es un ejemplo&lt;br /&gt;06-sep-2007 21:49:37&lt;br /&gt;1000&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;3 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3232878592967727620?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3232878592967727620/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3232878592967727620' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3232878592967727620'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3232878592967727620'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/anydata.html' title='ANYDATA'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1619250305992310060</id><published>2007-09-05T20:08:00.000-03:00</published><updated>2007-09-05T22:27:12.402-03:00</updated><title type='text'>Foreign Keys no indexadas</title><content type='html'>Suelo encontrarme en los diseños de las aplicaciones, foreign keys que no se encuentran indexadas. No indexar las foreign keys puede ser un gran problema de performance.&lt;br /&gt;&lt;br /&gt;Podemos encontrar 2 tipos de problemas:&lt;br /&gt;1.- Se produce un loqueo de la tabla si modificamos (muy inusual) o eliminamos algún registro de la primary key (tabla padre) y las foreign keys (tabla hija) no se encuentran indexadas.&lt;br /&gt;2.- Si tenemos la cláusula ON DELETE CASCADE y no tenemos índices creados en la foreign keys, si eliminamos algún registro de la primary key, como no tenemos indexadas las foreign keys, se producirá un full scan de la tabla que contiene las foreign keys. Esto puede ser el factor de un grave problema de performance ya que si eliminamos varios registros de la tabla padre, se realizará un full scan de la tabla hija por cada registro eliminado de la tabla padre.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE tabla_padre AS&lt;br /&gt;  2  SELECT level id&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 9 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE tabla_hija AS&lt;br /&gt;  2  SELECT decode(mod(level,9),0,9,mod(level,9)) id , 'detalle_'||level detalle&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 1000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE tabla_padre&lt;br /&gt;  2  ADD CONSTRAINT tabla_padre_pk&lt;br /&gt;  3  PRIMARY KEY (id) ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE tabla_hija&lt;br /&gt;  2  ADD CONSTRAINT tabla_padre_hija_fk&lt;br /&gt;  3  FOREIGN KEY (id) REFERENCES tabla_padre(id)&lt;br /&gt;  4  ON DELETE CASCADE ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TABLA_PADRE', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TABLA_HIJA', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;  &lt;br /&gt;Bien, ya tenemos nuestras tablas de ejemplo con sus respectivos datos, constraints y estadísticas.&lt;br /&gt;&lt;br /&gt;Ejecutamos un insert en la tabla hija:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO tabla_hija VALUES(5,'detalle'||5) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora ejecutamos un delete en la tabla padre:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  PRAGMA AUTONOMOUS_TRANSACTION ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      DELETE FROM tabla_padre&lt;br /&gt;  5      WHERE id = 2 ;&lt;br /&gt;  6&lt;br /&gt;  7      COMMIT ;&lt;br /&gt;  8  END ;&lt;br /&gt;  9  /&lt;br /&gt;DECLARE&lt;br /&gt;*&lt;br /&gt;ERROR at line 1:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-00060: deadlock detected while waiting for resource&lt;/span&gt;&lt;br /&gt;ORA-06512: at line 4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, al no tener un índice en la foreign key se produce un Deadlock.&lt;br /&gt;Veamos qué sucede si creamos un índice sobre la foreign key y repetimos el procedimiento.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; &lt;span style="font-weight:bold;"&gt;CREATE INDEX tabla_hija_id_idx ON tabla_hija(id) ;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'TABLA_HIJA', cascade =&gt; true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO tabla_hija VALUES(5,'detalle'||5) ;&lt;br /&gt;&lt;br /&gt;1 row created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; DECLARE&lt;br /&gt;  2  PRAGMA AUTONOMOUS_TRANSACTION ;&lt;br /&gt;  3  BEGIN&lt;br /&gt;  4      DELETE FROM tabla_padre&lt;br /&gt;  5      WHERE id = 2 ;&lt;br /&gt;  6&lt;br /&gt;  7      COMMIT ;&lt;br /&gt;  8  END ;&lt;br /&gt;  9  /&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;PL/SQL procedure successfully completed.&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Mi consejo es el siguiente: Si realizamos deletes en la tabla padre y/o updates en la primary key de la tabla padre, entonces debemos crear índices en las foreign keys de la tabla hija.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1619250305992310060?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1619250305992310060/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1619250305992310060' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1619250305992310060'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1619250305992310060'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/foreign-keys-no-indexadas.html' title='Foreign Keys no indexadas'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-428957677332512166</id><published>2007-09-05T12:10:00.000-03:00</published><updated>2007-09-27T00:20:52.042-03:00</updated><title type='text'>Deadlocks --&gt; ORA-00060</title><content type='html'>Qué es un Deadlock? Un Deadlock (abrazo mortal) es cuando 2 o más usuarios están esperando algún dato que está siendo loqueado por alguna sesión. Si ésto sucede, los usuarios involucrados en el Deadlock deben esperar y no pueden continuar con el procesamiento.&lt;br /&gt;&lt;br /&gt;Cuando Oracle detecta que se produjo un Deadlock, lo que hace es cortar la ejecución del procedimiento y mostrar el siguiente mensaje de error: ORA-00060: deadlock detected while waiting for resource. Tengamos en cuenta que cuando se produce éste error, Oracle genera un archivo de trace en el directorio UDUMP con información acerca del error.&lt;br /&gt;&lt;br /&gt;Generalmente éste problema se produce por un mal diseño de la aplicación. Veamos un ejemplo...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE sesion_1 AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nombre&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE sesion_2 AS&lt;br /&gt;  2  SELECT level id, 'nom_'||level nombre&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 10 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION 1 loqueo un registro de la tabla SESION_1 correspondiente al ID 1.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_1&lt;br /&gt;  2  SET nombre = 'nom_'||id*2&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;&lt;br /&gt;1 row updated.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION 2 loqueo un registro de la tabla SESION_2 correspondiente al ID 1.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_2&lt;br /&gt;  2  SET id = id+10&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;&lt;br /&gt;1 row updated.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION 1 modifico un registro de la tabla SESION_2 correspondiente al ID 1. Vemos que esta sesión se 'colgó' debido al loqueo y no nos devuelve el control. Todavía no se produjo el Deadlock... sólo se produjo un loqueo.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_2&lt;br /&gt;  2  SET id = id+10&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION 2 modifico un registro de la tabla SESION_1 correspondiente al ID 1.&lt;br /&gt;Vemos que esta sesión se 'colgó' debido al loqueo y no nos devuelve el control.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_1&lt;br /&gt;  2  SET nombre = 'nom_'||id*2&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Esto va a producir un Deadlock y luego de unos segundos aparece un mensaje de error en la SESION 1:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_2&lt;br /&gt;  2  SET id = id+10&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;UPDATE sesion_2&lt;br /&gt;*&lt;br /&gt;ERROR at line 1:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-00060: deadlock detected while waiting for resource&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La SESION 2 sigue colgada esperando que la SESION 1 termine la transacción que comenzó. Entonces en la SESION 1 ejecutamos...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; rollback ;&lt;br /&gt;&lt;br /&gt;Rollback complete.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la SESION 2 se libera automáticamente el loqueo...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; UPDATE sesion_1&lt;br /&gt;  2  SET nombre = 'nom_'||id*2&lt;br /&gt;  3  WHERE id = 1 ;&lt;br /&gt;&lt;br /&gt;1 row updated.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-428957677332512166?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/428957677332512166/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=428957677332512166' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/428957677332512166'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/428957677332512166'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/deadlocks-ora-00060.html' title='Deadlocks --&gt; ORA-00060'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3311182152707519560</id><published>2007-09-04T15:34:00.000-03:00</published><updated>2007-09-07T23:49:19.329-03:00</updated><title type='text'>Estadísticas en paralelo</title><content type='html'>Cuando necesitamos analizar objetos de muy grandes dimensiones, podemos obtener estadísticas en paralelo con el paquete DBMS_STATS (por esto, y por muchas cosas más, es el porque Oracle recomienda en la documentación utilizar éste paquete para obtener estadísticas en vez del comando ANALYZE).&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE t&lt;br /&gt;  2  NOLOGGING&lt;br /&gt;  3  AS&lt;br /&gt;  4  SELECT level a, mod(level,3) b, level c&lt;br /&gt;  5  FROM dual&lt;br /&gt;  6  CONNECT BY level &lt;= 10000000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:01:00.08&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX t_abc_ASC_uq ON t ( a ASC, b ASC, c ASC ) NOLOGGING ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;Elapsed: 00:00:31.05&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE UNIQUE INDEX t_abc_DESC_uq ON t ( a DESC, b DESC, c DESC ) NOLOGGING ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.gather_table_stats( ownname =&gt; USER, &lt;br /&gt;tabname =&gt; 'T', cascade =&gt; TRUE ) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:02:59.03&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que si obtenemos las estadísticas computadas en serial, tardamos 3 minutos. Veamos que sucede si realizamos lo mismo pero en paralelo...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.gather_table_stats( ownname =&gt; USER, &lt;br /&gt;tabname =&gt; 'T', cascade =&gt; TRUE , degree =&gt; 4 ) ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Mientras se ejecuta éste procedimiento, podemos consultar la vista V$PX_PROCESS (en otra sesión) para ver si efectivamente estamos realizando una ejecución en paralelo...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; select * from v$px_process ;&lt;br /&gt;&lt;br /&gt;SERV STATUS           PID SPID                SID    SERIAL#&lt;br /&gt;---- --------- ---------- ------------ ---------- ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;P000 IN USE            48 19360                74       1093&lt;br /&gt;P001 IN USE            60 19362                64        773&lt;br /&gt;P002 IN USE            62 19364                12       4904&lt;/span&gt;&lt;br /&gt;P003 AVAILABLE         70 19366&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;P004 IN USE            74 19368                86        447&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;5 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que hay 4 procesos que se están utilizando (IN USE) para obtener las estadísticas en paralelo. Veamos que sucede cuando termina de ejecutarse el procedimiento y volvemos a consultar la vista.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Elapsed: 00:01:21.04&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; select * from v$px_process ;&lt;br /&gt;&lt;br /&gt;SERV STATUS           PID SPID                SID    SERIAL#&lt;br /&gt;---- --------- ---------- ------------ ---------- ----------&lt;br /&gt;P000 AVAILABLE         48 19360&lt;br /&gt;P001 AVAILABLE         60 19362&lt;br /&gt;P002 AVAILABLE         62 19364&lt;br /&gt;P003 AVAILABLE         70 19366&lt;br /&gt;P004 AVAILABLE         74 19368&lt;br /&gt;&lt;br /&gt;5 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los procesos que estaban en estado IN USE pasaron a estar AVAILABLE nuevamente y obtuvimos las estadísticas en 1 minuto 20 segundos!!!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3311182152707519560?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3311182152707519560/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3311182152707519560' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3311182152707519560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3311182152707519560'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/estadsticas-en-paralelo.html' title='Estadísticas en paralelo'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3711349319499610505</id><published>2007-09-03T14:06:00.001-03:00</published><updated>2007-09-03T14:26:48.688-03:00</updated><title type='text'>Explain Plan vs. Autotrace Explain Plan</title><content type='html'>Hace unos meses me hicieron una pregunta que hoy en día veo que muchas personas no saben la respuesta. La pregunta era: ¿ Cuál es la diferencia de ejecutar el Explain Plan en SQL*Plus de ejecutar el Explain Plan desde la herramienta Autotrace ? Bueno, para contestar esta pregunta vamos a crear un muy sencillo ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE estudiantes&lt;br /&gt;  2  (&lt;br /&gt;  3  legajo     NUMBER(5),&lt;br /&gt;  4  nombre     VARCHAR2(30),&lt;br /&gt;  5  ingreso    DATE&lt;br /&gt;  6  )&lt;br /&gt;  7  PARTITION BY RANGE(ingreso)&lt;br /&gt;  8  (&lt;br /&gt;  9  PARTITION ingreso_2006 VALUES LESS THAN(TO_DATE('01/01/2007','DD/MM/YYYY'))&lt;br /&gt;,&lt;br /&gt; 10  PARTITION ingreso_2007 VALUES LESS THAN(TO_DATE('01/01/2008','DD/MM/YYYY'))&lt;br /&gt;&lt;br /&gt; 11  ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO estudiantes&lt;br /&gt;  2  SELECT level, 'nom_'||level, DECODE(MOD(level,2),0,sysdate,1,sysdate-500)&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 1000 ;&lt;br /&gt;&lt;br /&gt;1000 rows created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC dbms_stats.GATHER_TABLE_STATS(user,'ESTUDIANTES') ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya creamos nuestro ambiente de prueba. Veamos que sucede si vemos el Explain Plan desde el Autotrace:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; &lt;span style="font-weight:bold;"&gt;SET AUTOTRACE TRACEONLY EXPLAIN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT legajo, nombre&lt;br /&gt;  2  FROM estudiantes&lt;br /&gt;  3  WHERE ingreso = to_date('03/09/2007 12:11:27','dd/mm/yyyy hh24:mi:ss') ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=500 Bytes=9500)&lt;br /&gt;   1    0   TABLE ACCESS (FULL) OF 'ESTUDIANTES' (Cost=2 Card=500 Bytes=9500)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que estamos haciendo un full scan de la tabla, pero si observamos bien, podemos notar que en realidad no estamos leyendo la tabla entera (que tiene 1.000 registros), sino que sólo estamos leyendo 500 registros. Esto nos esta indicando que no estamos leyendo las 2 particiones que creamos, sólo estamos leyendo 1 de ellas... pero cuál?&lt;br /&gt;&lt;br /&gt;Veamos que sucede si ejecutamos el Explain Plan: &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; &lt;span style="font-weight:bold;"&gt;EXPLAIN PLAN FOR&lt;/span&gt;&lt;br /&gt;  2  SELECT legajo, nombre&lt;br /&gt;  3  FROM estudiantes&lt;br /&gt;  4  WHERE ingreso = to_date('03/09/2007 12:11:27','dd/mm/yyyy hh24:mi:ss') ;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation            |  Name        | Rows  | Bytes | Cost  | &lt;span style="font-weight:bold;"&gt;Pstart| Pstop&lt;/span&gt; |&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT     |              |   500 |  9500 |     2 |       |       |&lt;br /&gt;|*  1 |  TABLE ACCESS FULL   | ESTUDIANTES  |   500 |  9500 |     2 |     2 |     2 |&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - filter("ESTUDIANTES"."INGRESO"=TO_DATE(' 2007-09-03 12:11:27', 'syyyy-mm-ddhh24:mi:ss'))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Claramente vemos que en el Explain Plan tenemos 2 columnas nuevas: Pstart y Pstop. Estas columnas nos están indicando que sólo estamos haciendo un full scan de la partición 2, no de la tabla completa.&lt;br /&gt;&lt;br /&gt;Moraleja: Utilicen el Explain Plan cuando estén interesados en el plan de ejecución de la consulta. Utilicen el Autotrace cuando estén interesados en las estadísticas de la consulta.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3711349319499610505?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3711349319499610505/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3711349319499610505' title='9 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3711349319499610505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3711349319499610505'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/09/explain-plan-vs-autotrace-explain-plan.html' title='Explain Plan vs. Autotrace Explain Plan'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3893156615843437645</id><published>2007-08-31T15:47:00.000-03:00</published><updated>2007-08-31T16:18:23.657-03:00</updated><title type='text'>Estado de los cursores</title><content type='html'>A menudo veo que los desarrolladores y DBA's consultan la vista V$OPEN_CURSOR para identificar todos los cursores que se encuentran abiertos en la sesión. Pero esta vista muestra realmente los cursores actualmente abiertos en la sesión? La respuesta es NO.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;Consultamos la vista V$OPEN_CURSOR:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT count(*)&lt;br /&gt;  2  FROM v$open_cursor ;&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;       &lt;span style="font-weight:bold;"&gt;169&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La vista nos muestra que tenemos 169 cursores abiertos. Pero en realidad lo que nos está mostrando es la cantidad de cursores actualmente abiertos en la sesión y los cursores que se encuentran actualmente cerrados (pero abiertos). Estos cursores que se encuentran cerrados pero cacheados, son los cursores que Oracle mantiene silenciosamente abiertos con la esperanza de que los volvamos a utilizar en la sesión.&lt;br /&gt;Pero porque Oracle cachea los cursores? Principalmente para reducir la cantidad de parseos. Este cacheo de cursores es muy importante en aplicaciones como Oracle Forms, que suelen cerrar todos los cursores abiertos por el form principal cuando se cambia de un form a otro. Entonces, cacheando los cursores reducimos en gran medida los parseos que se realizan. Por otro lado, como reducimos la cantidad de parseos, reducimos también los loqueos (latches) que se realizan el la Library Cache y en la Shared Pool.&lt;br /&gt;&lt;br /&gt;Suelo crear en mis bases de datos la vista MY_STATS para que los desarrolladores puedan consultar fácilmente las estadísticas de sus respectivas sesiones:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE VIEW my_stats AS&lt;br /&gt;  2  SELECT s.*, n.name&lt;br /&gt;  3  FROM v$mystat s, v$statname n&lt;br /&gt;  4  WHERE s.statistic# = n.statistic# ;&lt;br /&gt;&lt;br /&gt;View created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Veamos que suecede si consultamos el valor del parámetro 'opened cursors current' de la vista MY_STATS:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;  2  FROM my_stats&lt;br /&gt;  3  WHERE name = 'opened cursors current' ;&lt;br /&gt;&lt;br /&gt;       SID STATISTIC#      VALUE NAME&lt;br /&gt;---------- ---------- ---------- -----------------------------------------------&lt;br /&gt;        43          3          &lt;span style="font-weight:bold;"&gt;4 opened cursors current&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Vemos que nos devuelve el valor 4. Esa es la cantidad real de cursores actualmente abiertos en nuestra sesión.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3893156615843437645?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3893156615843437645/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3893156615843437645' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3893156615843437645'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3893156615843437645'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/estado-de-los-cursores.html' title='Estado de los cursores'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3687906188759155636</id><published>2007-08-30T10:41:00.001-03:00</published><updated>2007-08-30T11:41:13.994-03:00</updated><title type='text'>Index-Skip Scans</title><content type='html'>El "Index-Skip Scans" está disponible a partir de Oracle 9i. Cuando tenemos un índice compuesto, permite en algunas circunstancias, que el CBO no tome en cuenta la primer columna del índice, sino que lea las restantes. Esto es útil cuando en una consulta hacemos referencia a alguna de las columnas que no están en la cabecera del índice pero que sin embargo queremos utilizar ese índice. Obviamente, el CBO utiliza Index-Skip Scans solamente cuando se cumplen 2 condiciones:&lt;br /&gt;&lt;br /&gt;1- La columna cabecera del índice debe contener muy pocos valores distintos. Osea, tiene que ser una columna no selectiva. Tipicamente son las columnas apropiadas para la utilización de un Bitmap Index.&lt;br /&gt;2- En la consulta debemos hacer referencia, por lo menos, a alguna de las columnas restantes del índice.&lt;br /&gt;&lt;br /&gt;Supongamos que tenemos 3 consultas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT d FROM test WHERE a = :a ;&lt;br /&gt;&lt;br /&gt;SELECT d FROM test WHERE a = :a AND b = :b ;&lt;br /&gt;&lt;br /&gt;SELECT d FROM test WHERE a = :a AND b = :b AND c = :c ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Qué índice nos conviene crear para que sea utilizado en las 3 consultas?&lt;br /&gt;Claramente crearíamos un índice B*Tree compuesto por las columnas A,B,C.&lt;br /&gt;&lt;br /&gt;Qué sucede si tenemos ésta consulta?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT d FROM test WHERE b = :b AND c = :c ;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El índice es utilizado?&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE test AS&lt;br /&gt;  2  SELECT mod(level,3) a, level b, level c, 'nom_'||level d&lt;br /&gt;  3  FROM dual&lt;br /&gt;  4  CONNECT BY level &lt;= 20000 ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE INDEX t_abc_idx ON test(a,b,c) ;&lt;br /&gt;&lt;br /&gt;Index created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'TEST',cascade=&gt;true) ;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya tenemos nuestra tabla creada y un índice por las columnas A,B,C. Observen que inserté en la columna A sólo 3 valores distintos (0,1 y 2).&lt;br /&gt;&lt;br /&gt;Bien, veamos el explain plan de la consulta que vimos anteriormente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT d&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  &lt;span style="font-weight:bold;"&gt;WHERE a = 2 AND b = 182 AND c = 182 ;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |             |     1 |    21 |     2 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| TEST        |     1 |    21 |     2 |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN          | T_ABC_IDX&lt;/span&gt;   |     1 |       |     1 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("TEST"."A"=2 AND "TEST"."B"=182 AND "TEST"."C"=182)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemes ver, el índice es utilizado porque estamos accediendo a las 3 columnas del índice. Que sucede si accedemos sólo a las columnas B y C ?&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; explain plan for&lt;br /&gt;  2  SELECT d&lt;br /&gt;  3  FROM test&lt;br /&gt;  4  &lt;span style="font-weight:bold;"&gt;WHERE b = 182 AND c = 182 ;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Explained.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; @explains&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   |  Name       | Rows  | Bytes | Cost  |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |             |     1 |    20 |     5 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| TEST        |     1 |    20 |     5 |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX SKIP SCAN           | T_ABC_IDX &lt;/span&gt;  |     1 |       |     4 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("TEST"."B"=182 AND "TEST"."C"=182)&lt;br /&gt;       filter("TEST"."B"=182 AND "TEST"."C"=182)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que el índice es utilizado porque cumplimos con las 2 condiciones necesarios para que el CBO utilice Index-Skip Scans.&lt;br /&gt;&lt;br /&gt;La utilización de ésta clase de acceso es más costosa que realizar un accedo directo a través del índice, pero en general es menos costosa que utilizar un full-scan.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3687906188759155636?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3687906188759155636/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3687906188759155636' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3687906188759155636'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3687906188759155636'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/index-skip-scans_30.html' title='Index-Skip Scans'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7524775928359524043</id><published>2007-08-28T09:21:00.001-03:00</published><updated>2008-02-26T15:26:17.257-02:00</updated><title type='text'>APPEND hint</title><content type='html'>El hint APPEND permite que Oracle comience a escribir bloques nuevos luego de la HWM (high water mark) de la tabla.&lt;br /&gt;&lt;br /&gt;Veo a menudo a desarrolladores y DBA's usar este hint, y en la mayoría de los casos, el hint no es usado por Oracle. Pero porque?&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_9iR2&gt; CREATE TABLE test ( a number, b number ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO test&lt;br /&gt;2  SELECT level, level&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 1000000 ;    &lt;br /&gt;&lt;br /&gt;1000000 rows created.    &lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------                    &lt;br /&gt;   9  recursive calls        &lt;br /&gt;10713  db block gets           &lt;br /&gt;4128  consistent gets                           &lt;br /&gt;   2  physical reads &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;20461776  redo size&lt;/span&gt;&lt;br /&gt; 626  bytes sent via SQL*Net to client&lt;br /&gt; 835  bytes received via SQL*Net from client&lt;br /&gt;   4  SQL*Net roundtrips to/from client&lt;br /&gt;   2  sorts (memory)&lt;br /&gt;   0  sorts (disk)&lt;br /&gt;&lt;br /&gt;1000000  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como podemos ver, se está generando aprox. 19 MB de Redo para insertar en la tabla 1.000.000 de registros. Qué sucedería si necesitamos realizar una carga masiva de datos? Estaríamos consumiendo gran cantidad de Redo.&lt;br /&gt;Para reducir la cantidad de Redo que se genera, podemos usar el hint APPEND.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_9iR2&gt; INSERT &lt;span style="font-weight: bold;"&gt;/*+ APPEND */&lt;/span&gt; INTO test&lt;br /&gt;2  SELECT level, level&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 1000000 ;   &lt;br /&gt;&lt;br /&gt;1000000 rows created.  &lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------              &lt;br /&gt;688  recursive calls              &lt;br /&gt;242  db block gets              &lt;br /&gt;242  consistent gets                    &lt;br /&gt;  0  physical reads    &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;17120808  redo size&lt;/span&gt;&lt;br /&gt;612  bytes sent via SQL*Net to client&lt;br /&gt;849  bytes received via SQL*Net from client&lt;br /&gt;  4  SQL*Net roundtrips to/from client&lt;br /&gt;  2  sorts (memory)&lt;br /&gt;  0  sorts (disk)&lt;br /&gt;&lt;br /&gt;1000000  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con el hint APPEND estamos utilizando aprox. 16 MB de Redo. Pero realmente se está utilizando el hint? No hubo una gran diferencia en la generación del Redo. Porque? Esto es debido a que existen 2 condiciones para que se utilice el hint, y debemos cumplir alguna de ellas:&lt;br /&gt;&lt;br /&gt;- La base de datos debe estar en modo NOARCHIVELOG.&lt;br /&gt;- La tabla que estamos utilizando debe estar en modo NOLOGGING.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_9iR2&gt; SELECT log_mode from v$database ;&lt;br /&gt;&lt;br /&gt;LOG_MODE&lt;br /&gt;------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ARCHIVELOG&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la base de datos que estoy utilizando para éste ejemplo estoy utilizando ARCHIVELOG, por lo cual vamos a colocar la tabla en modo NOLOGGING para ver el efecto de utilizar el hint.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL_9iR2&gt; ALTER TABLE test &lt;span style="font-weight: bold;"&gt;NOLOGGING&lt;/span&gt; ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT /*+ APPEND */ INTO test&lt;br /&gt;2  SELECT level, level&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 1000000 ;   &lt;br /&gt;&lt;br /&gt;1000000 rows created.   &lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------              &lt;br /&gt;153  recursive calls                 &lt;br /&gt; 18  db block gets                 &lt;br /&gt; 23  consistent gets                    &lt;br /&gt;  1  physical reads        &lt;span style="font-weight: bold;"&gt;&lt;br /&gt;5944  redo size&lt;/span&gt;&lt;br /&gt;614  bytes sent via SQL*Net to client&lt;br /&gt;849  bytes received via SQL*Net from client&lt;br /&gt;  4  SQL*Net roundtrips to/from client&lt;br /&gt;  7  sorts (memory)&lt;br /&gt;  0  sorts (disk)&lt;br /&gt;&lt;br /&gt;1000000  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Claramente podemos ver que ahora estamos utilizando sólo 5 KB de Redo. Esto nos permite optimizar las operaciones de carga masiva de datos.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NOTA:&lt;/span&gt; El APPEND sólo podemos utilizarlo en sentencias del tipo INSERT AS SELECT.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7524775928359524043?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7524775928359524043/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7524775928359524043' title='9 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7524775928359524043'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7524775928359524043'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/append-hint.html' title='APPEND hint'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-317976420336994794</id><published>2007-08-23T08:53:00.000-03:00</published><updated>2007-08-23T09:09:34.948-03:00</updated><title type='text'>La Clave del Tuning (CDT)</title><content type='html'>Cuando comencé con Tuning, ya hace unos años, hubo una pregunta a la cual no le encontraba respuesta. La pregunta era: Cuál es la clave del Tuning?&lt;br /&gt;A medida que iba pasando el tiempo, hubo una sola respuesta que me vino a la cabeza. Los días seguían pasando y a medida que iba adquiriendo experiencia en el Tuning, esa respuesta cada vez me iba convenciendo más... hasta que un día terminó por convencerme.&lt;br /&gt;La respuesta a mi pregunta era muy fácil y podemos resumirla en una simple fórmula:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_M2xLBs8np6Q/Rs13EaerD2I/AAAAAAAAAAg/oxMlJRLeOYg/s1600-h/formula.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_M2xLBs8np6Q/Rs13EaerD2I/AAAAAAAAAAg/oxMlJRLeOYg/s400/formula.JPG" alt="" id="BLOGGER_PHOTO_ID_5101864870591795042" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;El tiempo de respuesta es inversamente proporcional al tiempo de pensar. Veamos... El tiempo de respuesta es el tiempo total que demanda cierta ejecución (ej: una consulta). El tiempo de pensar es el tiempo total que dedico para la resolución de cierto problema (ese tiempo total incluye el análisis del problema, la generación de sus posibles soluciones, las pruebas de esas soluciones y la elección de la solución más performante).&lt;br /&gt;&lt;br /&gt;La fórmula se resume en lo siguiente: Si le dedicamos a cada problema el "tiempo de pensar" que se merece... estaremos más cerca de lograr un "tiempo de respuesta" más óptimo.&lt;br /&gt;&lt;br /&gt;Recuerden: Cada problema es un mundo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-317976420336994794?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/317976420336994794/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=317976420336994794' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/317976420336994794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/317976420336994794'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/la-clave-del-tuning.html' title='La Clave del Tuning (CDT)'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_M2xLBs8np6Q/Rs13EaerD2I/AAAAAAAAAAg/oxMlJRLeOYg/s72-c/formula.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-3578359095308869982</id><published>2007-08-15T12:18:00.000-03:00</published><updated>2007-08-15T12:31:03.586-03:00</updated><title type='text'>Download Oracle Database 11g Release 1</title><content type='html'>Ya se encuentra disponible &lt;a href="http://www.oracle.com/technology/products/database/oracle11g/index.html" target="_blank"&gt;AQUI&lt;/a&gt; la base de datos Oracle 11g Release 1 sólo para Linux x86.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-3578359095308869982?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/3578359095308869982/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=3578359095308869982' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3578359095308869982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/3578359095308869982'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/download-oracle-database-11g.html' title='Download Oracle Database 11g Release 1'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7677614382867803136</id><published>2007-08-01T09:41:00.000-03:00</published><updated>2007-08-01T09:45:23.933-03:00</updated><title type='text'>El hermoso Teorema de Pitágoras</title><content type='html'>Pueden ver &lt;a href="http://www.pagina12.com.ar/diario/contratapa/13-88928-2007-07-31.html" target="_blank"&gt;AQUI&lt;/a&gt; una de las demostraciones más hermosas que jamas vi del Teorema de Pitágoras. Lean el artículo... seguramente les fascinará como a mi.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7677614382867803136?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7677614382867803136/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7677614382867803136' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7677614382867803136'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7677614382867803136'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/08/el-hermoso-teorema-de-pitgoras.html' title='El hermoso Teorema de Pitágoras'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1529541826762854806</id><published>2007-07-27T15:58:00.000-03:00</published><updated>2007-07-27T16:38:12.052-03:00</updated><title type='text'>WHEN OTHERS THEN NULL ;</title><content type='html'>Veo muchas veces a los desarrolladores poniendo en sus códigos...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;EXCEPTION&lt;br /&gt;WHEN OTHERS THEN &lt;br /&gt;    NULL ;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Las programadores que hacen ésto no estan concientes de sus graves consecuencias. &lt;br /&gt;Un desarrollador JAMAS debe poner código de ese tipo. Porque? Por la simple razón que poniendolo, es como si jamas lo hubieramos puesto. Se entiende? No tiene ningún sentido ponerlo porque no cumple ninguna función. Ahh! Si! Cumple con la función de que si tenemos un error durante la ejecución de nuestro código... jamas nos enteramos; y si nos enteramos de que hubo un error es demasiado difícil encontrarlo.&lt;br /&gt;&lt;br /&gt;Veamos un muy sencillo ejemplo: &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PROCEDURE pr_log_errores&lt;br /&gt;  2  (&lt;br /&gt;  3  p_cod_error  IN  VARCHAR2 ,&lt;br /&gt;  4  p_msj_error  IN  VARCHAR2&lt;br /&gt;  5  )&lt;br /&gt;  6  IS&lt;br /&gt;  7  BEGIN&lt;br /&gt;  8      INSERT INTO log_errores&lt;br /&gt;  9      (&lt;br /&gt; 10      fecha ,&lt;br /&gt; 11      cod_error,&lt;br /&gt; 12      msj_error&lt;br /&gt; 13      )&lt;br /&gt; 14      VALUES&lt;br /&gt; 15      (&lt;br /&gt; 16      SYSDATE ,&lt;br /&gt; 17      p_cod_error,&lt;br /&gt; 18      p_msj_error&lt;br /&gt; 19      ) ;&lt;br /&gt; 20      COMMIT ;&lt;br /&gt; 21  EXCEPTION&lt;br /&gt; 22  &lt;span style="font-weight:bold;"&gt;WHEN OTHERS THEN&lt;/span&gt;&lt;br /&gt; 23      &lt;span style="font-weight:bold;"&gt;NULL ;&lt;/span&gt;&lt;br /&gt; 24  END ;&lt;br /&gt; 25  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; BEGIN&lt;br /&gt;  2      pr_log_errores('ORA-00001','unique constraint violated.') ;&lt;br /&gt;  3  END ;&lt;br /&gt;  4  /&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;PL/SQL procedure successfully completed.&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Como podemos ver, ejecutamos nuestro procedimiento de prueba y terminó perfectamente... pero, realmente terminó bien? Veamos...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;  2  FROM log_errores ;&lt;br /&gt;&lt;br /&gt;no rows selected&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Aha! El procedimiento no insertó en la tabla de logueo el error que le pasamos como parámetro. Pero como puede ser esto posible?&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; DESC log_errores&lt;br /&gt; Name               Null?    Type&lt;br /&gt; ------------------ -------- -------------&lt;br /&gt; FECHA                       DATE&lt;br /&gt; COD_ERROR                   VARCHAR2(100)&lt;br /&gt; MSJ_ERROR                   &lt;span style="font-weight:bold;"&gt;VARCHAR2(10)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El campo MSJ_ERROR tiene una longitud de 10 caracteres y nosotros queremos introducir una cadena más larga. Nosotros nunca nos enteramos de que hubo un error. &lt;br /&gt;Imaginense si es un proceso Batch crítico que se ejecuta todos los días de forma automática durante la madrugada. Si ocurre algún error... nadie se podría dar cuenta que el proceso en algún punto falló.&lt;br /&gt;&lt;br /&gt;Corregimos nuestro procedimiento...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PROCEDURE pr_log_errores&lt;br /&gt;  2  (&lt;br /&gt;  3  p_cod_error  IN  VARCHAR2 ,&lt;br /&gt;  4  p_msj_error  IN  VARCHAR2&lt;br /&gt;  5  )&lt;br /&gt;  6  IS&lt;br /&gt;  7  BEGIN&lt;br /&gt;  8      INSERT INTO log_errores&lt;br /&gt;  9      (&lt;br /&gt; 10      fecha ,&lt;br /&gt; 11      cod_error,&lt;br /&gt; 12      msj_error&lt;br /&gt; 13      )&lt;br /&gt; 14      VALUES&lt;br /&gt; 15      (&lt;br /&gt; 16      SYSDATE ,&lt;br /&gt; 17      p_cod_error,&lt;br /&gt; 18      p_msj_error&lt;br /&gt; 19      ) ;&lt;br /&gt; 20      COMMIT ;&lt;br /&gt; 21  EXCEPTION&lt;br /&gt; 22  &lt;span style="font-weight:bold;"&gt;WHEN OTHERS THEN&lt;/span&gt;&lt;br /&gt; 23      &lt;span style="font-weight:bold;"&gt;RAISE ;&lt;/span&gt;&lt;br /&gt; 24  END ;&lt;br /&gt; 25  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; BEGIN&lt;br /&gt;  2      pr_log_errores('ORA-00001','unique constraint violated.') ;&lt;br /&gt;  3  END ;&lt;br /&gt;  4  /&lt;br /&gt;BEGIN&lt;br /&gt;*&lt;br /&gt;ERROR at line 1:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-01401: inserted value too large for column&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;ORA-06512: at "IP_UTILS.PR_LOG_ERRORES", line 24&lt;br /&gt;ORA-06512: at line 2&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1529541826762854806?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1529541826762854806/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1529541826762854806' title='1 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1529541826762854806'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1529541826762854806'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/when-others-then-null.html' title='WHEN OTHERS THEN NULL ;'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7706796095582573426</id><published>2007-07-26T16:37:00.000-03:00</published><updated>2007-07-26T16:39:31.875-03:00</updated><title type='text'>Compulsive Tuning Disorder</title><content type='html'>Descubrí &lt;a href="http://searchoracle.techtarget.com/qna/0,289202,sid41_gci1075628,00.html" target="_blank"&gt;AQUI&lt;/a&gt; si tenés los síntomas del CTD...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7706796095582573426?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7706796095582573426/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7706796095582573426' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7706796095582573426'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7706796095582573426'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/compulsive-tuning-disorder.html' title='Compulsive Tuning Disorder'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-8648226829163333935</id><published>2007-07-26T09:41:00.000-03:00</published><updated>2007-08-02T15:48:58.834-03:00</updated><title type='text'>Geeks...</title><content type='html'>Muy interesante &lt;a href="http://nomadishere.com/2007/03/12/a-note-to-employers-8-things-intelligent-people-geeks-and-nerds-need-to-work-happy/" target="_blank"&gt;ARTÍCULO&lt;/a&gt; que explica cómo tratar en un ambiente laboral a los Geeks y cuales son sus necesidades.&lt;br /&gt;Darles trabajos desafiantes, importantes y significativos a ésta clase de personas es esencial.&lt;br /&gt;&lt;br /&gt;Si sos un Geek o crees que lo sos y estas en un trabajo en el cual sentis que estas desperdiciando mucho de vos, quizás sea hora de pensar en un cambio...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-8648226829163333935?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/8648226829163333935/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=8648226829163333935' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8648226829163333935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/8648226829163333935'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/geeks.html' title='Geeks...'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6461886062698708534</id><published>2007-07-17T12:24:00.000-03:00</published><updated>2007-07-23T17:13:58.378-03:00</updated><title type='text'>Evitá el uso de Variables Globales</title><content type='html'>Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3      PROCEDURE test_global_variable ;&lt;br /&gt; 4  END ;&lt;br /&gt; 5  /&lt;br /&gt;&lt;br /&gt;Package created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE BODY test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3  variable_globar    NUMBER ;&lt;br /&gt; 4  PROCEDURE mostrar_variable_global ( p_valor  IN  NUMBER )&lt;br /&gt; 5  IS&lt;br /&gt; 6  BEGIN&lt;br /&gt; 7      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt; 8      variable_globar := 2 ;&lt;br /&gt; 9      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt;10  END ;&lt;br /&gt;11&lt;br /&gt;12  PROCEDURE test_global_variable&lt;br /&gt;13  IS&lt;br /&gt;14  BEGIN&lt;br /&gt;15      variable_globar := 1 ;&lt;br /&gt;16      mostrar_variable_global(variable_globar) ;&lt;br /&gt;17  END ;&lt;br /&gt;18&lt;br /&gt;19  END test_pkg ;&lt;br /&gt;20  /&lt;br /&gt;&lt;br /&gt;Package body created.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC test_pkg.test_global_variable ;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Valor de la variable global: 1&lt;br /&gt;Valor de la variable global: 2&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Cómo puede ser ésto posible? Bueno, como los valores IN se pasan por &lt;a href="http://download-east.oracle.com/docs/cd/B19306_01/appdev.102/b14261/subprograms.htm#sthref1725" target="_blank"&gt;referencia&lt;/a&gt;, el valor de la variable global también es pasada al procedimiento mostrar_variable_global por referencia.&lt;br /&gt;&lt;br /&gt;Para evitar éste problema con las variables globales tenemos 2 opciones:&lt;br /&gt;1) No usar Variables Globales  :)&lt;br /&gt;2) Si estamos forzados a utilizarlas, podemos asignar el contenido de la variable global a una nueva variable y pasar el valor de esa nueva variable como parámetro. Sino, podemos modificar el input del parámetro.&lt;br /&gt;&lt;br /&gt;Veamos las 2 opciones implementadas:&lt;br /&gt;&lt;br /&gt;- Opción 1:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3  PROCEDURE test_global_variable ;&lt;br /&gt; 4  END test_pkg ;&lt;br /&gt; 5  /&lt;br /&gt;&lt;br /&gt;Package created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE BODY test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3  variable_globar    NUMBER ;&lt;br /&gt; 4  PROCEDURE mostrar_variable_global ( p_valor  IN  NUMBER )&lt;br /&gt; 5  IS&lt;br /&gt; 6  BEGIN&lt;br /&gt; 7      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt; 8      variable_globar := 2 ;&lt;br /&gt; 9      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt;10  END ;&lt;br /&gt;11&lt;br /&gt;12  PROCEDURE test_global_variable&lt;br /&gt;13  IS&lt;br /&gt;14  &lt;span style="font-weight: bold;"&gt;l_variable_global_aux    NUMBER ;&lt;/span&gt;&lt;br /&gt;15  BEGIN&lt;br /&gt;16      variable_globar := 1 ;&lt;br /&gt;17      &lt;span style="font-weight: bold;"&gt;l_variable_global_aux := variable_globar ;&lt;/span&gt;&lt;br /&gt;18      mostrar_variable_global(&lt;span style="font-weight: bold;"&gt;l_variable_global_aux&lt;/span&gt;) ;&lt;br /&gt;19  END ;&lt;br /&gt;20&lt;br /&gt;21  END test_pkg ;&lt;br /&gt;22  /&lt;br /&gt;&lt;br /&gt;Package body created.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC test_pkg.test_global_variable ;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Valor de la variable global: 1&lt;br /&gt;Valor de la variable global: 1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;- Opción 2:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3  PROCEDURE test_global_variable ;&lt;br /&gt; 4  END test_pkg ;&lt;br /&gt; 5  /&lt;br /&gt;&lt;br /&gt;Package created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PACKAGE BODY test_pkg&lt;br /&gt; 2  AS&lt;br /&gt; 3  variable_globar    NUMBER ;&lt;br /&gt; 4  PROCEDURE mostrar_variable_global ( p_valor  IN  NUMBER )&lt;br /&gt; 5  IS&lt;br /&gt; 6  BEGIN&lt;br /&gt; 7      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt; 8      variable_globar := 2 ;&lt;br /&gt; 9      dbms_output.put_line( 'Valor de la variable global: '||p_valor ) ;&lt;br /&gt;10  END ;&lt;br /&gt;11&lt;br /&gt;12  PROCEDURE test_global_variable&lt;br /&gt;13  IS&lt;br /&gt;14  BEGIN&lt;br /&gt;15      variable_globar := 1 ;&lt;br /&gt;16      mostrar_variable_global(&lt;span style="font-weight: bold;"&gt;variable_globar+0&lt;/span&gt;) ;&lt;br /&gt;17  END ;&lt;br /&gt;18&lt;br /&gt;19  END test_pkg ;&lt;br /&gt;20  /&lt;br /&gt;&lt;br /&gt;Package body created.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC test_pkg.test_global_variable ;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Valor de la variable global: 1&lt;br /&gt;Valor de la variable global: 1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6461886062698708534?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6461886062698708534/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6461886062698708534' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6461886062698708534'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6461886062698708534'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/evit-el-uso-de-variables-globales.html' title='Evitá el uso de Variables Globales'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-108998149958658796</id><published>2007-07-17T09:50:00.000-03:00</published><updated>2007-07-31T12:23:00.247-03:00</updated><title type='text'>The 10g Plan Table</title><content type='html'>Muy interesante &lt;a href="http://www.jlcomp.demon.co.uk/plan_table_hack.html" target="_blank"&gt;ARTÍCULO&lt;/a&gt; que muestra cómo sacar más provecho de la Plan Table en 10g...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-108998149958658796?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/108998149958658796/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=108998149958658796' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/108998149958658796'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/108998149958658796'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/10g-plan-table.html' title='The 10g Plan Table'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-61245436025872925</id><published>2007-07-14T13:19:00.000-03:00</published><updated>2007-08-17T11:58:19.188-03:00</updated><title type='text'>SQL Injection</title><content type='html'>SQL Injection es una técnica de ataque a una base de datos. Mediante una aplicación Web, el atacante puede modificar los parámetros que envía desde un formulario hacia la base de datos y de ésta manera, modificar la sentencia SQL que se ejecuta. Podemos ejecutar sentencias DML, DDL... entre otras cosas.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo de SQL Injection en Oracle:&lt;br /&gt;&lt;br /&gt;Creamos una tabla para nuestra prueba que contiene usuarios y passwords:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE login AS&lt;br /&gt;2  SELECT 'user_'||level username, to_char(level*10000) password&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 5 ;  Table created.  &lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT *&lt;br /&gt;2  FROM login ;&lt;br /&gt;&lt;br /&gt;USERNAME         PASSWORD&lt;br /&gt;---------------- ----------&lt;br /&gt;user_1           10000&lt;br /&gt;user_2           20000&lt;br /&gt;user_3           30000&lt;br /&gt;user_4           40000&lt;br /&gt;user_5           50000&lt;br /&gt;&lt;br /&gt;5 rows selected.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Creamos un procedimiento de login que recibe como parámetros el usuario y password de validación. Si los datos son correctos, se devuelven todos los usuarios y passwords de la tabla login, en caso contrario se devuelve una leyenda de error:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE OR REPLACE PROCEDURE pr_login&lt;br /&gt;2  (&lt;br /&gt;3      p_username IN VARCHAR2 ,&lt;br /&gt;4      p_password IN VARCHAR2&lt;br /&gt;5  )&lt;br /&gt;6  IS&lt;br /&gt;7      l_flag     BOOLEAN DEFAULT FALSE ;&lt;br /&gt;8      l_cursor   SYS_REFCURSOR ;&lt;br /&gt;9      l_query    VARCHAR2(4000) ;&lt;br /&gt;10      registro   login%ROWTYPE ;&lt;br /&gt;11  BEGIN&lt;br /&gt;12      l_query := 'SELECT *'&lt;br /&gt;13                 ||' FROM login'&lt;br /&gt;14                 ||' WHERE username = '''||p_username||''''&lt;br /&gt;15                 ||'   AND password = '''||p_password||'''' ;&lt;br /&gt;16&lt;br /&gt;17      dbms_output.put_line('CONSULTA: '||l_query) ;&lt;br /&gt;18&lt;br /&gt;19      OPEN l_cursor FOR l_query ;&lt;br /&gt;20      LOOP&lt;br /&gt;21          FETCH l_cursor INTO registro ;&lt;br /&gt;22          EXIT WHEN l_cursor%NOTFOUND ;&lt;br /&gt;23          dbms_output.put_line('USER : '||registro.username||' &lt;br /&gt;'||'PASS:'||registro.password) ;&lt;br /&gt;24          IF NOT ( l_flag ) THEN&lt;br /&gt;25              l_flag := TRUE ;&lt;br /&gt;26          END IF ;&lt;br /&gt;27      END LOOP ;&lt;br /&gt;28      CLOSE l_cursor ;&lt;br /&gt;29      IF NOT ( l_flag ) THEN&lt;br /&gt;30          dbms_output.put_line('LOGIN INCORRECTO!') ;&lt;br /&gt;31      END IF ;&lt;br /&gt;32  EXCEPTION&lt;br /&gt;33      WHEN OTHERS THEN&lt;br /&gt;34          dbms_output.put_line('LOGIN INCORRECTO!') ;&lt;br /&gt;35  END ;&lt;br /&gt;36  /&lt;br /&gt;&lt;br /&gt;Procedure created.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Bien, ahora vamos a probar nuestro procedimiento de login:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC pr_login('user_2','123456789') ;&lt;br /&gt;&lt;br /&gt;CONSULTA: SELECT * FROM login WHERE username = 'user_2'   AND password = '123456789'&lt;br /&gt;&lt;br /&gt;LOGIN INCORRECTO!&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El procedimiento anduvo a la perfección! Validó el usuario y password, y como resultan ser incorrectos, nos devolvió un error.&lt;br /&gt;Fijense que en el procedimiento incluimos una linea de código (la nro. 17) que nos va a mostrar la sentencia SQL que estamos ejecutando.&lt;br /&gt;&lt;br /&gt;Ahora probemos nuevamente ejecutar el procedimiento de login pero modificando el segundo parámetro:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; EXEC pr_login('user_2','123456789'' &lt;span style="font-weight: bold;"&gt;OR ''x''=''x'&lt;/span&gt;) ;&lt;br /&gt;&lt;br /&gt;CONSULTA: SELECT * FROM login WHERE username = 'user_2'   AND password = '123456789' OR 'x'='x'&lt;br /&gt;&lt;br /&gt;USER : user_1 PASS: 10000&lt;br /&gt;USER : user_2 PASS: 20000&lt;br /&gt;USER : user_3 PASS: 30000&lt;br /&gt;USER : user_4 PASS: 40000&lt;br /&gt;USER : user_5 PASS: 50000&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Les presento a SQL Injection! Como acabamos de observar, modificando uno de los parámetros, pudimos lograr modificar la sentencia SQL que se ejecuta en la base de datos y por consiguiente, ver todos los usuarios y passwords de la tabla login.&lt;br /&gt;&lt;br /&gt;La única pregunta que queda por hacer es: ¿ Qué tan segura es su aplicación ?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-61245436025872925?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/61245436025872925/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=61245436025872925' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/61245436025872925'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/61245436025872925'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/sql-injection.html' title='SQL Injection'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-4795903266710536735</id><published>2007-07-13T12:33:00.000-03:00</published><updated>2007-07-13T16:46:34.014-03:00</updated><title type='text'>Webcast lanzamiento Oracle Database 11g</title><content type='html'>Pueden ver &lt;a href="http://www.oracle.com/features/hp/oracle-database-11g.html?SC=NA05070212C0.GCM.8005.100.oracle.br" target="_blank"&gt;AQUI&lt;/a&gt;, los Webcast del lanzamiento de Oracle Database 11g del pasado 11 de Julio.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-4795903266710536735?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/4795903266710536735/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=4795903266710536735' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4795903266710536735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4795903266710536735'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/webcast-lanzamiento-oracle-database-11g.html' title='Webcast lanzamiento Oracle Database 11g'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6867404773960745523</id><published>2007-07-12T12:34:00.000-03:00</published><updated>2007-07-22T17:48:36.426-03:00</updated><title type='text'>High Water Mark (HWM)</title><content type='html'>Generalmente, cuando se crea una tabla o un índice... se crea un segmento asociado al objeto. El segmento se crea con determinados bloques del data file, pero muy poco espacio es destinado para nuestro uso. Cuando los datos van llenando los bloques asignados al segmento, se van alocando más bloques para que usemos.&lt;br /&gt;A medida que los bloques van siendo alocados, la HWM se posiciona en el último bloque para mostrarnos la cantidad total de bloques alocados hasta el momento y estaban disponibles para ser usados.&lt;br /&gt;Un error común es pensar que a medida que eliminamos datos de los bloques, la HWM se refresca para mostrarnos que estamos utilizando menos bloques; pero la realidad es que la HWM se mantiene siempre apuntando al último bloque alocado.&lt;br /&gt;Sabemos que cuando realizamos un Full Scan, son leídos secuencialmente todos los bloques de la tabla hasta la HWM, por lo cual, debemos tener en claro que aunque eliminemos datos de una tabla, ese espacio "vacío" se va a seguir leyendo, lo cual trae como consecuencia la lectura de bloques innecesarios.&lt;br /&gt;&lt;br /&gt;Veamos un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE TABLE emp&lt;br /&gt;2  (id NUMBER , sexo VARCHAR2(1 ) ;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO emp&lt;br /&gt;2  SELECT level , 'M'&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 800000 ; &lt;br /&gt;&lt;br /&gt;800000 rows created.  &lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; INSERT INTO emp&lt;br /&gt;2  SELECT level , 'F'&lt;br /&gt;3  FROM dual&lt;br /&gt;4  CONNECT BY level &lt;= 200000 ; &lt;br /&gt;&lt;br /&gt;200000 rows created.  &lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ANALYZE TABLE emp COMPUTE STATISTICS ;&lt;br /&gt;&lt;br /&gt;Table analyzed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sexo&lt;br /&gt;2  FROM emp&lt;br /&gt;3  WHERE id = 900000 ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;0   SELECT STATEMENT Optimizer=CHOOSE (Cost=160 Card=10000 Bytes=60000)&lt;br /&gt;1   0   TABLE ACCESS (FULL) OF 'EMP' (Cost=160 Card=10000 Bytes=60000)&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;      0  recursive calls&lt;br /&gt;      0  db block gets&lt;br /&gt;   &lt;span style="font-weight: bold;"&gt;1655  consistent gets&lt;/span&gt;&lt;br /&gt;      0  physical reads&lt;br /&gt;      0  redo size&lt;br /&gt;    213  bytes sent via SQL*Net to client&lt;br /&gt;    363  bytes received via SQL*Net from client&lt;br /&gt;      1  SQL*Net roundtrips to/from client&lt;br /&gt;      0  sorts (memory)&lt;br /&gt;      0  sorts (disk)&lt;br /&gt;      0  rows processed&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; DELETE FROM emp&lt;br /&gt;2  WHERE sexo = 'M' ;&lt;br /&gt;&lt;br /&gt;800000 rows deleted.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sexo&lt;br /&gt;2  FROM emp&lt;br /&gt;3  WHERE id = 900000 ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;0   SELECT STATEMENT Optimizer=CHOOSE (Cost=160 Card=10000 Bytes=60000)&lt;br /&gt;1   0   TABLE ACCESS (FULL) OF 'EMP' (Cost=160 Card=10000 Bytes=60000)&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;      0  recursive calls&lt;br /&gt;      0  db block gets&lt;br /&gt;   &lt;span style="font-weight: bold;"&gt;1655  consistent gets&lt;/span&gt;&lt;br /&gt;    419  physical reads&lt;br /&gt;      0  redo size&lt;br /&gt;    213  bytes sent via SQL*Net to client&lt;br /&gt;    363  bytes received via SQL*Net from client&lt;br /&gt;      1  SQL*Net roundtrips to/from client&lt;br /&gt;      0  sorts (memory)&lt;br /&gt;      0  sorts (disk)&lt;br /&gt;      0  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Como pudimos ver, aunque hayamos eliminados datos de la tabla, seguimos leyendo la misma cantidad de bloques que antes porque la HWM no se refrescó y sigue apuntando al último bloque alocado en el segmento.&lt;br /&gt;Para solucionar éste problema, podemos realizar...&lt;br /&gt;&lt;br /&gt;1) &lt;span style="font-weight: bold;"&gt;TRUNCATE ...&lt;/span&gt;&lt;br /&gt;- Si la tabla esta vacia podemos hacer un TRUNCATE para resetear la HWM. Sino, podemos hacer un export de los datos, luego truncar la tabla y realizar un import.&lt;br /&gt;2)&lt;span style="font-weight: bold;"&gt; ALTER TABLE ... MOVE&lt;/span&gt;&lt;br /&gt;- De ésta manera reorganizamos la tabla. Tener en cuenta que luego de ejecutar el ALTER, hay que hacer un REBUILD de todos los índices de la tabla!&lt;br /&gt;3) &lt;span style="font-weight: bold;"&gt;Dropear y recrear el objeto (export/import)&lt;/span&gt;&lt;br /&gt;4) &lt;span style="font-weight: bold;"&gt;ALTER TABLE ... SHRINK SPACE | SHRINK SPACE COMPACT &lt;/span&gt;&lt;br /&gt;- Para 10g en adelante.&lt;br /&gt;&lt;br /&gt;Apliquemos a nuestro ejemplo el punto 2...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL_9iR2&gt; ALTER TABLE emp MOVE ;&lt;br /&gt;&lt;br /&gt;Table altered.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; ANALYZE TABLE emp COMPUTE STATISTICS ;&lt;br /&gt;&lt;br /&gt;Table analyzed.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT sexo&lt;br /&gt;2  FROM emp&lt;br /&gt;3  WHERE id = 900000 ;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;0   SELECT STATEMENT Optimizer=CHOOSE (Cost=33 Card=2000 Bytes=12000)&lt;br /&gt;1   0   TABLE ACCESS (FULL) OF 'EMP' (Cost=33 Card=2000 Bytes=12000)&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;      0  recursive calls&lt;br /&gt;      0  db block gets&lt;br /&gt;  &lt;span style="font-weight: bold;"&gt;  335  consistent gets&lt;/span&gt;&lt;br /&gt;      0  physical reads&lt;br /&gt;      0  redo size&lt;br /&gt;    213  bytes sent via SQL*Net to client&lt;br /&gt;    363  bytes received via SQL*Net from client&lt;br /&gt;      1  SQL*Net roundtrips to/from client&lt;br /&gt;      0  sorts (memory)&lt;br /&gt;      0  sorts (disk)&lt;br /&gt;      0  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Como vemos, la HWM se refresco y ahora estamos leyendo sólo los bloques que contienen nuestros datos.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6867404773960745523?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6867404773960745523/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6867404773960745523' title='12 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6867404773960745523'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6867404773960745523'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/high-water-mark-hwm.html' title='High Water Mark (HWM)'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6265588033235985734</id><published>2007-07-10T13:15:00.000-03:00</published><updated>2007-07-15T00:03:20.708-03:00</updated><title type='text'>Overclocking your brain</title><content type='html'>Excelente &lt;a href="http://ririanproject.com/2006/11/03/22-ways-to-overclok-your-brain/" target="_blank"&gt;ARTÍCULO&lt;/a&gt; que explica varios ejercicios para optimizar el cerebro.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“I just found out that the brain is like a computer. If that’s true, then there really aren’t any stupid people. Just people running DOS.”&lt;/em&gt; &lt;/p&gt;&lt;/blockquote&gt; &lt;p align="right"&gt;- Anonymous&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6265588033235985734?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6265588033235985734/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6265588033235985734' title='2 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6265588033235985734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6265588033235985734'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/overclocking-your-brain.html' title='Overclocking your brain'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-1250297690231356836</id><published>2007-07-10T11:30:00.000-03:00</published><updated>2007-09-18T11:27:46.034-03:00</updated><title type='text'>Modificar el prompt de SQL*Plus</title><content type='html'>Cuando estamos trabajando con SQL*Plus, solemos trabajar con varias ventanas abiertas cada una conectada a una instancia diferente. Esto suele ser un problema ya que podemos confundirnos de instancia y trabajar en una instancia diferente. Cuando iniciamos SQL*Plus, Oracle busca un archivo llamado "login.sql" dentro del path definido en la variable ORACLE_PATH, y si no lo encuentra, busca el archivo "glogin.sql" (global login).&lt;br /&gt;Podemos modificar el prompt de SQL*Plus para identificar con facilidad el esquema e instancia en donde nos encontramos. &lt;br /&gt;Para realizar ésta modificación, abrimos el archivo "login.sql" o "glogin.sql" y colocamos lo siguiente:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set term off&lt;br /&gt;set serveroutput on size 1000000 format wrapped&lt;br /&gt;set linesize 70&lt;br /&gt;set trimspool on&lt;br /&gt;set pagesize 9999&lt;br /&gt;set heading on&lt;br /&gt;set underline -&lt;br /&gt;&lt;br /&gt;define sql_prompt=idle&lt;br /&gt;define sql_prompt = 'not connected'&lt;br /&gt;&lt;br /&gt;column id_plus_exp FOR 990 HEADING i&lt;br /&gt;column parent_id_plus_exp FOR 990 HEADING p&lt;br /&gt;column plan_plus_exp FOR a60&lt;br /&gt;column object_node_plus_exp FOR a8&lt;br /&gt;column other_tag_plus_exp FOR a29&lt;br /&gt;column other_plus_exp FOR a44&lt;br /&gt;&lt;br /&gt;column user_sid new_value sql_prompt&lt;br /&gt;&lt;br /&gt;select lower(user)||'@'||'&amp;_CONNECT_IDENTIFIER' user_sid &lt;br /&gt;from dual;&lt;br /&gt;&lt;br /&gt;set sqlprompt '&amp;sql_prompt&gt; '&lt;br /&gt;&lt;br /&gt;set timing on&lt;br /&gt;set term on&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Nota: Este script es el que tengo actualmente en mi cliente. Se puede modificar a gusto de cada uno.&lt;br /&gt;&lt;br /&gt;El prompt quedará de la siguiente manera:  "&lt;span class="SpellE"&gt;user@instancia&lt;/span&gt;&gt;"&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-1250297690231356836?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/1250297690231356836/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=1250297690231356836' title='3 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1250297690231356836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/1250297690231356836'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/modificar-el-prompt-de-sqlplus.html' title='Modificar el prompt de SQL*Plus'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-2262125131529757306</id><published>2007-07-10T11:24:00.001-03:00</published><updated>2007-07-23T17:14:56.791-03:00</updated><title type='text'>Fast Dual</title><content type='html'>Muchas aplicaciones ejecutan millones de veces consultas a la tabla DUAL. En versiones anteriores a la 10g, Oracle realizaba 3 LIO's (Logical I/O) por cada vez que se consultaba a la tabla. Esto es muy costoso en ambientes en los cuales se hace referencia a esta tabla todo el tiempo.&lt;br /&gt;Para solucionar este problema, podemos modificar la tabla DUAL y hacer referencia a la tabla X$DUAL. Esta tabla es una tabla virtual, lo cual no implica ningún LIO.&lt;br /&gt;&lt;br /&gt;Ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ sqlplus "/as sysdba"&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT * FROM dual ;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;       0  recursive calls&lt;br /&gt;       0  db block gets&lt;br /&gt;       &lt;span style="font-weight: bold;"&gt;3  consistent gets&lt;/span&gt;&lt;br /&gt;       0  physical reads&lt;br /&gt;       0  redo size&lt;br /&gt;     402  bytes sent via SQL*Net to client&lt;br /&gt;     504  bytes received via SQL*Net from client&lt;br /&gt;       2  SQL*Net roundtrips to/from client&lt;br /&gt;       0  sorts (memory)&lt;br /&gt;       0  sorts (disk)&lt;br /&gt;       1  rows processed&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; SELECT * FROM x$dual ;&lt;br /&gt;&lt;br /&gt;ADDR           INDX    INST_ID D&lt;br /&gt;-------- ---------- ---------- -&lt;br /&gt;0A8A90D8          0          1 X&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CREATE VIEW fast_dual AS&lt;br /&gt;2  SELECT 'X' dummy FROM x$dual ;&lt;br /&gt;&lt;br /&gt;View created.&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; GRANT SELECT ON fast_dual TO PUBLIC ;&lt;br /&gt;&lt;br /&gt;Grant succeeded.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL_9iR2&gt; CONN algun_esquema/algun_password ;&lt;br /&gt;&lt;br /&gt;SQL_algun_esquema&gt; CREATE SYNONYM dual FOR sys.fast_dual ;&lt;br /&gt;&lt;br /&gt;Synonym created.&lt;br /&gt;&lt;br /&gt;SQL_algun_esquema&gt; SET AUTOTRACE TRACEONLY STATISTICS&lt;br /&gt;SQL_algun_esquema&gt; SELECT * FROM dual ;&lt;br /&gt;&lt;br /&gt;1 row selected.&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;---------------------------------------------------&lt;br /&gt;       0  recursive calls&lt;br /&gt;       0  db block gets&lt;br /&gt;       &lt;span style="font-weight: bold;"&gt;0  consistent gets&lt;/span&gt;&lt;br /&gt;       0  physical reads&lt;br /&gt;       0  redo size&lt;br /&gt;     326  bytes sent via SQL*Net to client&lt;br /&gt;     498  bytes received via SQL*Net from client&lt;br /&gt;       2  SQL*Net roundtrips to/from client&lt;br /&gt;       0  sorts (memory)&lt;br /&gt;       0  sorts (disk)&lt;br /&gt;       1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En 10gR2 se soluciona este problema ya que se modificó la tabla DUAL existente y ahora se hace uso de la tabla virtual X$DUAL.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-2262125131529757306?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/2262125131529757306/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=2262125131529757306' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2262125131529757306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/2262125131529757306'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/fast-dual_10.html' title='Fast Dual'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-5913860253729320360</id><published>2007-07-10T09:38:00.000-03:00</published><updated>2007-07-25T09:19:28.251-03:00</updated><title type='text'>Hard Parse, Soft Parse &amp; “Softer” Soft Parse</title><content type='html'>Procesamiento de una consulta:&lt;br /&gt;&lt;br /&gt;1) Validación Sintáctica&lt;br /&gt;2) Validación Semántica&lt;br /&gt;3) Optimización&lt;br /&gt;4) Generación del QEP (Query Execution Plan)&lt;br /&gt;5) Ejecución del QEP (Query Execution Plan)&lt;br /&gt;&lt;br /&gt;El punto 1 al 4 forma parte del Parseo de la consulta, mientras que el punto 5 es la propia ejecución.&lt;br /&gt;&lt;br /&gt;Cuando ejecutamos una consulta, siempre se realizan, como mínimo, los pasos 1 y 2. Luego de ejecutarse estos pasos, Oracle transforma la consulta en un valor hash y la envía a la Shared Pool y en la Library Cache se busca si existe alguna consulta con el mismo valor hash (si alguna sesión ya la utilizó en algun momento). En caso de que exista, se compara el texto de la consulta con la que se encontró en la Library Cache para validar si son exactamente iguales (este paso adicional se realiza porque puede llegar a haber varias consultas con el mismo valor hash); en caso de que lo sean, se procede a ejecutar esa consulta. Esto es lo que llamamos un &lt;span style="font-weight: bold;"&gt;Soft Parse&lt;/span&gt;. Si la consulta no existe, Oracle realiza los pasos 3 y 4. Esto es conocido como un &lt;span style="font-weight: bold;"&gt;Hard Parse&lt;/span&gt;. El Hard Parse es muy costoso para Oracle Server ya que implica realizar varios latches (loqueos) en la SGA y consume mucha CPU.&lt;br /&gt;Como bien sabemos, cada consulta que ejecutamos implica la utilización de un cursor (un cursor es un espacio de memoria destinado a la ejecución de nuestra consulta). Lo ideal, es que nuestra aplicación abra los cursores que vaya a utilizar, ejecute las sentencias x veces y luego los cierre. Muchas aplicaciones como Forms no suelen ejecutar los cursores de esta forma, lo que implica que no podamos reutilizar los cursores y siempre tengamos que abrir nuevamente los que ya ejecutamos.&lt;br /&gt;Para reducir éste problema, podemos utilizar el parámetro de inicialización SESSION_CACHED_CURSORS que nos va a permitir realizar un &lt;span style="font-weight: bold;"&gt;"Softer" Soft Parse&lt;/span&gt;. Si setemos el parámetro en 100, Oracle mantendrá 100 cursores abiertos para que los podamos reutilizar y evitarnos tener que abrirlos cada vez. Este espacio de memoria destinado al manejo de cursores, se mantiene con una lista LRU.&lt;br /&gt;Oracle recomiendo que el parámetro se setee en una primera instancia en 50 e ir monitoreandolo para verificar si conviene incrementar su valor. Este parámetro debe setearse considerando el valor de OPEN_CURSORS.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;select to_char(100 * sess / calls, '999999999990.00') || '%' cursor_cache_hits,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;       to_char(100 * (calls - sess - hard) / calls, '999990.00') || '%' soft_parses,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;       to_char(100 * hard / calls, '999990.00') || '%' hard_parses&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;from ( select value calls from v$sysstat where name = 'parse count (total)' ),&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;     ( select value hard from v$sysstat where name = 'parse count (hard)' ),&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;     ( select value sess from v$sysstat where name = 'session cursor cache hits' ) ;&lt;/span&gt;&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;CURSOR_CACHE_HITS SOFT_PARSES HARD_PARSES&lt;br /&gt;----------------- ----------- -----------&lt;br /&gt;        59.11%      39.49%       1.39%&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select&lt;br /&gt;'session_cached_cursors'  parameter,&lt;br /&gt;lpad(value, 5)  value,&lt;br /&gt;decode(value, 0, '  n/a', to_char(100 * used / value, '990') || '%')  usage&lt;br /&gt;from&lt;br /&gt;( select&lt;br /&gt;    max(s.value)  used&lt;br /&gt;  from&lt;br /&gt;    sys.v_$statname  n,&lt;br /&gt;    sys.v_$sesstat  s&lt;br /&gt;  where&lt;br /&gt;    n.name = 'session cursor cache count' and&lt;br /&gt;    s.statistic# = n.statistic#&lt;br /&gt;),&lt;br /&gt;( select&lt;br /&gt;    value&lt;br /&gt;  from&lt;br /&gt;    sys.v_$parameter&lt;br /&gt;  where&lt;br /&gt;    name = 'session_cached_cursors'&lt;br /&gt;)&lt;br /&gt;union all&lt;br /&gt;select&lt;br /&gt;'open_cursors',&lt;br /&gt;lpad(value, 5),&lt;br /&gt;to_char(100 * used / value,  '990') || '%'&lt;br /&gt;from&lt;br /&gt;( select&lt;br /&gt;    max(sum(s.value))  used&lt;br /&gt;  from&lt;br /&gt;    sys.v_$statname  n,&lt;br /&gt;    sys.v_$sesstat  s&lt;br /&gt;  where&lt;br /&gt;    n.name in ('opened cursors current', 'session cursor cache count') and&lt;br /&gt;    s.statistic# = n.statistic#&lt;br /&gt;  group by&lt;br /&gt;    s.sid&lt;br /&gt;),&lt;br /&gt;( select&lt;br /&gt;    value&lt;br /&gt;  from&lt;br /&gt;    sys.v_$parameter&lt;br /&gt;  where&lt;br /&gt;    name = 'open_cursors'&lt;br /&gt;) ;&lt;br /&gt;&lt;br /&gt;PARAMETER              VALUE           USAGE&lt;br /&gt;---------------------- --------------- -----&lt;br /&gt;session_cached_cursors   100            100%&lt;br /&gt;open_cursors             300             57%&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Si el valor del SESSION_CACHED_CURSORS se encuentra en el 100%, deberíamos incrementar el valor del parámetro con normalidad.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-5913860253729320360?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/5913860253729320360/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=5913860253729320360' title='8 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5913860253729320360'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/5913860253729320360'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/hard-parse-soft-parse-softer-soft-parse.html' title='Hard Parse, Soft Parse &amp; “Softer” Soft Parse'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-7072589268153676843</id><published>2007-07-09T23:46:00.000-03:00</published><updated>2007-07-25T09:19:53.945-03:00</updated><title type='text'>How To Be A Programmer</title><content type='html'>Excelente &lt;a href="http://samizdat.mines.edu/howto/HowToBeAProgrammer.html" target="_blank"&gt;GUÍA&lt;/a&gt; de cómo se un programador. Recomiendo leerla.&lt;br /&gt;Me causó gracia la frase... "Debugging is fun, because it begins with a mystery".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-7072589268153676843?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/7072589268153676843/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=7072589268153676843' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7072589268153676843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/7072589268153676843'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/how-to-be-programmer.html' title='How To Be A Programmer'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-6866319120147402489</id><published>2007-07-09T23:17:00.000-03:00</published><updated>2007-07-09T23:25:31.681-03:00</updated><title type='text'>Oracle Database 11g</title><content type='html'>&lt;div style="text-align: right;"&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.oracle.com/webapps/events/EventsDetail.jsp?p_eventId=66665"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 200px;" src="http://www.oracle.com/global/de/events/images/oracle_db11g_clr.gif" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;Oracle anunció el lanzamiento de Oracle Database 11g el día 11 de Julio de 2007 en la ciudad de Nueva York.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-6866319120147402489?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/6866319120147402489/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=6866319120147402489' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6866319120147402489'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/6866319120147402489'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/oracle-database-11g.html' title='Oracle Database 11g'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4063906708258638821.post-4999182472743303187</id><published>2007-07-09T19:59:00.000-03:00</published><updated>2007-07-09T20:06:32.519-03:00</updated><title type='text'>Nieva en Buenos Aires! histórico!!!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.clarin.com/diario/2007/07/09/um/fotos/snow2.jpg"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 320px;" src="http://www.clarin.com/diario/2007/07/09/um/fotos/snow2.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Qué mejor manera de celebrar el día de la Independencia que con una nevada???&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;La última vez que ocurrió fue en junio de 1918. El fenómeno se registra a raíz de la irrupción de aire polar en los niveles medios de la atmósfera y la temperatura promedio. La térmica llegó 1,2 grados bajo grado. "Continuará hasta la madrugada de mañana y puede intensificarse durante la noche", dijo el Servicio Meteorológico.&lt;br /&gt;&lt;br /&gt;fuente: Clarin.com&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4063906708258638821-4999182472743303187?l=lhorikian.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://lhorikian.blogspot.com/feeds/4999182472743303187/comments/default' title='Comentarios de la entrada'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4063906708258638821&amp;postID=4999182472743303187' title='0 Comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4999182472743303187'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4063906708258638821/posts/default/4999182472743303187'/><link rel='alternate' type='text/html' href='http://lhorikian.blogspot.com/2007/07/nieva-en-buenos-aires-histrico.html' title='Nieva en Buenos Aires! histórico!!!'/><author><name>Leonardo Horikian</name><uri>http://www.blogger.com/profile/15192319884550377591</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='30' height='32' src='http://3.bp.blogspot.com/-mEa9Mesppus/TyiTPZtbtZI/AAAAAAAAADM/qtLqT5SUDR4/s220/leo4.png'/></author><thr:total>0</thr:total></entry></feed>
