Guía de ROW_NUMBER en MySQL: básico, ejemplos y alternativas

目次

1. Introducción

En la versión 8.0 de MySQL se han añadido muchas funciones nuevas, y una de las más destacadas es el soporte de funciones de ventana. En este artículo nos enfocaremos en la función ROW_NUMBER() que es de uso frecuente. La función ROW_NUMBER() ofrece capacidades poderosas para el análisis de datos y la generación de informes, permitiendo ordenar y clasificar datos según condiciones específicas de manera sencilla. En este artículo explicaremos en detalle desde el uso básico de esta función hasta ejemplos avanzados y métodos alternativos en versiones anteriores.

Audiencia objetivo

  • Principiantes a intermedios con conocimientos básicos de SQL
  • Ingenieros y analistas de datos que utilizan MySQL para procesamiento y análisis de datos
  • Personas que están considerando migrar a la última versión de MySQL

Ventajas de la función ROW_NUMBER()

Esta función puede asignar un número único a los datos según condiciones específicas. Así, tareas como “crear un ranking por ventas más altas” o “extraer y organizar datos duplicados” pueden describirse de forma concisa. Además, en versiones anteriores era necesario usar consultas complejas con variables de usuario, pero al utilizar la función ROW_NUMBER() se mejora la simplicidad y legibilidad del código. En este artículo, presentaremos ejemplos concretos de consultas y ofreceremos una explicación fácil de entender incluso para principiantes. En la siguiente sección, examinaremos en detalle la sintaxis básica y el funcionamiento de esta función.

2. ¿Qué es la función ROW_NUMBER?

El función ROW_NUMBER() añadida recientemente en MySQL 8.0 es un tipo de función ventana que asigna números secuenciales a cada fila de datos. Posee la capacidad de numerar según un orden o grupo específico, lo que resulta muy útil para el análisis de datos y la generación de informes. En esta sección se explica en detalle su sintaxis básica y ejemplos concretos.

Sintaxis básica de la función ROW_NUMBER

Primero, la sintaxis básica de la función ROW_NUMBER() es la siguiente.
SELECT
    columna,
    ROW_NUMBER() OVER (PARTITION BY columna_grupo ORDER BY columna_orden) AS numero_fila
FROM
    nombre_tabla;

Significado de cada elemento

  • ROW_NUMBER(): Función que asigna un número secuencial a cada fila.
  • OVER: Palabra clave que define la función ventana.
  • PARTITION BY: Agrupa los datos por la columna especificada. Es opcional; si no se indica, se asigna una secuencia a todas las filas.
  • ORDER BY: Especifica el orden en que se asignan los números, permitiendo definir el criterio de clasificación.

Ejemplo básico de uso

Por ejemplo, supongamos que existe una tabla llamada «sales» con los siguientes datos.
employeedepartmentsale
AVentas500
BVentas800
CDesarrollo600
DDesarrollo700
Para asignar números secuenciales por cada departamento según el orden descendente de ventas, se utiliza la siguiente consulta.
SELECT
    employee,
    department,
    sale,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS row_num
FROM
    sales;

Resultado de la ejecución

employeedepartmentsalerow_num
BVentas8001
AVentas5002
DDesarrollo7001
CDesarrollo6002
De este resultado se observa que se muestra la clasificación de ventas por cada departamento.

Cómo usar PARTITION BY

En el ejemplo anterior, los datos se agrupan por la columna «department», lo que permite asignar números secuenciales diferentes para cada departamento. Si se omite PARTITION BY, se asigna un número secuencial único a todas las filas.
SELECT
    employee,
    sale,
    ROW_NUMBER() OVER (ORDER BY sale DESC) AS row_num
FROM
    sales;

Resultado de la ejecución

employeesalerow_num
B8001
D7002
C6003
A5004

Características y consideraciones de la función ROW_NUMBER()

  • Numeración única: Incluso con valores idénticos, los números secuenciales son únicos.
  • Manejo de valores NULL: Si ORDER BY incluye NULL, se colocan al inicio en orden ascendente y al final en orden descendente.
  • Impacto en el rendimiento: En conjuntos de datos grandes, la carga de procesamiento de ORDER BY puede ser alta, por lo que es importante configurar índices adecuados.

3. Ejemplos prácticos de uso

Aquí, presentamos escenarios concretos que utilizan la función ROW_NUMBER() de MySQL. Esta función se puede aplicar en muchos casos útiles en el trabajo, como la clasificación de datos y el procesamiento de datos duplicados.

3-1. Clasificación por grupos

