tablas - mysql join types
Consulta MySQL para obtener "intersección" de numerosas consultas con límites (10)
Supongamos que tengo una sola tabla mySQL (usuarios) con los siguientes campos:
userid
gender
region
age
ethnicity
income
Quiero poder devolver el número de registros totales según el número que ingresa un usuario. Además, también proporcionarán criterios adicionales.
En el ejemplo más simple, pueden solicitar 1,000 registros, donde 600 registros deben tener sexo = ''Hombre'' y 400 registros donde el género = ''Mujer''. Eso es lo suficientemente simple de hacer.
Ahora, vaya un paso más allá. Supongamos que ahora quieren especificar Región:
GENDER
Male: 600 records
Female: 400 records
REGION
North: 100 records
South: 200 records
East: 300 records
West: 400 records
De nuevo, solo se deben devolver 1000 registros, pero al final, debe haber 600 hombres, 400 mujeres, 100 habitantes del norte, 200 sureños, 300 orientales y 400 occidentales.
Sé que esto no es una sintaxis válida, pero usando el código pseudo-mySQL, con suerte ilustra lo que estoy tratando de hacer:
(SELECT * FROM users WHERE gender = ''Male'' LIMIT 600
UNION
SELECT * FROM users WHERE gender = ''Female'' LIMIT 400)
INTERSECT
(SELECT * FROM users WHERE region = ''North'' LIMIT 100
UNION
SELECT * FROM users WHERE region = ''South'' LIMIT 200
UNION
SELECT * FROM users WHERE region = ''East'' LIMIT 300
UNION
SELECT * FROM users WHERE region = ''West'' LIMIT 400)
Tenga en cuenta que no estoy buscando una consulta de una sola vez. El número total de registros y el número de registros dentro de cada criterio cambiará constantemente en función de los datos ingresados por el usuario. Por lo tanto, estoy tratando de encontrar una solución genérica que pueda reutilizarse una y otra vez, no una solución codificada.
Para hacer las cosas más complicadas, ahora agregue más criterios. También podría haber edad, etnia e ingresos, cada uno con su propio número de registros para cada grupo, código adicional adjunto al anterior:
INTERSECT
(SELECT * FROM users WHERE age >= 18 and age <= 24 LIMIT 300
UNION
SELECT * FROM users WHERE age >= 25 and age <= 36 LIMIT 200
UNION
SELECT * FROM users WHERE age >= 37 and age <= 54 LIMIT 200
UNION
SELECT * FROM users WHERE age >= 55 LIMIT 300)
INTERSECT
etc.
No estoy seguro si esto es posible escribir en una consulta o si esto requiere múltiples declaraciones e iteraciones.
Aplana tus criterios
Puede aplanar sus criterios multidimensionales en un criterio de un solo nivel
Ahora este criterio se puede lograr en una consulta como sigue
(SELECT * FROM users WHERE gender = ''Male'' AND region = ''North'' LIMIT 40) UNION ALL
(SELECT * FROM users WHERE gender = ''Male'' AND region = ''South'' LIMIT 80) UNION ALL
(SELECT * FROM users WHERE gender = ''Male'' AND region = ''East'' LIMIT 120) UNION ALL
(SELECT * FROM users WHERE gender = ''Male'' AND region = ''West'' LIMIT 160) UNION ALL
(SELECT * FROM users WHERE gender = ''Female'' AND region = ''North'' LIMIT 60) UNION ALL
(SELECT * FROM users WHERE gender = ''Female'' AND region = ''South'' LIMIT 120) UNION ALL
(SELECT * FROM users WHERE gender = ''Female'' AND region = ''East'' LIMIT 180) UNION ALL
(SELECT * FROM users WHERE gender = ''Female'' AND region = ''West'' LIMIT 240)
Problema
- No siempre devuelve el resultado correcto. Por ejemplo, si hay menos de 40 usuarios que son hombres y del norte, la consulta devolverá menos de 1,000 registros.
Ajuste sus criterios
Digamos que hay menos de 40 usuarios que son hombres y del norte. Luego, debe ajustar otra cantidad de criterios para cubrir la cantidad faltante de "Hombre" y "Norte". Creo que no es posible hacerlo con SQL desnudo. Esto es un pseudo código que tengo en mente. En aras de la simplificación, creo que solo consultaremos para hombres, mujeres, norte y sur
conditions.add({ gender: ''Male'', region: ''North'', limit: 40 })
conditions.add({ gender: ''Male'', region: ''South'', limit: 80 })
conditions.add({ gender: ''Female'', region: ''North'', limit: 60 })
conditions.add({ gender: ''Female'', region: ''South'', limit: 120 })
foreach(conditions as condition) {
temp = getResultFromDatabaseByCondition(condition)
conditions.remove(condition)
// there is not enough result for this condition,
// increase other condition quantity
if (temp.length < condition.limit) {
adjust(...);
}
}
Digamos que solo hay 30 hombres del noreste. Entonces tenemos que ajustar +10 hombres y +10 norteños.
To Adjust
---------------------------------------------------
Male +10
North +10
Remain Conditions
----------------------------------------------------
{ gender: ''Male'', region: ''South'', limit: 80 }
{ gender: ''Female'', region: ''North'', limit: 60 }
{ gender: ''Female'', region: ''South'', limit: 120 }
''Hombre'' + ''Sur'' es la primera condición que coincide con la condición de ajuste ''Masculino''. Aumentarlo en +10, y eliminarlo de la lista "permanecer condición". Dado que, aumentamos el Sur, tenemos que disminuirlo en otras condiciones. Así que agrega la condición "Sur" a la lista "A Ajustar"
To Adjust
---------------------------------------------------
South -10
North +10
Remain Conditions
----------------------------------------------------
{ gender: ''Female'', region: ''North'', limit: 60 }
{ gender: ''Female'', region: ''South'', limit: 120 }
Final Conditions
----------------------------------------------------
{ gender: ''Male'', region: ''South'', limit: 90 }
Encuentra la condición que coincida con el ''Sur'' y repite el mismo proceso.
To Adjust
---------------------------------------------------
Female +10
North +10
Remain Conditions
----------------------------------------------------
{ gender: ''Female'', region: ''North'', limit: 60 }
Final Conditions
----------------------------------------------------
{ gender: ''Female'', region: ''South'', limit: 110 }
{ gender: ''Male'', region: ''South'', limit: 90 }
Y finalmente
{ gender: ''Female'', region: ''North'', limit: 70 }
{ gender: ''Female'', region: ''South'', limit: 110 }
{ gender: ''Male'', region: ''South'', limit: 90 }
Todavía no he presentado la implementación exacta del ajuste. Es más difícil de lo que esperaba. Lo actualizaré una vez que pueda descubrir cómo implementarlo.
Construiría un mapa de la distribución de la base de datos y lo usaría para implementar la lógica de muestreo. Las bonificaciones incluyen la posibilidad de agregar comentarios demográficos rápidos para el usuario y ninguna carga adicional para el servidor. Por el contrario, necesitaría implementar un mecanismo para mantener la base de datos y el mapa sincronizados.
Podría verse así usando JSON:
{"gender":{
"Male":{
"amount":35600,
"region":{
"North":{
"amount":25000,
"age":{
"18":{
"amount":2400,
"ethnicity":{
...
"income":{
...
}
},
"income":{
...
"ethnicity":{
...
}
}
},
"19":{
...
},
...
"120":{
...
}
},
"ethnicity":{
...
},
"income":{
...
}
},
"South":{
...
},
...
}
"age":{
...
}
"ethnicity":{
...
},
"income":{
...
}
},
"Female":{
...
}
},
"region":{
...
},
"age":{
...
},
"ethnicity":{
...
},
"income":{
...
}}
Entonces el usuario selecciona
total 1000
600 Male
400 Female
100 North
200 South
300 East
400 West
300 <20 years old
300 21-29 years old
400 >=30 years old
Calcule una distribución lineal:
male-north-u20: 1000*0.6*0.1*0.3=18
male-north-21to29: 18
male-north-o29: 24 (keep a track of rounding errors)
etc
entonces verificaríamos el mapa:
tmp.male.north.u20=getSumUnder(JSON.gender.Male.region.North.age,20) // == 10
tmp.male.north.f21to29=getSumBetween(JSON.gender.Male.region.North.age,21,29) // == 29
tmp.male.north.o29=getSumOver(JSON.gender.Male.region.north.age,29) // == 200
etc
Marque todo lo que coincida con la distribución lineal como correcto y realice un seguimiento del excedente. Si algo (como male.north.u20) está debajo, primero ajústelo en parent (para asegurarse de que male.north por ejemplo cumpla con los criterios), le falta 8 para u20 y se usa en exceso 8 para f21to29. Después de la primera ejecución, ajuste cada criterio faltante en otras regiones. Entonces, como tmp.male.south.u20+=8;tmp.male.south.f21to29-=8;
.
Es bastante tedioso hacerlo bien.
Al final tiene la distribución correcta que se puede usar para construir una consulta SQL trivial.
El problema con su solicitud es que hay una enorme cantidad de opciones que se pueden usar para alcanzar los números propuestos:
Male Female Sum
-----------------------------
North: 100 0 100
South: 200 0 200
East: 300 0 300
West: 0 400 400
Sum: 600 400
-----------------------------
North: 99 1 100
South: 200 0 200
East: 300 0 300
West: 1 399 400
Sum: 600 400
-----------------------------
....
-----------------------------
North: 0 100 100
South: 200 0 200
East: 0 300 300
West: 400 0 400
Sum: 600 400
Simplemente combinando Norte, Este y Oeste (con sur siempre masculino: 200) obtendrás 400 posibilidades de cómo lograr los números propuestos. Y se vuelve aún más complicado cuando solo tienes una cantidad limitada de registros por cada " clase " (Hombre / Norte = " clase ").
Es posible que necesite registros de hasta MIN(COUNT(gender), COUNT(location))
para cada celda de la tabla anterior (en el caso de que su contraparte sea cero).
Eso es hasta:
Male Female
---------------------
North: 100 100
South: 200 200
East: 300 300
West: 400 400
Por lo tanto, debe contar los registros disponibles de cada par de género / ubicación AVAILABLE(gender, location)
.
Encontrar un ajuste particular parece estar cerca de cuadrados semimágicos [1] [2] .
Y hay varias preguntas sobre math.stackexchange.com sobre esto [3] [4] .
Terminé leyendo un artículo sobre cómo construir estos y dudo que sea posible hacer esto con un selecto.
Si tiene suficientes registros y no terminará en una situación como esta:
Male Female
---------------------
North: 100 0
South: 200 200
East: 300 0
West: 200 200
Me gustaría ir iterando a través de ubicaciones y agregar el número proporcional de hombres / mujeres en cada paso:
- M: 100 (16%); F: 0 (0%)
- M: 100 (16%); F: 200 (50%)
- M: 400 (66%); F: 200 (50%)
- M: 600 (100%); F: 400 (100%)
Pero esto solo le dará resultados aproximados y, después de validarlos, es posible que desee repetir el resultado pocas veces y ajustar los recuentos en cada categoría para que sean " lo suficientemente buenos ".
El problema que describes es un problema de modelado multidimensional. En particular, intenta obtener una muestra estratificada a lo largo de múltiples dimensiones al mismo tiempo. La clave para esto es bajar al nivel más pequeño de granularidad y construir la muestra desde allí.
Además, estoy suponiendo que desea que la muestra sea representativa en todos los niveles. Es decir, no desea que todos los usuarios de "Norte" sean mujeres. O todos los "varones" son de "Oeste", incluso si eso cumple con los criterios finales.
Comience por pensar en términos de un número total de registros, dimensiones y asignaciones a lo largo de cada dimensión. Por ejemplo, para la primera muestra, piense en ello como:
- 1000 registros
- 2 dimensiones: género, región
- división de género: 60%, 40%
- división de la región: 10%, 20%, 30%, 40%
Luego, desea asignar estos números a cada combinación de género / región. Los números son:
- Norte, Hombre: 60
- Norte, Mujer: 40
- Sur, Masculino: 120
- Sur, Mujer: 80
- Oriente, Masculino: 180
- Este, Mujer: 120
- Oeste, Masculino: 240
- Oeste, Mujer: 160
Verás que estos se suman a lo largo de las dimensiones.
El cálculo de los números en cada celda es bastante fácil. Es el producto de los porcentajes multiplicados por el total. Entonces, "Este, Mujer" es 30% * 40% * 1000. . . Voila! El valor es 120.
Aquí está la solución:
- Tome la entrada a lo largo de cada dimensión como porcentajes del total. Y asegúrese de que sumen 100% a lo largo de cada dimensión.
- Cree una tabla de los porcentajes esperados para cada una de las celdas. Este es el producto de los porcentajes a lo largo de cada dimensión.
- Múltiples los porcentajes esperados por el total general.
- La consulta final se describe a continuación.
Supongamos que tiene cells
una tabla con el recuento esperado y los datos originales ( users
).
select enumerated.*
from (select u.*,
(@rn := if(@dims = concat_ws('':'', dim1, dim2, dim3), @rn + 1,
if(@dims := concat_ws('':'', dim1, dim2, dim3), 1, 1)
)
) as seqnum
from users u cross join
(select @dims = '''', @rn := '''') vars
order by dim1, dim2, dim3, rand()
) enumerated join
cells
on enumerated.dims = cells.dims
where enuemrated.seqnum <= cells.expectedcount;
Tenga en cuenta que este es un boceto de la solución. Tienes que completar los detalles sobre las dimensiones.
Esto funcionará siempre que tenga datos suficientes para todas las celdas.
En la práctica, al realizar este tipo de muestreo estratificado multidimensional, se corre el riesgo de que las celdas estén vacías o muy pequeñas. Cuando esto sucede, a menudo puede arreglar esto con un pase adicional después. Toma lo que puedas de las celdas que sean lo suficientemente grandes. Estos generalmente representan la mayoría de los datos necesarios. Luego agregue registros para cumplir con el recuento final. Los registros que se agregarán son aquellos cuyos valores coinciden con lo que se necesita a lo largo de las dimensiones más necesarias. Sin embargo, esta solución simplemente supone que hay suficientes datos para satisfacer sus criterios.
Esto se puede resolver en dos pasos. Describiré cómo hacerlo para el ejemplo donde el género y la región son las dimensiones. Luego describiré el caso más general. En el primer paso resolvemos un sistema de ecuaciones de 8 variables, luego tomamos la unión disjunta de 8 declaraciones de selección limitadas por las soluciones encontradas en el paso uno. Tenga en cuenta que solo hay 8 posibilidades para cualquier fila. Pueden ser hombres o mujeres y luego la región es una de norte, sur, este u oeste. Ahora deja,
X1 equal the number of rows that are male and from the north,
X2 equal the number of rows that are male and from the south,
X3 equal the number of rows that are male and from the east,
X4 equal then number that are male and from the west
X5 equal the number of rows that are female and from the north,
X6 equal the number of rows that are female and from the south,
X7 equal the number of rows that are female and from the east,
X8 equal then number that are female and from the west
Las ecuaciones son:
X1+X2+X3+X4=600
X5+X6+X7+X8=400
X1+X5=100
X2+X6=200
X3+X7=300
X4+X8=400
Ahora resuelve para X1, X2, ... X8 en lo anterior. Hay muchas soluciones (describiré cómo resolverlas en un momento) Aquí hay una solución:
X1=60, X2=120, X3=180,X4=240,X5=40,X6=80,X7=120,X8=160.
Ahora podemos obtener el resultado mediante una simple unión de 8 selecciones:
(select * from user where gender=''m'' and region="north" limit 60)
union distinct(select * from user where gender=''m'' and region=''south'' limit 120)
union distinct(select * from user where gender=''m'' and region=''east'' limit 180)
union distinct(select * from user where gender=''m'' and region=''west'' limit 240)
union distinct(select * from user where gender=''f'' and region=''north'' limit 40)
union distinct(select * from user where gender=''f'' and region=''south'' limit 80)
union distinct(select * from user where gender=''f'' and region=''east'' limit 120)
union distinct(select * from user where gender=''f'' and region=''west'' limit 160);
Tenga en cuenta que si no hay 60 filas en la base de datos, satisfaga la primera selección anterior, entonces la solución particular dada no funcionará. Entonces tenemos que agregar otras restricciones, LT:
0<X1 <= (select count(*) from user where from user where gender=''m'' and region="north")
0<X2 <= (select count(*) from user where gender=''m'' and region=''south'')
0<X3 <= (select count(*) from user where gender=''m'' and region=''east'' )
0<X4 <= (select count(*) from user where gender=''m'' and region=''west'')
0<X5 <= (select count(*) from user where gender=''f'' and region=''north'' )
0<X6 <= (select count(*) from user where gender=''f'' and region=''south'')
0<X7 <= (select count(*) from user where gender=''f'' and region=''east'' )
0<X8 <= (select count(*) from user where gender=''f'' and region=''west'');
Ahora vamos a generalizar para este caso, permitiendo cualquier división. Las ecuaciones son E:
X1+X2+X3+X4=n1
X5+X6+X7+X8=n2
X1+X5=m1
X2+X6=m2
X3+X7=m3
X4+X8=m4
Se dan los números n1, n2, m1, m2, m3, m4 y satisfacen n1 + n2 = (m1 + m2 + m3 + m4). Así que hemos reducido el problema a resolver las ecuaciones LT y E anteriores. Este es solo un problema de programación lineal y se puede resolver usando el método simplex u otros métodos. Otra posibilidad es ver esto como un sistema de ecuaciones diofánticas lineales y usar métodos para encontrar soluciones. En cualquier caso, reduje el problema para encontrar la solución a las ecuaciones anteriores. (Dado que las ecuaciones son de una forma especial, puede haber una manera más rápida que usar el método símplex o resolver un sistema de ecuaciones diofánticas lineales). Una vez que resolvemos para Xi, la solución final es:
(select * from user where gender=''m'' and region="north" limit :X1)
union distinct(select * from user where gender=''m'' and region=''south'' limit :X2)
union distinct(select * from user where gender=''m'' and region=''east'' limit :X3)
union distinct(select * from user where gender=''m'' and region=''west'' limit :X4)
union distinct(select * from user where gender=''f'' and region=''north'' limit :X5)
union distinct(select * from user where gender=''f'' and region=''south'' limit :X6)
union distinct(select * from user where gender=''f'' and region=''east'' limit :X7)
union distinct(select * from user where gender=''f'' and region=''west'' limit :X8);
Permite denotar una dimensión D con n posibilidades como D: n. Supongamos que tiene dimensiones D1: n1, D2: n2, ... DM: nM. El generaría n1 * n2 * ... nM variables. La cantidad de ecuaciones generadas es n1 + n2 + ... nM. En lugar de definir el método general, tomemos otro caso de 3 dimensiones, 4 dimensiones y 2 dimensiones; Permite llamar a los valores posibles para que D1 sea d11, d12, d13, D2 es d21, d22, d23, d24 y los valores de D3 son d31, d32. Tendremos 24 variables, y las ecuaciones son:
X1 + X2 + ...X8=n11
X9 + X10 + ..X16=n12
X17+X18 + ...X24=n13
X1+X2+X9+x10+x17+x18=n21
X3+X4+X11+x12+x19+x20=n22
X5+X6+X13+x14+x21+x22=n23
X7+X8+X15+x116+x23+x24=n24
X1+X3+X5+...X23=n31
X2+X4+......X24=n32
Dónde
X1 equals number with D1=d11 and D2=d21 and D3=d31
X2 equals number with D1=d11 and D2=d21 and D3 = d31
....
X24 equals number with D1=D13 and D2=d24, and D3=d32.
Agregue las restricciones menos entonces. Luego resuelve para X1, X2, ... X24. Crea las 24 declaraciones selectivas y toma la unión disjunta. Podemos resolverlo de manera similar para cualquier dimensión.
Entonces, en resumen: dadas las dimensiones D1: n1, D2: n2, ... DM: nM podemos resolver el problema de programación lineal correspondiente como se describe arriba para las variables n1 * n2 * ... nM y luego generar una solución tomando la disyuntiva union sobre n1 * n2 * ... nM select statements. Entonces, sí, podemos generar una solución mediante sentencias select, pero primero debemos resolver las ecuaciones y determinar los límites obteniendo recuentos para cada una de las variables n1 * n2 * ... nM.
Aunque la recompensa haya terminado, voy a agregar un poco más para aquellos a los que les interese. Reclamo aquí que he demostrado completamente cómo resolver esto si hay una solución.
Para aclarar mi enfoque. En el caso de 3 dimensiones, digamos que dividimos la edad en una de 3 posibilidades. Entonces usa bien el género y la región como en la pregunta. Hay 24 posibilidades diferentes para cada usuario correspondiente a donde caen en esas categorías. Deje Xi ser el número de cada una de esas posibilidades en el resultado final. Déjame escribir una matriz donde cada fila representa una de cada posibilidad. Cada usuario contribuirá como máximo 1 a m o f, 1 a norte, sur, este u oeste, y 1 a la categoría de edad. Y solo hay 24 posibilidades para el usuario. Vamos a mostrar una matriz: (abc) las 3 edades, (nsew) las regiones y (mf) masculino o femenino: a es menor o igual a 10 años, b tiene entre 11 y 30 años yc tiene entre 31 y 50 años .
abc nsew mf
X1 100 1000 10
X2 100 1000 01
X3 100 0100 10
X4 100 0100 01
X5 100 0010 10
X6 100 0010 01
X7 100 0001 10
X8 100 0001 01
X9 010 1000 10
X10 010 1000 01
X11 010 0100 10
X12 010 0100 01
X13 010 0010 10
X14 010 0010 01
X15 010 0001 10
X16 010 0001 01
X17 001 1000 10
X18 001 1000 01
X19 001 0100 10
X20 001 0100 01
X21 001 0010 10
X22 001 0010 01
X23 001 0001 10
X24 001 0001 01
Cada fila representa un usuario donde hay un 1 en la columna si contribuye a un resultado. Por ejemplo, la primera fila muestra 1 para a, 1 para n y 1 para m. Lo que significa que la edad del usuario es menor o igual a 10, es del norte y es un hombre. El Xi representa cuántos de ese tipo de fila se encuentran en el resultado final. Así que digamos que X1 es 10, lo que significa que estamos diciendo que el resultado final tiene 10 resultados, todos del norte, son hombres y son menos o iguales 10. Bien, ahora solo tenemos que sumar cosas. Tenga en cuenta que los primeros 8 X1+X2+X3+X4+X5+X6+X7+X8
son todas las filas cuya edad es menor o igual a 10. Deben sumarse a lo que elegimos para esa categoría. Del mismo modo para los próximos 2 juegos de 8.
Hasta ahora, obtenemos las ecuaciones: (na es el número con menos de 10 años, n la edad entre 10 y 20 años, nc el número cuya edad es menor que 50
X1+X2+X3+X4+X5+X6+X7+X8 = na
X9+X10+X11 + .... X16 = nb
X17+X18+X19+... X24=nc
Esas son las divisiones de edad. Ahora veamos la región se divide. Solo suma las variables en la columna "n",
X1+X2+X9+X10+X17+X18 = nn
X3+X4+X11+X12+X19+20=ns
...
¿Ves cómo estoy obteniendo esas ecuaciones mirando las columnas? Continuar por ew y mf. dando 3 + 4 + 2 ecuaciones en total. Entonces, lo que hice aquí es bastante simple. He razonado que cualquier fila que elijas contribuye con una de las 3 dimensiones y solo hay 24 posibilidades. Luego, deje que Xi sea el número de cada posibilidad y obtendrá las ecuaciones que necesita resolver. Me parece que cualquier método que se te ocurra debe ser una solución a esas ecuaciones. En otras palabras, simplemente reformulé el problema en términos de resolver esas ecuaciones.
Ahora queremos una solución entera porque no podemos tener una fila fraccionaria. Tenga en cuenta que estas son todas las ecuaciones lineales. Pero queremos una solución entera. Aquí hay un enlace a un documento que describe cómo resolver estos: https://www.math.uwaterloo.ca/~wgilbert/Research/GilbertPathria.pdf
Formar la lógica de negocios en SQL nunca es una buena idea, ya que obstaculizará la capacidad de absorber cambios menores.
Mi sugerencia sería hacer esto en un ORM y mantener la lógica empresarial abstraída de SQL.
Por ejemplo, si estabas usando Django :
Su modelo se vería así:
class User(models.Model):
GENDER_CHOICES = (
(''M'', ''Male''),
(''F'',''Female'')
)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
REGION_CHOICES = (
(''E'', ''East''),
(''W'',''West''),
(''N'',''North''),
(''S'',''South'')
)
region = models.CharField(max_length=1, choices=REGION_CHOICES)
age = models.IntegerField()
ETHNICITY_CHOICES = (
.......
)
ethnicity = models.CharField(max_length=1, choices=ETHNICITY_CHOICES)
income = models.FloatField()
Y su función de consulta podría ser algo como esto:
# gender_limits is a dict like {''M'':400, ''F'':600}
# region_limits is a dict like {''N'':100, ''E'':200, ''W'':300, ''S'':400}
def get_users_by_gender_and_region(gender_limits,region_limits):
for gender in gender_limits:
gender_queryset = gender_queryset | User.objects.filter(gender=gender)[:gender_limits[gender]]
for region in region_limits:
region_queryset = region_queryset | User.objects.filter(region=region)[:region_limits[region]]
return gender_queryset & region_queryset
La función de consulta se puede abstraer aún más con el conocimiento de todas las consultas que planea admitir, pero esto debería servir como un ejemplo.
Si está utilizando un ORM diferente, la misma idea se puede traducir a eso también, ya que cualquier buen ORM tendría la unión y la abstracción de intersección.
Usaría un lenguaje de programación para generar las sentencias SQL, pero a continuación hay una solución en mySQL puro. Una suposición hecha: siempre hay suficiente hombre / mujer en una región para ajustarse a los números (por ejemplo, ¿qué ocurre si no hay mujeres viviendo en el norte?).
La rutina es el cálculo previo de las cantidades de fila necesarias. El límite no se puede especificar con una variable. Soy más un tipo oráculo donde tenemos funciones analíticas. MySQL also provides this to some extend by allowing variables. So I set the target regions and gender and calculate the breakdown. Then I limit my output using the calculations.
This query shows the counts to proof the concept.
set @male=600;
set @female=400;
set @north=100;
set @south=200;
set @east=300;
set @west=400;
set @north_male=@north*(@male/(@male+@female));
set @south_male=@south*(@male/(@male+@female));
set @east_male =@east *(@male/(@male+@female));
set @west_male =@west *(@male/(@male+@female));
set @north_female=@north*(@female/(@male+@female));
set @south_female=@south*(@female/(@male+@female));
set @east_female =@east *(@female/(@male+@female));
set @west_female =@west *(@female/(@male+@female));
select gender, region, count(*)
from (
select * from (select @north_male :=@north_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''North'' ) mn where row>=0
union all select * from (select @south_male :=@south_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''South'' ) ms where row>=0
union all select * from (select @east_male :=@east_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''East'' ) me where row>=0
union all select * from (select @west_male :=@west_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''West'' ) mw where row>=0
union all select * from (select @north_female:=@north_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''North'' ) fn where row>=0
union all select * from (select @south_female:=@south_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''South'' ) fs where row>=0
union all select * from (select @east_female :=@east_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''East'' ) fe where row>=0
union all select * from (select @west_female :=@west_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''West'' ) fw where row>=0
) a
group by gender, region
order by gender, region;
Salida:
Female East 120
Female North 40
Female South 80
Female West 160
Male East 180
Male North 60
Male South 120
Male West 240
Remove the outer part to get the real records:
set @male=600;
set @female=400;
set @north=100;
set @south=200;
set @east=300;
set @west=400;
set @north_male=@north*(@male/(@male+@female));
set @south_male=@south*(@male/(@male+@female));
set @east_male =@east *(@male/(@male+@female));
set @west_male =@west *(@male/(@male+@female));
set @north_female=@north*(@female/(@male+@female));
set @south_female=@south*(@female/(@male+@female));
set @east_female =@east *(@female/(@male+@female));
set @west_female =@west *(@female/(@male+@female));
select * from (select @north_male :=@north_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''North'' ) mn where row>=0
union all select * from (select @south_male :=@south_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''South'' ) ms where row>=0
union all select * from (select @east_male :=@east_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''East'' ) me where row>=0
union all select * from (select @west_male :=@west_male-1 as row, userid, gender, region from users where gender = ''Male'' and region = ''West'' ) mw where row>=0
union all select * from (select @north_female:=@north_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''North'' ) fn where row>=0
union all select * from (select @south_female:=@south_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''South'' ) fs where row>=0
union all select * from (select @east_female :=@east_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''East'' ) fe where row>=0
union all select * from (select @west_female :=@west_female-1 as row, userid, gender, region from users where gender = ''Female'' and region = ''West'' ) fw where row>=0
;
For testing I have written a procedure which does create 10000 sample records fully random:
use test;
drop table if exists users;
create table users (userid int not null auto_increment, gender VARCHAR (20), region varchar(20), primary key (userid) );
drop procedure if exists load_users_table;
delimiter #
create procedure load_users_table()
begin
declare l_max int unsigned default 10000;
declare l_cnt int unsigned default 0;
declare l_gender varchar(20);
declare l_region varchar(20);
declare l_rnd smallint;
truncate table users;
start transaction;
WHILE l_cnt < l_max DO
set l_rnd = floor( 0 + (rand()*2) );
if l_rnd = 0 then
set l_gender = ''Male'';
else
set l_gender = ''Female'';
end if;
set l_rnd=floor(0+(rand()*4));
if l_rnd = 0 then
set l_region = ''North'';
elseif l_rnd=1 then
set l_region = ''South'';
elseif l_rnd=2 then
set l_region = ''East'';
elseif l_rnd=3 then
set l_region = ''West'';
end if;
insert into users (gender, region) values (l_gender, l_region);
set l_cnt=l_cnt+1;
end while;
commit;
end #
delimiter ;
call load_users_table();
select gender, region, count(*)
from users
group by gender, region
order by gender, region;
Hope this all helps you. The bottom line is: Use a UNION ALL
and restrict with pre-calculated variables not LIMIT
.
I expect you''d want to generate a bunch of queries based on the required filters.
I''ll explain a possible approach, with a full code sample - but note the caveats later on.
I''ll also address the issue where you can''t fulfil the requested sample from a proportional distribution, but you can from an adjusted distribution - and explain how to do that adjustment
The basic algorithm goes like this:
Start with a set of filters {F1, F2, ... Fn}
, each which has a group of values, and percentages which should be distributed amongst those values. For example F1 might be gender, with 2 values (F1V1 = Male: 60%, F1V2 = Female: 40%) You''ll also want the total sample size required (call this X
) From this starting point you can then combine all the filters items from each filter to get a single set all of the combined filter items, and the quantities required for each. The code should be able to handle any number of filters, with any number of values (either exact values, or ranges)
EG: suppose 2 filters, F1: gender, {F1V1 = Male: 60%, F1V2 = Female: 40%}, F2: region, {F2V1 = North: 50%, F2V2 = South: 50%} and a total sample required of X = 10 people.
In this sample we''d like 6 of them to be male, 4 of them to be female, 5 to be from the north, and 5 to be from the south.
Then we do
- Create an sql stub for each value in F1 - with an associated fraction of the initial percentage (ie
-
WHERE gender = ''Male''
: 0.6, -
WHERE gender = ''Female''
: 0.4 )
-
- For each item in F2 - create a new sql stub from every item from the step above - with the filter now being both the F1 Value & the F2 Value, and the associated fraction being the product of the 2 fractions. So we now have 2 x 2 = 4 items of
-
WHERE gender = ''Male'' AND region = ''North''
: 0.6 * 0.5 = 0.3, -
WHERE gender = ''Female'' AND region = ''North''
: 0.4 * 0.5 = 0.2, -
WHERE gender = ''Male'' AND region = ''South''
: 0.6*0.5 = 0.3, -
WHERE gender = ''Female'' AND region = ''South''
: 0.4*0.5 = 0.2
-
- Repeat step 2 above for every additional Filter F3 to Fn. (in our example there were only 2 filters, so we are already done)
- Calculate the limit for each SQL stub as being [fraction associated with stub] * X = total required sample size (so for our example thats 0.3 * 10 = 3 for Male/North, 0.2 * 10 = 2 for Female/North etc)
- Finally for every sql stub - turn it into a complete SQL statement , and add the limit
Code Sample
I''ll provide C# code for this, but it should be easy enough to translate this to other languages. It would be pretty tricky to attempt this in pure dynamic SQL
Note this is untested - and probably full of errors - but its an idea of the approach you could take.
I''ve defined a public method and a public class - which would be the entry point.
// This is an example of a public class you could use to hold one of your filters
// For example - if you wanted 60% male / 40% female, you could have an item with
// item1 = {Fraction: 0.6, ValueExact: ''Male'', RangeStart: null, RangeEnd: null}
// & item2 = {Fraction: 0.4, ValueExact: ''Female'', RangeStart: null, RangeEnd: null}
public class FilterItem{
public decimal Fraction {get; set;}
public string ValueExact {get; set;}
public int? RangeStart {get; set;}
public int? RangeEnd {get; set;}
}
// This is an example of a public method you could call to build your SQL
// - passing in a generic list of desired filter
// for example the dictionary entry for the above filter would be
// {Key: "gender", Value: new List<FilterItem>(){item1, item2}}
public string BuildSQL(Dictionary<string, List<FilterItem>> filters, int TotalItems)
{
// we want to build up a list of SQL stubs that can be unioned together.
var sqlStubItems = new List<SqlItem>();
foreach(var entry in filters)
{
AddFilter(entry.Key, entry.Value, sqlStubItems);
}
// ok - now just combine all of the sql stubs into one big union.
var result = ""; // Id use a stringbuilder for this normally,
// but this is probably more cross-language readable.
int limitSum = 0;
for(int i = 0; i < sqlStubItems.Count; i++) // string.Join() would be more succinct!
{
var item = sqlStubItems[i];
if (i > 0)
{
result += " UNION ";
}
int limit = (int)Math.Round(TotalItems * item.Fraction, 0);
limitSum+= limit;
if (i == sqlStubItems.Count - 1 && limitSum != TotalItems)
{
//may need to adjust one of the rounded items to account
//for rounding errors making a total that is not the
//originally required total limit.
limit += (TotalItems - limitSum);
}
result += item.Sql + " LIMIT "
+ Convert.ToString(limit);
}
return result;
}
// This method expands the number of SQL stubs for every filter that has been added.
// each existing filter is split by the number of items in the newly added filter.
private void AddFilter(string filterType,
List<FilterItem> filterValues,
List<SqlItem> SqlItems)
{
var newItems = new List<SqlItem>();
foreach(var filterItem in filterValues)
{
string filterAddon;
if (filterItem.RangeStart.HasValue && filterItem.RangeEnd.HasValue){
filterAddon = filterType + " >= " + filterItem.RangeStart.ToString()
+ " AND " + filterType + " <= " + filterItem.RangeEnd.ToString();
} else {
filterAddon = filterType + " = ''"
+ filterItem.ValueExact.Replace("''","''''") + "''";
//beware of SQL injection. (hence the .Replace() above)
}
if(SqlItems.Count() == 0)
{
newItems.Add(new SqlItem(){Sql = "Select * FROM users WHERE "
+ filterAddon, Fraction = filterItem.Fraction});
} else {
foreach(var existingItem in SqlItems)
{
newItems.Add(new SqlItem()
{
Sql = existingItem + " AND " + filterAddon,
Fraction = existingItem.Fraction * filterItem.Fraction
});
}
}
}
SqlItems.Clear();
SqlItems.AddRange(newItems);
}
// this class is for part-built SQL strings, with the fraction
private class SqlItem{
public string Sql { get; set;}
public decimal Fraction{get; set;}
}
Notes (as per comment by Sign)
- Rounding errors may mean you don''t get exactly the 600 / 400 split you were aiming for when applying a large number of filters - but should be close.
- If your dataset is not very diverse then it may not be possible to always generate the required split. This method will require an even distribution amongst the filters (so if you were doing a total of 10 people, 6 male, 4 female , 5 from the north, 5 from the south it would require 3 males from the north, 3 males from the south, 2 females from the north and 2 females from the south.)
- The people are not going to be retrieved at random - just whatever the default sort is. You would need to add something like ORDER BY RAND() (but not that as its VERY inefficient) to get a random selection.
- Beware of SQL injection. Sanitise all user input, replacing single quote
''
chars.
Badly distributed sample problem
How do you address the problem of there being insufficient items in one of our buckets to create our sample as per a representative split (that the above algorithm gives)? Or what if your numbers are not integers?
Well I won''t go so far as to provide code, but I will describe a possible approach. You''d need to alter the code above quite a bit, because a flat list of sql stubs isn''t going to cut it anymore. Instead you''d need to build a n-dimensional matrix of SQL stubs (adding a dimension for every filter F1 - n) After step 4 above has been completed (where we have our desired, but not necessarily possible numbers for each SQL stub item), what I''d expect to do is
- generate SQL to select counts for all the combined sql WHERE stubs.
- Then you''d iterate the collection - and if you hit an item where the requested limit is higher than the count (or not an integer),
- adjust the requested limit down to the count (or nearest integer).
- Then pick another item on each of the axis that is at least the above adjustment lower that its max count, and adjust it up by the same. If its not possible to find qualifying items then your requested split is not possible.
- Then adjust all the intersecting items for the upward adjusted items down again
- Repeat the step above for intersects between the intersecting points for every additional dimension to n (but toggle the adjustment between negative and positive each time)
So suppose continuing our previous example - our representative split is:
Male/North = 3, Female/North = 2, Male/South = 3, Female/South = 2, but there are only 2 Males in the north (but theres loads of people in the other groups we could pick)
- We adjust Male/North down to 2 (-1)
- We adjust Female/North to 3 (+1) and Male/South to 4 (+1)
- We adjust the Intersecting Female/South to 1 (-1). Voila! (there are no additional dimensions as we only had 2 criteria/dimensions)
This illustration may be helpful when adjusting intersecting items in higher dimensions (only showing up to 4 dimensions, but should help to picture what needs to be done! Each point represents one of our SQL stub items in the n-dimensional matrix (and has an associated limit number) A line represents a common criteria value (such as gender = male). The objective is that the total along any line should remain the same after adjustments have finished! We start with the red point, and continue for each additional dimension... In the example above we would only be looking at 2 dimensions - a square formed from the red point, the 2 orange points above and to the right of it, and the 1 green point to the NE to complete the square.
I''d go with GROUP BY
:
SELECT gender,region,count(*) FROM users GROUP BY gender,region
+----------------------+
|gender|region|count(*)|
+----------------------+
|f |E | 129|
|f |N | 43|
|f |S | 84|
|f |W | 144|
|m |E | 171|
|m |N | 57|
|m |S | 116|
|m |W | 256|
+----------------------+
You can verify you have 600 males, 400 females, 100 North, 200 South, 300 East and 400 West.
You can include other fields as well.
Para campos de rango, como edad e ingresos, puede seguir este ejemplo:
SELECT
gender,
region,
case when age < 30 then ''Young''
when age between 30 and 59 then ''Middle aged''
else ''Old'' end as age_range,
count(*)
FROM users
GROUP BY gender,region, age_range
Entonces, los resultados serían como:
+----------------------------------+
|gender|region|age |count(*)|
+----------------------------------+
|f |E |Middle aged| 56|
|f |E |Old | 31|
|f |E |Young | 42|
|f |N |Middle aged| 14|
|f |N |Old | 11|
|f |N |Young | 18|
|f |S |Middle aged| 40|
|f |S |Old | 23|
|f |S |Young | 21|
|f |W |Middle aged| 67|
|f |W |Old | 42|
|f |W |Young | 35|
|m |E |Middle aged| 77|
|m |E |Old | 56|
|m |E |Young | 38|
|m |N |Middle aged| 13|
|m |N |Old | 25|
|m |N |Young | 19|
|m |S |Middle aged| 46|
|m |S |Old | 39|
|m |S |Young | 31|
|m |W |Middle aged| 103|
|m |W |Old | 66|
|m |W |Young | 87|
+----------------------------------+
Well, I think the question is about randomly getting the records and not in the proportion of 60/40 for all regions. I have done for Region and Gender. It can be generalized to other fields like age, income and ethnicity in the same way.
Declare @Mlimit bigint
Declare @Flimit bigint
Declare @Northlimit bigint
Declare @Southlimit bigint
Declare @Eastlimit bigint
Declare @Westlimit bigint
Set @Mlimit= 600
Set @Flimit=400
Set @Northlimit= 100
Set @Southlimit=200
Set @Eastlimit=300
Set @Westlimit=400
CREATE TABLE #Users(
[UserId] [int] NOT NULL,
[gender] [varchar](10) NULL,
[region] [varchar](10) NULL,
[age] [int] NULL,
[ethnicity] [varchar](50) NULL,
[income] [bigint] NULL
)
Declare @MnorthCnt bigint
Declare @MsouthCnt bigint
Declare @MeastCnt bigint
Declare @MwestCnt bigint
Declare @FnorthCnt bigint
Declare @FsouthCnt bigint
Declare @FeastCnt bigint
Declare @FwestCnt bigint
Select @MnorthCnt=COUNT(*) from users where gender=''male'' and region=''north''
Select @FnorthCnt=COUNT(*) from users where gender=''female'' and region=''north''
Select @MsouthCnt=COUNT(*) from users where gender=''male'' and region=''south''
Select @FsouthCnt=COUNT(*) from users where gender=''female'' and region=''south''
Select @MeastCnt=COUNT(*) from users where gender=''male'' and region=''east''
Select @FeastCnt=COUNT(*) from users where gender=''female'' and region=''east''
Select @MwestCnt=COUNT(*) from users where gender=''male'' and region=''west''
Select @FwestCnt=COUNT(*) from users where gender=''female'' and region=''west''
If (@Northlimit=@MnorthCnt+@FnorthCnt)
begin
Insert into #Users select * from Users where region=''north''
set @Northlimit=0
set @Mlimit-=@MnorthCnt
set @Flimit-=@FnorthCnt
set @MnorthCnt=0
set @FnorthCnt=0
end
If (@Southlimit=@MSouthCnt+@FSouthCnt)
begin
Insert into #Users select * from Users where region=''South''
set @Southlimit=0
set @Mlimit-=@MSouthCnt
set @Flimit-=@FSouthCnt
set @MsouthCnt=0
set @FsouthCnt=0
end
If (@Eastlimit=@MEastCnt+@FEastCnt)
begin
Insert into #Users select * from Users where region=''East''
set @Eastlimit=0
set @Mlimit-=@MEastCnt
set @Flimit-=@FEastCnt
set @MeastCnt=0
set @FeastCnt=0
end
If (@Westlimit=@MWestCnt+@FWestCnt)
begin
Insert into #Users select * from Users where region=''West''
set @Westlimit=0
set @Mlimit-=@MWestCnt
set @Flimit-=@FWestCnt
set @MwestCnt=0
set @FwestCnt=0
end
If @MnorthCnt<@Northlimit
Begin
insert into #Users select top (@Northlimit-@MnorthCnt) * from Users where gender=''female'' and region=''north''
and userid not in (select userid from #users)
set @Flimit-=(@Northlimit-@MnorthCnt)
set @FNorthCnt-=(@Northlimit-@MnorthCnt)
set @Northlimit-=(@Northlimit-@MnorthCnt)
End
If @FnorthCnt<@Northlimit
Begin
insert into #Users select top (@Northlimit-@FnorthCnt) * from Users where gender=''male'' and region=''north''
and userid not in (select userid from #users)
set @Mlimit-=(@Northlimit-@FnorthCnt)
set @MNorthCnt-=(@Northlimit-@FnorthCnt)
set @Northlimit-=(@Northlimit-@FnorthCnt)
End
if @MsouthCnt<@southlimit
Begin
insert into #Users select top (@southlimit-@MsouthCnt) * from Users where gender=''female'' and region=''south''
and userid not in (select userid from #users)
set @Flimit-=(@southlimit-@MsouthCnt)
set @FSouthCnt-=(@southlimit-@MsouthCnt)
set @southlimit-=(@southlimit-@MsouthCnt)
End
if @FsouthCnt<@southlimit
Begin
insert into #Users select top (@southlimit-@FsouthCnt) * from Users where gender=''male'' and region=''south''
and userid not in (select userid from #users)
set @Mlimit-=(@southlimit-@FsouthCnt)
set @MSouthCnt-=(@southlimit-@FsouthCnt)
set @southlimit-=(@southlimit-@FsouthCnt)
End
if @MeastCnt<@eastlimit
Begin
insert into #Users select top (@eastlimit-@MeastCnt) * from Users where gender=''female'' and region=''east''
and userid not in (select userid from #users)
set @Flimit-=(@eastlimit-@MeastCnt)
set @FEastCnt-=(@eastlimit-@MeastCnt)
set @eastlimit-=(@eastlimit-@MeastCnt)
End
if @FeastCnt<@eastlimit
Begin
insert into #Users select top (@eastlimit-@FeastCnt) * from Users where gender=''male'' and region=''east''
and userid not in (select userid from #users)
set @Mlimit-=(@eastlimit-@FeastCnt)
set @MEastCnt-=(@eastlimit-@FeastCnt)
set @eastlimit-=(@eastlimit-@FeastCnt)
End
if @MwestCnt<@westlimit
Begin
insert into #Users select top (@westlimit-@MwestCnt) * from Users where gender=''female'' and region=''west''
and userid not in (select userid from #users)
set @Flimit-=(@westlimit-@MwestCnt)
set @FWestCnt-=(@westlimit-@MwestCnt)
set @westlimit-=(@westlimit-@MwestCnt)
End
if @FwestCnt<@westlimit
Begin
insert into #Users select top (@westlimit-@FwestCnt) * from Users where gender=''male'' and region=''west''
and userid not in (select userid from #users)
set @Mlimit-=(@westlimit-@FwestCnt)
set @MWestCnt-=(@westlimit-@FwestCnt)
set @westlimit-=(@westlimit-@FwestCnt)
End
IF (@MnorthCnt>=@Northlimit and @FnorthCnt>=@Northlimit and @MsouthCnt>=@southlimit and @FsouthCnt>=@southlimit and @MeastCnt>=@eastlimit and @FeastCnt>=@eastlimit and @MwestCnt>=@westlimit and @FwestCnt>=@westlimit and not(@Mlimit=0 and @Flimit=0))
Begin
---Create Cursor
DECLARE UC CURSOR FAST_forward
FOR
SELECT *
FROM Users
where userid not in (select userid from #users)
Declare @UserId [int] ,
@gender [varchar](10) ,
@region [varchar](10) ,
@age [int] ,
@ethnicity [varchar](50) ,
@income [bigint]
OPEN UC
FETCH NEXT FROM UC
INTO @UserId ,@gender, @region, @age, @ethnicity, @income
WHILE @@FETCH_STATUS = 0 and not (@Mlimit=0 and @Flimit=0)
BEGIN
If @gender=''male'' and @region=''north'' and @Northlimit>0 AND @Mlimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Mlimit-=1
set @MNorthCnt-=1
set @Northlimit-=1
end
If @gender=''male'' and @region=''south'' and @southlimit>0 AND @Mlimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Mlimit-=1
set @MsouthCnt-=1
set @Southlimit-=1
end
If @gender=''male'' and @region=''east'' and @eastlimit>0 AND @Mlimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Mlimit-=1
set @MeastCnt-=1
set @eastlimit-=1
end
If @gender=''male'' and @region=''west'' and @westlimit>0 AND @Mlimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Mlimit-=1
set @MwestCnt-=1
set @westlimit-=1
end
If @gender=''female'' and @region=''north'' and @Northlimit>0 AND @flimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Flimit-=1
set @FNorthCnt-=1
set @Northlimit-=1
end
If @gender=''female'' and @region=''south'' and @southlimit>0 AND @flimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @Flimit-=1
set @FsouthCnt-=1
set @Southlimit-=1
end
If @gender=''female'' and @region=''east'' and @eastlimit>0 AND @flimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @flimit-=1
set @feastCnt-=1
set @eastlimit-=1
end
If @gender=''female'' and @region=''west'' and @westlimit>0 AND @flimit>0
begin
insert into #Users values (@UserId ,@gender, @region, @age, @ethnicity, @income)
set @flimit-=1
set @fwestCnt-=1
set @westlimit-=1
end
FETCH NEXT FROM UC
INTO @UserId ,@gender, @region, @age, @ethnicity, @income
END
CLOSE UC
DEALLOCATE UC
end
Select * from #Users
SELECT GENDER, REGION, COUNT(*) AS COUNT FROM #USERS
GROUP BY GENDER, REGION
DROP TABLE #Users