MySQL - ВИБІРІТЬ, де поле IN (підзапит) - надзвичайно повільно, чому?


133

У мене в базі даних є кілька дублікатів, які я хочу перевірити, тому що я зробив, щоб побачити, що таке дублікати, я зробив це:

SELECT relevant_field
FROM some_table
GROUP BY relevant_field
HAVING COUNT(*) > 1

Таким чином, я отримаю всі рядки з відповідним полем, що виникає не один раз. Цей запит потребує мілісекунд на виконання.

Тепер я хотів перевірити кожен з дублікатів, тому я подумав, що можу ВИБІРАТИ кожен рядок у some_table із релевантним полем у наведеному вище запиті, тому мені це подобалось:

SELECT *
FROM some_table 
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)

Це виявляється дуже повільно чомусь (це займає хвилини). Що саме тут відбувається, щоб зробити це так повільно? relevant_field індексується.

Врешті-решт я спробував створити перегляд "temp_view" з першого запиту (SELECT relevant_field FROM some_table GROUP BY relevant_field HAVING COUNT(*) > 1), а потім замість цього зробив свій другий запит:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT relevant_field
    FROM temp_view
)

І це працює чудово. MySQL робить це за кілька мілісекунд.

Будь-які фахівці SQL тут можуть пояснити, що відбувається?


чого ти точно хочеш? хочете видалити повторювані записи, крім однієї ?? Пропозиція: будь ласка, прочитайте Self Join
diEcho

1
Очевидно, це група, яка повільна ...
ajreal

Перший запит виконується в мілісекундах (групування та фільтрування за допомогою HAVING). Це лише в поєднанні з іншим запитом, який робить все повільним (це займає хвилини).
quano

@diEcho, я хочу знайти дублікати, перевірити їх та видалити деякі вручну.
quano

Відповіді:


112

Перепишіть запит у це

SELECT st1.*, st2.relevant_field FROM sometable st1
INNER JOIN sometable st2 ON (st1.relevant_field = st2.relevant_field)
GROUP BY st1.id  /* list a unique sometable field here*/
HAVING COUNT(*) > 1

Я думаю, що st2.relevant_fieldповинен бути у виборі, тому що в іншому випадку havingставиться помилка, але я не впевнений на 100%

Ніколи не використовуйте INпідзапит; це сумно повільно.
Використовувати лише INз фіксованим списком значень.

Більше порад

  1. Якщо ви хочете робити запити швидше, не SELECT *вибирайте лише ті поля, які вам справді потрібні.
  2. Переконайтеся, що у вас є індекс, relevant_fieldщоб пришвидшити приєднання.
  3. Переконайтеся, що group byна первинному ключі.
  4. Якщо ви перебуваєте на InnoDB, і ви вибираєте лише індексовані поля (і речі не надто складні), ніж MySQL вирішить ваш запит, використовуючи лише індекси, прискоривши речей.

Загальне рішення для 90% ваших IN (select запитів

Використовуйте цей код

SELECT * FROM sometable a WHERE EXISTS (
  SELECT 1 FROM sometable b
  WHERE a.relevant_field = b.relevant_field
  GROUP BY b.relevant_field
  HAVING count(*) > 1) 

1
Ви також можете написати це за допомогою HAVING COUNT(*) > 1. Зазвичай це швидше в MySQL.
ypercubeᵀᴹ

@ypercube, зроблений для нижнього запиту, я думаю, що для верхнього запиту це змінить результат.
Йоган

@Johan: Оскільки st2.relevant_fieldце не так NULL(це вже включено в ONпункт), це не змінить результату.
ypercubeᵀᴹ

@ypercube, тож ви можете змінити count (afield) на count (*), якщо ви впевнені, що afieldцього ніколи не буде null, отримайте. Спасибі
Йоган

1
@quano, так, у ньому перераховані всі дублікати, оскільки group byувімкнено st1.id, а не увімкнено st1.relevant_field.
Йоган

110

Підзапрос запускається для кожного рядка, оскільки це співвіднесений запит. Можна перетворити співвіднесений запит у некорельований запит, вибравши все з підзапиту, наприклад:

SELECT * FROM
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
) AS subquery