Por ejemplo, consideremos el caso de querer asignar un ranking por orden de mayor venta dentro de cada departamento a partir de datos de ventas. A continuación se muestra un ejemplo de datos.
employeedepartmentsale
AVentas500
BVentas800
CDesarrollo600
DDesarrollo700
Ejemplo de consulta: Ranking de ventas por departamento
SELECT
    employee,
    department,
    sale,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
FROM
    sales;
Resultado:
employeedepartmentsalerank
BVentas8001
AVentas5002
DDesarrollo7001
CDesarrollo6002
De esta manera, se asigna una numeración secuencial por orden de ventas en cada departamento, lo que permite una clasificación sencilla.

3-2. Extracción de los N mejores registros

A continuación, veamos el caso de querer extraer los empleados con las tres mayores ventas en cada departamento. Ejemplo de consulta: Consulta para extraer los N mejores registros
WITH RankedSales AS (
    SELECT
        employee,
        department,
        sale,
        ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
    FROM
        sales
)
SELECT
    employee,
    department,
    sale
FROM
    RankedSales
WHERE
    rank <= 3;
Resultado:
employeedepartmentsale
BVentas800
AVentas500
DDesarrollo700
CDesarrollo600
En este ejemplo, solo se obtienen los datos de las tres mayores ventas por departamento. Así, la función ROW_NUMBER() es útil no solo para ranking, sino también para filtrar los datos superiores.

3-3. Extracción y eliminación de datos duplicados

En bases de datos pueden existir datos duplicados. En tales casos, también se puede manejar fácilmente usando la función ROW_NUMBER(). Ejemplo de consulta: Extracción de datos duplicados
SELECT *
FROM (
    SELECT
        employee,
        sale,
        ROW_NUMBER() OVER (PARTITION BY employee ORDER BY sale DESC) AS rank
    FROM
        sales
) tmp
WHERE rank > 1;
Esta consulta detecta duplicados cuando existen varios registros con el mismo nombre de empleado. Ejemplo de consulta: Eliminación de datos duplicados
DELETE FROM sales
WHERE id IN (
    SELECT id
    FROM (
        SELECT
            id,
            ROW_NUMBER() OVER (PARTITION BY employee ORDER BY sale DESC) AS rank
        FROM
            sales
    ) tmp
    WHERE rank > 1
);

Resumen

función ROW_NUMBER() es,
  1. Clasificación por grupos
  2. Extracción de los N mejores registros
  3. Detección y eliminación de datos duplicados
Se utiliza en diversos escenarios como los anteriores. Esto permite realizar procesos y análisis de datos complejos de manera sencilla y eficiente.

4. Comparación con otras funciones de ventana

MySQL 8.0, además de ROW_NUMBER(), se proporcionan funciones de ventana como RANK() y DENSE_RANK() que pueden usarse para clasificar y calcular posiciones. Estas funciones tienen roles similares, pero difieren en su comportamiento y resultados. Aquí comparamos cada función y explicamos los escenarios de uso apropiados.

4-1. Función RANK()

Función RANK() es una función de clasificación que asigna el mismo rango a valores idénticos y omite el siguiente rango.

Sintaxis básica

SELECT
    column_name,
    RANK() OVER (PARTITION BY group_column ORDER BY order_column) AS rank
FROM
    table_name;

Ejemplo de uso

Usaremos los siguientes datos para obtener el ranking de ventas.
employeedepartmentsale
AVentas800
BVentas800
CVentas600
DVentas500
Ejemplo de consulta: Uso de RANK()
SELECT
    employee,
    sale,
    RANK() OVER (ORDER BY sale DESC) AS rank
FROM
    sales;
Resultado:
employeesalerank
A8001
B8001
C6003
D5004
Características:
  • Los empleados A y B, que tienen el mismo monto de ventas (800), se consideran en el «1.º» lugar.
  • El siguiente rango, «2.º», se omite, y C ocupa el «3.º» lugar.

4-2. Función DENSE_RANK()

Función DENSE_RANK() es similar a RANK() en que asigna el mismo rango a valores idénticos, pero no omite el siguiente rango.

Sintaxis básica

SELECT
    column_name,
    DENSE_RANK() OVER (PARTITION BY group_column ORDER BY order_column) AS rank
FROM
    table_name;

Ejemplo de uso

Usaremos los mismos datos de antes para probar la función DENSE_RANK(). Ejemplo de consulta: Uso de DENSE_RANK()
SELECT
    employee,
    sale,
    DENSE_RANK() OVER (ORDER BY sale DESC) AS dense_rank
FROM
    sales;
