Eliminar registros de una tabla al Azar

Tengo una tabla tblNrosAsignados con los campos idvendedor, codigoloteria, nroasignado necesito una función en Access que permita eliminar determinada cantidad de registros de un vendedor. La función debe recibir como parámetros el idvendedor y la cantidad de registros a eliminar.

Anticipo los agradecimientos.

1 Respuesta

Respuesta
2

Martha hay varias formas, pero veo que en tu pregunta falta el parámetro del código de la lotería. Te dejo 2 formas:

1. Cuando la tabla no contiene muchos registros

Function EliminarRegistrosAleatorios(lnVendedor As Long, intCantidad As Integer, lnCodigoLoteria As Long)
  'Eficiente cuando la tabla no es muy grande
    On Error GoTo hay_error
    Dim db As DAO.Database
    Dim rstCount As DAO.Recordset
    Dim sql As String
    Dim N As Long
    Dim idVend As Long
    Dim codLote As Long
    Dim totalRegs As Long
    idVend = lnVendedor
    codLote = lnCodigoLoteria
    N = intCantidad
    Set db = CurrentDb
    ' --- 1?? Ajustar N al número real de registros disponibles ---
    sql = "SELECT COUNT(*) AS TotalRegs FROM tblNrosAsignados " & _
          "WHERE idvendedor = " & idVend & " AND codigoloteria = " & codLote & ";"
    Set rstCount = db.OpenRecordset(sql, dbOpenSnapshot)
    If Not rstCount.EOF Then totalRegs = rstCount!totalRegs
    rstCount.Close
    Set rstCount = Nothing
    If totalRegs = 0 Then
        MsgBox "?? No hay registros para eliminar para el vendedor " & idVend & _
               " y la lotería " & codLote, vbInformation, "Eliminando al azar"
        GoTo salida
    End If
    If totalRegs < N Then N = totalRegs
    ' --- 2?? Eliminar en un solo paso ---
    sql = _
      "DELETE FROM tblNrosAsignados " & _
      "WHERE idvendedor = " & idVend & " AND codigoloteria = " & codLote & " " & _
      "AND (CStr(codigoloteria) & '|' & CStr(nroasignado) & '|' & CStr(idvendedor)) IN (" & _
      "  SELECT TOP " & N & " (CStr(codigoloteria) & '|' & CStr(nroasignado) & '|' & CStr(idvendedor)) AS clave " & _
      "  FROM tblNrosAsignados " & _
      "  WHERE idvendedor = " & idVend & " AND codigoloteria = " & codLote & " " & _
      "  ORDER BY Rnd(-Timer() * Val(CStr(nroasignado)))" & _
      ");"
    db.Execute sql, dbFailOnError
    MsgBox "? Se eliminaron " & N & " registros aleatorios del vendedor " & idVend & _
           " (lotería " & codLote & ")", vbInformation, "Eliminación completada"
salida:
    Set db = Nothing
    Exit Function
hay_error:
    MsgBox "? Error " & Err.Number & ": " & Err.Description, vbCritical, "Error"
    Resume salida
End Function

2. Creando tabla temporal (recomendado) cuando hay muchos registros

