programing

제한이 있는 수많은 쿼리의 "교차"를 가져오기 위한 MySQL 쿼리

copysource 2023. 8. 23. 23:39
반응형

제한이 있는 수많은 쿼리의 "교차"를 가져오기 위한 MySQL 쿼리

다음 필드가 있는 단일 mySQL 테이블(사용자)이 있다고 가정합니다.

userid  
gender  
region  
age  
ethnicity  
income

나는 사용자가 입력한 숫자를 기준으로 총 레코드 수를 반환할 수 있기를 원합니다.게다가, 그들은 또한 추가적인 기준을 제공할 것입니다.

가장 단순한 예에서, 그들은 1,000개의 레코드를 요구할 수 있습니다. 여기서 600개의 레코드는 성별 = '남성'이고 400개의 레코드는 성별 = '여성'입니다.그것은 충분히 간단합니다.

자, 이제 한 걸음 더 나아가세요.이제 지역을 지정하려고 한다고 가정합니다.

GENDER  
    Male:   600 records  
    Female: 400 records  

REGION  
    North:  100 records  
    South:  200 records  
    East:   300 records  
    West:   400 records

다시 1000개의 기록만 반환해야 하지만, 결국에는 남자 600명, 여자 400명, 북부 100명, 남부 200명, 동부 300명, 서부 400명이 되어야 합니다.

이것이 올바른 구문이 아니라는 것을 알지만 유사 마이를 사용합니다.SQL 코드는 제가 하려는 일을 설명해 줍니다.

(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)

일회성 쿼리를 찾는 것이 아닙니다.각 기준 내의 총 레코드 수와 레코드 수는 사용자의 입력에 따라 지속적으로 변경됩니다.그래서 저는 하드 코딩된 솔루션이 아닌 반복적으로 재사용될 수 있는 일반적인 솔루션을 생각해 내려고 노력하고 있습니다.

상황을 더 복잡하게 만들려면 이제 기준을 더 추가합니다.또한 연령, 민족 및 소득이 각각 그룹별로 설정된 레코드 수와 위에 추가된 추가 코드가 있을 수 있습니다.

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.

이것이 하나의 쿼리에 쓸 수 있는지 아니면 여러 개의 문과 반복이 필요한지 모르겠습니다.

기준 완화


다차원 기준을 단일 수준 기준으로 평탄화할 수 있습니다.

enter image description here

이제 이 기준은 다음과 같이 하나의 쿼리에서 달성할 수 있습니다.

(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)

문제

  • 항상 올바른 결과를 반환하지는 않습니다.예를 들어, 북쪽에서 온 남성 사용자가 40명 미만인 경우 쿼리는 1,000개 미만의 레코드를 반환합니다.

기준 조정


남성과 북부 출신의 사용자가 40명 미만이라고 가정해 보겠습니다.그런 다음 "남성"과 "북부"의 누락된 수량을 커버할 수 있도록 다른 기준 수량을 조정해야 합니다.저는 bare SQL로는 불가능하다고 생각합니다.이것은 제가 염두에 두고 있는 사이비 코드입니다.단순화를 위해 남성, 여성, 북부, 남부에 대해서만 문의할 것 같습니다.

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(...);
    }
}

북부 남성이 30명밖에 없다고 치자.그래서 우리는 +10 수컷과 +10 북쪽을 조정해야 합니다.

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  }

'남성' + '남쪽'은 '남성' 조정 조건과 일치하는 첫 번째 조건입니다.+10만큼 증가시키고 "잔류 조건" 목록에서 제거합니다.우리가 남쪽을 늘렸기 때문에 다른 조건에서 다시 줄여야 합니다.따라서 "남부" 조건을 "조정할" 목록에 추가합니다.

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 }

'남쪽'과 일치하는 조건을 찾고 동일한 프로세스를 반복합니다.

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 }

그리고 마지막으로

{ gender: 'Female', region: 'North', limit: 70  }
{ gender: 'Female', region: 'South', limit: 110  }
{ gender: 'Male',   region: 'South', limit: 90 }

저는 아직 정확한 조정 시행을 생각해내지 못했습니다.그것은 제가 예상했던 것보다 더 어렵습니다.구현 방법을 알게 되면 업데이트하겠습니다.