Resultado:
employeesaledense_rank
A8001
B8001
C6002
D5003
Características:
  • Los empleados A y B, con el mismo monto de ventas (800), se consideran en el «1.º» lugar.
  • A diferencia de RANK(), el siguiente rango comienza en «2.º», manteniendo la continuidad de los rangos.

4-3. Diferencias con la función ROW_NUMBER()

Función ROW_NUMBER() difiere de las otras dos funciones en que asigna un número único incluso cuando los valores son iguales.

Ejemplo de uso

SELECT
    employee,
    sale,
    ROW_NUMBER() OVER (ORDER BY sale DESC) AS row_num
FROM
    sales;
Resultado:
employeesalerow_num
A8001
B8002
C6003
D5004
Características:
  • Incluso si los valores son iguales, se asigna un número único, por lo que no hay duplicación de rangos.
  • Es adecuado cuando se necesita controlar estrictamente el orden de los datos o cuando se requiere unicidad por fila.

4-4. Resumen de usos de cada función

FunciónComportamiento de clasificaciónEjemplo de uso
ROW_NUMBER()Asigna un número únicoCuando se necesita numerar los datos secuencialmente o se requiere identificación única
RANK()Asigna el mismo número a empates y omite el siguiente númeroCuando se desea mostrar el ranking tal cual existen valores iguales
DENSE_RANK()Asigna el mismo número a empates sin omitir númerosCuando se prioriza la continuidad de los rangos

Resumen

ROW_NUMBER(), RANK(), DENSE_RANK() deben usarse según diferentes escenarios.
  1. ROW_NUMBER() es adecuado cuando se necesita un número único.
  2. RANK() es útil cuando se desea asignar el mismo rango a datos con valores iguales y resaltar los huecos en el ranking.
  3. DENSE_RANK() es apropiado para un ranking continuo, sin huecos entre rangos.

5. Métodos alternativos para versiones anteriores a MySQL 8.0

En versiones anteriores a MySQL 8.0, la función ROW_NUMBER() y otras funciones de ventana no son compatibles. Sin embargo, al utilizar variables de usuario, se pueden lograr funcionalidades similares. Aquí se explica concretamente los métodos alternativos para versiones anteriores a MySQL 8.0.

5-1. Numeración secuencial usando variables de usuario

En entornos con MySQL 5.7 o anteriores, es posible asignar números secuenciales a cada fila usando variables de usuario. Veamos el siguiente ejemplo.

Ejemplo de uso: ranking de ventas por departamento

Ejemplo de datos:
employeedepartmentsale
AVentas500
BVentas800
CDesarrollo600
DDesarrollo700
Consulta:
SET @row_num = 0;
SET @dept = '';

SELECT
    employee,
    department,
    sale,
    @row_num := IF(@dept = department, @row_num + 1, 1) AS rank,
    @dept := department
FROM
    (SELECT * FROM sales ORDER BY department, sale DESC) AS sorted_sales;
Resultado de la ejecución:
employeedepartmentsalerank
BVentas8001
AVentas5002
DDesarrollo7001
CDesarrollo6002

5-2. Extracción de los N registros superiores

Para obtener los N registros superiores, se utilizan variables de usuario de manera similar. Consulta:
SET @row_num = 0;
SET @dept = '';

SELECT *
FROM (
    SELECT
        employee,
        department,
        sale,
        @row_num := IF(@dept = department, @row_num + 1, 1) AS rank,
        @dept := department
    FROM
        (SELECT * FROM sales ORDER BY department, sale DESC) AS sorted_sales
) AS ranked_sales
WHERE rank <= 3;
Resultado:
employeedepartmentsalerank
BVentas8001
AVentas5002
DDesarrollo7001
CDesarrollo6002
Esta consulta asigna un ranking por departamento y luego extrae solo los datos dentro de los 3 primeros registros.

5-3. Detección y eliminación de datos duplicados

El manejo de datos duplicados también se puede abordar usando variables de usuario. Ejemplo de consulta: detección de datos duplicados
SET @row_num = 0;
SET @id_check = '';

SELECT *
FROM (
    SELECT
        id,
        name,
        @row_num := IF(@id_check = name, @row_num + 1, 1) AS rank,
        @id_check := name
    FROM
        (SELECT * FROM customers ORDER BY name, id) AS sorted_customers
) AS tmp
WHERE rank > 1;
Ejemplo de consulta: eliminación de datos duplicados
DELETE FROM customers
WHERE id IN (
    SELECT id
    FROM (
        SELECT
            id,
            @row_num := IF(@id_check = name, @row_num + 1, 1) AS rank,
            @id_check := name
        FROM
            (SELECT * FROM customers ORDER BY name, id) AS sorted_customers
    ) AS tmp
    WHERE rank > 1
);

