한 번의 선택으로 현재와 다음으로 큰 가치를 얻는 방법은 무엇입니까?


18

열이있는 InnoDB 테이블 'idtimes'(MySQL 5.0.22-log)가 있습니다.

`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]

복합 고유 키로

UNIQUE KEY `id_time` (`id`,`time`)

따라서 ID 당 여러 타임 스탬프와 타임 스탬프 당 여러 ID가있을 수 있습니다.

모든 항목과 각 항목에 대해 다음에 더 큰 시간을 얻는 쿼리를 설정하려고하는데 다음과 같이 반환해야합니다.

+-----+------------+------------+
| id  | time       | nexttime   |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 |       NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 |       NULL |
+-----+------------+------------+

지금은 지금까지입니다.

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
    WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

물론 이것은 r.time> l.time 인 모든 행을 반환하며 첫 번째 행뿐만 아니라 ...

같은 하위 선택이 필요할 것 같습니다.

SELECT outer.id, outer.time, 
    (SELECT time FROM idtimes WHERE id = outer.id AND time > outer.time 
        ORDER BY time ASC LIMIT 1)
    FROM idtimes AS outer ORDER BY outer.id ASC, outer.time ASC;

그러나 현재 시간을 참조하는 방법을 모르겠습니다 (위의 SQL이 유효하지 않다는 것을 알고 있습니다).

단일 쿼리 로이 작업을 수행하는 방법은 무엇입니까? (한 번에 한 행씩 테이블을 밟고 마지막 값을 기억하는 것에 의존하는 @variables를 사용하지 않으려는 경우)?

답변:


20

가입하는 것이 필요할 수도 있습니다.

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id

외부 조인이 고의적이라고 가정하고 null을 얻고 싶습니다. 나중에 더 자세히.

WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

당신은 r 만 원합니다. l.time보다 높은 최저 (MIN) 시간을 갖는 행. 그것은 하위 쿼리가 필요한 곳입니다.

WHERE r.time = (SELECT MIN(time) FROM idtimes r2 where r2.id = l.id AND r2.time > l.time)

이제 널입니다. "다음에 더 높은 시간이 없으면"SELECT MIN ()이 널 (또는 더 나쁨)로 평가되고 그 자체가 다른 것과 동일하게 비교되지 않으므로 WHERE 절이 절대 충족되지 않으며 "가장 높은 시간" 각 ID에 대해 결과 집합에 표시되지 않았습니다.

JOIN을 제거하고 스칼라 하위 쿼리를 SELECT 목록으로 이동하여 문제를 해결합니다.

SELECT id, time, 
    (SELECT MIN(time) FROM idtimes sub 
        WHERE sub.id = main.id AND sub.time > main.time) as nxttime
  FROM idtimes AS main 

4

코드를 "더 티어 (dirtier)"로 만들고 때로는 덜 효율적이기 때문에 SELECT블록이나 블록 에서 하위 쿼리를 사용하지 않는FROM 것이 좋습니다.

더 우아한 방법은 다음과 같습니다.

찾기 1 댄 더 시간 행의를

당신은이 작업을 수행 할 수 있습니다 JOIN사이에 idtimes의 (가) 같은 조인 제약, 자체 테이블 ID 와에 댄 더 시간 현재 행의.

현재 행의 시간 보다 크지 LEFT JOIN않은 행을 제외하지 않아야합니다 .

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS greater_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time

언급했듯이 문제는 next_timetime 보다 큰 여러 행이 있다는 것 입니다.

+-----+------------+--------------+
| id  | time       | greater_time |
+-----+------------+--------------+
| 155 | 1300000000 | 1311111111   |
| 155 | 1300000000 | 1322222222   |
| 155 | 1311111111 | 1322222222   |
| 155 | 1322222222 |       NULL   |
| 156 | 1312345678 | 1318765432   |
| 156 | 1318765432 |       NULL   |
+-----+------------+--------------+

2. greater_time 이 더 클뿐만 아니라 next_time 인 행을 찾습니다

이 쓸모없는 모든 행을 필터링하는 가장 좋은 방법이 있습니다 있는지 확인하는 시간 사이의 시간 (이상) 및 greater_time (보다 작은)이이에 대한 ID .

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS next_time,
    i3.time AS intrudor_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time
    LEFT JOIN idtimes AS i3 ON i2.id = i3.id AND i3.time > i1.time AND i3.time < i2.time

ops, 우리는 여전히 잘못된 next_time을 가지고 있습니다 !

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1300000000 | 1322222222   |    1311111111 |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

이 이벤트가 발생하는 행을 필터링하여 WHERE아래 제약 조건을 추가하십시오.

WHERE
    i3.time IS NULL

Voilà, 우리는 우리가 필요한 것을 가지고 있습니다!

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

4 년 후에도 답변이 필요하기를 바랍니다.


영리 해요 그래도 이해하기 쉽지는 않습니다. 우리가 is null와 i3에 대한 조인을로 바꾸면 where not exists (select 1 from itimes i3 where [same clause])코드가 표현하려는 것을 더 자세히 반영한다고 생각합니다.
Andrew Spencer

thx 친구 당신은 내 (다음) 날을 구했습니다!
Jakob

2

해결책을 제시하기 전에, 그것이 예쁘지 않다는 것을 알아야합니다. AUTO_INCREMENT테이블에 열 이 있으면 훨씬 쉬울 것입니다.

SELECT 
  l.id, l.time, 
  SUBSTRING_INDEX(GROUP_CONCAT(r.time ORDER BY r.time), ',', 1)
FROM 
  idtimes AS l 
  LEFT JOIN idtimes AS r ON (l.id = r.id)
WHERE 
  l.time < r.time
GROUP BY
  l.id, l.time

설명:

  • 당신과 같은 조인 : 두 테이블을 조인하십시오. 오른쪽 테이블은 더 높은 시간 만 얻습니다.
  • 왼쪽 테이블의 두 열 모두에 의해 GROUP BY : 모든 (id, time)조합 (유일한 것으로 알려진)을 얻습니다 .
  • 각각에 대해 보다 큰 첫 번째(l.id, l.time)얻으십시오 . 이는 첫 번째 토큰 via를 슬라이싱 하여 s via 를 먼저 주문할 때 발생합니다 . r.timel.timer.timeGROUP_CONCAT(r.time ORDER BY r.time)SUBSTRING_INDEX

행운을 빕니다. 그리고이 표가 크면 좋은 성능을 기대하지 마십시오.


2

당신은 또한 당신이에서 원하는 것을 얻을 수 min()GROUP BY어떤 내부 선택과 :

SELECT l.id, l.time, min(r.time) 
FROM idtimes l 
LEFT JOIN idtimes r on (r.id = l.id and r.time > l.time)
GROUP BY l.id, l.time;

나는 것이 거의 옵티마이 어쨌든 어윈 Smout의 대답과 같은 일에이 켜지는지 큰 돈을 베팅하고, 어떤 명확를인지 그것의 논쟁의 여지가 있지만, 완성도가 ...


1
어떻게 그 가치를 들어, SSMS 및 SQLServer에 2016 어윈의 (2S 런타임 대 24 폭격기가 ~ 24K 결과 집합에 런타임)보다 더 많은 쿼리를 좋아했다
나단 래퍼 티

앤드류는 당신이 내기를 잃은 것 같습니다 :-)
Erwin Smout

흥미로운 것은 PK 열 중 하나에 의해 외부 쿼리 테이블로 다시 조인되는 하위 쿼리가 그룹과 동일하다는 것이 일반적인 경우이기 때문입니다. 다른 데이터베이스가 더 잘 최적화하는지 궁금합니다. (내가 BTW 데이터베이스 최적화에 대해 거의 알고, 그냥 호기심.)
앤드류 스펜서
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.