Function EliminarRegistrosAleatorios(lnVendedor As Long, intCantidad As Long, lnCodigoLoteria As Long)
 'Eficiente en tablas con bastante información
    On Error GoTo hay_error
    Dim db As DAO.Database
    Dim sql As String
    Dim lngCantidad As Long
    Dim lngRegistrosEliminados As Long
    Dim totalRegs As Long
    Dim tblExists As Boolean
    Const TABLA_PRINCIPAL = "tblNrosAsignados"
    Const TABLA_TEMPORAL = "tblTempRegsDel"
    Set db = CurrentDb
    lngCantidad = intCantidad
    ' --- 1?? Validar registros disponibles ---
    sql = "idvendedor = " & lnVendedor & " AND codigoloteria = " & lnCodigoLoteria
    totalRegs = DCount("*", TABLA_PRINCIPAL, sql)
    If totalRegs = 0 Then
        MsgBox "?? No hay registros para eliminar.", vbInformation
        GoTo salida
    End If
    If totalRegs < lngCantidad Then lngCantidad = totalRegs
    ' --- 2?? Verificar si la tabla temporal existe ---
    tblExists = False
    Dim tdf As DAO.TableDef
    For Each tdf In db.TableDefs
        If tdf.Name = TABLA_TEMPORAL Then
            tblExists = True
            Exit For
        End If
    Next tdf
    On Error Resume Next
    If tblExists Then
        ' Si existe, la vaciamos en lugar de borrarla (más rápido y seguro)
        db.Execute "DELETE FROM " & TABLA_TEMPORAL & ";"
    Else
        ' Si no existe, la creamos
        sql = "CREATE TABLE " & TABLA_TEMPORAL & " (" & _
              "codigoloteria LONG, " & _
              "nroasignado TEXT(50), " & _
              "idvendedor LONG);"
        db.Execute sql
    End If
    On Error GoTo hay_error
    ' --- 3?? Insertar registros aleatorios en la tabla temporal ---
    sql = "INSERT INTO " & TABLA_TEMPORAL & " (codigoloteria, nroasignado, idvendedor) " & _
          "SELECT TOP " & lngCantidad & " codigoloteria, nroasignado, idvendedor " & _
          "FROM " & TABLA_PRINCIPAL & " " & _
          "WHERE idvendedor = " & lnVendedor & " AND codigoloteria = " & lnCodigoLoteria & " " & _
          "ORDER BY Rnd(-Timer() * Val(CStr(nroasignado)));"
    db.Execute sql, dbFailOnError
    ' --- 4?? Eliminar los registros coincidentes ---
    sql = "DELETE " & TABLA_PRINCIPAL & ".* " & _
          "FROM " & TABLA_PRINCIPAL & " INNER JOIN " & TABLA_TEMPORAL & " " & _
          "ON (" & TABLA_PRINCIPAL & ".codigoloteria = " & TABLA_TEMPORAL & ".codigoloteria) " & _
          "AND (" & TABLA_PRINCIPAL & ".nroasignado = " & TABLA_TEMPORAL & ".nroasignado) " & _
          "AND (" & TABLA_PRINCIPAL & ".idvendedor = " & TABLA_TEMPORAL & ".idvendedor);"
    db.Execute sql, dbFailOnError
    lngRegistrosEliminados = db.RecordsAffected
    ' --- 5?? Confirmación ---
    MsgBox "? Se eliminaron " & lngRegistrosEliminados & _
           " registros aleatorios del vendedor " & lnVendedor & _
           " (lotería " & lnCodigoLoteria & ")", vbInformation
salida:
    Set db = Nothing
    Exit Function
hay_error:
    MsgBox "? Error " & Err.Number & ": " & Err.Description, vbCritical, "Error al eliminar"
    Resume salida
End Function

Explicación del Uso de ORDER BY Rnd(-Timer() * Val(CStr(nroasignado)))

Esta expresión se utiliza dentro de la cláusula ORDER BY de la consulta SELECT TOP para ordenar los registros de la tabla de forma aleatoria, permitiendo que el TOP lngCantidad seleccione una muestra al azar de la tabla principal.

1. La Función Rnd()
Generador de Números Aleatorios: La función Rnd() devuelve un número decimal de punto flotante pseudoaleatorio entre 0 y 1.

2. La Semilla (Seed) Negativa: Rnd(-Timer() * Val(CStr(nroasignado)))
El argumento negativo es lo crucial:

Timer(): Devuelve un valor Long que representa el número de segundos transcurridos desde la medianoche. Este valor es único cada vez que se ejecuta la función, lo que garantiza que la semilla sea diferente en cada ejecución.

Val(CStr(nroasignado)):

CStr(nroasignado) convierte el campo nroasignado (que es TEXTO en la tabla temporal, pero se asume que contiene un valor numérico) a una cadena.

Val(...) convierte esa cadena a un valor numérico Double.

Este valor se multiplica por Timer(), haciendo que la semilla sea dependiente de los datos del registro y del momento de la ejecución.

El signo negativo (-): Cuando la función Rnd() se llama con un argumento negativo, establece la semilla para el generador de números aleatorios usando ese número. Esto es vital porque asegura que la secuencia de números aleatorios generada sea diferente para cada fila de la tabla.