5-4. Consideraciones sobre variables de usuario

  1. Dependencia de sesión
  • Las variables de usuario solo son válidas dentro de la sesión. No pueden usarse en otras consultas o sesiones.
  1. Dependencia del orden de procesamiento
  • Las variables de usuario dependen del orden de ejecución de la consulta, por lo que la configuración de la cláusula ORDER BY es importante.
  1. Legibilidad y mantenibilidad del SQL
  • El código tiende a volverse complejo, por lo que se recomienda usar funciones de ventana en MySQL 8.0 o superior.

Resumen

En versiones anteriores a MySQL 8.0, al no poder usar funciones de ventana, se pueden emplear variables de usuario para lograr numeración secuencial y procesamiento de rankings. Sin embargo, dado que las consultas pueden volverse complejas, se recomienda considerar la migración a una versión más reciente cuando sea posible.

6. Consideraciones y mejores prácticas

La función ROW_NUMBER() de MySQL y los métodos alternativos que utilizan variables de usuario son muy útiles, pero hay puntos que deben considerarse para operarlos de manera precisa y eficiente. Aquí se explican en detalle los aspectos a considerar y las mejores prácticas para la optimización del rendimiento.

6-1. Consideraciones de rendimiento

1. Carga del ORDER BY

función ROW() debe usarse siempre en combinación con ORDER BY. Este proceso implica reordenar los datos, por lo que en conjuntos de datos grandes el tiempo de procesamiento aumenta. Medidas:
  • Uso de índices: Establezca índices en las columnas usadas en ORDER BY para acelerar el proceso de ordenación.
  • Uso de LIMIT: Obtenga solo la cantidad de datos necesaria y reduzca el volumen de datos a procesar.
Ejemplo:
SELECT
    employee,
    sale,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
FROM
    sales
LIMIT 1000;

2. Aumento del uso de memoria y de I/O de disco

Las funciones de ventana utilizan tablas temporales y memoria para procesarse, por lo que al aumentar la cantidad de datos también aumentan el consumo de memoria y el I/O de disco. Medidas:
  • División de consultas: Divida el procesamiento en consultas más pequeñas y extraiga los datos de forma incremental para reducir la carga.
  • Uso de tablas temporales: Almacene los datos extraídos en una tabla temporal y realice la agregación desde allí para distribuir la carga.

6-2. Puntos clave para el afinado de consultas

1. Verificación del plan de ejecución

En MySQL se puede usar la sentencia EXPLAIN para verificar el plan de ejecución de una consulta. Esto permite comprobar si los índices se están utilizando correctamente. Ejemplo:
EXPLAIN
SELECT
    employee,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
FROM
    sales;
Ejemplo de salida:
idselect_typetabletypepossible_keyskeykey_lenrefrowsExtra
1SIMPLEsalesindexNULLsale4NULL500Using index
De esta manera, si se muestra Using index, indica que el índice se está utilizando adecuadamente.

2. Optimización de índices

Asegúrese de crear índices en las columnas usadas en cláusulas ORDER BY y WHERE. Preste especial atención a los siguientes puntos.
  • Índice de columna única: Aplicable cuando la condición de ordenación es simple
  • Índice compuesto: Útil cuando la condición incluye varias columnas
Ejemplo:
CREATE INDEX idx_department_sale ON sales(department, sale DESC);

3. Uso de procesamiento por lotes

En lugar de procesar una gran cantidad de datos de una sola vez, use procesamiento por lotes para dividir los datos y distribuir la carga. Ejemplo:
SELECT * FROM sales WHERE department = '営業部' LIMIT 1000 OFFSET 0;
SELECT * FROM sales WHERE department = '営業部' LIMIT 1000 OFFSET 1000;

6-3. Mantenimiento de la integridad de los datos

1. Actualización y recálculo de datos

La adición o eliminación de datos puede desalinear la numeración. Por lo tanto, se debe establecer un mecanismo para recalcular los datos numerados cuando sea necesario. Ejemplo:
CREATE VIEW ranked_sales AS
SELECT
    employee,
    sale,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
FROM
    sales;
Al utilizar una vista, se puede mantener el ranking basado en los datos más recientes de forma constante.

6-4. Mejores prácticas de ejemplos de consultas