당신이 설명하는 문제는 다차원 모델링 문제입니다.특히 여러 차원을 따라 계층화된 표본을 동시에 얻으려고 합니다.이것의 핵심은 가장 작은 수준의 세분화로 내려가서 거기서 샘플을 구축하는 것입니다.

저는 당신이 모든 수준에서 표본이 대표적이기를 원한다고 생각합니다.즉, "North"의 모든 사용자가 여성인 것은 아닙니다.아니면 모든 "남성"이 "서부" 출신일 수도 있습니다. 비록 그것이 최종 기준을 충족하더라도 말입니다.

각 차원의 총 레코드 수, 차원 및 할당에 대해 생각하는 것으로 시작합니다.예를 들어, 첫 번째 샘플의 경우 다음과 같이 생각합니다.

  • 1000개의 레코드
  • 2차원: 성별, 지역
  • 성별 분할: 60%, 40%
  • 지역 분할: 10%, 20%, 30%, 40%

그런 다음 이 숫자를 각 성별/지역 조합에 할당하려고 합니다.숫자는 다음과 같습니다.

  • 북쪽, 수컷: 60
  • 북, 여: 40
  • 남, 남: 120
  • 남,여:80
  • 동쪽, 수컷: 180
  • 동쪽, 여자: 120
  • 서쪽, 남자: 240
  • 서, 여: 160

여러분은 이것들이 치수를 따라 합산되는 것을 보실 수 있을 것입니다.

각 셀의 숫자 계산은 매우 쉽습니다.백분율에 총계를 곱한 값입니다.그래서 "동쪽, 여자"는 30%*40%*1000%... Voila!값은 120입니다.

솔루션은 다음과 같습니다.

  1. 각 차원의 입력을 전체의 백분율로 사용합니다.그리고 각 차원을 따라 최대 100%까지 추가해야 합니다.
  2. 각 셀에 대한 예상 백분율 표를 만듭니다.이것은 각 차원에 따른 백분율의 곱입니다.
  3. 예상 백분율에 전체 합계를 곱합니다.
  4. 최종 쿼리는 아래에 요약되어 있습니다.

해 보겠습니다.cells와 원본 데이터 카 원 데 이 터 본 와 트 운 터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;

이것은 솔루션의 개요입니다.당신은 치수에 대한 세부사항을 작성해야 합니다.

이것은 모든 셀에 대한 충분한 데이터가 있는 한 작동합니다.

실제로 이러한 유형의 다차원 계층화 표본 추출을 수행할 때 셀이 비어 있거나 너무 작을 위험이 있습니다.이 경우 나중에 추가 패스를 사용하여 이 문제를 해결할 수 있습니다.충분히 큰 세포에서 가능한 것을 채취합니다.이러한 데이터는 일반적으로 필요한 데이터의 대부분을 차지합니다.그런 다음 에 레코드를 추가하여 최종 카운트를 충족합니다.추가할 레코드는 가장 필요한 치수에 따라 필요한 값과 일치하는 레코드입니다.그러나 이 솔루션은 단순히 기준을 충족하기에 충분한 데이터가 있다고 가정합니다.

귀하의 요청의 문제는 제안된 숫자를 달성하는 데 사용할 수 있는 옵션이 엄청나게 많다는 것입니다.

       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

북쪽, 동쪽, 서쪽(남쪽은 항상 남성: 200)을 결합하는 것만으로도 제안된 숫자를 달성하는 방법에 대한 400가지 가능성을 얻을 수 있습니다.또한 각 "클래스"(남성/북부 = "클래스")당 레코드 수가 제한적일 때는 더욱 복잡해집니다.

최필요경이 할 수 .MIN(COUNT(gender), COUNT(location))위 표의 모든 셀에 대한 레코드입니다(상대 셀이 0인 경우).

이는 다음과 같습니다.

       Male    Female    
---------------------
North:  100       100      
South:  200       200
East:   300       300 
West:   400       400 

은 각 쌍의 필요가 .AVAILABLE(gender, location).

특정 적합치를 찾는 것은 반마법 제곱[1][2]에 가깝습니다.

