MySQL ПРИЄДНАЙТЕСЯ лише до останнього рядка?


103

У мене є таблиця клієнта, яка зберігає ідентифікатор клієнта, електронну пошту та посилання. Існує додаткова таблиця customer_data, яка зберігає історичний запис змін, внесених до замовника, тобто при внесенні змін вставляється новий рядок.

Щоб відобразити інформацію про клієнта в таблиці, дві таблиці потрібно об'єднати, однак до таблиці клієнтів слід приєднати лише найсвіжіший рядок із даних_замовника.

Це дещо ускладнюється тим, що запит є пагінаційним, тому має обмеження та зміщення.

Як я можу це зробити за допомогою MySQL? Думаю, я хочу десь помістити ВИЗНАЧЕННЯ ...

Запит у хвилину такий:

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 with LIKE таким чином?

(Я розумію, що INNER JOIN може бути неправильним типом JOIN для використання. Я насправді не маю уявлення, яка різниця між різними JOINs. Я зараз це розгляну!)


Як виглядає таблиця історії клієнтів? Як визначається останній рядок? Чи є поле мітки часу?
Даніель Вассалло,

Останній - це просто останній вставлений рядок - отже, його первинний ключ - це найбільше число.
bcmcfc

Чому не тригер? погляньте на цю відповідь: stackoverflow.com/questions/26661314/…
Родріго Поло

Більшість / усіх відповідей зайняли занадто багато часу з мільйонами рядків. Є кілька рішень з кращою продуктивністю.
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;

Зверніть увагу, що 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)

2
Дякуємо за рівень деталізації, який ви там вивчили. Сподіваюся, це допомагає іншим, як і мені!
bcmcfc

21
У довгостроковій перспективі такий підхід може створити проблеми з продуктивністю, оскільки потрібно буде створити тимчасову таблицю. Отже, іншим рішенням (якщо це можливо) є додавання нового логічного поля (is_last) до даних customer_data, яке вам доведеться оновлювати кожного разу, коли додається новий запис. Останній запис матиме is_last = 1, усі інші для цього клієнта - is_last = 0.
cephuo

5
Люди повинні (будь ласка) також прочитати таку відповідь (від Денні Куломба), оскільки ця відповідь (вибачте, Даніель) страшенно повільна з довшими запитами / більшою кількістю даних. Змусив мою сторінку "почекати" протягом 12 секунд для завантаження; Тож перевірте також stackoverflow.com/a/35965649/2776747 . Я помітив це лише після багатьох інших змін, тому мені знадобилось дуже багато часу, щоб дізнатись.
Арт

Ви навіть не уявляєте, наскільки мені це допомогло :) Дякую майстру
node_man

104

Якщо ви працюєте з важкими запитами, краще перемістіть запит на останній рядок у речення 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
Ого, я майже не вірю в те, наскільки велика різниця в продуктивності. Ще не впевнений, чому це було настільки різко, але поки це було набагато швидше, що здається, що я зіпсував десь ще ...
Брайан Лейшман,

2
Мені дуже хотілося б, щоб я це поставив +1 більше одного разу, щоб його побачили більше. Я тестував це зовсім небагато, і це робить мої запити практично миттєвими (WorkBench буквально говорить 0,000 секунди, навіть з sql_no_cache set), тоді як пошук у об'єднанні тривав кілька секунд. Все ще збентежений, але я маю на увазі, що ви не можете сперечатися з такими результатами.
Брайан Лейшман

1
Спочатку ви безпосередньо приєднуєтеся до 2 таблиць, а потім фільтруєте WHERE. Я думаю, що це величезна проблема продуктивності, якщо у вас мільйон клієнтів і десятки мільйонів історії дзвінків. Оскільки SQL спробує спочатку об’єднати 2 таблиці, а потім фільтрувати до одного клієнта. Я скоріше відфільтрував би клієнтів та пов’язану історію викликів із таблиць спочатку у підзапиті, а потім приєднався до таблиць.
Тарік

1
Я вважаю, що "ca.client_id" та "ca.cal_event_id" мають бути "c" для обох.
Герберт Ван-Вліє

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 іш), ви не можете робити підзапити для цього типу запитів. Ось рішення, яке я зміг зробити, і воно, здавалося, працювало чудово.

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;

По суті, це знаходження максимального ідентифікатора вашої таблиці даних, що приєднує його до клієнта, а потім приєднання таблиці даних до знайденого максимального ідентифікатора. Причиною цього є те, що вибір максимуму групи не гарантує, що решта даних співпадає з ідентифікатором, якщо ви не приєднаєте їх назад до себе.

Я не тестував це на нових версіях MySQL, але він працює на 4.0.30.


Це вишукано своєю простотою. Чому я вперше бачу такий підхід? Зауважте, EXPLAINщо це вказує на те, що тут використовується тимчасова таблиця та сортування файлів. Додавання ORDER BY NULLв кінці відсіває сортування файлів.
Тимо

На мій жаль, моє власне, не таке гарне рішення в 3,5 рази швидше для моїх даних. Я використовував підзапит для вибору основної таблиці плюс найновіші ідентифікатори об’єднаних таблиць, а потім зовнішній запит, який вибирає підзапит і зчитує фактичні дані із об’єднаних таблиць. Я приєдную 5 таблиць до основної таблиці та тестую з умовою where, яка відбирає 1000 записів. Індекси оптимальні.
Тимо

Я використовував ваше рішення з SELECT *, MAX(firstData.id), MAX(secondData.id) [...]. Логічно, що, змінившись на, SELECT main.*, firstData2.*, secondData2.*, MAX(firstData.id), MAX(secondData.id), [...]я зміг зробити це значно швидше. Це дозволяє першим об'єднанням читати лише з індексу, а не читати всі дані з первинного індексу. Зараз гарне рішення займає лише в 1,9 рази більше часу, ніж рішення на основі підзапитів.
Тимо

Це більше не працює в MySQL 5.7. Тепер d2. * Поверне дані для першого рядка в групі, а не останнього. ВИБЕРІТЬ 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 може подбати про вставлення значень, оновлення ідентифікаторів та автоматичне приєднання двох таблиць для вас.

Ось як 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

ще оновити структуру таблиці


Я проголосував проти, бо неправильно прочитав вашу відповідь, і спочатку вважав, що це неправильно. Поспіх - поганий радник :-)
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" за вашим бажанням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.