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
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 Function2. 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 FunctionExplicació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.
- Compartir respuesta