math.stackexchange.com 에는 이에 대한 몇 가지 질문이 있습니다 [3][4].

저는 이것들을 어떻게 구성할 것인지에 대한 논문을 읽게 되었습니다. 그리고 저는 이것을 하나의 선택으로 하는 것이 가능한지 의심스럽습니다.

레코드가 충분하지만 이런 상황이 발생하지 않을 경우:

       Male    Female    
---------------------
North:  100         0      
South:  200       200
East:   300         0 
West:   200       200 

저는 반복적인 수조 위치를 선택하고 각 단계에서 비례적인 수의 남성/여성을 추가할 것입니다.

  1. M: 100(16%);F: 0(0%)
  2. M: 100(16%), F: 200(50%)
  3. M: 400(66%), F: 200(50%)
  4. M: 600(100%);F: 400(100%)

그러나 대략적인 결과만 제공되며, 이러한 결과를 검증한 후에는 결과를 몇 번 반복하고 각 범주의 카운트를 "충분히 양호"하도록 조정할 수 있습니다.

저는 데이터베이스의 분포 지도를 만들고 샘플링 논리를 구현하는 데 사용할 것입니다.보너스에는 사용자에게 빠른 기록 정보 피드백을 추가하고 서버에 추가 부담을 주지 않을 수 있습니다.반대로, 당신은 데이터베이스와 지도를 동기화하기 위한 메커니즘을 구현해야 할 것입니다.

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":{
  ...
}}

그래서 사용자는 선택합니다.

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

선형 분포 계산:

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

지도를 확인해 보겠습니다.

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

선형 분포를 충족하는 모든 항목을 정상으로 표시하고 흑자를 기록합니다. 것 (를 들어 남성..north) , 에 대해 에서 29male.north.u20에 대해 하게 사용하게 됩니다.첫 번째 실행 후 다른 영역에서 누락된 각 기준을 조정합니다. 래서처럼.tmp.male.south.u20+=8;tmp.male.south.f21to29-=8;.

그것을 맞추는 것은 꽤 지루합니다.

결국 사소한 SQL 쿼리를 구성하는 데 사용할 수 있는 올바른 배포를 갖게 됩니다.

이것은 두 단계로 해결할 수 있습니다.성별과 지역이 차원인 예를 들어 어떻게 하는지 설명하겠습니다.그러면 좀 더 일반적인 경우를 설명하겠습니다.첫 번째 단계에서 우리는 8개 변수의 방정식 시스템을 푼 다음 1단계에서 발견된 솔루션에 의해 제한된 8개 선택 문의 분리된 결합을 취합니다.모든 행에는 8개의 가능성만 있습니다.그들은 수컷이나 암컷이 될 수 있고 그 지역은 북쪽, 남쪽, 동쪽 또는 서쪽 중 하나입니다.자, 이제.

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 

방정식은 다음과 같습니다.

 X1+X2+X3+X4=600
 X5+X6+X7+X8=400
 X1+X5=100
 X2+X6=200
 X3+X7=300
 X4+X8=400

이제 X1, X2에 대해 해결합니다.위의 X8.여러 가지 솔루션이 있습니다(잠시 후에 해결 방법을 설명하겠습니다). 솔루션은 다음과 같습니다.

X1=60, X2=120, X3=180,X4=240,X5=40,X6=80,X7=120,X8=160.

이제 8개의 선택 항목을 조합하여 결과를 얻을 수 있습니다.

(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);

데이터베이스에 60개의 행이 없는 경우 위의 첫 번째 선택을 충족하면 지정된 특정 솔루션이 작동하지 않습니다.그래서 우리는 다른 제약을 추가해야 합니다, 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');

이제 모든 분열을 허용하는 이 사례에 대해 일반화해 보겠습니다.방정식은 E:

 X1+X2+X3+X4=n1
 X5+X6+X7+X8=n2
 X1+X5=m1
 X2+X6=m2
 X3+X7=m3
 X4+X8=m4

