MySQL GABUNG hanya baris terbaru?

103

Saya memiliki pelanggan meja yang menyimpan customer_id, email dan referensi. Terdapat tabel tambahan customer_data yang menyimpan catatan sejarah dari perubahan yang dilakukan pada pelanggan, yaitu ketika ada perubahan membuat baris baru dimasukkan.

Untuk menampilkan informasi pelanggan dalam sebuah tabel, kedua tabel tersebut perlu digabungkan, namun hanya baris terbaru dari data_pelanggan yang harus digabungkan ke tabel pelanggan.

Ini menjadi sedikit lebih rumit karena kueri diberi nomor halaman, jadi memiliki batas dan offset.

Bagaimana saya bisa melakukan ini dengan MySQL? Saya pikir saya ingin menempatkan DISTINCT di sana di suatu tempat ...

Pertanyaan saat ini adalah seperti ini-

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

Selain itu, apakah saya benar dalam berpikir saya dapat menggunakan CONCAT dengan LIKE dengan cara ini?

(Saya menghargai bahwa INNER JOIN mungkin jenis JOIN yang salah untuk digunakan. Saya sebenarnya tidak tahu apa perbedaan antara JOIN yang berbeda. Saya akan memeriksanya sekarang!)

bcmcfc.dll
sumber
Bagaimana tampilan tabel riwayat pelanggan? Bagaimana cara menentukan baris terbaru? Apakah ada bidang cap waktu?
Daniel Vassallo
Yang terbaru hanyalah baris terakhir yang disisipkan - jadi kunci utamanya adalah angka tertinggi.
bcmcfc
Mengapa bukan pemicu? lihat jawaban ini: stackoverflow.com/questions/26661314/…
Rodrigo Polo
Sebagian besar / semua jawaban terlalu lama dengan jutaan baris. Ada beberapa solusi dengan kinerja yang lebih baik.
Halil Özgür

Jawaban:

146

Anda mungkin ingin mencoba yang berikut ini:

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;

Perhatikan bahwa a JOINhanyalah sinonim dari INNER JOIN.

Kasus cobaan:

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');

Hasil (kueri tanpa LIMITdan 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)
Daniel Vassallo
sumber
3
Terima kasih atas tingkat detail yang Anda lakukan di sana. Saya harap ini membantu orang lain serta hanya saya!
bcmcfc
21
Dalam jangka panjang, pendekatan ini mungkin menimbulkan masalah kinerja karena perlu membuat tabel sementara. Jadi solusi lain (jika memungkinkan) adalah menambahkan bidang boolean baru (is_last) di customer_data yang harus Anda perbarui setiap kali entri baru ditambahkan. Entri terakhir akan memiliki is_last = 1, semua lainnya untuk pelanggan ini - is_last = 0.
cephuo
5
Orang-orang harus (tolong) juga membaca jawaban berikut (dari Danny Coulombe), karena jawaban ini (maaf Daniel) sangat lambat dengan pertanyaan yang lebih panjang / lebih banyak data. Membuat halaman saya "menunggu" selama 12 detik untuk dimuat; Jadi, periksa juga stackoverflow.com/a/35965649/2776747 . Saya tidak menyadarinya sampai setelah banyak perubahan lain jadi butuh waktu lama untuk mengetahuinya.
Seni
Anda tidak tahu betapa ini telah membantu saya :) Terima kasih master
node_man
106

Jika Anda bekerja dengan kueri yang berat, lebih baik Anda memindahkan permintaan untuk baris terbaru di klausa where. Ini jauh lebih cepat dan terlihat lebih bersih.

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
   )
Danny Coulombe
sumber
4
Wow, saya hampir tidak percaya pada seberapa besar perbedaan kinerja ini. Tidak yakin mengapa itu begitu drastis, tetapi sejauh ini itu jauh lebih cepat sehingga saya merasa seperti saya mengacaukan di tempat lain ...
Brian Leishman
2
Saya benar-benar berharap dapat memberi ini +1 lebih dari sekali agar lebih sering dilihat. Saya telah menguji ini sedikit dan entah bagaimana itu membuat kueri saya hampir seketika (WorkBench secara harfiah mengatakan 0.000 detik, bahkan dengan sql_no_cache set), sedangkan melakukan pencarian dalam gabungan membutuhkan beberapa detik untuk menyelesaikannya. Masih bingung, tapi maksud saya Anda tidak bisa membantah hasil seperti itu.
Brian Leishman
1
Anda langsung bergabung dengan 2 tabel terlebih dahulu dan kemudian memfilter dengan WHERE. Menurut saya ini adalah masalah kinerja yang sangat besar jika Anda memiliki satu juta klien dan puluhan juta riwayat panggilan. Karena SQL akan mencoba untuk menggabungkan 2 tabel terlebih dahulu dan kemudian memfilter ke klien tunggal. Saya lebih suka memfilter klien dan riwayat panggilan terkait dari tabel terlebih dahulu di sub-kueri dan kemudian bergabung dengan tabel.
Tarik
1
Saya rasa "ca.client_id" dan "ca.cal_event_id" harus "c" untuk keduanya.
Herbert Van-Vliet
1
Saya setuju dengan @NickCoons. Nilai NULL tidak akan dikembalikan karena dikecualikan oleh klausa where. Bagaimana Anda akan memasukkan nilai NULL dan tetap menjaga kinerja yang sangat baik dari kueri ini?
aanders77
10