Остаточний запит виглядатиме так:

SELECT *
FROM some_table
WHERE relevant_field IN
(
    SELECT * FROM
    (
        SELECT relevant_field
        FROM some_table
        GROUP BY relevant_field
        HAVING COUNT(*) > 1
    ) AS subquery
)

3
Це для мене спрацювало напрочуд добре. У мене був інший IN (підзапит) в IN (підзапит), і це зайняло більше 10 хвилин, настільки довго, що я гугл, поки я чекав. Обгортання кожного запиту в SELECT * FROM (), як ви запропонували, зменшило його до 2 секунд!
Ліам

ДЯКУЙТЕ, я вже пару годин намагаюся з'ясувати хороший спосіб зробити це. Це спрацювало чудово. Хочеться, я міг би дати вам більше грошей! Це безумовно має бути відповіддю.
Таспій

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

Цікаво, чому оптимізатор вважає, що запити з профспілками співвіднесені ... Як би там не було, цей трюк спрацював як магія
Брайан Лейшман

2
Не могли б ви пояснити, що робить цей співвіднесений запит? Я розумію, що підзапит стає співвіднесеним, коли він використовує значення, яке залежить від зовнішнього запиту. Але в цьому прикладі я не бачу ніяких взаємозалежностей. Це дасть однаковий результат для кожного рядка, повернутого зовнішнім запитом. У мене є подібний приклад, що реалізується на MariaDB, і я не бачу жодного результативного результату (поки що), тому я хотів би чітко побачити, коли це SELECT *обгортання потрібно.
sbnc.eu

6

Я підозрював щось подібне, що виконується підзапит для кожного ряду.
quano

Деякі версії MySQL навіть не використовують індекс в IN. Я додав ще одне посилання.
edze

1
MySQL 6 ще не стабільний, я б не рекомендував це для виробництва!
Йоган

1
Я б не рекомендував це. Але тут пояснено, як він працює внутрішньо (4.1 / 5.x -> 6). Це демонструє деякі підводні камені поточних версій.
edze

5
SELECT st1.*
FROM some_table st1
inner join 
(
    SELECT relevant_field
    FROM some_table
    GROUP BY relevant_field
    HAVING COUNT(*) > 1
)st2 on st2.relevant_field = st1.relevant_field;

Я спробував ваш запит на одній із моїх баз даних, а також спробував його переписати як приєднання до підзапиту.

Це спрацювало набагато швидше, спробуйте!


Так, це, ймовірно, створить темп-таблицю з результатами групи, тому вона буде такою ж швидкістю, як і версія перегляду. Але плани запитів повинні говорити правду.
ypercubeᵀᴹ

3

Спробуйте це

SELECT t1.*
FROM 
 some_table t1,
  (SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT (*) > 1) t2
WHERE
 t1.relevant_field = t2.relevant_field;

2

Я переформатував ваш повільний запит sql з www.prettysql.net

SELECT *
FROM some_table
WHERE
 relevant_field in
 (
  SELECT relevant_field
  FROM some_table
  GROUP BY relevant_field
  HAVING COUNT ( * ) > 1
 );

Використовуючи таблицю і в запиті, і в підзапиті, ви завжди повинні мати псевдонім обох, як це:

SELECT *
FROM some_table as t1
WHERE
 t1.relevant_field in
 (
  SELECT t2.relevant_field
  FROM some_table as t2
  GROUP BY t2.relevant_field
  HAVING COUNT ( t2.relevant_field ) > 1
 );

Чи допомагає це?


1
На жаль, це не допомагає. Це виконується так само повільно.
quano

Я оновив свою відповідь, чи можете ви спробувати ще раз? Навіть якщо група по повільному, вона повинна бути виконана лише один раз ...
заплановано

Я випадково убив живий сервер mysql востаннє, тому боюся, що не можу зараз спробувати це. Пізніше мені доведеться створити тестову базу даних. Але я не розумію, чому це має вплинути на запит. Оператор HAVING повинен стосуватися лише запиту, який знаходиться в ньому, чи не так? Я дійсно не розумію, чому "справжній" запит повинен впливати на підзапит.
quano