n1, n2, m1, m2, m3, m4는 주어지고 n1+n2=(m1+m2+m3+m4)을 만족합니다.그래서 우리는 위의 방정식 LT와 E를 푸는 것으로 문제를 줄였습니다.이것은 단순한 선형 프로그래밍 문제이며 simplex 방법 또는 다른 방법을 사용하여 해결할 수 있습니다.또 다른 가능성은 이것을 선형 디오판틴 방정식의 시스템으로 보고 해결책을 찾기 위해 방법을 사용하는 것입니다.어쨌든 저는 위의 방정식에 대한 해를 찾는 것으로 문제를 줄였습니다. (식들이 특별한 형태라는 것을 고려할 때, 단순한 방법을 사용하거나 선형 디오판틴 방정식의 시스템을 푸는 것보다 더 빠른 방법이 있을 수 있습니다.)Xi를 위해 해결하면 최종 해결책은 다음과 같습니다.

(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);

n개의 가능성을 갖는 차원 D를 D:n으로 표시합니다.D1:n1, D2:n2, ...가 있다고 가정합니다.DM:nM 치수.그러면 n1*n2*가 생성됩니다.nM 변수.생성된 방정식의 수는 n1+n2+...nM. 대신 일반적인 방법을 정의합니다. 3차원, 4차원 및 2차원의 다른 경우를 예로 들겠습니다. D1의 가능한 값을 d11, d12, d13, D2는 d21, d22, d23, d24, D3 값은 d31, d32입니다.변수는 24개가 될 것이며 방정식은 다음과 같습니다.

 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

어디에

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.

제약 조건을 더 적게 추가합니다.그런 다음 X1, X2, X24에 대해 해결합니다.24개의 선택 문을 만들고 분리된 결합을 취합니다.우리는 어떤 차원에서도 비슷하게 풀 수 있습니다.

그래서 요약하자면: 주어진 차원 D1:n1, D2:n2, ...DM:nM은 위에서 설명한 n1*n2*에 해당하는 선형 프로그래밍 문제를 해결할 수 있습니다.nM 변수를 사용한 다음 n1*n2*에 대한 분리 결합을 취하여 솔루션을 생성합니다.nM select 문.예, 선택된 문을 통해 솔루션을 생성할 수 있지만 먼저 방정식을 풀고 각 n1*n2*에 대한 카운트를 구하여 한계를 결정해야 합니다.nM 변수.

현상금은 끝났지만 당신이 관심 있는 사람들을 위해 조금 더 추가할 것입니다.저는 여기서 해결책이 있다면 어떻게 해결해야 하는지를 완전히 보여줬다고 주장합니다.

내 접근 방식을 명확히 하기 위해서야3차원의 경우, 나이를 3가지 가능성 중 하나로 나눈다고 가정해 보겠습니다.그런 다음 질문과 같이 성별과 지역을 잘 사용합니다.각 사용자는 해당 범주에 속하는 위치에 따라 24개의 서로 다른 가능성이 있습니다.Xi가 최종 결과에서 각 가능성의 수가 되도록 합니다.각 행이 각 가능성 중 하나를 나타내는 행렬을 작성하겠습니다.각 사용자는 최대 1에서 f까지, 1에서 북쪽, 남쪽, 동쪽 또는 서쪽, 1에서 연령 범주에 기여합니다.그리고 사용자에게 주어진 가능성은 24개뿐입니다.행렬을 보여드리겠습니다. (abc) 3세, (nsew) 부위 및 (mf) 남성 또는 여성: a는 10세 이하, b는 11세에서 30세, c는 31세에서 50세 사이입니다.

     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

각 행은 결과에 기여하는 경우 열에 1이 있는 사용자를 나타냅니다.예를 들어, 첫 번째 행에는 a에 대해 1, n에 대해 1, 폼에 대해 1이 표시됩니다.에서 온입니다.Xi는 최종 결과에서 그러한 종류의 행의 수를 나타냅니다.X1이 10이라고 가정해보죠. 이는 최종 결과가 10개라는 것을 의미합니다. 이 결과들은 모두 북쪽에서 온 것이고, 남성이고, 10보다 작거나 같습니다., .", 이제우리모것합을야산해합니다든자는▁ok합,니다야합해자산. 8은 8입니다.X1+X2+X3+X4+X5+X6+X7+X8모든 행의 수명이 10보다 작거나 같은 행입니다.그들은 우리가 그 범주에 대해 선택한 것을 합산해야 합니다.다음 두 세트의 8도 마찬가지입니다.

