값 집합에서 테이블 열에 저장되지 않은 값을 어떻게 찾습니까?


12

잠재적으로 수십만 개의 정수를 저장할 테이블이 있습니다.

desc id_key_table;

+----------------+--------------+------+-----+---------+-------+
| Field          | Type         | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+-------+
| id_key         | int(16)      | NO   | PRI | NULL    |       |
+----------------+--------------+------+-----+---------+-------+

프로그램에서 큰 정수 세트가 있습니다. 이 정수 중 어떤 것이 위의 id_key 열에 없는지 알고 싶습니다.

지금까지 나는 다음과 같은 접근법을 생각해 냈습니다.

1) 각 정수를 반복하고 다음을 수행하십시오.

select count(*) count from id_key_table where id_key = :id_key

count가 0이면 id_key가 테이블에서 누락 된 것입니다.

이것은 끔찍하고 끔찍한 방법으로 보입니다.


2) 임시 테이블을 작성하고 각 값을 임시 테이블에 삽입 한 후 두 테이블에서 JOIN을 수행하십시오.

create temporary table id_key_table_temp (id_key int(16) primary key );

insert into id_key_table_temp values (1),(2),(3),...,(500),(501);

select temp.id_key
from id_key_table_temp temp left join id_key_table as main 
         on temp.id_key = main.id_key 
where main.killID is null;

drop table id_key_table_temp;

이것은 최선의 접근 방법 인 것처럼 보이지만 아직 생각하지 않은 훨씬 더 나은 접근 방법이 있다고 확신합니다. 임시 테이블을 만들지 않고 하나의 쿼리를 사용하여 누락 된 정수를 결정하고 싶습니다.

이 유형의 검색에 대한 적절한 쿼리가 있습니까?

(MySQL)


2
나는 당신이 당신의 질문을 어떻게했는지 좋아하지만 (DBA에 오신 것을 환영합니다) 아마도 일종의 프로그램 (dba가 아닌)과의 상호 작용을 다루기 때문에 아마도 stackoverflow에 훨씬 더 적합 할 것입니다.
Derek Downey

환영합니다. 이곳에는 stackoverflow보다 더 많은 전문가가있을 것이라고 생각했습니다. 그래도 다시 물어 보는 것은 괜찮습니다.
클린턴

2
제안한대로 StackOverflow에 다시 게시했습니다. stackoverflow.com/questions/5967822/…
Clinton

이 질문에서 비슷한 상황이 SQL Server에 대해 처리되었습니다 . 저장된 proc에 많은 양의 데이터를 보내는 기술 . 다른 db 환경에서도 문제가 비슷하다는 것을 알 수 있습니다. 어쨌든, 나는 해결책 번호로 간다. 2-ID 목록을 보내고 구문 분석하고 테이블에 넣고 기본 테이블에 조인하십시오. 다른 솔루션을 사용할 수 없다면 여기서 파헤쳐 야합니다 :-).
Marian

답변:


7

LEFT JOIN을 사용하는 두 번째 솔루션은 지금까지 가장 좋은 방법입니다. 임시 테이블을 사용하지 않고 일반 테이블을 사용하여 쿼리를 실행할 때마다 새 값으로 채 웁니다.


5

"큰 정수 세트"가 여전히 "수백 개의 정수"가있는 테이블보다 상당히 작은 것처럼 들립니다. 그 가정에 따라 MySQL에서 정수 배열을 SQL 문의 테이블로 사용하는 방법이 없다면 두 번째 옵션이 가장 좋습니다. 임시 테이블 및 기본 테이블의 인덱스를 전체 스캔해야합니다. 주요 이점은 수십만 개의 정수를 포함하는 인덱스를 한 번만 스캔하면되고 클라이언트에게 결과 만 보내면된다는 것입니다. 쿼리는 다음과 같이 다시 작성할 수 있습니다 (필요하지는 않음).

SELECT * FROM id_key_table_temp 
WHERE id_key NOT IN (select id_key FROM id_key_table);

MySQL 플랫폼의 차이점에 대해 알지 못하므로 일반 테이블보다 임시 테이블을 보증하지 않습니다. Oracle에서는 임시 테이블이 가장 좋을 수도 있지만 Oracle에서는 배열을 테이블로 사용하여 직접 조인합니다.
레이 리펠

3