Я знайшов це: xaprb.com/blog/2006/04/30 / ... . Я думаю, що це може бути рішенням. Спробую, коли знайду час.
quano

2

По-перше, ви можете знайти повторювані рядки і знайти кількість рядків, скільки разів використовується, і впорядкувати їх за таким числом;

SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

після цього створіть таблицю і вставте в неї результат.

create table CopyTable 
SELECT q.id,q.name,q.password,q.NID,(select count(*) from UserInfo k where k.NID= q.NID) as Count,
(
		CASE q.NID
		WHEN @curCode THEN
			@curRow := @curRow + 1
		ELSE
			@curRow := 1
		AND @curCode := q.NID
		END
	) AS No
FROM UserInfo q,
(
		SELECT
			@curRow := 1,
			@curCode := ''
	) rt
WHERE q.NID IN
(
    SELECT NID
    FROM UserInfo
    GROUP BY NID
    HAVING COUNT(*) > 1
) 

Нарешті, видаліть дублікати рядків. Не починається 0. За винятком номера кулака кожної групи, видаліть усі повторювані рядки.

delete from  CopyTable where No!= 0;


1

Іноді, коли дані зростають, mysql WHERE IN може бути досить повільним через оптимізацію запитів. Спробуйте використовувати STRAIGHT_JOIN, щоб сказати mysql виконувати запит, як, наприклад,

SELECT STRAIGHT_JOIN table.field FROM table WHERE table.id IN (...)

але будьте обережні: у більшості випадків оптимізатор mysql працює досить добре, тому я рекомендую використовувати його лише тоді, коли у вас є такі проблеми


0

Це схоже на мій випадок, де у мене є таблиця з назвою tabel_buku_besar. Що мені потрібно

  1. Шукаєте записи, які мають, account_code='101.100'у tabel_buku_besarяких є, companyarea='20000'а також є IDRякcurrency

  2. Мені потрібно отримати весь запис, з tabel_buku_besarякого рахунок_код акаунта такий самий, як крок 1, але маю результат transaction_numberна кроці 1

під час використання select ... from...where....transaction_number in (select transaction_number from ....)мій запит працює надзвичайно повільно і іноді викликає час очікування запиту або змушує мою заявку не відповідати ...

Я пробую це поєднання і результат ... непогано ...

`select DATE_FORMAT(L.TANGGAL_INPUT,'%d-%m-%y') AS TANGGAL,
      L.TRANSACTION_NUMBER AS VOUCHER,
      L.ACCOUNT_CODE,
      C.DESCRIPTION,
      L.DEBET,
      L.KREDIT 
 from (select * from tabel_buku_besar A
                where A.COMPANYAREA='$COMPANYAREA'
                      AND A.CURRENCY='$Currency'
                      AND A.ACCOUNT_CODE!='$ACCOUNT'
                      AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) L 
INNER JOIN (select * from tabel_buku_besar A
                     where A.COMPANYAREA='$COMPANYAREA'
                           AND A.CURRENCY='$Currency'
                           AND A.ACCOUNT_CODE='$ACCOUNT'
                           AND (A.TANGGAL_INPUT BETWEEN STR_TO_DATE('$StartDate','%d/%m/%Y') AND STR_TO_DATE('$EndDate','%d/%m/%Y'))) R ON R.TRANSACTION_NUMBER=L.TRANSACTION_NUMBER AND R.COMPANYAREA=L.COMPANYAREA 
LEFT OUTER JOIN master_account C ON C.ACCOUNT_CODE=L.ACCOUNT_CODE AND C.COMPANYAREA=L.COMPANYAREA 
ORDER BY L.TANGGAL_INPUT,L.TRANSACTION_NUMBER`

0

Я вважаю це найефективнішим для пошуку, якщо значення існує, логіку можна легко перевернути, щоб знайти, якщо значення не існує (тобто IS NULL);

SELECT * FROM primary_table st1
LEFT JOIN comparision_table st2 ON (st1.relevant_field = st2.relevant_field)
WHERE st2.primaryKey IS NOT NULL

* Замініть relevant_field на ім'я значення, яке ви хочете перевірити, існує у вашій таблиці

* Замініть PrimaryKey на ім'я стовпця первинного ключа в таблиці порівняння.

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