지금까지 우리는 방정식을 얻습니다. (na는 10세 미만의 숫자, nb는 10세에서 20세 사이의 숫자, nc는 50세 미만의 숫자입니다.

X1+X2+X3+X4+X5+X6+X7+X8 =  na
X9+X10+X11 + .... X16 = nb
X17+X18+X19+...           X24=nc

그것들은 나이 차이입니다.이제 지역 분할을 살펴보겠습니다."n"열에 있는 변수를 더하면 됩니다.

X1+X2+X9+X10+X17+X18 = nn
X3+X4+X11+X12+X19+20=ns
...

등. 열을 아래로 내려다보는 것만으로 어떻게 방정식을 얻을 수 있는지 알 수 있습니까?총 3+4+2 방정식을 제시하면서 w와 mf를 계속합니다.제가 여기서 한 일은 아주 간단합니다.저는 당신이 선택한 행이 각각의 3차원에 1개씩 기여하고 24개의 가능성만 있다고 생각합니다.그런 다음 Xi를 각 가능성의 숫자로 지정하면 해결해야 할 방정식을 얻을 수 있습니다.제가 보기에 어떤 방법을 생각해내든지 간에 그 방정식들에 대한 해결책이 되어야 할 것 같습니다.다시 말해서, 저는 단순히 그 방정식들을 푸는 관점에서 문제를 재구성했습니다.

이제 우리는 분수 행을 가질 수 없기 때문에 정수 솔루션을 원합니다.이것들은 모두 선형 방정식입니다.하지만 우리는 정수 솔루션을 원합니다.https://www.math.uwaterloo.ca/ ~wgilbert/Research/GilbertPathria.pdf를 해결하는 방법을 설명하는 논문에 대한 링크가 있습니다.

SQL에서 비즈니스 로직을 구성하는 것은 사소한 변경사항도 흡수하는 능력을 방해하기 때문에 결코 좋은 생각이 아닙니다.

ORM에서 이 작업을 수행하고 SQL에서 비즈니스 로직을 추상화하는 것이 좋습니다.

예를 들어 Django를 사용하는 경우:

모델의 모양은 다음과 같습니다.

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()

쿼리 기능은 다음과 같습니다.

# 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

지원하려는 모든 쿼리에 대한 지식으로 쿼리 기능을 추가로 추상화할 수 있지만 이를 예로 들 수 있습니다.

다른 ORM을 사용하는 경우, 좋은 ORM이 결합 및 교차로 추상화를 갖는 것과 동일한 아이디어가 그것으로 변환될 수 있습니다.

SQL 문을 생성하기 위해 프로그래밍 언어를 사용하지만 아래는 순수 mySQL의 솔루션입니다.한 가지 가정:한 지역에는 항상 숫자에 맞는 충분한 수의 남성/여성이 있습니다(예: 북쪽에 거주하는 여성이 없는 경우).

루틴에서는 필요한 행 수량을 사전 계산합니다.변수를 사용하여 한계를 지정할 수 없습니다.저는 분석 기능이 있는 오라클에 더 가깝습니다.MySQL은 또한 변수를 허용함으로써 이 기능을 어느 정도 제공합니다.그래서 저는 목표 지역과 성별을 정하고 그 내역을 계산합니다.그런 다음 계산을 사용하여 출력을 제한합니다.

이 쿼리는 개념을 증명하기 위한 카운트를 표시합니다.

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;

출력:

Female  East   120
Female  North   40
Female  South   80
Female  West   160
Male    East   180
Male    North   60
Male    South  120
Male    West   240

외부 부분을 제거하여 실제 레코드를 가져옵니다.

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
;

테스트를 위해 10000개의 샘플 레코드를 완전히 무작위로 생성하는 절차를 작성했습니다.

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;

이 모든 것이 당신에게 도움이 되길 바랍니다.결론은 다음과 같습니다.을 합니다.UNION ALL 사전 가 아닌 사전 계산된 변수로 합니다.LIMIT.

글쎄요, 저는 모든 지역에서 60/40의 비율이 아니라 무작위로 기록을 얻는 것이 문제라고 생각합니다.저는 지역과 성별을 위해 했습니다.그것은 같은 방식으로 나이, 소득, 민족과 같은 다른 분야로 일반화될 수 있습니다.

    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

필요한 필터를 기반으로 쿼리를 많이 생성해야 할 것으로 예상됩니다.

전체 코드 샘플을 사용하여 가능한 접근 방식을 설명하겠지만, 나중에 주의할 점을 유의하십시오.
한 표본을 수 분포에서는 할 수 수정 합니다. 그리고 조정 방법을 설명합니다.

기본 알고리즘은 다음과 같습니다.

하기 필터 로으시작{F1, F2, ... Fn}각 값의 그룹과 해당 값 사이에 분포되어야 하는 백분율을 가집니다.를 들어, 은 두 %, = %)을 성별일 수 필요한 총 입니다. (이것을 F1V1 = 60%, F1V2 = 40%라고 .)X그런 다음 이 시작점에서 각 필터의 모든 필터 항목을 결합하여 결합된 모든 필터 항목과 각 필터에 필요한 수량을 단일 세트로 얻을 수 있습니다.코드는 임의의 수의 값(정확한 값 또는 범위)으로 임의의 수의 필터를 처리할 수 있어야 합니다.