Importancia: Al sembrar el generador de números aleatorios (Rnd) con un valor único para cada fila y ejecución (la combinación de nroasignado y Timer), se obliga a Rnd() a calcular un valor aleatorio distinto para cada registro.

3. La Cláusula ORDER BY
El motor de Access asigna el valor aleatorio único de la expresión a cada fila.

ORDER BY toma estos números aleatorios (que son decimales entre 0 y 1, aunque la expresión puede generar números fuera de ese rango al ser la semilla) y los ordena de menor a mayor (o viceversa).

El resultado es una lista de registros en un orden totalmente aleatorio.

4. La Cláusula SELECT TOP lngCantidad
Finalmente, SELECT TOP lngCantidad toma los primeros lngCantidad registros de esta lista ya desordenada.

Conclusión: Se logra seleccionar un conjunto de registros de forma aleatoria y diferente en cada ejecución de la función.

¿Por qué es un Enfoque Robusto?

Este método se considera robusto porque:

Garantiza Aleatoriedad: Al usar Rnd(-Timer()...) se evita que Access use la misma secuencia de números aleatorios en diferentes ejecuciones de la función, lo cual es un problema común si solo se usa Rnd() sin una semilla.

Eficiencia con TOP: Es más eficiente en comparación con otros métodos que podrían requerir un bucle o el uso de conjuntos de registros complejos para seleccionar aleatoriamente la misma cantidad de datos.

Martha, te recomiendo el método 2 con tabla temporal.

Te explico esta instrucción. Db. Execute sql, dbFailOnError

La línea de código db. Execute sql, dbFailOnError es fundamental en Access VBA para ejecutar comandos de acción SQL (como INSERT, UPDATE, DELETE, CREATE TABLE, etc.) y gestionar errores de manera robusta.

Esta instrucción le indica al motor de base de datos que ejecute una consulta de acción almacenada en la variable sql. Los dos componentes clave son:

1. Db. Execute sql

db: Es la variable de objeto que se ha declarado como DAO.Database y se ha establecido como la base de datos actual (Set db = CurrentDb). Este objeto proporciona el método para interactuar con la base de datos.

. Execute: Es el método del objeto Database que se utiliza para ejecutar consultas SQL que no devuelven registros (consultas de acción).

sql: Es la variable de tipo String que contiene la sentencia SQL de acción que se desea ejecutar (por ejemplo, el INSERT INTO... o el DELETE... en tu función).

2., dbFailOnError

Este es el argumento opcional Options y es crucial para el manejo de errores y la integridad de los datos.

Constante        Valor    Propósito

DbFailOnError 1 Obliga a que la consulta se ejecute como una transacción atómica.

Perfecto Eduardo es lo que buscaba, no era fácil. Aprovecho para preguntarle ya que es el único experto que me ha respondido sobre PostgreSQL, aunque este no es un foro sobre este servidor. ¿Cómo se haría la eliminación al azar en PostgreSQL?

Con gusto le dejo esta función en PostgreSQL utilizando plpgsql para eliminar los registros al azar

-- ============================================================
-- FUNCIÓN: eliminarregistrosaleatorios()
-- OBJETIVO: Elimina N registros aleatorios de un vendedor y lotería específicos
-- Con opción de registrar los eliminados en un log.
-- AUTOR: Eduardo Pérez Fernández
-- FECHA: 2025-11-02
-- ============================================================
CREATE OR REPLACE FUNCTION eliminarregistrosaleatorios(
    lnVendedor BIGINT,
    intCantidad INT,
    lnCodigoLoteria BIGINT
)
RETURNS BIGINT AS
$$
DECLARE
    totalRegs BIGINT;
    lngRegistrosEliminados BIGINT := 0;
    guardar_log BOOLEAN := TRUE;  -- ? Cambia a FALSE si no quieres registrar el log
