MySQL은 가장 최근 행에만 가입 하시겠습니까?


103

customer_id, 이메일 및 참조를 저장하는 테이블 customer가 있습니다. 고객에 대한 변경 내역 기록을 저장하는 추가 테이블 customer_data가 있습니다. 즉, 변경 사항이있을 때 새 행이 삽입됩니다.

테이블에 고객 정보를 표시하려면 두 테이블을 조인해야하지만 customer_data의 가장 최근 행만 고객 테이블에 조인해야합니다.

쿼리에 페이지가 매겨져 있으므로 제한과 오프셋이 있다는 점에서 조금 더 복잡해집니다.

MySQL로 어떻게 할 수 있습니까? 어딘가에 DISTINCT를 넣고 싶은 것 같아요 ...

순간의 쿼리는 다음과 같습니다.

SELECT *, CONCAT(title,' ',forename,' ',surname) AS name
FROM customer c
INNER JOIN customer_data d on c.customer_id=d.customer_id
WHERE name LIKE '%Smith%' LIMIT 10, 20

추가로, 이런 식으로 LIKE와 함께 CONCAT을 사용할 수 있다고 생각하는 것이 맞습니까?

(INNER JOIN이 사용하기에 잘못된 유형의 JOIN 일 수 있다는 점에 감사드립니다. 실제로 서로 다른 JOIN의 차이점이 무엇인지 전혀 알 수 없습니다. 지금 살펴 보겠습니다!)


고객 기록 테이블은 어떻게 생겼습니까? 가장 최근 행은 어떻게 결정됩니까? 타임 스탬프 필드가 있습니까?
Daniel Vassallo

가장 최근은 삽입 된 마지막 행이므로 기본 키가 가장 높은 숫자입니다.
bcmcfc

방아쇠가 아닌 이유는 무엇입니까? 이 답변을 살펴 : stackoverflow.com/questions/26661314/...을
로드리고 폴로에게

대부분 / 모든 답변은 수백만 개의 행으로 너무 오래 걸렸습니다. 더 나은 성능을 가진 몇 가지 솔루션 이 있습니다 .
Halil Özgür

답변:


146

다음을 시도해 볼 수 있습니다.

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
JOIN      (
              SELECT    MAX(id) max_id, customer_id 
              FROM      customer_data 
              GROUP BY  customer_id
          ) c_max ON (c_max.customer_id = c.customer_id)
JOIN      customer_data cd ON (cd.id = c_max.max_id)
WHERE     CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT     10, 20;

a JOIN는 단지 INNER JOIN.

테스트 케이스 :

CREATE TABLE customer (customer_id int);
CREATE TABLE customer_data (
   id int, 
   customer_id int, 
   title varchar(10),
   forename varchar(10),
   surname varchar(10)
);

INSERT INTO customer VALUES (1);
INSERT INTO customer VALUES (2);
INSERT INTO customer VALUES (3);

INSERT INTO customer_data VALUES (1, 1, 'Mr', 'Bobby', 'Smith');
INSERT INTO customer_data VALUES (2, 1, 'Mr', 'Bob', 'Smith');
INSERT INTO customer_data VALUES (3, 2, 'Mr', 'Jane', 'Green');
INSERT INTO customer_data VALUES (4, 2, 'Miss', 'Jane', 'Green');
INSERT INTO customer_data VALUES (5, 3, 'Dr', 'Jack', 'Black');

결과 ( LIMIT및 없이 쿼리 WHERE) :

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
JOIN      (
              SELECT    MAX(id) max_id, customer_id 
              FROM      customer_data 
              GROUP BY  customer_id
          ) c_max ON (c_max.customer_id = c.customer_id)
JOIN      customer_data cd ON (cd.id = c_max.max_id);

+-----------------+
| name            |
+-----------------+
| Mr Bob Smith    |
| Miss Jane Green |
| Dr Jack Black   |
+-----------------+
3 rows in set (0.00 sec)

3
거기까지 자세히 설명 해주셔서 감사합니다. 저뿐만 아니라 다른 사람들에게도 도움이되기를 바랍니다!
bcmcfc

21
장기적으로이 접근 방식은 임시 테이블을 생성해야하므로 성능 문제가 발생할 수 있습니다. 따라서 (가능한 경우) 또 다른 솔루션은 새 항목이 추가 될 때마다 업데이트해야하는 customer_data에 새 부울 필드 (is_last)를 추가하는 것입니다. 마지막 항목은 is_last = 1이고이 고객에 대한 다른 항목은 is_last = 0입니다.
cephuo

5
사람들은 (Danny Coulombe의) 다음 답변을 (제발) 읽어야합니다. 내 페이지가로드 될 때까지 12 초 동안 "대기"하도록했습니다. 따라서 stackoverflow.com/a/35965649/2776747 도 확인하십시오 . 다른 많은 변화가있을 때까지 알아 차리지 못해서 알아내는 데 시간이 많이 걸렸습니다.
Art

당신이 내게 도움이되었습니다 얼마나 아무 생각이 :) 당신에게 마스터 감사
node_man

105

