lunes, 20 de septiembre de 2010

rownum mayor que...

Cuando trabajamos con consultas en Oracle, disponemos de la pseudo-columna rownum. Se le llama pseudo-columna porque aunque no sea una columna de nuestra tabla, Oracle la añade al resultado de la consulta, poniéndola a nuestra disposición, si la queremos.

El valor obtenido es un número que indica el orden en el que Oracle selecciona la fila de la tabla. Generalmente esta pseudo-columna se utiliza en consultas con una cláusula de ordenación, para obtener los N primeros registros según el criterio de ordenación indicado. Por ejemplo:

select * from (select * from empleados order by salario) where rownum < 10

Es lo que en otros SGBD se consigue con TOP.

Y en este punto es donde uno se ve tentado de ir más allá, y utilizar el rownum para paginar nuestras consultas. Es decir, para obtener, por ejemplo, del décimo al vigésimo empleado con mayor salario.

select * from (select * from empleados order by salario) where rownum <20 and rownum > 10

Sin embargo, nos llevamos la sorpresa de que esta consulta no devuelve datos. Explicar por qué es ligeramente complicado, pero lo intento.

Cuando ejecutas esa consulta Oracle obtiene la ristra de filas, y asigna a la primera el valor rownum=1. Entonces, evalúa la condición para esa fila, y como 1 no está entre 10 y 20, la elimina del resultado. Entonces, vuelve a comenzar el proceso, asignando de nuevo el valor rownum=1, a la que realmente era la segunda fila obtenida. Lógicamente, la condición vuelve a no cumplirse, la segunda fila es eliminada, y el valor rownum=1 es asignado a la tercera fila. Así hasta que nos hemos quedado sin nada.

¿Significa eso que no podemos utilizar una condición de tipo rownum mayor o igual que... en una consulta? Pues no, existe una forma de realizar esta comprobación. Para ello necesitamos que la pseudo-columna evolucione y se convierta en una columna con todas las de la ley. Si realizamos la siguiente consulta

select rownum rnum, empleados.* from (select * from empleados order by salario) empleados

obtenemos la tabla empleados ordenada por salario, con una columna (ya no pseudo-columna) rnum añadida, que asigna un número secuencial a cada una de las filas ordenadas. Por tanto, si sobre esta consulta obtenemos sólo los registros con rnum entre 10 y 20, tenemos lo que queríamos.

select * from (
select rownum rnum, empleados.* from (select * from empleados order by salario) empleados
) where rnum < 20 and rnum > 10

Es importante fijarse en que la consulta original, en la que se obtienen todas las filas de la tabla ordenadas, debe ser siempre la primera en ejecutarse, sin añadir nada. Si por ejemplo intentásemos hacer:

select * from (
select rownum rnum, empleados.* from empleados order by salario empleados
) where rnum < 20 and rnum > 10

Estaríamos obteniendo rnum y ordenando en la misma sentencia. Lo que pasaría entonces es que la ordenación se haría después de asignar los rnum, por lo que estaríamos asignando los secuenciales a las filas sin seguir el criterio de ordenación. La consulta devolvería registros, pero no los que nos interesan.

Para los políglotas, pongo un enlace bibliográfico y todo:

5 comentarios:

  1. Este fin de semana te voy a freír a preguntas de Nvu :p

    ResponderEliminar
  2. en caso de no poder utilizar rownum, y quiero sacar lo 4 empleados con mayor suedo ,como seria?

    ResponderEliminar
    Respuestas
    1. Pues por ejemplo podrías hacer algo así:

      select * from empleados e1 where (select count(*) from empleados e2 where e2.sueldo > e1.sueldo) < 4

      ¡Espero que te sirva!

      Eliminar

Cualquier aportación será bienvenida