BEGIN
    -----------------------------------------------------------------
    -- 1. Validar que existan registros
    -----------------------------------------------------------------
    SELECT COUNT(*) INTO totalRegs
    FROM tblNrosAsignados
    WHERE idvendedor = lnVendedor
      AND codigoloteria = lnCodigoLoteria;
    If totalRegs = 0 Then
        Raise NOTICE 'No hay registros para eliminar para vendedor % (lotería %)', lnVendedor, lnCodigoLoteria;
        RETURN 0;
    END IF;
    -----------------------------------------------------------------
    -- 2. Ajustar cantidad a eliminar si excede los registros disponibles
    -----------------------------------------------------------------
    If intCantidad > totalRegs Then
        intCantidad := totalRegs;
    END IF;
    -----------------------------------------------------------------
    -- 3. Seleccionar y eliminar registros aleatorios
    -- (Y capturar los eliminados si el log está habilitado)
    -----------------------------------------------------------------
    If guardar_log Then
        -- Crear la tabla de log si no existe
        PERFORM 1
        FROM information_schema.tables
        WHERE table_name = 'tbl_log_eliminaciones'
          AND table_schema = 'public';
        If Not FOUND Then
            Raise NOTICE 'Creando tabla de log tbl_log_eliminaciones...';
            Execute '
                CREATE TABLE IF NOT EXISTS tbl_log_eliminaciones (
                    idlog SERIAL PRIMARY KEY,
                    fecha TIMESTAMP NOT NULL DEFAULT NOW(),
                    idvendedor BIGINT,
                    codigoloteria BIGINT,
                    nroasignado TEXT,
                    usuario TEXT DEFAULT current_user
                );
            ';
        END IF;
        -- CTE para seleccionar aleatoriamente y eliminar con log
        WITH RegistrosAEliminar AS (
            SELECT nroasignado
            FROM tblNrosAsignados
            WHERE idvendedor = lnVendedor
              AND codigoloteria = lnCodigoLoteria
            ORDER BY RANDOM()
            LIMIT intCantidad
        ),
        Eliminados AS (
            DELETE FROM tblNrosAsignados p
            USING RegistrosAEliminar r
            WHERE p.idvendedor = lnVendedor
              AND p.codigoloteria = lnCodigoLoteria
              AND p.nroasignado = r.nroasignado
            RETURNING p.nroasignado
        )
        INSERT INTO tbl_log_eliminaciones (idvendedor, codigoloteria, nroasignado)
        SELECT lnVendedor, lnCodigoLoteria, e.nroasignado
        FROM Eliminados e;
        -- Contar eliminados
        GET DIAGNOSTICS lngRegistrosEliminados = ROW_COUNT;
    Else
        -- Si no se desea registrar el log
        WITH RegistrosAEliminar AS (
            SELECT nroasignado
            FROM tblNrosAsignados
            WHERE idvendedor = lnVendedor
              AND codigoloteria = lnCodigoLoteria
            ORDER BY RANDOM()
            LIMIT intCantidad
        )
        DELETE FROM tblNrosAsignados p
        USING RegistrosAEliminar r
        WHERE p.idvendedor = lnVendedor
          AND p.codigoloteria = lnCodigoLoteria
          AND p.nroasignado = r.nroasignado;
        GET DIAGNOSTICS lngRegistrosEliminados = ROW_COUNT;
    END IF;
    -----------------------------------------------------------------
    -- 4. Confirmación (mensaje informativo)
    -----------------------------------------------------------------
    Raise NOTICE 'Se eliminaron % registros aleatorios del vendedor % (lotería %)',
        lngRegistrosEliminados, lnVendedor, lnCodigoLoteria;
    RETURN lngRegistrosEliminados;
EXCEPTION WHEN OTHERS THEN
    Raise WARNING 'Error al eliminar registros aleatorios: %', SQLERRM;
    RETURN 0;
END;
$$
LANGUAGE plpgsql;

En este caso, se considera que la tabla no requiere un campo autonumérico, sino un índice único compuesto por los tres campos. Por otro lado, he añadido el registro de un log para auditar los números que se retiran al vendedor.

Martha, espero que esta función le permita realizar su proceso de una forma eficiente. Nuevamente, la felicito por utilizar PostgreSQL como back end, ya que supera las limitaciones de Access.

¡Gracias! Eduardo, personas como usted son de mucha importancia, sus aportes siempre son efectivos, todo indica que sus conocimientos de Access y PostgreSQL son avanzados. Lo estaré molestando, Nuevamente mil gracias.

Añade tu respuesta

Haz clic para o

Más respuestas relacionadas