MySQLは最新の行のみに参加しますか?


101

customer_id、電子メール、および参照を格納する表customerがあります。顧客に加えられた変更の履歴レコードを格納する追加のテーブルcustomer_dataがあります。つまり、変更があった場合、新しい行が挿入されます。

テーブルに顧客情報を表示するには、2つのテーブルを結合する必要がありますが、customer_dataからの最新の行のみをcustomerテーブルに結合する必要があります。

クエリがページ分割されるという点で少し複雑になるため、制限とオフセットがあります。

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

さらに、このようにCONCATをLIKEと併用できると思いますか?

(INNER JOINは、使用するJOINのタイプが間違っている可能性があることを理解しています。実際には、さまざまなJOINの違いは何なのかわかりません。これから調べます!)


顧客履歴テーブルはどのように見えますか?最新の行はどのように決定されますか?タイムスタンプフィールドはありますか?
Daniel Vassallo、2010

最新のものは単に挿入された最後の行です。そのため、その主キーが最大の番号です。
bcmcfc

なぜトリガーしないのですか?この回答をご覧ください
Rodrigo Polo

回答のほとんど/すべてが数百万の行で時間がかかりすぎていました。より良いパフォーマンスのいくつかの ソリューションがあります。
HalilÖzgür

回答:


142

次のことを試してみてください。

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;

JOINaはの同義語であることに注意してください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)

2
あなたがそこに行った詳細のレベルをありがとう。私だけでなく他の人にも役立つことを願っています!
bcmcfc

20
長期的には、この方法では一時テーブルを作成する必要があるため、パフォーマンスの問題が発生する可能性があります。したがって、別の解決策(可能な場合)は、customer_dataに新しいブール値フィールド(is_last)を追加することです。これは、新しいエントリが追加されるたびに更新する必要があります。最後のエントリにはis_last = 1が含まれ、その他すべての顧客にはis_last = 0が含まれます。
cephuo 2014

4
次の回答も読んでください(Danny Coulombeから)。この回答(申し訳ありません、Daniel)は、クエリが長くなったり、データが増えたりすると、非常に遅くなります。ページが読み込まれるまで12秒間待機しました。したがって、stackoverflow.com / a / 35965649/2776747も確認してください。他の多くの変更を加えるまで気づかなかったので、見つけるのに非常に時間がかかりました。
アート

これがどれほど私に役立ったかはわかりません:)ありがとうマスター
node_man

102

重いクエリで作業している場合は、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
すごい、これがパフォーマンスの違いのどれだけあるのか、ほとんど信じられません。なぜそれがそれほど劇的だったのかはわかりませんが、今のところ非常に速かったため、どこか他の場所でめちゃくちゃになってしまったような気がします...
Brian Leishman 2017年

2
もっと見やすくなるように、これを2回以上+1できたらと思います。私はこれをかなりテストし、どういうわけかそれは私のクエリを実質的に瞬時にします(WorkBenchは文字通り0.000秒と言いますsql_no_cache set)、結合での検索の実行は完了するまでに数秒かかりました。それでも困惑しますが、そのような結果について議論することはできません。
ブライアンリーシュマン

1
最初に2つのテーブルを直接結合してから、WHEREでフィルタリングしています。100万のクライアントと数千万の通話履歴がある場合、これは大きなパフォーマンスの問題だと思います。SQLは最初に2つのテーブルを結合してから、単一のクライアントにフィルターをかけるためです。最初にサブクエリでテーブルからクライアントと関連する呼び出し履歴をフィルタリングしてから、テーブルを結合します。
Tarik

1
「ca.client_id」と「ca.cal_event_id」はどちらも「c」である必要があると思います。
Herbert Van-Vliet 2017

1
@NickCoonsに同意します。NULL値は、where句によって除外されるため、返されません。どのようにしてNULL値を含め、このクエリの優れたパフォーマンスを維持しますか?
aanders77

10

の自動インクリメント列の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以前のish)で作業する必要がある人は、このタイプのクエリに対してサブクエリを実行できません。ここに私がすることができた解決策があります、そしてそれは素晴らしいように見えました。

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これは、これが一時テーブルとfilesortを使用することを示しています。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。* FROM請求書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保持するフィールドをテーブルに追加することをお勧めします。idcustomer_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の更新、および2つのテーブルの結合を自動的に処理します。

変更可能な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();
    }
}

そして、CustomerDataゲッターのみを含む不変モデル:

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のソリューション(上記)と組み合わせて、サブクエリなしで希望の結果を得ました。
ジンジャーとラベンダー

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に変更する必要があると思います

それ以外の場合はテーブル構造を更新する


私はあなたの答えを間違って読んだので、最初は間違っていると思ったので、反対票を投じました。Hasteは悪いカウンセラーです:-)
Wirone 2015年

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.