임시 테이블 대신으로 삽입하는 대신 확인하려는 모든 값 으로 하위 쿼리구성insert into id_key_table_temp values (1),(2),(3),...,(500),(501); 할 수 있습니다.

select id_key
from ( select @row := @row + 1 as id_key 
       from (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s1,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s2,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s3,
            (select 0 union all select 1 union all select 3 union all select 4 union all select 5 union all select 6 union all select 6 union all select 7 union all select 8 union all select 9) s4,
            (select @row:=0) s5 ) s
where id_key in(1, 2, 3, 500, 501)
      and id_key not in (select id_key from main);

2

내 의견에서 언급했듯이 이것은 아마도 스택 오버 플로우에 더 적합합니다. 그러나 두 솔루션 모두 최고가 아니라고 생각합니다.

솔루션 1에는 여러 번의 선택 호출이 필요하며 매우 비효율적입니다.

솔루션 2가 더 좋지만 많은 값을 삽입하는 비용이 최상의 솔루션인지 확실하지 않습니다.

가능한 해결책 3은 하나의 쿼리를 만드는 것입니다.

SELECT DISTINCT id_key FROM id_key_table

프로그래밍 방식으로 정수 세트와 DB의 내용과의 차이를 얻습니다. 최악의 경우 (정수가 많기 때문에)이 경로는 솔루션 1보다 낫습니다. 솔루션 2에는 많은 정수를 반환 할 가능성이 있습니다 (테이블에 데이터 세트에없는 묶음이있는 경우). depend ™!


결과 집합이 매우 커서이 솔루션을 좋아하지 않습니다.
클린턴

@Clinton true이지만 필터링하기에 충분한 정수를 제공하지 않으면 두 번째 솔루션에서도 매우 클 수 있습니다.
데릭 다우니

2

StackOverflow 에서이 문제를 해결 했지만 영구 임시 (PermTemp) 테이블 사용에 대해 더 자세히 설명하고 싶습니다. ( 영구 온도, 옥시 모론 아닙니까?)

에서 StackOverflow의 내가 저장 프로 시저 test.CreateSampleTable 및 test.GetMissingIntegers은 샘플 테이블을 만든 다음 차이점을 찾기 위해 가입 큰 일을하기 전에 채우려면 동적 임시 테이블을 만들 수 있었다.

이번에는 영구 테이블 테이블과 함께 샘플 테이블을 만들어 봅시다.

다음은 test.LoadSampleTables입니다.

DELIMITER $$

DROP PROCEDURE IF EXISTS `LoadSampleTables` $$
CREATE DEFINER=`lwdba`@`127.0.0.1` PROCEDURE `LoadSampleTables`(maxinttoload INT)
BEGIN

  DECLARE X,OKTOUSE,MAXLOOP INT;

  DROP TABLE IF EXISTS test.id_key_table;
  DROP TABLE IF EXISTS test.id_key_table_keys;
  CREATE TABLE test.id_key_table (id_key INT(16)) ENGINE=MyISAM;
  CREATE TABLE test.id_key_table_keys (id_key INT(16)) ENGINE=MyISAM;

  SET X=1;
  WHILE X <= maxinttoload DO
    INSERT INTO test.id_key_table VALUES (X);
    SET X = X + 1;
  END WHILE;
  ALTER TABLE test.id_key_table ADD PRIMARY KEY (id_key);

  SET MAXLOOP = FLOOR(SQRT(maxinttoload));
  SET X = 2;
  WHILE X <= MAXLOOP DO
    DELETE FROM test.id_key_table WHERE MOD(id_key,X) = 0 AND id_key > X;
    SELECT MIN(id_key) INTO OKTOUSE FROM test.id_key_table WHERE id_key > X;
    SET X = OKTOUSE;
  END WHILE;
  OPTIMIZE TABLE test.id_key_table;

  INSERT INTO test.id_key_table_keys SELECT id_key FROM test.id_key_table;
  ALTER TABLE test.id_key_table_keys ADD PRIMARY KEY (id_key);
  OPTIMIZE TABLE test.id_key_table_keys;

END $$

DELIMITER ;

이것을 실행 한 후, 테이블과 그 내용은 다음과 같습니다.

mysql> call test.loadsampletables(25);
+-------------------+----------+----------+----------+
| Table             | Op       | Msg_type | Msg_text |
+-------------------+----------+----------+----------+
| test.id_key_table | optimize | status   | OK       |
+-------------------+----------+----------+----------+
1 row in set (0.20 sec)

+------------------------+----------+----------+----------+
| Table                  | Op       | Msg_type | Msg_text |
+------------------------+----------+----------+----------+
| test.id_key_table_keys | optimize | status   | OK       |
+------------------------+----------+----------+----------+
1 row in set (0.28 sec)

Query OK, 0 rows affected (0.29 sec)

mysql> select * from test.id_key_table;
+--------+
| id_key |
+--------+
|      1 |
|      2 |
|      3 |
|      5 |
|      7 |
|     11 |
|     13 |
|     17 |
|     19 |
|     23 |
+--------+
10 rows in set (0.00 sec)

mysql> select * from test.id_key_table_keys;
+--------+
| id_key |
+--------+
|      1 |
|      2 |
|      3 |
|      5 |
|      7 |
|     11 |
|     13 |
|     17 |
|     19 |
|     23 |
+--------+
10 rows in set (0.00 sec)

PermTemp 테이블에 대한 트리거는 다음과 같습니다.

mysql> DELIMITER $$
mysql>
mysql> CREATE TRIGGER test.AddPermTempKey AFTER INSERT ON test.id_key_table
    -> FOR EACH ROW
    -> BEGIN
    ->     INSERT IGNORE INTO test.id_key_table_keys VALUES (NEW.id_key);
    -> END $$
Query OK, 0 rows affected (0.09 sec)

mysql>
mysql> CREATE TRIGGER test.DeletePermTempKey AFTER DELETE ON test.id_key_table
    -> FOR EACH ROW
    -> BEGIN
    ->     DELETE FROM test.id_key_table_keys WHERE id_key = OLD.id_key;
    -> END $$
Query OK, 0 rows affected (0.08 sec)

mysql>
mysql> DELIMITER ;

이제 새로운 레코드 배치, 테이블 test.weekly_batch, 이전에 사용 된 일부 키, 다른 키가 새로운 스패닝을 가져올 수 있습니다.

mysql> CREATE TABLE test.weekly_batch (id_key INT(16)) ENGINE=MyISAM;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO test.weekly_batch VALUES (17),(19),(23),(29),(31),(37),(41);
Query OK, 7 rows affected (0.00 sec)
Records: 7  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE test.weekly_batch ADD PRIMARY KEY (id_key);
Query OK, 7 rows affected (0.08 sec)
Records: 7  Duplicates: 0  Warnings: 0

test.weekly_batch를 가져 와서 test.id_key_table_keys에 안전하게 병합하고 test.new_keys_to_load 테이블을 만들어 봅시다 :

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`ImportWeeklyBatch` $$
CREATE PROCEDURE `test`.`ImportWeeklyBatch` ()
TheStoredProcedure:BEGIN

  DECLARE RCOUNT INT;

  SELECT COUNT(1) INTO RCOUNT FROM information_schema.tables
  WHERE table_schema='test' AND table_name='weekly_batch';
  IF RCOUNT = 0 THEN
    LEAVE TheStoredProcedure;
  END IF;
  SELECT COUNT(1) INTO RCOUNT FROM test.weekly_batch;
  IF RCOUNT = 0 THEN
    LEAVE TheStoredProcedure;
  END IF;
  DROP TABLE IF EXISTS test.new_keys_to_load;
  CREATE TABLE test.new_keys_to_load (id_key INT(16));
  INSERT INTO test.new_keys_to_load (id_key)
  SELECT id_key FROM test.weekly_batch A
  LEFT JOIN test.id_key_table_keys B USING (id_key)
  WHERE B.id_key IS NULL;

  SELECT * FROM test.new_keys_to_load;

END $$

DELIMITER ;

결과는 다음과 같습니다.

mysql> call test.importweeklybatch;
+--------+
| id_key |
+--------+
|     29 |
|     31 |
|     37 |
|     41 |
+--------+
4 rows in set (0.14 sec)

이 시점에서 new_keys_to_load 테이블을 가져올 새 키를 스팬하는 브랜드 목록으로 사용하십시오. new_keys_to_load는 PermTemp 테이블보다 작으므로 LEFT JOIN의 왼쪽에 항상 new_keys_to_load를 사용해야합니다.


나는 이것을 SO에 이미 대답했다
RolandoMySQLDBA
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.