Задача звучала просто: посчитать, у скольких товаров в базе в составе есть хоть одна Е-добавка. Я думал, это работа на полчаса. Работа заняла вечер, и большая часть вечера ушла на одну букву.
Наивная версия
where ingredients_text ilike '%Е%'
Восемьдесят с чем-то тысяч совпадений. То есть почти всё. Потому что буква «е» встречается примерно в каждом русском слове, а ilike про регистр не спрашивает.
Ладно, ищем букву с цифрами.
where ingredients_text ~ 'Е[0-9]{3}'
Стало пятнадцать тысяч, и вот тут начинается интересное.
Грабля первая: их две
Буква «Е» в кодах добавок пишется и кириллицей, и латиницей. Производители используют обе, часто в одном составе: «Е330, E202, краситель Е160а». Глазом не отличить, для regexp это два разных символа.
Лечится в лоб — классом [EЕ], где первая латинская, вторая кириллическая. В коде это выглядит как опечатка, и каждый, кто лезет в репозиторий, первым делом порывается её «исправить». Я теперь пишу рядом комментарий капслоком.
Грабля вторая: пробелы и дефисы
«Е471», «Е 471», «Е-471», «Е — 471». Всё это встречается, всё это один эмульгатор. Добавляем \s?-?\s?.
Грабля третья: граница слева
Вот это самое подлое. Без границы Е[0-9]{3} радостно ловит хвосты артикулов, маркировок упаковки и всякого мусора, который затесался в поле состава: «…ТУ 9226-015-00419785». Внутри сидит идеальная «Е785», которой не существует в природе.
Поэтому слева нужен не-буквенно-цифровой символ или начало строки:
ingredients_text ~* '(^|[^0-9A-Za-zА-Яа-яЁё])[EЕ]\s?-?\s?[0-9]{3,4}'
{3,4} — потому что есть четырёхзначные, тот же Е1422, модифицированный крахмал. Он, кстати, в топе.
После этого цифра успокоилась и перестала прыгать между прогонами.
Что получилось
87 055 товаров с распознанным составом. Е-добавка есть у 17 270 — 19,8%. Каждый пятый.
Я ждал больше. Сильно больше.
Разброс по категориям, если брать крайности:
вода 0,0% конфеты 46,7% мука 0,9% жвачка 45,8% чай 1,4% ветчина 44,2% сок 1,8% колбаса 40,6% крупы 5,4% сосиски 38,9%
Середина тоже любопытная: газировка — 19,9%, ровно среднее по базе, хотя я был уверен, что там сплошняком. Сыр — 30,6%, почти как кетчуп.
Топ по частоте: Е621 (2 872 товара), Е536, Е476, Е330, Е471. Е536 — это антислёживатель из пачки соли, Е330 — лимонная кислота. На восьмом месте Е300, то есть витамин C, 1 200 товаров.
А теперь то, из-за чего цифру нельзя брать в лоб
Мой regexp ищет код. Он не ищет добавку.
Производитель имеет полное право написать «рибофлавин» вместо Е101 или «лецитин соевый» вместо Е322. Вещество то же, регламент тот же, в мою выборку оно не попадает. Никакого обмана, просто другой способ записи — и он, между прочим, честнее выглядит для покупателя при ровно том же содержимом пачки.
Так что 19,8% — это не «доля товаров с добавками». Это доля товаров, где добавка названа кодом. Нижняя граница. Настоящая цифра выше, а насколько — я не знаю, и любой, кто вам назовёт её точно, тоже не знает.
Отдельно веселит вывод: хочешь, чтобы твой товар в любом сканере состава выглядел чище — просто пиши словами. Полностью легально.
Бонус: как я считал длину состава
Вторым критерием мне нужно было число позиций в составе. Сделал очевидное:
cardinality(array_remove(string_to_array( regexp_replace(btrim(ingredients_text), '[;]', ',', 'g'), ','), ''))
Точки с запятой в запятые, split, посчитать. Работает — ровно до первой скобки.
«Шоколад (сахар, какао тёртое, масло какао, лецитин)» — это одна позиция состава. Мой счётчик видит пять. И чем сложнее товар, тем сильнее он завышает: у печенья с начинкой скобки вложены в скобки.
Я это оставил как есть, потому что критерий у меня грубый — «больше пяти позиций или нет». Но если кто-то соберётся считать состав всерьёз, начинать надо с парсера скобок, а не с split(','). Я вас предупредил.
Мораль
Половина работы с составами — это не аналитика, а археология: как именно на этой конкретной фабрике решили написать одну и ту же вещь. Две буквы Е, четыре способа поставить дефис, скобки без системы.
Зато когда цифра наконец сошлась, выяснилось, что «еда — сплошная химия» всё-таки преувеличение. Ну, если верить кодам. А словам верить сложнее.

