Эффективный SQL-запрос или индекс, позволяющий определить, имеет ли весь столбец только 1 значение

#mysql #sql #indexing #triggers #database-performance

Вопрос:

У меня есть база данных MySQL с items таблицей, и каждый элемент привязан к учетной записи. Каждый из этих предметов может иметь один из нескольких цветов, что-то вроде этого:

Товары

 id | account_id | color
------------------------
1  | 1          | white
2  | 1          | white
3  | 1          | blue
4  | 1          | red
5  | 2          | white
6  | 2          | white
7  | 2          | white
 

На странице отображения элемента мы хотим показать цвет элемента, но только в том случае, если учетная запись когда-либо использовала более одного цвета. Как только они начнут использовать другой цвет в этой учетной записи, мы хотим показать цвета для всех элементов.

Например:

  • При отображении элемента с идентификатором 1 , мы бы показали , что это так white , потому что в учетной записи есть элементы, которые представляют собой смесь белого, синего и красного.
  • При отображении элемента с идентификатором 7 мы бы не показывали цвет , потому что все цвета в учетной записи одинаковы (белые), поэтому нет необходимости использовать цвет для их различения.

Чтобы сделать эту проверку, я в настоящее время выбираю любой элемент в учетной записи (первый, но он может быть любым) и запускаю запрос, чтобы узнать, есть ли в учетной записи элементы, которые не имеют такого же цвета, как этот:

 // Get any color in the account:
SELECT color FROM items WHERE account_id = 1 LIMIT 1;

// See if any other color is used ("red" was returned from the previous query):
SELECT 1 AS one FROM `items` WHERE account_id = 1 AND color != 'red' LIMIT 1;
 

Это работает и довольно эффективно для учетных записей, у которых есть множество цветов, которые они меняют, но в некоторых учетных записях могут быть тысячи или миллионы элементов, и они могут использовать только один цвет, поэтому, возможно, придется сканировать каждую строку в учетной записи, чтобы увидеть, нет ли других.

Я уже пытался добавить составной индекс в account_id и color , но даже для этого индекса пришлось бы сканировать тысячи или миллионы записей, чтобы быть уверенным, так как существует всего несколько цветов для группировки.

Is this acceptable? Is there a special kind of index I could use that would be efficient here, or would I need to add a trigger/procedure to update a cached column or table on every insert?

What I mean about triggers is something like this:

 DELIMITER $

CREATE TRIGGER items_after_insert_update_account_colors
AFTER INSERT
ON items FOR EACH ROW
BEGIN
  // Insert row, but ignore if the exact row already exists.
  INSERT IGNORE INTO account_colors
  SET color = NEW.color, account_id = NEW.account_id; 
END$

DELIMITER;
 

Затем проверьте эту таблицу, чтобы определить, следует ли отображать цвет или нет с помощью запроса, подобного этому:

 // True if other color is found for account.
SELECT 1 AS one FROM account_colors WHERE account_id = 1 AND color != 'red' LIMIT 1;
 

Существует ли метод индексирования, который я могу использовать для эффективной работы, или я использую триггер, подобный приведенному выше, для кэширования текущего состояния таблицы и обновления ее при каждой вставке-лучшее, что я могу сделать?

Подводя итог, я думаю, что спрашиваю, есть ли способ заставить индекс работать так, как описано выше, где даже для миллионов записей индекс крошечный и должен содержать только минимальную информацию, подобную этой:

 account_id | uses_color
-----------------------
1          | white
1          | blue
1          | red
2          | white
 

или

 account_id | used_colors
-------------------------------------
1          | ['blue', 'white', 'red']
2          | ['white']
 

Комментарии:

1. Какое отношение к этому имеет триггер? Какие результаты вы хотите получить от запроса?

2. К счастью, это относится ко всем представленным до сих пор учетным записям

3. @GordonLinoff Триггер-это просто пример того, как я мог бы это сделать, если бы не было хорошей стратегии индексирования, которая работает быстро для миллионов строк. Я хочу, чтобы запрос быстро сообщил мне, используется ли во всей учетной записи только один цвет.

4. Пожалуйста, приведите примеры данных для различных вариантов использования и покажите желаемый результат.

5. @RickJames Я добавил еще несколько столбцов в пример, чтобы показать учетную запись со всеми соответствующими цветами, и объяснил свой желаемый результат. Спасибо, что дали мне знать, где я был менее чем ясен.

Ответ №1:

Я бы предложил exists :

 select i.*
from items i
where exists (select 1
              from items i2
              where i2.account_id = i.account_id and
                    i2.color <> i.color
             )
order by i2.account_id;
 

Затем для производительности вам нужен индекс items(account_id, color) .

Комментарии:

1. Спасибо, что показали, как я мог бы выполнить это за один запрос, но это не моя цель здесь. Я уже добавил этот индекс, и, хотя он работает лучше, он все еще медленнее, чем мне хотелось бы. Я ищу метод компактного индекса, который, возможно, работает как триггер, я обновил вопрос с более подробной информацией и примерами.

Ответ №2:

 SELECT COUNT(DISTINCT(col)) FROM table;
 

вернет 1, если в этом столбце есть только одно другое значение.

К WHERE нему можно прикрепить оговорку, чтобы каким-то образом ограничить его. И вы можете использовать результат в подзапросе.

Комментарии:

1. Не COUNT(DISTINCT(col)) было бы намного медленнее, чем COUNT(col) > 1 или WHERE col != 'value' LIMIT 1 ? У меня уже есть решения, которые работают, я просто хочу знать, есть ли что-то быстрое для миллионов строк.

2. @Unixmonkey — Вы знаете одно «значение»? Было бы медленнее сначала получить «значение». В вашем первом примере COUNT(DISTINCT value) равно 3 для acct 1; и равно 1 для acct 2. COUNT(value) это >1 для каждого acct.