EG: 2개의 필터, F1: 성별, {F1V1 = 남성: 60%, F1V2 = 여성: 40%, F2: 지역, {F2V1 = 북쪽: 50%, F2V2 = 남쪽: 50%}, 총 샘플이 필요한 X = 10명이라고 가정합니다.
이 샘플에서 우리는 그들 중 6명이 남성이고, 4명이 여성이고, 5명은 북쪽에서, 5명은 남쪽에서 오기를 원합니다.

그럼 우리는.

  1. F1의 각 값에 대한 sql 스텁을 생성합니다. 초기 백분율의 관련 부분(예:
    • WHERE gender = 'Male': 0.6,
    • WHERE gender = 'Female': 0.4 )
  2. F2의 각 항목에 대해 - 위의 단계의 모든 항목에서 새 sql 스텁을 생성합니다. 필터는 이제 F1 값과 F2 값이 되고 관련 분수는 두 분수의 곱이 됩니다.이제 2 x 2 = 4개의 항목이 있습니다.
    • 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
  3. 모든 추가 필터 F3 - Fn에 대해 위의 2단계를 반복합니다(예에서는 필터가 2개뿐이므로 이미 완료되었습니다).
  4. 각 SQL 스텁에 대한 제한을 [스텁과 연결됨] * X = 총 필요한 샘플 크기로 계산합니다(따라서 이 예에서는 수컷/북쪽의 경우 0.3 * 10 = 3, 암컷/북쪽의 경우 0.2 * 10 = 2 등).
  5. 마지막으로 모든 SQL 스텁에 대해 - 완전한 SQL 문으로 변환하고 제한을 추가합니다.

코드 샘플

이에 대한 C# 코드를 제공하겠지만, 다른 언어로 번역하는 것은 충분히 쉬울 것입니다.순수 동적 SQL에서 이를 시도하는 것은 매우 까다로울 것입니다.

이는 테스트되지 않은 오류로 가득 차 있지만 사용자가 취할 수 있는 접근 방식에 대한 아이디어입니다.

저는 공개 방법과 공개 클래스를 정의했는데, 이는 시작점이 될 것입니다.

// 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;}
}

참고사항(사인별 설명에 따름)

  • 반올림 오차는 많은 수의 필터를 적용할 때 목표로 했던 600/400 분할을 정확하게 얻지 못한다는 것을 의미할 수 있지만 근접해야 합니다.
  • 데이터 집합이 매우 다양하지 않으면 항상 필요한 분할을 생성할 수 없습니다.이 방법은 필터 간에 균등한 분포가 필요합니다(따라서 총 10명, 남자 6명, 여자 4명, 북쪽에서 5명, 남쪽에서 5명, 북쪽에서 남자 3명, 여자 2명, 남쪽에서 여자 2명).
  • 사람들은 기본 정렬이 무엇이든 무작위로 검색되지 않을 것입니다.랜덤으로 선택하려면 ORDER BY RAND()와 같은 것을 추가해야 합니다(단, 매우 비효율적이어서는 안 됩니다).
  • SQL 주입에 주의합니다.입력을 따옴표를 합니다.'s.s.s.