Menganggap kolom autoincrement di customer_datadiberi nama Id, Anda dapat melakukan:

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
Thomas
sumber
9

Bagi siapa pun yang harus bekerja dengan versi MySQL yang lebih lama (sebelum 5.0 ish), Anda tidak dapat melakukan sub-kueri untuk jenis kueri ini. Berikut adalah solusi yang dapat saya lakukan dan tampaknya berhasil dengan baik.

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;

Pada dasarnya ini adalah menemukan id maksimum dari tabel data Anda menggabungkannya ke pelanggan kemudian menggabungkan tabel data ke id maksimum yang ditemukan. Alasannya adalah karena memilih maksimal grup tidak menjamin bahwa sisa data cocok dengan id kecuali Anda menggabungkannya kembali ke dirinya sendiri.

Saya belum menguji ini pada versi MySQL yang lebih baru tetapi berfungsi pada 4.0.30.

payne8
sumber
Ini sangat indah dalam kesederhanaannya. Mengapa ini pertama kalinya saya melihat pendekatan ini? Catatan yang EXPLAINmenunjukkan bahwa ini menggunakan tabel sementara dan filesort. Menambahkan ORDER BY NULLdi akhir menyingkirkan filesort.
Timo
Sayangnya, solusi saya sendiri yang tidak secantik ini 3,5 kali lebih cepat untuk data saya. Saya menggunakan subkueri untuk memilih tabel utama ditambah ID terbaru dari tabel yang digabungkan, dan kemudian kueri luar yang memilih subkueri dan membaca data aktual dari tabel yang digabungkan. Saya menggabungkan 5 tabel ke tabel utama, dan menguji dengan kondisi di mana yang memilih 1000 catatan. Indeks sudah optimal.
Timo
Saya menggunakan solusi Anda dengan SELECT *, MAX(firstData.id), MAX(secondData.id) [...]. Logikanya, dengan mengubah ke SELECT main.*, firstData2.*, secondData2.*, MAX(firstData.id), MAX(secondData.id), [...]saya bisa membuatnya lebih cepat secara signifikan. Ini memungkinkan gabungan pertama untuk membaca hanya dari indeks, daripada juga harus membaca semua data dari indeks utama. Sekarang solusi cantik hanya membutuhkan 1,9 kali selama solusi berbasis subquery.
Timo
Itu tidak berfungsi lagi di MySQL 5.7. Sekarang d2. * Akan mengembalikan data untuk baris pertama dalam grup, bukan yang terakhir. PILIH MAX (R1.id), R2. * DARI faktur SAYA KIRI BERGABUNG tanggapan R1 PADA I.id = R1.invoice_id KIRI BERGABUNG tanggapan R2 PADA R1.id = R2.id KELOMPOK OLEH I.id LIMIT 0,10
Marco Marsala
5

Saya tahu pertanyaan ini sudah lama, tetapi mendapat banyak perhatian selama bertahun-tahun dan saya pikir itu kehilangan konsep yang dapat membantu seseorang dalam kasus serupa. Saya menambahkannya di sini demi kelengkapan.

Jika Anda tidak dapat mengubah skema database asli Anda, maka banyak jawaban bagus telah disediakan dan menyelesaikan masalah dengan baik.

Namun, jika Anda dapat mengubah skema, saya sarankan untuk menambahkan bidang di customertabel Anda yang menyimpan rekor idterbaru customer_datauntuk pelanggan ini:

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
);

Menanyakan pelanggan

Membuat kueri semudah dan secepat mungkin:

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

Kekurangannya adalah kerumitan ekstra saat membuat atau memperbarui pelanggan.

Memperbarui pelanggan

Setiap kali Anda ingin memperbarui pelanggan, Anda memasukkan catatan baru di customer_datatabel, dan memperbarui customercatatan.

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;

Menciptakan pelanggan

Membuat pelanggan hanyalah masalah memasukkan customerentri, lalu menjalankan pernyataan yang sama:

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;

Membungkus

Kompleksitas ekstra untuk membuat / memperbarui pelanggan mungkin menakutkan, tetapi dapat dengan mudah diotomatiskan dengan pemicu.

Terakhir, jika Anda menggunakan ORM, ini bisa sangat mudah dikelola. ORM dapat menangani penyisipan nilai, memperbarui id, dan menggabungkan dua tabel secara otomatis untuk Anda.

Berikut adalah tampilan Customermodel Anda yang bisa berubah :

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();
    }
}

Dan CustomerDatamodel Anda yang tidak dapat diubah , yang hanya berisi getter:

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;
    }
}
Benjamin
sumber
Saya menggabungkan pendekatan ini dengan solusi @ payne8 (di atas) untuk mendapatkan hasil yang saya inginkan tanpa subkueri.
Ginger dan 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%' 

saya rasa Anda perlu mengubah c.customer_id menjadi c.id

lain memperbarui struktur tabel

Pramendra Gupta
sumber
Saya telah menurunkan suara karena saya salah membaca jawaban Anda dan saya awalnya mengira itu salah. Tergesa-gesa adalah konselor yang buruk :-)
Wirone
1

Anda juga bisa melakukan ini

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;
Ajay Kumar
sumber
0

Ada baiknya Anda memasukkan data aktual ke dalam tabel " customer_data ". Dengan data ini Anda dapat memilih semua data dari tabel "customer_data" sesuai keinginan.

Burçin
sumber