문제

이것은 내가 알아낼 수없는 매우 기본적인 쿼리입니다 ....

다음과 같은 두 개의 열 테이블이 있다고 가정 해 봅시다.

userid  |  roleid
--------|--------
   1    |    1
   1    |    2
   1    |    3
   2    |    1

나는 모든 독특한 사용자를 얻고 싶습니다 roleids 1, 2 및 3. 위의 예를 사용하여 내가 반환하려는 유일한 결과는 userid 1. 어떻게해야합니까?

도움이 되었습니까?

해결책

SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;

이 글을 읽는 사람에게 : 내 대답은 간단하고 간단하며 '허용 된'상태를 얻었지만 읽어주세요. 대답 @cletus에 의해 주어졌습니다. 훨씬 더 나은 성능을 가지고 있습니다.


@cletus가 묘사 한 자체 조명을 쓰는 또 다른 방법은 다음과 같습니다.

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid
JOIN userrole t3 ON t2.userid = t3.userid
WHERE (t1.roleid, t2.roleid, t3.roleid) = (1, 2, 3);

이것은 당신을 위해 읽기가 더 쉬울 수 있으며 MySQL은 그런 튜플의 비교를 지원합니다. MySQL은 또한이 쿼리에 대해 지능적으로 인덱스를 지능적으로 사용하는 방법을 알고 있습니다. 그냥 실행하십시오 EXPLAIN 그리고 세 테이블 모두에 대한 메모에서 "색인 사용"을 참조하십시오. 즉, 인덱스를 읽고 데이터 행을 터치 할 필요조차 없습니다.

MacBook에서 MySQL 5.1.48을 사용하여 210 만 행 이상 (Posttags의 스택 오버 플로우 7 월 데이터 덤프)을 실행했으며 결과를 1.08 초로 반환했습니다. 충분한 메모리가 할당 된 괜찮은 서버에서 innodb_buffer_pool_size, 훨씬 더 빠릅니다.

다른 팁

좋아, 나는 이것에 대해 탐구를 받았기 때문에 그것을 테스트하기로 결정했다.

CREATE TABLE userrole (
  userid INT,
  roleid INT,
  PRIMARY KEY (userid, roleid)
);

CREATE INDEX ON userrole (roleid);

이것을 실행하십시오 :

<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records 

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
    $roles = rand(1, 4);
    $available = range(1, 5);
    for ($j=0; $j<$roles; $j++) {
        $extract = array_splice($available, rand(0, sizeof($available)-1), 1);
        $id = $extract[0];
        query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
        $count++;
    }
}

$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

산출:

499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.

이로 인해 50 만 명의 무작위 사용자 롤 조합이 추가되며 선택한 기준과 일치하는 약 25,000 명이 있습니다.

첫 번째 쿼리 :

SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3

쿼리 시간 : 0.312s

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1

쿼리 시간 : 0.016S

좋아요. 내가 제안한 조인 버전은입니다 집계 버전보다 20 배 더 빠릅니다.

미안하지만 나는 실제 세계에서 살아있는 일을하고 현실 세계에서 우리는 SQL을 테스트하고 결과는 스스로를 말합니다.

그 이유는 분명해야합니다. 집계 쿼리는 테이블 크기에 따라 비용이 늘어납니다. 모든 행은 HAVING 절. 조인 버전은 (색인 사용) 주어진 역할을 기반으로 사용자의 하위 집합을 선택한 다음 두 번째 역할에 대한 해당 하위 집합을 확인하고 마지막으로 세 번째 역할에 대한 하위 집합을 확인합니다. 각 선택 (안에 관계 대수 용어)는 점점 더 작은 하위 집합에서 작동합니다. 이것으로부터 당신은 다음과 같이 결론을 내릴 수 있습니다.

일치 발생률이 낮아지면 조인 버전의 성능이 향상됩니다.

세 가지 명시된 역할을 가진 500 명의 사용자 (위의 500K 샘플 중) 만 있으면 조인 버전이 훨씬 빨라집니다. 집계 버전은 그렇지 않습니다 (성능 향상은 25K 대신 500 명의 사용자를 전송 한 결과로 결합 버전도 분명히 얻습니다).

나는 또한 실제 데이터베이스 (예 : Oracle)가 이것을 어떻게 다룰 것인지 궁금했습니다. 따라서 기본적으로 Oracle XE (이전 예제의 MySQL과 동일한 Windows XP 데스크탑 시스템에서 실행)에서 동일한 연습을 반복했으며 결과는 거의 동일합니다.

조인은 눈살을 찌푸리는 것처럼 보이지만, 내가 시연 한대로 집계 쿼리는 몇 배 느릴 수 있습니다.

업데이트: 일부 후 광범위한 테스트, 그림은 더 복잡하고 답은 데이터, 데이터베이스 및 기타 요인에 따라 다릅니다. 이야기의 도덕은 시험, 시험, 시험입니다.

이를 수행하는 고전적인 방법은이를 관계 부문 문제로 취급하는 것입니다.

영어로 : 원하는 역할 값이없는 사용자를 선택하십시오.

Userrole 테이블이 참조하는 사용자 테이블이 있다고 가정하고 원하는 역할 값이 테이블에 있다고 가정하겠습니다.

create table RoleGroup(
  roleid int not null,
  primary key(roleid)
)
insert into RoleGroup values (1);
insert into RoleGroup values (2);
insert into RoleGroup values (3);

또한 모든 관련 열을 무효화 할 수 없다고 가정하므로 IN에 놀라지 않거나 놀라운 일이 없습니다. 위의 영어를 표현하는 SQL 쿼리는 다음과 같습니다.

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where not exists (
    select R.roleid from UserRole as R
    where R.roleid = G.roleid
    and R.userid = U.userid
  )
);

그것을 쓰는 또 다른 방법은 이것입니다

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where G.roleid not in (
    select R.roleid from UserRole as R
    where R.userid = U.userid
  )
);

인덱스, 플랫폼, 데이터 등에 따라 효율적이거나 효율적이지 않을 수도 있습니다. 웹에서 "관계 부서"를 검색하면 많은 것을 찾을 수 있습니다.

userID를 가정하면 RoleID가 고유 인덱스에 포함되어 있습니다 (userID = X 및 reaconID = 1 인 2 개의 레코드가있을 수 없습니다.

select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3

이것이 문제를 해결하지 않습니까? 이것은 일반적인 관계형 DBS에서 솔루션이 얼마나 좋은가요? 쿼리 Optimizer가 자동으로 최적화됩니까?

여기에 어떤 종류의 일반성이 필요한 경우 (다른 3OLE 조합 또는 다른 N-role 조합) ... 역할에 약간의 마스킹 시스템을 사용하고 비트 연산자를 사용하여 쿼리를 수행하는 것이 좋습니다 ...

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top