Etiqueta: sql-server

  • Duplicados y calendario recursivo: dos técnicas SQL útiles

    Dos problemas frecuentes en el día a día con SQL: detectar registros que se repiten cuando no deberían, y generar un calendario completo (incluyendo los días sin actividad) para auditar si un proceso programado corrió todos los días que debía. Ambos se resuelven con técnicas de SQL avanzado que vale la pena tener siempre a mano.

    Contexto del problema

    Partiendo de una tabla que registra movimientos (por ejemplo, renovaciones de un registro que debería ocurrir una sola vez por año), se detectó que algunos registros tenían más de un movimiento del mismo tipo en el mismo año — algo que, por reglas de negocio, no debería pasar.

    1. Ver el detalle completo de los registros duplicados

    SELECT 
        r.codigo_registro,
        r.tipo_movimiento,
        r.fecha_carga,
        r.cliente,
        r.producto,
        r.monto,
        r.vigencia_inicio,
        r.vigencia_fin
    FROM movimientos r
    WHERE r.tipo_movimiento = 'Renovacion'
      AND r.fecha_carga >= '2025-01-01'
      AND r.codigo_registro IN (
            SELECT codigo_registro
            FROM movimientos
            WHERE tipo_movimiento = 'Renovacion'
              AND fecha_carga >= '2025-01-01'
            GROUP BY codigo_registro
            HAVING COUNT(*) > 1
      )
    ORDER BY r.codigo_registro, r.fecha_carga;

    La lógica en dos pasos: la subconsulta (dentro del IN) identifica primero qué códigos de registro tienen más de un movimiento del tipo buscado (usando GROUP BY + HAVING COUNT(*) > 1), y la consulta externa trae el detalle completo de esos registros específicos, ordenados para poder comparar visualmente las fechas de cada duplicado.

    2. Agrupar por año para dimensionar el problema

    SELECT 
        r.codigo_registro,
        YEAR(r.fecha_carga) AS Anio,
        COUNT(*) AS Cantidad
    FROM movimientos r
    WHERE r.tipo_movimiento = 'Renovacion'
      AND r.fecha_carga >= '2025-01-01'
    GROUP BY r.codigo_registro, YEAR(r.fecha_carga)
    HAVING COUNT(*) > 1;

    Una vista rápida y resumida, útil antes de bucear al detalle: cuántos registros están duplicados, y en qué años específicamente ocurre — sin traer todavía toda la información de cada fila.

    3. Detectar duplicados con funciones de ventana

    SELECT *
    FROM (
        SELECT 
            r.*,
            ROW_NUMBER() OVER (PARTITION BY r.codigo_registro, YEAR(r.fecha_carga) ORDER BY r.fecha_carga) AS rn
        FROM movimientos r
        WHERE r.tipo_movimiento = 'Renovacion'
          AND r.fecha_carga >= '2025-01-01'
    ) t
    WHERE rn > 1;

    Esta es una forma alternativa (y en muchos casos más flexible) de lograr lo mismo que el primer query, pero sin subconsulta. ROW_NUMBER() numera cada fila dentro de su partición (codigo_registro + año), en orden de fecha. La primera aparición de cada combinación recibe rn = 1; cualquier repetición adicional recibe 2, 3, etc. Filtrando por rn > 1, el resultado son exactamente los duplicados sobrantes — es decir, todo lo que vendría «de más» después del primer registro legítimo. Esta forma es la base para, además de detectar, poder eliminar duplicados fácilmente (borrando directamente las filas con rn > 1).

    4. Generar un calendario completo con CTE recursivo

    Un caso distinto pero relacionado: verificar si un proceso automatizado corrió todos los días que debía, entre una fecha de inicio y hoy — incluyendo explícitamente los días donde no hay registro de ejecución (que es justo lo que se necesita detectar).

    DECLARE @FechaInicio DATE = '2025-01-01';
    
    ;WITH Calendar AS (
        SELECT CAST(@FechaInicio AS DATE) AS Fecha
        UNION ALL
        SELECT DATEADD(DAY, 1, Fecha)
        FROM Calendar
        WHERE Fecha < CAST(GETDATE() AS DATE)
    )
    SELECT 
        c.Fecha,
        l.Fecha_ejecucion,
        CASE 
            WHEN l.Fecha_ejecucion IS NULL THEN 'FALTA'
            ELSE 'OK'
        END AS Estado
    FROM Calendar c
    LEFT JOIN (
        SELECT CAST(Fecha_ejecucion AS DATE) AS Fecha_ejecucion
        FROM Proceso_Log
        WHERE proceso = 'Carga_Diaria'
    ) l
        ON l.Fecha_ejecucion = c.Fecha
    OPTION (MAXRECURSION 366);

    Cómo funciona el CTE recursivo

    Un CTE recursivo (Common Table Expression) se define en dos partes unidas por UNION ALL: el ancla (el primer SELECT, que da el punto de partida — aquí, la fecha de inicio) y la parte recursiva (el segundo SELECT, que hace referencia a sí misma, sumando un día cada vez). SQL Server repite la parte recursiva una y otra vez, generando una fila nueva por cada iteración, hasta que la condición del WHERE deja de cumplirse (cuando Fecha alcanza el día de hoy). El resultado es una tabla virtual con una fila por cada día del rango, sin necesitar ninguna tabla física de calendario pre-creada.

    OPTION (MAXRECURSION 366) es obligatorio para rangos largos: por defecto, SQL Server limita cualquier CTE recursivo a 100 iteraciones como medida de seguridad (para evitar loops infinitos accidentales) — 366 cubre incluso un año bisiesto completo. Si el rango a cubrir fuera mayor a un año, este número debe ajustarse en consecuencia.

    El LEFT JOIN contra el log real de ejecuciones es lo que revela los huecos: como es un LEFT JOIN (no un INNER JOIN), todas las fechas del calendario aparecen en el resultado sin importar si hay o no un registro de ejecución correspondiente — cuando no lo hay, el campo Fecha_ejecucion queda en NULL, y el CASE lo marca explícitamente como 'FALTA'.

    Nota sobre deadlocks al consultar tablas con carga masiva activa

    Al ejecutar consultas de solo lectura (SELECT) contra tablas que en ese momento están recibiendo una carga masiva de datos (un INSERT o UPDATE grande), es posible toparse con un error de deadlock, donde SQL Server elige una de las dos transacciones en conflicto como «víctima» y la cancela para poder resolver el bloqueo mutuo.

    Es importante entender que, si el SELECT es elegido como víctima, no significa que haya dañado ni afectado el proceso de carga masiva — SQL Server simplemente decide cuál de las dos operaciones en conflicto cancelar, generalmente priorizando la transacción más costosa de revertir. Lo más seguro en ese caso es simplemente esperar a que el proceso masivo termine, y volver a intentar la consulta después, en vez de forzar la ejecución repetidamente mientras la carga sigue en curso.

  • MySQL portable en Windows: instalación y conexión con DBeaver

    Versión probada: MySQL 8.0.46 · DBeaver Community — requisito: no se necesitan permisos de administrador.

    Esta guía cubre una instalación completamente portable de MySQL 8 en Windows, útil en equipos corporativos donde no hay acceso de administrador, o simplemente para tener una instancia aislada de pruebas sin tocar el sistema y sin molestar a los departamentos de soporte o a infraestructura.

    1. Descargar MySQL 8

    1. Ir a dev.mysql.com/downloads/mysql
    2. Seleccionar Windows (x86, 64-bit), ZIP Archive
    3. Descargar y descomprimir en una carpeta de tu usuario, por ejemplo:
    C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\

    2. Crear el archivo de configuración my.ini

    Crear el archivo my.ini en la raíz de la carpeta descomprimida (un nivel arriba de bin\):

    C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64\my.ini

    Contenido del archivo (ajustar las rutas a tu usuario):

    [mysqld]
    basedir=C:/Users/TU_USUARIO/Downloads/mysql-8.0.46-winx64/mysql-8.0.46-winx64
    datadir=C:/Users/TU_USUARIO/Downloads/mysql-8.0.46-winx64/mysql-8.0.46-winx64/data
    port=3306

    [client]

    port=3306

    Importante: usar siempre / (forward slash) en las rutas, aunque sea Windows.

    3. Inicializar la base de datos

    Abrir PowerShell (no se requiere administrador) y navegar a la carpeta raíz:

    cd C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64

    Ejecutar la inicialización:

    .\bin\mysqld.exe --initialize --console

    Buscar en la salida la línea con la contraseña temporal y copiarla:

    [Note] ... A temporary password is generated for root@localhost: XXXXXXXX

    Si aparece el error data directory has files in it, limpiar primero la carpeta data:

    Remove-Item -Recurse -Force .\data\*

    Luego repetir el comando de inicialización.

    4. Arrancar el servidor

    Desde la misma ventana PowerShell:

    .\bin\mysqld.exe --defaults-file="C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64\my.ini" --console

    Dejar esta ventana abierta. Es el proceso del servidor MySQL. Para detenerlo usar Ctrl+C o el comando de apagado del paso siguiente.

    5. Conectar y cambiar la contraseña

    Abrir otra ventana de PowerShell y ejecutar:

    cd C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64\bin
    .\mysql.exe -u root -p

    Pegar la contraseña temporal cuando la solicite. Luego cambiarla obligatoriamente:

    ALTER USER 'root'@'localhost' IDENTIFIED BY 'TuNuevaPassword123!';

    6. Apagar el servidor limpiamente

    Cuando necesites detener MySQL, desde PowerShell:

    .\mysqladmin.exe -u root -p shutdown

    7. Script de arranque (opcional)

    Crear un archivo start_mysql.bat en la carpeta raíz para arrancar fácilmente:

    @echo off
    cd /d C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64
    .\bin\mysqld.exe --defaults-file="C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64\my.ini" --console

    Doble clic en el .bat para arrancar el servidor.

    8. Instalar DBeaver Community

    1. Descargar desde dbeaver.io/download la versión Windows installer
    2. Instalar normalmente (no requiere permisos de administrador si se instala en la carpeta de perfil del usuario)

    9. Conectar DBeaver a MySQL

    1. Abrir DBeaver
    2. Database → New Database Connection → MySQL
    3. Completar los datos:
    CampoValor
    Server Hostlocalhost
    Port3306
    Usernameroot
    PasswordLa que configuraste en el paso 5

    En la primera conexión, DBeaver pedirá descargar el driver de MySQL — clic en Download.

    10. Solucionar error: Public Key Retrieval is not allowed

    Este error es común con MySQL 8. Solucionarlo en la pestaña Driver Properties de la conexión:

    PropertyValue
    allowPublicKeyRetrievaltrue
    useSSLfalse

    Alternativamente, modificar el JDBC URL directamente:

    jdbc:mysql://localhost:3306/?allowPublicKeyRetrieval=true&useSSL=false

    Luego hacer clic en Test Connection para verificar.

    11. Restaurar un dump .sql

    Opción A — Desde CMD (recomendado para archivos grandes)

    Abrir cmd.exe (no PowerShell, ya que PowerShell no soporta redirección con <):

    cd C:\Users\TU_USUARIO\Downloads\mysql-8.0.46-winx64\mysql-8.0.46-winx64\bin
    mysql.exe -u root -p < "C:\ruta\completa\tu_dump.sql"

    Opción B — Desde PowerShell

    Get-Content "C:\ruta\completa\tu_dump.sql" | .\mysql.exe -u root -p

    Opción C — Desde DBeaver

    1. Clic derecho sobre la base de datos → Tools → Execute Script
    2. Seleccionar el archivo .sql
    3. Ejecutar

    Si el dump no incluye CREATE DATABASE, crear la base de datos primero:

    CREATE DATABASE nombre_db;

    Notas adicionales

    • El servidor no se inicia automáticamente al encender el equipo (sin permisos de admin no se puede registrar como servicio de Windows). Hay que arrancarlo manualmente con el .bat del paso 7.
    • Para verificar que el servidor está corriendo antes de conectar, ejecutar desde PowerShell:
    .\bin\mysqladmin.exe -u root -p status
    • IMPORTANTE: En PowerShell, todos los ejecutables deben ir precedidos de .\ (ej. .\mysql.exe, .\mysqld.exe).