MySQL yalnızca en son satıra KATILIN MI?


107

Müşteri kimliği, e-posta ve referans depolayan bir masa müşterim var. Müşteriye yapılan değişikliklerin geçmiş kaydını saklayan ek bir müşteri_verisi tablosu vardır, yani bir değişiklik yapıldığında yeni bir satır eklenir.

Müşteri bilgilerini bir tabloda görüntülemek için, iki tablonun birleştirilmesi gerekir, ancak yalnızca customer_data'dan en son satır müşteri tablosuna birleştirilmelidir.

Sorgunun sayfalara ayrılmış olması nedeniyle biraz daha karmaşık hale gelir, dolayısıyla bir sınırı ve bir ofseti vardır.

Bunu MySQL ile nasıl yapabilirim? Sanırım orada bir yere bir DISTINCT koymak istiyorum ...

Dakikadaki sorgu şu şekildedir:

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

Ek olarak, CONCAT'ı LIKE ile bu şekilde kullanabileceğimi düşünmekte haklı mıyım?

(INNER JOIN'in kullanılacak yanlış JOIN türü olabileceğini anlıyorum. Farklı JOIN'ler arasındaki farkın ne olduğuna dair hiçbir fikrim yok. Şimdi buna bakacağım!)


Müşteri geçmişi tablosu nasıl görünüyor? En son satır nasıl belirlenir? Zaman damgası alanı var mı?
Daniel Vassallo

En yenisi, sadece eklenen son satırdır - dolayısıyla birincil anahtarı en yüksek sayıdır.
bcmcfc

Neden tetikleyici değil? şu yanıta bir göz atın: stackoverflow.com/questions/26661314/…
Rodrigo Polo

Yanıtların çoğu / tümü milyonlarca satırla çok uzun sürüyordu. Orada bazı çözümler daha iyi bir performans ile.
Halil Özgür

Yanıtlar:


150

Aşağıdakileri denemek isteyebilirsiniz:

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;

Bir unutmayın JOINsadece eş anlamlıdır INNER JOIN.

Test durumu:

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

