Generar tres números al azar para una rifa
1. Pregunta
Estoy elaborando una aplicación de una rifa y necesito crear una función para llenar una tabla con 3 números generados al azar, ningún número se puede repetir en la tabla, si el vendedor tiene activo el campo juegacuatro se le asignarán los números, los parámetros son: idvendedor y totalfilas. Cada fila contendrá los 3 números separados por "-" por ejemplo: 0025-5684-8657
Tengo 2 tablas:
Tblvendedores
Idvendedor -- Autonumérico
Nombre -- Cadena
juegacuatro -- Boolean (Si es True se le generarán números al azar)
Tbl3opciones
Idfraccion -- Auitonumérico
Idvendedor - Entero largo (para relacionar con la tabla tblvendedores)
Numeros -- Cadena (14 caractres)
La tabla tb3opciones debe quedar así (por ejemplo)
idfraccion idvendedor numeros
1 10 8373-0755-1404
2 10 0441-7257-4409
3 10 2029-6315-8896
4 10 4211-0965-6979
2. Pregunta
Necesito una consulta para desglosar los números de la tabla tbl3opciones en tres columnas (nro1, nro2, nro3), algo como:
idfraccion idvendedor nro1 nro2 nro3
1 10 8373 0755 1404
2 10 0441 7257 4409
3 10 2029 6315 8896
4 10 4211 0965 6979
Quedo altamente agradecida por la solución que los expertos me aporten.
3 respuestas
Martha su pregunta es compleja y requiere de varias funciones. Estas funciones hacen uso de colecciones y diccionarios. Personalmente hace muchos años elabore en Access VBA un sistema de rifas en Colombia con muchas más posibilidades. Le he preparado este ejemplo donde considero 3 o 4 opciones, con unos ajustes lo puede adaptar para 1,2,5 opciones y más. Este es el código.
Public Function LlenarTabla(idVendedorFiltro As Long, totalFilas As Integer)
'Parámetros:
' idVendedorFiltro= El idvendedor de la tabla tblvendedores
' totalfilas= Número de bonos o boletas a crear
'Autor: Eduardo Pérez Fernández
'Versión 1.1 (Con barra de progreso)
'Fecha 04/12/2024
'Si utiliza este código en sus proyecto respete la autoría
Dim db As DAO.Database
Dim rsVendedor As DAO.Recordset
Dim rsOpciones As DAO.Recordset
Dim idVendedor As Long
Dim cantidadNumeros As Integer
Dim juegacuatro As Boolean
Dim numerosGenerados As Collection
Dim numerosConcatenados As String
Dim i As Integer
Dim saldo_nros As Integer
Dim nros_enfilas As Integer
Dim frmProgress As Form
On Error GoTo ManejarErrores
Set db = CurrentDb
Randomize ' Inicializa el generador de números aleatorios
' Filtrar la consulta para obtener solo el vendedor especificado
Set rsVendedor = db.OpenRecordset("SELECT idvendedor, juegacuatro FROM tblvendedores WHERE idvendedor = " & idVendedorFiltro)
Set rsOpciones = db.OpenRecordset("SELECT idvendedor, numeros FROM tbl4opciones", dbOpenDynaset)
' Verifica si se encontró el vendedor especificado
If rsVendedor.EOF Then
MsgBox "No se encontró un vendedor con el ID proporcionado.", vbExclamation, "Error..."
Exit Function
End If
' Procesar el vendedor filtrado
idVendedor = rsVendedor!idVendedor
juegacuatro = rsVendedor!juegacuatro 'Obtiene si juega 4 o 3 opciones
' Determina la cantidad de números según juegacuatro
If juegacuatro Then
cantidadNumeros = 4
Else
cantidadNumeros = 3
End If
' Verifico los números disponibles
nros_enfilas = cantidadNumeros * totalFilas
saldo_nros = SaldoNumeros()
If saldo_nros < nros_enfilas Then
MsgBox "No quedan sino " & Int(saldo_nros / cantidadNumeros) & " fracciones disponibles" & vbCrLf & _
"de " & cantidadNumeros & " números", vbInformation, "Le informo"
Exit Function
End If
' Abre el formulario de la barra de progreso
DoCmd.OpenForm "frmProgressBar", acNormal
Set frmProgress = Forms("frmProgressBar")
' Inicializa la barra de progreso
frmProgress.txtProgress.Width = 0
frmProgress.lblStatus.Caption = "0%"
' Genera números aleatorios para las filas
For i = 1 To totalFilas
' Genera y concatena números únicos para la fila
Set numerosGenerados = New Collection
Call GenerarNumerosUnicos(numerosGenerados, cantidadNumeros)
numerosConcatenados = ConcatenarNumeros(numerosGenerados)
' Inserta la fila en tbl4opciones
rsOpciones.AddNew
rsOpciones!idVendedor = idVendedor
rsOpciones!numeros = numerosConcatenados
rsOpciones.Update
' Actualiza la barra de progreso
Call ActualizarBarraProgreso(frmProgress, i, totalFilas)
' Permite que Access procese eventos pendientes
DoEvents
Next i
' Limpieza
RsOpciones. Close
RsVendedor. Close
Set rsOpciones = Nothing
Set rsVendedor = Nothing
Set db = Nothing
' Cierra el formulario de la barra de progreso
DoCmd.Close acForm, "frmProgressBar"
Exit Function
ManejarErrores:
MsgBox "Se produjo un error: " & Err.Description, vbCritical, "Error"
If Not frmProgress Is Nothing Then
DoCmd.Close acForm, "frmProgressBar"
End If
End Function
Private Sub ActualizarBarraProgreso(frm As Form, progresoActual As Integer, progresoTotal As Integer)
Dim porcentaje As Integer
Dim anchoMaximo As Integer
Dim anchoProgreso As Integer
anchoMaximo = 300 ' Ancho máximo de la barra de progreso en píxeles (ajusta este valor si lo deseas)
' Calcula el porcentaje de progreso
porcentaje = (progresoActual / progresoTotal) * 100
' Calcula el ancho proporcional de la barra en función del porcentaje
anchoProgreso = (anchoMaximo * porcentaje) / 100
' Asegúrate de que el ancho calculado no exceda el ancho máximo permitido
If anchoProgreso > anchoMaximo Then
anchoProgreso = anchoMaximo
End If
' Actualiza la barra de progreso visual
frm.txtProgress.Width = anchoProgreso
frm.lblStatus.Caption = porcentaje & "%"
End Sub
' Función auxiliar para generar números únicos
Private Sub GenerarNumerosUnicos(ByRef numerosGenerados As Collection, ByVal cantidadNumeros As Integer)
Dim nuevoNumero As String
Do While numerosGenerados.Count < cantidadNumeros
nuevoNumero = Format(Int((9999 - 0 + 1) * Rnd + 0), "0000")
If Not ExisteNumero(numerosGenerados, nuevoNumero) And Not NumeroExisteEnTabla(nuevoNumero) Then
numerosGenerados.Add nuevoNumero, nuevoNumero
End If
Loop
End Sub
' Función auxiliar para concatenar números generados
Private Function ConcatenarNumeros(ByVal numeros As Collection) As String
Dim numero As Variant
Dim resultado As String
resultado = ""
For Each numero In numeros
If resultado = "" Then
resultado = numero
Else
resultado = resultado & "-" & numero
End If
Next numero
ConcatenarNumeros = resultado
End Function
' Función para verificar si un número ya existe en la tabla tbl4opciones
Private Function NumeroExisteEnTabla(nuevoNumero As String) As Boolean
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim existe As Boolean
Dim i As Integer
Set db = CurrentDb
' Consulta la tabla para ver si el número ya existe en otra fila
Set rs = db.OpenRecordset("SELECT numeros FROM tbl4opciones")
' Busca el número exacto en la tabla
Do While Not rs.EOF
Dim numerosArray() As String
numerosArray = Split(rs!numeros, "-")
For i = LBound(numerosArray) To UBound(numerosArray)
If numerosArray(i) = nuevoNumero Then
existe = True
Exit Do
End If
Next i
rs.MoveNext
Loop
' Cerrar recordset
rs.Close
Set rs = Nothing
Set db = Nothing
NumeroExisteEnTabla = existe
End Function
' Función auxiliar para verificar si un número ya existe en una colección
Function ExisteNumero(ByVal col As Collection, ByVal numero As String) As Boolean
Dim item As Variant
ExisteNumero = False
' Recorre la colección para ver si el número ya existe
For Each item In col
If item = numero Then
ExisteNumero = True
Exit Function
End If
Next item
End Function
' Función para validar el saldo de números disponibles
Function SaldoNumeros() As Integer
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim numerosUsados As Collection
Dim numeroActual As String
Dim totalNumerosEnUso As Integer
Dim numerosDisponibles As Integer
Dim i As Integer
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT numeros FROM tbl4opciones")
Set numerosUsados = New Collection
' Recorrer la tabla y almacenar los números únicos en la colección
Do While Not rs.EOF
Dim numerosArray() As String
numerosArray = Split(rs!numeros, "-")
For i = LBound(numerosArray) To UBound(numerosArray)
numeroActual = numerosArray(i)
' Solo agregar si el número no está ya en la colección
On Error Resume Next
numerosUsados.Add numeroActual, numeroActual
On Error GoTo 0
Next i
rs.MoveNext
Loop
' Calcular el total de números únicos en uso
totalNumerosEnUso = numerosUsados.Count
' Calcular cuántos números quedan disponibles
numerosDisponibles = 10000 - totalNumerosEnUso
SaldoNumeros = numerosDisponibles
' Limpieza
rs.Close
Set rs = Nothing
Set db = Nothing
End Function
Public Function ExisteEnTabla(nuevoNumero As String) As Long
' Función para verificar si un número ya existe en la tabla tbl4opciones
' Retorna el idvendedor, si no existe retorna 0
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim i As Integer
Dim id As Long
id = 0 ' Asegurar que está inicializado a 0
Set db = CurrentDb
' Consulta la tabla para ver si el número ya existe en otra fila
Set rs = db.OpenRecordset("SELECT idvendedor, numeros FROM tbl4opciones")
' Busca el número exacto en la tabla
Do While Not rs.EOF
Dim numerosArray() As String
numerosArray = Split(rs!numeros, "-")
For i = LBound(numerosArray) To UBound(numerosArray)
If Trim(numerosArray(i)) = Trim(nuevoNumero) Then ' Se asegura de comparar sin espacios en blanco
id = rs!idVendedor
Exit Do
End If
Next i
rs.MoveNext
Loop
' Cerrar recordset
rs.Close
Set rs = Nothing
Set db = Nothing
ExisteEnTabla = id
End Function
Public Function ObtenerNumerosNoEnTabla() As String
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim dictNumerosExistentes As Object
Dim numeros As String
Dim numero As Variant
Dim resultado As String
Dim numeroActual As String
Dim numerosSeparados As Variant
Dim i As Long
' Crear un diccionario para almacenar los números existentes en la tabla
Set dictNumerosExistentes = CreateObject("Scripting.Dictionary")
' Abrir la tabla de opciones para obtener los números existentes
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT numeros FROM tbl4opciones", dbOpenSnapshot)
' Llenar el diccionario con los números existentes (como cadenas de texto)
Do While Not rs.EOF
' Obtener los números de la columna "numeros" y separarlos por guiones
numeros = rs!numeros
numerosSeparados = Split(numeros, "-") ' Separar la cadena por el guion
' Llenar el diccionario con cada número individual
For i = LBound(numerosSeparados) To UBound(numerosSeparados)
dictNumerosExistentes(CStr(numerosSeparados(i))) = True
Next i
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
Set db = Nothing
' Ahora iteramos sobre todos los números de 4 dígitos posibles ("0000"-"9999")
resultado = ""
For numero = 0 To 9999
numeroActual = Format(numero, "0000") ' Asegura que el número sea de 4 dígitos (con ceros a la izquierda)
' Verifica si el número no está en el diccionario
If Not dictNumerosExistentes.Exists(numeroActual) Then
' Si no está en el diccionario, lo agregamos al resultado
resultado = resultado & numeroActual & ", "
End If
Next numero
' Si se encontraron números faltantes, devolverlos, eliminando la última coma
If Len(resultado) > 0 Then
resultado = Left(resultado, Len(resultado) - 2) ' Elimina la última coma y espacio
End If
' Devolvemos los números faltantes
ObtenerNumerosNoEnTabla = resultado
End Function
Public Function InsertarNumerosNoEnTabla()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim dictNumerosExistentes As Object
Dim numeros As String
Dim numero As Variant
Dim numeroActual As String
Dim numerosSeparados As Variant
Dim i As Long
Dim rsInsertar As DAO.Recordset
'Elimino los datos de la tabla temporal
CurrentDb.Execute "DELETE FROM tem_libres_cuatro_opciones"
' Crear un diccionario para almacenar los números existentes en la tabla
Set dictNumerosExistentes = CreateObject("Scripting.Dictionary")
' Abrir la tabla de opciones para obtener los números existentes
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT numeros FROM tbl4opciones", dbOpenSnapshot)
' Llenar el diccionario con los números existentes (como cadenas de texto)
Do While Not rs.EOF
' Obtener los números de la columna "numeros" y separarlos por guiones
numeros = rs!numeros
numerosSeparados = Split(numeros, "-") ' Separar la cadena por el guion
' Llenar el diccionario con cada número individual
For i = LBound(numerosSeparados) To UBound(numerosSeparados)
dictNumerosExistentes(CStr(numerosSeparados(i))) = True
Next i
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
' Abrir la tabla "tem_libres_cuatro_opciones" para insertar los números faltantes
Set rsInsertar = db.OpenRecordset("tem_libres_cuatro_opciones", dbOpenDynaset)
' Iteramos sobre todos los números de 4 dígitos posibles ("0000"-"9999")
For numero = 0 To 9999
numeroActual = Format(numero, "0000") ' Asegura que el número sea de 4 dígitos (con ceros a la izquierda)
' Verifica si el número no está en el diccionario
If Not dictNumerosExistentes.Exists(numeroActual) Then
' Si no está en el diccionario, lo insertamos en la tabla "tem_libres_cuatro_opciones"
rsInsertar.AddNew
rsInsertar!numero = numeroActual
rsInsertar.Update
End If
Next numero
' Limpieza
rsInsertar.Close
Set rsInsertar = Nothing
Set db = Nothing
End Function
Este código de VBA tiene varias funciones relacionadas con la generación y manejo de números aleatorios asociados a vendedores, así como la gestión de una barra de progreso para mostrar visualmente el avance de la generación. A continuación, le dejo un desglose de las secciones principales:
1. Función LlenarTabla
Esta es la función principal que genera números aleatorios para los vendedores. Parámetros:
- IdVendedorFiltro: El ID del vendedor para el cual se generarán los números.
- TotalFilas: Cantidad de filas o registros a generar.
Pasos principales:
Inicialización:
- Conecta a la base de datos (db) y abre los Recordset de las tablas tblvendedores y tbl4opciones.
- Verifica si el vendedor con el idVendedorFiltro existe y obtiene si utiliza 3 o 4 números por fila (juegacuatro).
Validación:
- Calcula cuántos números son necesarios (nros_enfilas) y verifica si hay suficientes números disponibles en la tabla (SaldoNumeros()).
Generación de números:
- Por cada fila, genera una colección de números aleatorios únicos (GenerarNumerosUnicos) y los concatena en una cadena (ConcatenarNumeros).
- Inserta la fila con el vendedor y los números generados en tbl4opciones.
Barra de progreso:
- Abre un formulario con una barra de progreso (frmProgressBar) para mostrar el porcentaje completado.
- La barra se actualiza en cada iteración usando la función ActualizarBarraProgreso.
Limpieza:
- Cierra los objetos abiertos (recordsets y conexión a la base de datos).
- Maneja errores y asegura que el formulario de progreso se cierra si ocurre un error.
2. Función ActualizarBarraProgreso
Esta función actualiza la barra de progreso visual del formulario:
- Calcula el porcentaje completado.
- Ajusta el ancho de la barra en píxeles proporcional al porcentaje.
- Actualiza el texto que muestra el porcentaje completado.
3. Función GenerarNumerosUnicos
Genera una colección de números aleatorios de 4 dígitos que:
- No estén repetidos dentro de la colección generada.
- No existan ya en la tabla tbl4opciones (verifica con NumeroExisteEnTabla).
Cómo funciona:
- Usa Rnd para generar un número aleatorio entre 0000 y 9999.
- Verifica si el número ya está en uso antes de agregarlo a la colección.
4. Función ConcatenarNumeros
Concatena los números generados en una cadena separada por guiones (-).
Ejemplo: Si los números son 1234, 5678, y 9101, la función retorna 1234-5678-9101.
5. Función SaldoNumeros
Calcula cuántos números de 4 dígitos están aún disponibles:
- Crea una colección de todos los números únicos usados en tbl4opciones.
- Resta el total de números en uso de los 10,000 posibles (0000-9999).
6. Función NumeroExisteEnTabla
Verifica si un número ya está registrado en la tabla tbl4opciones:
- Abre un Recordset de tbl4opciones.
- Divide cada registro en una lista de números (separados por -) y busca el número proporcionado.
7. Función ObtenerNumerosNoEnTabla
Devuelve una lista de todos los números de 4 dígitos que no están en uso:
- Usa un diccionario (Scripting. Dictionary) para almacenar los números usados.
- Itera sobre todos los números posibles (0000 a 9999) y selecciona los que no estén en el diccionario.
Propósito General
El código:
- Automatiza la generación y asignación de números a vendedores.
- Garantiza que los números sean únicos y que no se repitan.
- Utiliza una barra de progreso para mejorar la experiencia del usuario.
- Incluye validaciones para evitar errores y asegurar que las operaciones sean consistentes.
Cómo tengo su correo le envío el ejemplo, este código es la clave para numerar boletos de rifas, quien quiera el ejemplo lo puede solicitar a [email protected]
Le dejo la respuesta a su segunda pregunta.
SELECT tbl3opciones.idfraccion, tbl3opciones.idvendedor, Mid([numeros],1,4) AS Nro1, Mid([numeros],6,4) AS Nro2, IIf(Len([numeros])>9,Mid([numeros],11,4),Null) AS Nro3, IIf(Len([numeros])>14,Mid([numeros],16,4),Null) AS Nro4 FROM tbl3opciones;
Esta consulta muestra las columnas:
Idfraccion idvendedor Nro1 Nro2 Nro3 Nro4
Les dejo el link de este software que elaboré en Python. Hacer boletas para rifas en PDF - RifaPDF 2.1
Martha observe porque utilizo PostgreSQL por eficiencia. Para los amantes de PostgreSQL les dejo el script de la función.
CREATE OR REPLACE FUNCTION llenar_tabla(
id_vendedor_filtro BIGINT,
total_filas INTEGER
) RETURNS BOOLEAN AS $$
DECLARE
cantidad_numeros INTEGER;
saldo_numeros INTEGER;
nros_en_filas INTEGER;
numeros_generados TEXT[] := '{}';
numeros_concatenados TEXT;
i INTEGER;
numero_generado TEXT;
juega_cuatro_flag BOOLEAN;
ancho_maximo INTEGER := 10000; -- Total de combinaciones posibles
BEGIN
-- Verifica si el vendedor existe y obtiene juegacuatro
SELECT juegacuatro INTO juega_cuatro_flag
FROM tblvendedores
WHERE idvendedor = id_vendedor_filtro;
IF NOT FOUND THEN
RETURN FALSE; -- Retorna FALSE si el vendedor no existe
END IF;
-- Determina la cantidad de números a generar por fila
cantidad_numeros := CASE WHEN juega_cuatro_flag THEN 4 ELSE 3 END;
-- Calcula cuántos números quedan disponibles
SELECT ancho_maximo - COUNT(*)
INTO saldo_numeros
FROM tbl3opciones, unnest(string_to_array(numeros, '-')) AS numero;
nros_en_filas := cantidad_numeros * total_filas;
IF saldo_numeros < nros_en_filas THEN
RETURN FALSE; -- Retorna FALSE si no hay suficientes números disponibles
END IF;
-- Genera filas con números únicos
FOR i IN 1..total_filas LOOP
-- Genera números únicos para la fila
numeros_generados := '{}';
WHILE array_length(numeros_generados, 1) IS NULL OR array_length(numeros_generados, 1) < cantidad_numeros LOOP
numero_generado := to_char(FLOOR(random() * ancho_maximo)::INTEGER, 'FM0000');
IF NOT EXISTS (
SELECT 1
FROM tbl3opciones, unnest(string_to_array(numeros, '-')) AS numero
WHERE numero = numero_generado
) AND NOT (numero_generado = ANY(numeros_generados)) THEN
numeros_generados := array_append(numeros_generados, numero_generado);
END IF;
END LOOP;
-- Concatena los números generados
numeros_concatenados := array_to_string(numeros_generados, '-');
-- Inserta la nueva fila en tbl3opciones
INSERT INTO tbl3opciones (idvendedor, numeros)
VALUES (id_vendedor_filtro, numeros_concatenados);
END LOOP;
-- Si todo se ejecuta correctamente, retorna TRUE
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
-- Captura errores y retorna FALSE
RETURN FALSE;
END;
$$ LANGUAGE plpgsql;¿Sabe cuánto tarda el PostgreSQL en procesar 500 líneas?... 6 segundos. Espero con esto dar por concluida la respuesta a su pregunta.
- Compartir respuesta
Localizar numeros irrepetibles guardados en formato de texto y agrupados, aunque es factible, es oneroso en terminos de recursos y muy pobre en eficiencia.
Cada registro = un numero (en formato numero) con un campo asociado a la apuesta (para poder aunarlos de tres en tres o siete en siete) y datos auxiliares tales como el id del vendedor, sorteo, fechas ... importe etc.
El campo con el numero (en la tabla) indexado y sin repeticiones (eso impide la inserción de repeticiones de forma no autorizada o errónea).
La presentación de resultados: una consulta de agrupación que los una y genere en una misma línea los números (de ello hay muchos ejemplos publicados) en base al ID y sorteo ... etc.
(La consulta puede ser tan plana o complicada como se necesite, Access ofrece bastantes recursos para ello).
Un buen diseño de las tablas es una garantía de excelentes resultados.
- Compartir respuesta
Su respuesta carece de sentido no preguntan sobre consumo de recursos y creo desconoce la lógica del proceso. Responder como por no dejar es confundir. - Eduardo Pérez Fernández
Dedica tu tiempo a algo útil y no a dirigir el trafico. - Enrique Feijóo
Deje que el tráfico fluya, pero con comentarios que enriquezcan, sus conocimientos son muy pobres en Access, VBA y ni hablar de PostgreSQL - Eduardo Pérez Fernández
I. Hola Martha, en mi caso desconozco la respuesta y no soy usuario habitual de Access pero ví una información que deseaba trasladarle conformada por enlaces, por si pudiesen serle de utilidad mientras le atiende un experto o experta de primera mano, las que si desea podríamos llamar en caso de que no recibiese respuestas durante lo que resta de semana.
Le ruego me disculpe las molestias de tan ingente lectura y el tipo de respuesta, mucho ánimo.
https://stackoverflow.com/questions/22577916/generate-three-different-random-numbers
https://stackoverflow-com.translate.goog/questions/68293665/unique-random-number-generation-in-access?_x_tr_sl=en&_x_tr_tl=es&_x_tr_hl=es&_x_tr_pto=sc
https://stackoverflow.com/questions/63409554/multiple-unique-random-number-generation-ms-access
https://stackoverflow.com/questions/63409554/multiple-unique-random-number-generation-ms-access
https://stackoverflow.com/questions/32552075/java-generating-3-random-numbers
https://www.youtube.com/watch?v=CRfH0D2QpV0
- Compartir respuesta