A continuación se muestra un ejemplo de mejores prácticas considerando rendimiento y mantenibilidad. Ejemplo: extracción de los N primeros registros
WITH RankedSales AS (
    SELECT
        employee,
        department,
        sale,
        ROW_NUMBER() OVER (PARTITION BY department ORDER BY sale DESC) AS rank
    FROM
        sales
)
SELECT *
FROM RankedSales
WHERE rank <= 3;
Esta estructura utiliza expresiones de tabla comunes (CTE) para mejorar la legibilidad y reutilización del código.

Resumen

Al utilizar la función ROW_NUMBER() o sus métodos alternativos, es importante tener en cuenta los siguientes puntos.
  1. Optimización de índices para mejorar la velocidad de procesamiento.
  2. Verificación del plan de ejecución para identificar cuellos de botella de rendimiento.
  3. Adaptación a actualizaciones de datos considerando la implementación de mecanismos para mantener la integridad.
  4. Procesamiento por lotes y CTE para distribuir la carga.
Al aplicar estas mejores prácticas, el análisis de datos a gran escala y la generación de informes pueden realizarse de manera eficiente.

7. Resumen

En los artículos anteriores, nos hemos centrado en la función ROW_NUMBER(), explicando en detalle desde su uso básico hasta ejemplos avanzados, métodos alternativos para versiones anteriores, y consideraciones y mejores prácticas. En esta sección, repasaremos los puntos clave de todo el artículo y resumiremos los puntos prácticos de aplicación.

7-1. Ventajas de la función ROW_NUMBER()

función ROW_NUMBER() es muy útil en el análisis de datos y la creación de informes por los siguientes motivos.
  1. Asignación de números secuenciales por grupo: Permite realizar fácilmente rankings de ventas por departamento y clasificación por categoría.
  2. Extracción de los N primeros registros: Permite extraer y filtrar datos basados en condiciones específicas de manera eficiente.
  3. Detección y eliminación de datos duplicados: Es útil para la organización y limpieza de datos.
Estas funciones permiten escribir consultas complejas de forma sencilla, mejorando significativamente la legibilidad y mantenibilidad del SQL.

7-2. Comparación con otras funciones de ventana

En comparación con funciones de ventana como RANK() o DENSE_RANK(), ROW_NUMBER() se diferencia al asignar un número único a cada valor idéntico.
FunciónCaracterísticasUso
ROW_NUMBER()Asigna un número secuencial único a cada filaIdeal cuando se necesita identificar datos o clasificar sin permitir duplicados
RANK()Asigna el mismo número a filas con el mismo rango y salta el siguiente rangoÚtil cuando se considera la duplicación en el ranking y los huecos de posición son importantes
DENSE_RANK()Asigna el mismo número a filas con el mismo rango sin saltar rangosRanking que mantiene la continuidad de los rangos mientras considera duplicados
Selección adecuada de la función: Elegir la función óptima según el caso de uso permite un procesamiento de datos eficiente.

7-3. Estrategias para versiones anteriores

En entornos con MySQL inferior a 8.0, también se mostró cómo utilizar variables de usuario para lograr funcionalidades similares. Sin embargo, es necesario considerar los siguientes puntos al emplear estos métodos.
  • Reducción de la legibilidad debido a la mayor complejidad del código
  • Puede ser difícil optimizar el procesamiento de consultas
  • Se requieren procesos adicionales para mantener la integridad de los datos
Si es posible, se recomienda encarecidamente migrar a MySQL 8.0 o superior y utilizar funciones de ventana.

7-4. Puntos clave para la optimización del rendimiento

  1. Uso de índices: Configurar índices en las columnas utilizadas en ORDER BY para mejorar la velocidad de procesamiento.
  2. Revisión del plan de ejecución: Utilizar la sentencia EXPLAIN para validar el rendimiento anticipadamente.
  3. Adopción de procesamiento por lotes: Dividir el procesamiento de conjuntos de datos grandes en partes más pequeñas para distribuir la carga.
  4. Uso de vistas y CTE: Aumentar la reutilización del código y simplificar la organización de consultas complejas.
Al aplicar estas técnicas de optimización, es posible lograr un procesamiento de datos eficiente y estable.

7-5. Conclusión

función ROW_NUMBER() es una herramienta poderosa que mejora significativamente la eficiencia del análisis de datos. En este artículo, se explicó detalladamente desde la sintaxis básica hasta ejemplos avanzados, consideraciones y métodos alternativos. Invitamos a los lectores a probar el contenido del artículo ejecutando consultas en la práctica. Al mejorar sus habilidades en SQL, podrán abordar con confianza análisis de datos y generación de informes más complejos.

Apéndice: Recursos de referencia