Первое, что бросается в глаза как источник всех неприятностей:
Таблица приложения PHP представляет собой таблицу в стиле E-A-V ...
Попытка преобразовать данные в формате EAV в обычный реляционный формат на лету с использованием SQL неизбежно будет неудобной и неэффективной. Так что не пытайтесь разбить его в обычный формат столбца на атрибут. Следующий запрос возвращает несколько строк на подписчика, по одной строке на атрибут EAV:
SELECT ls.subscriberid AS id,
SUBSTRING_INDEX(l.name, _utf8'_', 1) AS user_id,
COALESCE(ls.emailaddress, _utf8'') AS email_address,
s.fieldid, s.data
FROM list_subscribers ls JOIN lists l ON (ls.listid = l.listid)
LEFT JOIN subscribers_data s ON (ls.subscriberid = s.subscriberid
AND s.fieldid IN (2,3,34,35,36,81,100,154)
WHERE SUBSTRING_INDEX(l.name, _utf8'_', 1) REGEXP _utf8'[[:digit:]]+'
Это исключает GROUP BY
, который не очень хорошо оптимизирован в MySQL, - обычно это временная таблица, которая убивает производительность.
id user_id email_address fieldid data
1 1 jdoe@example.com 2 John
1 1 jdoe@example.com 3 Doe
1 1 jdoe@example.com 81 5551234567
Но вам придется отсортировать атрибуты EAV в коде приложения. То есть в этом случае нельзя использовать ActiveRecord. Извините за это, но это один из недостатков использования нереляционного дизайна, такого как EAV.
Следующее, что я замечаю, это манипуляции со строкой-убийцей (даже после того, как я упростил ее с помощью SUBSTRING_INDEX()
). Когда вы выбираете подстроки из столбца, это говорит вам о том, что вы перегружали один столбец двумя различными частями информации. Одним из них является name
, а другим - какой-то атрибут типа списка, который вы бы использовали для фильтрации запроса. Храните один фрагмент информации в каждом столбце.
Вы должны добавить столбец для этого атрибута и проиндексировать его. Тогда предложение WHERE
может использовать индекс:
SELECT ls.subscriberid AS id,
SUBSTRING_INDEX(l.name, _utf8'_', 1) AS user_id,
COALESCE(ls.emailaddress, _utf8'') AS email_address,
s.fieldid, s.data
FROM list_subscribers ls JOIN lists l ON (ls.listid = l.listid)
LEFT JOIN subscribers_data s ON (ls.subscriberid = s.subscriberid
AND s.fieldid IN (2,3,34,35,36,81,100,154)
WHERE l.list_name_contains_digits = 1;
Кроме того, вы должны всегда анализировать SQL-запрос с помощью EXPLAIN
, если для них важно иметь хорошую производительность. В MS SQL Server есть аналогичная функция, поэтому вы должны привыкнуть к этой концепции, но терминология MySQL может отличаться.
Вам придется прочитать документацию, чтобы узнать, как интерпретировать отчет EXPLAIN
в MySQL, здесь слишком много информации, чтобы описать ее.
Дополнительная информация: Да, я понимаю, что вы не можете покончить со структурой таблицы EAV. Можете ли вы создать дополнительную таблицу? Затем вы можете загрузить в него данные EAV:
CREATE TABLE subscriber_mirror (
subscriberid INT PRIMARY KEY,
first_name VARCHAR(100),
last_name VARCHAR(100),
first_name2 VARCHAR(100),
last_name2 VARCHAR(100),
mobile_phone VARCHAR(100),
sms_only VARCHAR(100),
mobile_carrier VARCHAR(100)
);
INSERT INTO subscriber_mirror (subscriberid)
SELECT DISTINCT subscriberid FROM list_subscribers;
UPDATE subscriber_data s JOIN subscriber_mirror m USING (subscriberid)
SET m.first_name = IF(s.fieldid = 2, s.data, m.first_name),
m.last_name = IF(s.fieldid = 3, s.data, m.last_name),
m.first_name2 = IF(s.fieldid = 35, s.data, m.first_name2),
m.last_name2 = IF(s.fieldid = 36, s.data, m.last_name2),
m.mobile_phone = IF(s.fieldid = 81, s.data, m.mobile_phone),
m.sms_only = IF(s.fieldid = 100, s.data, m.sms_only),
m.mobile_carrer = IF(s.fieldid = 34, s.data, m.mobile_carrier);
Это займет некоторое время, но вам нужно будет сделать это только тогда, когда вы получите новое обновление данных от поставщика. Впоследствии вы можете запросить subscriber_mirror
в гораздо более традиционном SQL-запросе:
SELECT ls.subscriberid AS id, l.name+0 AS user_id,
COALESCE(s.first_name, s.first_name2) AS first_name,
COALESCE(s.last_name, s.last_name2) AS last_name,
COALESCE(ls.email_address, '') AS email_address),
COALESCE(s.mobile_phone, '') AS mobile_phone,
COALESCE(s.sms_only, '') AS sms_only,
COALESCE(s.mobile_carrier, '') AS mobile_carrier
FROM lists l JOIN list_subscribers USING (listid)
JOIN subscriber_mirror s USING (subscriberid)
WHERE l.name+0 > 0
Что касается идентификатора пользователя, который встроен в столбец l.name
, то если цифры являются ведущими символами в значении столбца, MySQL позволяет преобразовать целочисленное значение намного проще:
Выражение типа '123_bill'+0
дает целочисленное значение 123. Выражение типа 'bill_123'+0
не имеет цифр в начале, поэтому оно возвращает целочисленное значение 0.