불량하게 분포된 샘플 문제

(위 알고리즘이 제공하는) 대표 분할에 따라 샘플을 생성하기에 버킷 중 하나에 부족한 항목이 있는 문제를 어떻게 해결합니까?아니면 여러분의 숫자가 정수가 아니라면요?

코드를 제공하는 것까지는 하지 않겠습니다. 하지만 가능한 접근법을 설명하겠습니다.위의 코드를 꽤 많이 변경해야 할 것입니다. 왜냐하면 sql stub의 플랫 리스트는 더 이상 삭제되지 않을 것이기 때문입니다.대신 SQL 스텁의 차원 매트릭스를 구축해야 합니다(모든 필터 F1 - n에 대한 차원 추가). 위의 4단계가 완료된 후(각 SQL 스텁 항목에 대해 원하는 숫자가 있지만 반드시 가능한 것은 아님).

  1. SQL을 생성하여 결합된 모든 SQL WHERE 스텁의 개수를 선택합니다.
  2. 그런 다음 컬렉션을 반복합니다. 그리고 요청된 제한이 카운트보다 높은(또는 정수가 아닌) 항목을 누르면,
    • 요청한 한계를 카운트(또는 가장 가까운 정수)까지 조정합니다.
    • 그런 다음 각 축에서 최소 위의 조정이 최대 카운트보다 낮은 다른 항목을 선택하고 동일하게 조정합니다.적격한 항목을 찾을 수 없는 경우 요청한 분할이 불가능합니다.
    • 그런 다음 위쪽으로 조정된 항목에 대해 교차하는 모든 항목을 아래쪽으로 다시 조정합니다.
    • 추가 차원 tn마다 교차점 사이의 교차점에 대해 위의 단계를 반복합니다(단, 매번 음과 양 사이의 조정을 전환합니다).

앞의 예를 계속해서 들어보겠습니다. 대표적인 분할은 다음과 같습니다.
수컷/북쪽 = 3, 암컷/북쪽 = 2, 수컷/남쪽 = 3, 암컷/남쪽 = 2, 그러나 북쪽에는 수컷이 2마리밖에 없습니다(그러나 우리가 선택할 수 있는 다른 그룹에는 많은 사람들이 있습니다).

  • 수컷/북쪽을 2(-1)로 조정합니다.
  • 암컷/북쪽은 3(+1), 수컷/남쪽은 4(+1)로 조정합니다.
  • 교차하는 암/남쪽을 1(-1)로 조정합니다.Voila! (기준/차원이 2개밖에 없어서 추가 차원은 없습니다.)

이 그림은 교차 항목을 더 높은 차원으로 조정할 때 도움이 될 수 있습니다(최대 4차원만 표시되지만 수행해야 할 작업을 그림으로 그리는 데 도움이 될 것입니다!각 점은 n차원 행렬에서 SQL 스텁 항목 중 하나를 나타냅니다(관련된 제한 번호 포함). 선은 공통 기준 값(성별 = 남성)을 나타냅니다.목표는 조정이 완료된 후 모든 라인의 총계가 동일하게 유지되어야 한다는 것입니다!빨간색 점부터 시작해서 각 추가 차원에 대해 계속 진행합니다.위의 예제에서는 빨간색 점, 오른쪽 위의 주황색 점 2개로 구성된 정사각형, NE로 구성된 녹색 점 1개로 사각형을 완성합니다.

adjustments

하겠습니다.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|
+----------------------+

남성 600명, 여성 400명, 북부 100명, 남부 200명, 동부 300명, 서부 400명을 확인할 수 있습니다.

다른 필드도 포함할 수 있습니다.

연령 및 소득과 같은 범위 필드의 경우 다음 예를 따를 수 있습니다.

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

결과는 다음과 같습니다.

+----------------------------------+
|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|
+----------------------------------+

언급URL : https://stackoverflow.com/questions/27160122/mysql-query-to-get-intersection-of-numerous-queries-with-limits

반응형