무거운 쿼리로 작업하는 경우 where 절에서 최신 행에 대한 요청을 이동하는 것이 좋습니다. 훨씬 빠르고 깨끗해 보입니다.

SELECT c.*,
FROM client AS c
LEFT JOIN client_calling_history AS cch ON cch.client_id = c.client_id
WHERE
   cch.cchid = (
      SELECT MAX(cchid)
      FROM client_calling_history
      WHERE client_id = c.client_id AND cal_event_id = c.cal_event_id
   )

4
와우 나는 이것이 성능 차이가 얼마나 큰지 거의 믿지 않습니다. 확실하지가 너무 과감한 아직,하지만 지금까지 너무 빨리 그것을이 느끼는 나 ... 다른 곳 엉망 것처럼 왜
브라이언 Leishman을

2
나는 이것을 한 번 이상 +1하여 더 많이 볼 수 있기를 바랍니다. 나는 이것을 꽤 많이 테스트했으며 어떻게 든 내 쿼리를 거의 즉각적으로 만듭니다 (WorkBench는 문자 그대로 0.000 초라고 말함 sql_no_cache set) 조인에서 검색을 수행하는 데 몇 초가 걸렸습니다. 여전히 당황하지만 그런 결과에 대해 논쟁 할 수 없다는 뜻입니다.
Brian Leishman

1
먼저 2 개의 테이블을 직접 조인 한 다음 WHERE로 필터링합니다. 백만 명의 클라이언트와 수천만 통화 기록이 있다면 엄청난 성능 문제라고 생각합니다. SQL은 먼저 2 개의 테이블을 조인 한 다음 단일 클라이언트로 필터링을 시도하기 때문입니다. 차라리 하위 쿼리에서 먼저 테이블의 클라이언트 및 관련 호출 기록을 필터링 한 다음 테이블을 조인합니다.
Tarik

1
"ca.client_id"와 "ca.cal_event_id"는 둘 다 "c"여야한다고 가정합니다.
Herbert Van-Vliet

1
@NickCoons에 동의합니다. NULL 값은 where 절에 의해 제외되므로 반환되지 않습니다. NULL 값을 포함하고이 쿼리의 우수한 성능을 유지하려면 어떻게해야합니까?
aanders77

10

의 autoincrement 열의 customer_data이름이라고 가정하면 Id다음을 수행 할 수 있습니다.

SELECT CONCAT(title,' ',forename,' ',surname) AS name *
FROM customer c
    INNER JOIN customer_data d 
        ON c.customer_id=d.customer_id
WHERE name LIKE '%Smith%'
    AND d.ID = (
                Select Max(D2.Id)
                From customer_data As D2
                Where D2.customer_id = D.customer_id
                )
LIMIT 10, 20

9

이전 버전의 MySQL (5.0 이전 버전)으로 작업해야하는 사람은 이러한 유형의 쿼리에 대해 하위 쿼리를 수행 할 수 없습니다. 여기 내가 할 수 있었던 해결책이 있고 잘 작동하는 것 같았습니다.

SELECT MAX(d.id), d2.*, CONCAT(title,' ',forename,' ',surname) AS name
FROM customer AS c 
LEFT JOIN customer_data as d ON c.customer_id=d.customer_id 
LEFT JOIN customer_data as d2 ON d.id=d2.id
WHERE CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%'
GROUP BY c.customer_id LIMIT 10, 20;

본질적으로 이것은 고객과 결합하는 데이터 테이블의 최대 ID를 찾은 다음 발견 된 최대 ID에 데이터 테이블을 결합하는 것입니다. 그 이유는 그룹의 최대 값을 선택해도 나머지 데이터가 ID와 일치하는 것을 보장하지 않기 때문입니다.

나는 이것을 MySQL의 최신 버전에서 테스트하지 않았지만 4.0.30에서 작동합니다.


이것은 단순함이 절묘합니다. 이 접근 방식을 처음 본 이유는 무엇입니까? EXPLAIN이것은 임시 테이블과 파일 정렬을 사용함 을 나타냅니다. ORDER BY NULL끝에 추가 하면 파일 정렬이 제거됩니다.
Timo

유감스럽게도 내 자신의 아름답 지 않은 솔루션은 내 데이터보다 3.5 배 빠릅니다. 하위 쿼리를 사용하여 기본 테이블과 조인 된 테이블의 최신 ID를 선택한 다음 하위 쿼리를 선택하고 조인 된 테이블에서 실제 데이터를 읽는 외부 쿼리를 사용했습니다. 5 개의 테이블을 기본 테이블에 조인하고 1000 개의 레코드를 선택하는 where 조건으로 테스트합니다. 인덱스가 최적입니다.
Timo

귀하의 솔루션을 SELECT *, MAX(firstData.id), MAX(secondData.id) [...]. 논리적으로 SELECT main.*, firstData2.*, secondData2.*, MAX(firstData.id), MAX(secondData.id), [...]나는로 변경함으로써 훨씬 더 빠르게 만들 수있었습니다. 이렇게하면 첫 번째 조인이 기본 인덱스에서 모든 데이터를 읽을 필요없이 인덱스에서만 읽을 수 있습니다. 이제 예쁜 솔루션은 하위 쿼리 기반 솔루션의 1.9 배만 걸립니다.
Timo