Sonuç ( LIMITve olmadan sorgu 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
Oraya girdiğiniz ayrıntı düzeyi için teşekkürler. Umarım bana olduğu kadar başkalarına da yardımcı olur!
bcmcfc

21
Uzun vadede bu yaklaşım, geçici bir tablo oluşturması gerekeceğinden performans sorunları yaratabilir. Bu nedenle başka bir çözüm (mümkünse), müşteri_verisine her yeni giriş eklendiğinde güncellemeniz gereken yeni bir boole alanı (is_last) eklemektir. Son girişte is_last = 1 olacak, diğerleri bu müşteri için - is_last = 0 olacaktır.
cephuo

6
İnsanlar (lütfen) aşağıdaki cevabı da okumalıdır (Danny Coulombe'den), çünkü bu cevap (üzgünüm Daniel), daha uzun sorgular / daha fazla veriyle son derece yavaştır. Sayfamın yüklenmesi için 12 saniye bekletildi; Bu yüzden lütfen stackoverflow.com/a/35965649/2776747 adresini de kontrol edin . Diğer birçok değişikliğin sonrasına kadar fark etmedim, bu yüzden öğrenmem çok uzun sürdü.
Sanat

Bunun bana ne kadar yardımcı olduğu hakkında hiçbir fikriniz yok :) Teşekkürler usta
node_man

110

Yoğun sorgularla çalışıyorsanız, nerede cümlesindeki en son satır için isteği taşımanız daha iyi olur. Çok daha hızlı ve daha temiz görünüyor.

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
Vay canına, bunun ne kadar performans farkı olduğuna neredeyse inanamıyorum. Bunun neden bu kadar sert olduğundan henüz emin değilim, ama şimdiye kadar o kadar hızlıydı ki başka bir yeri berbat etmişim gibi hissettiriyor ...
Brian Leishman

2
Keşke bunu birden çok kez + 1'leyebilseydim, böylece daha fazla görülür. Bunu epeyce test ettim ve bir şekilde sorgularımı neredeyse anında yapıyor (WorkBench kelimenin tam anlamıyla 0.000 saniye diyor sql_no_cache set), oysa birleşimde arama yapmak birkaç saniye sürdü. Hâlâ şaşkın ama böyle sonuçlarla tartışamazsın demek istiyorum.
Brian Leishman

1
Doğrudan önce 2 tabloya katılıyorsunuz ve sonra WHERE ile filtreliyorsunuz. Bir milyon müşteriniz ve on milyonlarca arama geçmişiniz varsa, bunun büyük bir performans sorunu olduğunu düşünüyorum. Çünkü SQL önce 2 tabloyu birleştirmeye çalışacak ve sonra tek istemciye filtreleyecektir. Müşterileri ve ilgili arama geçmişlerini önce bir alt sorguda tablolardan filtrelemeyi ve sonra tabloları birleştirmeyi tercih ederim.
Tarık

1
Sanırım "ca.client_id" ve "ca.cal_event_id" her ikisi için de "c" olmalıdır.
Herbert Van-Vliet

1
@NickCoons'a katılıyorum. NULL değerler, where cümlesi tarafından hariç tutuldukları için döndürülmez. NULL değerlerini dahil etmek ve yine de bu sorgunun mükemmel performansını nasıl sürdürürsünüz?
aanders77

10

İçindeki otomatik artış sütununun customer_dataadlandırılmış olduğunu varsayarak Idşunları yapabilirsiniz:

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'in eski bir sürümüyle (5.0 öncesi) çalışması gereken herhangi biri için bu tür bir sorgu için alt sorgular yapamazsınız. İşte yapabildiğim çözüm ve harika çalışıyor gibiydi.

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;

Esasen bu, veri tablonuzun maksimum kimliğini bulmak, onu müşteriye katmak ve ardından veri tablosunu bulunan maksimum kimlik ile birleştirmektir. Bunun nedeni, bir grubun maksimum değerinin seçilmesinin, siz onu tekrar birleştirmediğiniz sürece geri kalan verilerin id ile eşleşeceğini garanti etmemesidir.

Bunu MySQL'in daha yeni sürümlerinde test etmedim ama 4.0.30'da çalışıyor.


Bu, sadeliğiyle mükemmeldir. Neden bu yaklaşımı ilk kez görüyorum? Not EXPLAINBu geçici bir tablo ve filesort kullandığını gösterir. ORDER BY NULLSonunda eklemek , dosya sırasını ortadan kaldırır.
Timo

Ne yazık ki güzel olmayan çözümüm, verilerimin 3,5 katı hızlı. Ana tabloyu ve birleştirilen tabloların en son kimliklerini seçmek için bir alt sorgu ve ardından alt sorguyu seçen ve birleştirilen tablolardan gerçek verileri okuyan bir dış sorgu kullandım. Ana masaya 5 tabloyu birleştiriyorum ve 1000 kayıt seçen bir where koşulu ile test ediyorum. Dizinler optimaldir.
Timo

Çözümünüzü kullanıyordum SELECT *, MAX(firstData.id), MAX(secondData.id) [...]. Mantıksal olarak, olarak değiştirerek SELECT main.*, firstData2.*, secondData2.*, MAX(firstData.id), MAX(secondData.id), [...]önemli ölçüde daha hızlı hale getirebildim. Bu, ilk birleşimlerin birincil dizinden tüm verileri okumak zorunda kalmadan yalnızca dizinden okumasına izin verir. Şimdi güzel çözüm, alt sorgu tabanlı çözümden yalnızca 1,9 kat daha uzun sürüyor.
Timo

Artık MySQL 5.7'de çalışmıyor. Şimdi d2. *, Sonuncusu değil, gruptaki ilk satır için veri döndürecektir. Faturalardan MAKS (R1.id), R2. * SEÇİN I LEFT JOIN yanıtları R1 ON I.id = R1.invoice_id LEFT JOIN yanıtları R2 ON R1.id = R2.id GROUP BY I.id LIMIT 0,10
Marco Marsala

6

Bu sorunun eski olduğunu biliyorum, ancak yıllar içinde çok dikkat çekti ve bence benzer bir durumda birine yardımcı olabilecek bir kavramı gözden kaçırıyor. Bütünlük uğruna onu buraya ekliyorum.

Orijinal veritabanı şemanızı değiştiremiyorsanız, birçok iyi yanıt verilmiştir ve sorunu gayet iyi çözebilirsiniz.

Eğer varsa olabilir , ancak, şemada değişiklik, senin bir alan eklemek tavsiye ediyorum customertutan tablonun iden geç customer_databu müşteri için rekor:

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

Müşterileri sorgulama

Sorgulama, olabildiğince kolay ve hızlıdır:

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

Bunun dezavantajı, bir müşteri oluştururken veya güncellerken ortaya çıkan ekstra karmaşıklıktır.

Bir müşteriyi güncelleme

Bir müşteriyi her güncellemek istediğinizde, customer_datatabloya yeni bir kayıt eklersiniz ve customerkaydı güncellersiniz .

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;

Bir müşteri yaratmak

Bir müşteri oluşturmak yalnızca customergirişi eklemek ve ardından aynı ifadeleri çalıştırmakla ilgilidir:

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;

Sarmak

Bir müşteri yaratmanın / güncellemenin ekstra karmaşıklığı korkutucu olabilir, ancak tetikleyicilerle kolayca otomatikleştirilebilir.

Son olarak, bir ORM kullanıyorsanız, bunu yönetmek gerçekten kolay olabilir. ORM sizin için değerlerin girilmesi, kimliklerin güncellenmesi ve iki tablonun otomatik olarak birleştirilmesiyle ilgilenebilir.

Değişken Customermodeliniz şöyle görünecektir:

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

Ve CustomerDatasadece alıcıları içeren değişmez modeliniz:

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

İstediğim sonucu herhangi bir alt sorgu olmadan elde etmek için bu yaklaşımı @ payne8'in çözümüyle (yukarıda) birleştirdim.
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'yi c.id olarak değiştirmeniz gerektiğini düşünüyorum

yoksa tablo yapısını güncelle


Olumsuz oy verdim çünkü cevabınızı yanlış anladım ve başlangıçta bunun yanlış olduğunu düşündüm. Haste kötü bir danışman :-)
Wirone

1

Bunu da yapabilirsin

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

Gerçek verileri " customer_data " tablosuna kaydetmek iyi bir fikirdir . Bu veriler ile "customer_data" tablosundaki tüm verileri dilediğiniz gibi seçebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.