MySQL 5.7에서는 더 이상 작동하지 않습니다. 이제 d2. *는 마지막이 아닌 그룹의 첫 번째 행에 대한 데이터를 반환합니다. SELECT MAX (R1.id), R2. * 인보이스에서 I LEFT JOIN 응답 R1 ON I.id = R1.invoice_id LEFT JOIN 응답 R2 ON R1.id = R2.id GROUP BY I.id LIMIT 0,10
Marco Marsala

5

이 질문이 오래되었다는 것을 알고 있지만 수년에 걸쳐 많은 관심을 받고 있으며 비슷한 경우에 누군가를 도울 수있는 개념이 누락 된 것 같습니다. 완전성을 위해 여기에 추가하겠습니다.

원래 데이터베이스 스키마를 수정할 수 없다면 많은 좋은 답변이 제공되었으며 문제를 잘 해결했습니다.

당신이 경우 수 있습니다 , 그러나, 스키마를 수정, 당신의 필드를 추가 할 권합니다 customer보류를 테이블 id최신의 customer_data이 고객에 대한 기록 :

CREATE TABLE customer (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  current_data_id INT UNSIGNED NULL DEFAULT NULL
);

CREATE TABLE customer_data (
   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
   customer_id INT UNSIGNED NOT NULL, 
   title VARCHAR(10) NOT NULL,
   forename VARCHAR(10) NOT NULL,
   surname VARCHAR(10) NOT NULL
);

고객 쿼리

쿼리는 다음과 같이 쉽고 빠릅니다.

SELECT c.*, d.title, d.forename, d.surname
FROM customer c
INNER JOIN customer_data d on d.id = c.current_data_id
WHERE ...;

단점은 고객을 생성하거나 업데이트 할 때 추가 복잡성입니다.

고객 업데이트

고객을 업데이트 할 때마다 customer_data테이블에 새 레코드를 삽입하고 레코드를 업데이트합니다 customer.

INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(2, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = 2;

고객 만들기

고객을 생성하는 것은 customer항목 을 삽입 한 다음 동일한 명령문을 실행하기 만하면됩니다 .

INSERT INTO customer () VALUES ();

SET @customer_id = LAST_INSERT_ID();
INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(@customer_id, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = @customer_id;

마무리

고객 생성 / 업데이트에 대한 추가 복잡성은 두려울 수 있지만 트리거를 사용하여 쉽게 자동화 할 수 있습니다.

마지막으로 ORM을 사용하는 경우 관리하기가 정말 쉽습니다. ORM은 값 삽입, ID 업데이트 및 자동으로 두 테이블 결합을 처리 할 수 ​​있습니다.

변경 가능한 Customer모델은 다음과 같습니다.

class Customer
{
    private int id;
    private CustomerData currentData;

    public Customer(String title, String forename, String surname)
    {
        this.update(title, forename, surname);
    }

    public void update(String title, String forename, String surname)
    {
        this.currentData = new CustomerData(this, title, forename, surname);
    }

    public String getTitle()
    {
        return this.currentData.getTitle();
    }

    public String getForename()
    {
        return this.currentData.getForename();
    }

    public String getSurname()
    {
        return this.currentData.getSurname();
    }
}

그리고 CustomerDatagetter 만 포함하는 변경 불가능한 모델 :

class CustomerData
{
    private int id;
    private Customer customer;
    private String title;
    private String forename;
    private String surname;

    public CustomerData(Customer customer, String title, String forename, String surname)
    {
        this.customer = customer;
        this.title    = title;
        this.forename = forename;
        this.surname  = surname;
    }

    public String getTitle()
    {
        return this.title;
    }

    public String getForename()
    {
        return this.forename;
    }

    public String getSurname()
    {
        return this.surname;
    }
}

이 접근 방식을 @ payne8의 솔루션 (위)과 결합하여 하위 쿼리없이 원하는 결과를 얻었습니다.
Ginger and Lavender

2
SELECT CONCAT(title,' ',forename,' ',surname) AS name * FROM customer c 
INNER JOIN customer_data d on c.id=d.customer_id WHERE name LIKE '%Smith%' 

c.customer_id를 c.id로 변경해야한다고 생각합니다.

그렇지 않으면 테이블 구조를 업데이트합니다.


나는 귀하의 답변을 잘못 읽었고 처음에는 그것이 잘못되었다고 생각했기 때문에 반대 투표를했습니다. 가속은 :-) 나쁜 카운슬러이다
Wirone

1

당신은 또한 이것을 할 수 있습니다

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
LEFT JOIN  (
              SELECT * FROM  customer_data ORDER BY id DESC
          ) customer_data ON (customer_data.customer_id = c.customer_id)
GROUP BY  c.customer_id          
WHERE     CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT     10, 20;

0

실제 데이터를 " customer_data "테이블에 로깅하는 것이 좋습니다 . 이 데이터로 "customer_data"테이블에서 원하는 모든 데이터를 선택할 수 있습니다.

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