Как найти ошибочные запросы

Как найти ошибку в SQL-запросе

SQL-запрос – это то, что либо работает хорошо, либо не работает вообще, частично он никак работать не может, в отличие, например, от того же PHP. Как следствие, найти ошибку в SQL-запросе, просто рассматривая его – трудно, особенно если этот запрос снабжён целой кучей JOIN и UNION. Однако, в этой статье я расскажу о методе поиска ошибок в SQL-запросе.

Поскольку обычно в SQL-запрос подставляются какие-то переменные в PHP, то необходимо его сначала вывести. Сделать это можно, например, так:

<?php
  $a = 5;
  $query = "SELECT FROM `table` WHERE `id` = '$a'";
  $result_set = $mysqli->query($query); // Не работает
  echo $query; // Выводим запрос, который отправляется
?>

В результате, скрипт выведет такой запрос: SELECT FROM `table` WHERE `id` = ‘5’. Теперь чтобы найти ошибку в нём, надо зайти в phpMyAdmin, открыть базу данных, с которой происходит работа, открыть вкладку “SQL” и попытаться выполнить запрос.

И вот здесь уже ошибка будет показана, не в самой понятной форме (иногда прямо точно описывает ошибку), но она будет. Вот что написал phpMyAdmin: “#1064 – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘FROM `table` WHERE `id` = ‘5’ ORDER BY `table`.`id` ASC LIMIT 0, 30′ at line 1“. Это означает, что ошибка рядом с FROM. Присматриваемся к этому выделенному нами небольшому участку и обнаруживаем, что мы забыли поставить “*“. Исправляем сразу в phpMyAdmin эту ошибку, убеждаемся, что запрос сработал и после этого идём исправлять ошибку уже в коде.

С помощью этого метода я нахожу абсолютно все ошибки в SQL-запросе, которые мне не удаётся обнаружить непосредственно при осмотре в PHP-коде.

Надеюсь, теперь и Вы сможете найти ошибку в любом SQL-запросе.

  • Создано 01.05.2013 10:54:01


  • Михаил Русаков

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как создать свой сайт

  2. Текстовая ссылка:

    Она выглядит вот так: Как создать свой сайт

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Наверное, любой сервис, на котором вообще есть поиск, рано или поздно приходит к потребности научиться исправлять ошибки в пользовательских запросах. Errare humanum est; пользователи постоянно опечатываются и ошибаются, и качество поиска от этого неизбежно страдает — а с ним и пользовательский опыт.

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

Может показаться, что мы отказали пользователю в его мечте о вертикальной реальности, но на самом деле буква К просто стоит на клавиатуре рядом с буквой У.

В этой статье мы разберём один из классических подходов к исправлению опечаток, от построения модели до написания кода на Python и Go. И в качестве бонуса — видео с моего доклада «”Очки верткальной реальности”: исправляем опечатки в поисковых запросах» на Highload++.

Постановка задачи

Итак, нам пришёл опечатанный запрос и его надо исправить. Обычно математически задача ставится таким образом:

Эта постановка — самая элементарная — предполагает, что если нам пришёл запрос из нескольких слов, то мы исправляем каждое слово по отдельности. В реальности, конечно, мы захотим исправлять всю фразу целиком, учитывая сочетаемость соседних слов; об этом я расскажу ниже, в разделе “Как исправлять фразы”.

Неясных моментов здесь два — где взять словарь и как посчитать

$P(w|s)$. Первый вопрос считается простым. В 1990 году [1] словарь собирали из базы утилиты spell и доступных в электронном виде словарей; в 2009 году в Google [4] поступили проще и просто взяли топ самых популярных слов в Интернете (вместе с популярными ошибочными написаниями). Этот подход взял и я для построения своего опечаточника.

Второй вопрос сложнее. Хотя бы потому, что его решение обычно начинается с применения формулы Байеса!

$P(w|s) = mathrm{const} cdot P(s|w) cdot P(w)$

Теперь вместо исходной непонятной вероятности нам нужно оценить две новые, чуть более понятные:

$P(s|w)$ — вероятность того, что при наборе слова

$w$ можно опечататься и получить

$s$, и

$P(w)$ — в принципе вероятность использования пользователем слова

$w$.

Как оценить

$P(s|w)$? Очевидно, что пользователь с большей вероятностью путает А с О, чем Ъ с Ы. А если мы исправляем текст, распознанный с отсканированного документа, то велика вероятность путаницы между rn и m. Так или иначе, нам нужна какая-то модель, описывающая ошибки и их вероятности.

Такая модель называется noisy channel model (модель зашумлённого канала; в нашем случае зашумлённый канал начинается где-то в центре Брока пользователя и заканчивается по другую сторону его клавиатуры) или более коротко error model — модель ошибок. Эта модель, которой ниже посвящен отдельный раздел, будет ответственна за учёт как орфографических ошибок, так и, собственно, опечаток.

Оценить вероятность использования слова —

$P(w)$ — можно по-разному. Самый простой вариант — взять за неё частоту, с которой слово встречается в некотором большом корпусе текстов. Для нашего опечаточника, учитывающего контекст фразы, потребуется, конечно, что-то более сложное — ещё одна модель. Эта модель называется language model, модель языка.

Модель ошибок

Первые модели ошибок считали

$P(s|w)$, подсчитывая вероятности элементарных замен в обучающей выборке: сколько раз вместо Е писали И, сколько раз вместо ТЬ писали Т, вместо Т — ТЬ и так далее [1]. Получалась модель с небольшим числом параметров, способная выучить какие-то локальные эффекты (например, что люди часто путают Е и И).

В наших изысканиях мы остановились на более развитой модели ошибок, предложенной в 2000 году Бриллом и Муром [2] и многократно использованной впоследствии (например, специалистами Google [4]). Представим, что пользователи мыслят не отдельными символами (спутать Е и И, нажать К вместо У, пропустить мягкий знак), а могут изменять произвольные куски слова на любые другие — например, заменять ТСЯ на ТЬСЯ, У на К, ЩА на ЩЯ, СС на С и так далее. Вероятность того, что пользователь опечатался и вместо ТСЯ написал ТЬСЯ, обозначим

$P(text{тся} rightarrow text{ться})$ — это параметр нашей модели. Если для всех возможных фрагментов

$alpha, beta$ мы можем посчитать

$P(alpharightarrowbeta)$, то искомую вероятность

$P(s|w)$ набора слова s при попытке набрать слово w в модели Брилла и Мура можно получить следующим образом: разобьем всеми возможными способами слова w и s на более короткие фрагменты так, чтобы фрагментов в двух словах было одинаковое количество. Для каждого разбиения посчитаем произведение вероятностей всех фрагментов w превратиться в соответствующие фрагменты s. Максимум по всем таким разбиениям и примем за значение величины

$P(s|w)$:

$P(s|w) = max_{s=alpha_1alpha_2ldotsalpha_k, w=beta_1beta_2ldotsbeta_k}P(alpha_1rightarrowbeta_1)cdot P(alpha_2rightarrowbeta_2)cdot ldots cdot P(alpha_krightarrowbeta_k),.$

Давайте посмотрим на пример разбиения, возникающего при вычислении вероятности напечатать «аксесуар» вместо «аксессуар»:

$begin{matrix} text{ак} & text{сес} & text{су} & text{а} & text{р} \ downarrow & downarrow & downarrow & downarrow & downarrow \ text{а} & text{кс} & text{е} & text{суа} & text{р} end{matrix}$

Как вы наверняка заметили, это пример не очень удачного разбиения: видно, что части слов легли друг под другом не так удачно, как могли бы. Если величины

$P(text{ак} rightarrow text{а})$ и

$P(text{р} rightarrow text{р})$ ещё не так плохи, то

$P(text{су} rightarrow text{е})$ и

$P(text{а} rightarrow text{суа})$, скорее всего, сделают итоговый «счёт» этого разбиения совсем печальным. Более удачное разбиение выглядит как-то так:

$begin{matrix} text{ак} & text{се} & text{сс} & text{у} & text{ар} \ downarrow & downarrow & downarrow & downarrow & downarrow \ text{ак} & text{се} & text{с} & text{у} & text{ар} end{matrix}$

Здесь всё сразу стало на свои места, и видно, что итоговая вероятность будет определяться преимущественно величиной

$P(text{сс} rightarrow text{с})$.

Как вычислить $P(s|w)$

Несмотря на то, что возможных разбиений для двух слов имеется порядка

$O(2^{|s|+|w|})$, с помощью динамического программирования алгоритм вычисления

$P(s|w)$ можно сделать довольно быстрым — за

$O(|s|^2|w|^2)$. Сам алгоритм при этом будет очень сильно напоминать алгоритм Вагнера-Фишера для вычисления расстояния Левенштейна.

Мы заведём прямоугольную таблицу, строки которой будут соответствовать буквам правильного слова, а столбцы — опечатанного. В ячейке на пересечении строки i и столбца j к концу алгоритма будет лежать в точности вероятность получить s[:j] при попытке напечатать w[:i]. Для того, чтобы её вычислить, достаточно вычислить значения всех ячеек в предыдущих строках и столбцах и пробежаться по ним, домножая на соответствующие

$P(alpharightarrowbeta)$. Например, если у нас заполнена таблица

, то для заполнения ячейки в четвёртой строке и третьем столбце (серая) нужно взять максимум из величин

$0.8 cdot P(text{кс} rightarrow text{к})$ и

$0.16 cdot P(text{с} rightarrow text{к})$. При этом мы пробежались по всем ячейкам, подсвеченным на картинке зелёным. Если также рассматривать модификации вида

$P(alpha rightarrow text{пустая строка})$ и

$P(text{пустая строка} rightarrow beta)$, то придётся пробежаться и по ячейкам, подсвеченным жёлтым.

Сложность этого алгоритма, как я уже упомянул выше, составляет

$O(|s|^2|w|^2)$: мы заполняем таблицу

$|s|times|w|$, и для заполнения ячейки (i, j) нужно

$O(icdot j)$ операций. Впрочем, если мы ограничим рассмотрение фрагментами не больше какой-то ограниченной длины

$L$ (например, не больше двух букв, как в [4]), сложность уменьшится до

$O(|s|cdot|w|cdot L^2)$. Для русского языка в своих экспериментах я брал

$L = 3$.

Как максимизировать $P(s|w)$

Мы научились находить

$P(s|w)$ за полиномиальное время — это хорошо. Но нам нужно научиться быстро находить наилучшие слова во всём словаре. Причём наилучшие не по

$P(s|w)$, а по

$P(w|s)$! На деле нам достаточно получить какой-то разумный топ (например, лучшие 20) слов по

$P(s|w)$, который мы потом отправим в модель языка для выбора наиболее подходящих исправлений (об этом ниже).

Чтобы научиться быстро проходить по всему словарю, заметим, что таблица, представленная выше, будет иметь много общего для двух слов с общими префиксами. Действительно, если мы, исправляя слово «аксесуар», попробуем заполнить её для двух словарных слов «аксессуар» и «аксессуары», мы заметим, что первые девять строк в них вообще не отличаются! Если мы сможем организовать проход по словарю так, что у двух последующих слов будут достаточно длинные общие префиксы, мы сможем круто сэкономить вычисления.

И мы сможем. Давайте возьмём словарные слова и составим из них trie. Идя по нему поиском в глубину, мы получим желаемое свойство: большинство шагов — это шаги от узла к его потомку, когда у таблицы достаточно дозаполнить несколько последних строк.

Этот алгоритм, при некоторых дополнительных оптимизациях, позволяет нам перебирать словарь типичного европейского языка в 50-100 тысяч слов в пределах сотни миллисекунд [2]. А кэширование результатов сделает процесс ещё быстрее.

Как получить $P(alpharightarrowbeta)$

Вычисление

$P(alpharightarrowbeta)$ для всех рассматриваемых фрагментов — самая интересная и нетривиальная часть построения модели ошибок. Именно от этих величин будет зависеть её качество.

Подход, использованный в [2, 4], сравнительно прост. Давайте найдём много пар

$(s_i, w_i)$, где

$w_i$ — правильное слово из словаря, а

$s_i$ — его опечатанный вариант. (Как именно их находить — чуть ниже.) Теперь нужно извлечь из этих пар вероятности конкретных опечаток (замен одних фрагментов на другие).

Для каждой пары возьмём составляющие её

$w$ и

$s$ и построим соответствие между их буквами, минимизирующее расстояние Левенштейна:

$begin{matrix} text{а} & text{к} & text{с} & text{е} & text{с} & text{с} & text{у} & text{а} & text{р} \ text{а} & text{к} & text{с} & text{е} & text{с} & text{} & text{у} & text{а} & text{р} end{matrix}$

Теперь мы сразу видим замены: а → а, е → и, с → с, с → пустая строка и так далее. Также мы видим замены двух и более символов: ак → ак, се → си, ес → ис, сс → с, сес → сис, есс → ис и прочая, и прочая. Все эти замены необходимо посчитать, причём каждую столько раз, сколько слово s встречается в корпусе (если мы брали слова из корпуса, что очень вероятно).

После прохода по всем парам

$(s_i, w_i)$ за вероятность

$P(alpharightarrowbeta)$ принимается количество замен α → β, встретившихся в наших парах (с учётом встречаемости соответствующих слов), делённое на количество повторений фрагмента α.

Как найти пары

$(s_i, w_i)$? В [4] предлагается такой подход. Возьмём большой корпус сгенерированного пользователями контента (UGC). В случае Google это были просто тексты сотен миллионов веб-страниц; в нашем — миллионы пользовательских поисковых запросов и отзывов. Предполагается, что обычно правильное слово встречается в корпусе чаще, чем любой из ошибочных вариантов. Так вот, давайте для каждого слова находить близкие к нему по Левенштейну слова из корпуса, которые значительно менее популярны (например, в десять раз). Популярное возьмём за

$w$, менее популярное — за

$s$. Так мы получим пусть и шумный, но зато достаточно большой набор пар, на котором можно будет провести обучение.

Этот алгоритм подбора пар оставляет большое пространство для улучшений. В [4] предлагается только фильтр по встречаемости (

$w$ в десять раз популярнее, чем

$s$), но авторы этой статьи пытаются делать опечаточник, не используя какие-либо априорные знания о языке. Если мы рассматриваем только русский язык, мы можем, например, взять набор словарей русских словоформ и оставлять только пары со словом

$w$, найденном в словаре (не лучшая идея, потому что в словаре, скорее всего, не будет специфичной для сервиса лексики) или, наоборот, отбрасывать пары со словом s, найденном в словаре (то есть почти гарантированно не являющимся опечатанным).

Чтобы повысить качество получаемых пар, я написал несложную функцию, определяющую, используют ли пользователи два слова как синонимы. Логика простая: если слова w и s часто встречаются в окружении одних и тех же слов, то они, вероятно, синонимы — что в свете их близости по Левенштейну значит, что менее популярное слово с большой вероятностью является ошибочной версией более популярного. Для этих расчётов я использовал построенную для модели языка ниже статистику встречаемости триграмм (фраз из трёх слов).

Модель языка

Итак, теперь для заданного словарного слова w нам нужно вычислить

$P(w)$ — вероятность его использования пользователем. Простейшее решение — взять встречаемость слова в каком-то большом корпусе. Вообще, наверное, любая модель языка начинается с собирания большого корпуса текстов и подсчёта встречаемости слов в нём. Но ограничиваться этим не стоит: на самом деле при вычислении P(w) мы можем учесть также и фразу, слово в которой мы пытаемся исправить, и любой другой внешний контекст. Задача превращается в задачу вычисления

$P(w_1w_2ldots w_k)$, где одно из

$w_i$ — слово, в котором мы исправили опечатку и для которого мы теперь рассчитываем

$P(w)$, а остальные

$w_i$ — слова, окружающие исправляемое слово в пользовательском запросе.

Чтобы научиться учитывать их, стоит пройтись по корпусу ещё раз и составить статистику n-грамм, последовательностей слов. Обычно берут последовательности ограниченной длины; я ограничился триграммами, чтобы не раздувать индекс, но тут всё зависит от вашей силы духа (и размера корпуса — на маленьком корпусе даже статистика по триграммам будет слишком шумной).

Традиционная модель языка на основе n-грамм выглядит так. Для фразы

$w_1w_2ldots w_k$ её вероятность вычисляется по формуле

$ P(w_1w_2ldots w_k) =P(w_1) cdot P(w_2|w_1) cdot P(w_3|w_1w_2) P(w_k|w_1w_2w_{k-1}),, $

где

$P(w_1)$ — непосредственно частота слова, а

$P(w_3|w_1w_2)$ — вероятность слова

$w_3$ при условии, что перед ним идут

$w_1w_2$ — не что иное, как отношение частоты триграммы

$w_1 w_2 w_3$ к частоте биграммы

$w_1 w_2$. (Заметьте, что эта формула — просто результат многократного применения формулы Байеса.)

Иными словами, если мы захотим вычислить

$P(text{мама мыла раму})$, обозначив частоту произвольной n-граммы за

$f$, мы получим формулу

$ P(text{мама мыла раму}) = f(text{мама}) cdot frac{f(text{мама мыла})}{f(text{мама})} cdot frac{f(text{мама мыла раму})}{f(text{мама мыла})} = f(text{мама мыла раму}),. $

Логично? Логично. Однако трудности начинаются, когда фразы становятся длиннее. Что, если пользователь ввёл впечатляющий своей подробностью поисковый запрос в десять слов? Мы не хотим держать статистику по всем 10-граммам — это дорого, а данные, скорее всего, будут шумными и не показательными. Мы хотим обойтись n-граммами какой-то ограниченной длины — например, уже предложенной выше длины 3.

Здесь-то и пригождается формула выше. Давайте предположим, что на вероятность слова появиться в конце фразы значительно влияют только несколько слов непосредственно перед ним, то есть что

$ P(w_k|w_1w_2ldots w_{k-1}) approx P(w_k|w_{k-L+1}ldots w_{k-1}),. $

Положив

$L = 3$, для более длинной фразы получим формулу

$ P(text{карл у клары украл кораллы}) approx f(text{карл}) cdot frac{f(text{карл у})}{f(text{карл})}cdot frac{f(text{карл у клары})}{f(text{карл у})} cdot frac{f(text{у клары украл})}{f(text{у клары})} cdot frac{f(text{клары украл кораллы})}{f(text{клары украл})},. $

 

Обратите внимание: фраза состоит из пяти слов, но в формуле фигурируют n-граммы не длиннее трёх. Это как раз то, чего мы добивались.

Остался один тонкий момент. Что, если пользователь ввёл совсем странную фразу и соответствующих n-грамм у нас в статистике и нет вовсе? Было бы легко для незнакомых n-грамм положить

$f = 0$, если бы на эту величину не надо было делить. Здесь на помощь приходит сглаживание (smoothing), которое можно делать разными способами; однако подробное обсуждение серьёзных подходов к сглаживанию вроде Kneser-Ney smoothing выходит далеко за рамки этой статьи.

Как исправлять фразы

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

Подходов здесь может быть много. Можно, например, учитывать только левый контекст слова в фразе. Тогда, идя по словам слева направо и по мере необходимости исправляя их, мы получим новую фразу какого-то качества. Качество будет низким, если, например, первое слово оказалось похоже на несколько популярных слов и мы выберем неправильный вариант. Вся оставшаяся фраза (возможно, изначально вообще безошибочная) будет подстраиваться нами под неправильное первое слово и мы можем получить на выходе полностью нерелевантный оригиналу текст.

Можно рассматривать слова по отдельности и применять некий классификатор, чтобы понимать, опечатано данное слово или нет, как это предложено в [4]. Классификатор обучается на вероятностях, которые мы уже умеем считать, и ряде других фичей. Если классификатор говорит, что нужно исправлять — исправляем, учитывая имеющийся контекст. Опять-таки, если несколько слов написаны с ошибкой, принимать решение насчёт первого из них придётся, опираясь на контекст с ошибками, что может приводить к проблемам с качеством.

В реализации нашего опечаточника мы использовали такой подход. Давайте для каждого слова

$s_i$ в нашей фразе найдём с помощью модели ошибок топ-N словарных слов, которые могли иметься в виду, сконкатенируем их во фразы всевозможными способами и для каждой из

$N^K$ получившихся фраз, где

$K$ — количество слов в исходной фразе, посчитаем честно величину

$ P(s_1|w_1) cdot P(s_2|w_2) cdot ldots cdot P(s_K|w_K) cdot P(w_1w_2ldots w_K)^{lambda},. $

Здесь

$s_i$ — слова, введённые пользователем,

$w_i$ — подобранные для них исправления (которые мы сейчас перебираем), а

$lambda$ — коэффициент, определяемый сравнительным качеством модели ошибок и модели языка (большой коэффициент — больше доверяем модели языка, маленький коэффициент — больше доверяем модели ошибок), предложенный в [4]. Итого для каждой фразы мы перемножаем вероятности отдельных слов исправиться в соответствующие словарные варианты и ещё домножаем это на вероятность всей фразы в нашем языке. Результат работы алгоритма — фраза из словарных слов, максимизирующая эту величину.

Так, стоп, что? Перебор

$N^K$ фраз?

К счастью, за счёт того, что мы ограничили длину n-грамм, найти максимум по всем фразам можно гораздо быстрее. Вспомните: выше мы упростили формулу для

$P(w_1w_2ldots w_K)$ так, что она стала зависеть только от частот n-грамм длины не выше трёх:

$ P(w_1w_2ldots w_K) =P(w_1) cdot P(w_2|w_1) cdot P(w_3|w_1w_2) cdot ldots cdot P(w_K|w_{K-2}w_{K-1}),. $

Если мы домножим эту величину на

$P(s_i|w_i)$ и попытаемся максимизировать по

$w_K$, мы увидим, что достаточно перебрать всевозможные

$w_{K-2}$ и

$w_{K-1}$ и решить задачу для них — то есть для фраз

$w_1w_2ldots w_{K-2}w_{K-1}$. Итого задача решается динамическим программированием за

$O(KN^3)$.

Реализация

Собираем корпус и считаем n-граммы

Сразу оговорюсь: данных в моём распоряжении было не так много, чтобы заводить какой-то сложный MapReduce. Так что я просто собрал все тексты отзывов, комментариев и поисковых запросов на русском языке (описания товаров, увы, приходят на английском, а использование результатов автоперевода скорее ухудшило, чем улучшило результаты) с нашего сервиса в один текстовый файл и поставил сервер на ночь считать триграммы простым скриптом на Python.

В качестве словарных я брал топ слов по частотности так, чтобы получалось порядка ста тысяч слов. Исключались слишком длинные слова (больше 20 символов) и слишком короткие (меньше трёх символов, кроме захардкоженных известных русских слов). Отдельно пощадил слова по регулярке r"^[a-z0-9]{2}$" — чтобы уцелели версии айфонов и другие интересные идентификаторы длины 2.

При подсчёте биграмм и триграмм во фразе может встретиться несловарное слово. В этом случае это слово я выбрасывал и бил всю фразу на две части (до и после этого слова), с которыми работал отдельно. Так, для фразы «А вы знаете, что такое «абырвалг»? Это… ГЛАВРЫБА, коллега» учтутся триграммы “а вы знаете”, “вы знаете что”, “знаете что такое” и “это главрыба коллега” (если, конечно, слово “главрыба” попадёт в словарь…).

Обучаем модель ошибок

Дальше всю обработку данных я проводил в Jupyter. Статистика по n-граммам грузится из JSON, производится постобработка, чтобы быстро находить близкие друг к другу по Левенштейну слова, и для пар в цикле вызывается (довольно громоздкая) функция, выстраивающая слова и извлекающая короткие правки вида сс → с (под спойлером).

Код на Python

def generate_modifications(intended_word, misspelled_word, max_l=2):
    # Выстраиваем буквы слов оптимальным по Левенштейну образом и
    # извлекаем модификации ограниченной длины. Чтобы после подсчёта
    # расстояния восстановить оптимальное расположение букв, будем
    # хранить в таблице помимо расстояний указатели на предыдущие
    # ячейки: memo будет хранить соответствие
    # i -> j -> (distance, prev i, prev j).
    
    # Дальше немного непривычно страшного Python кода - вот что
    # бывает, когда язык используется не по назначению!
    m, n = len(intended_word), len(misspelled_word)
    memo = [[None] * (n+1) for _ in range(m+1)]
    memo[0] = [(j, (0 if j > 0 else -1), j-1) for j in range(n+1)]
    for i in range(m + 1):
        memo[i][0] = i, i-1, (0 if i > 0 else -1)
    for j in range(1, n + 1):
        for i in range(1, m + 1):
            if intended_word[i-1] == misspelled_word[j-1]:
                memo[i][j] = memo[i-1][j-1][0], i-1, j-1
            else:
                best = min(
                    (memo[i-1][j][0], i-1, j),
                    (memo[i][j-1][0], i, j-1),
                    (memo[i-1][j-1][0], i-1, j-1),
                )
                # Отдельная обработка для перепутанных местами
                # соседних  букв (распространённая ошибка при
                # печати).
                if (i > 1
                    and j > 1
                    and intended_word[i-1] == misspelled_word[j-2]
                    and intended_word[i-2] == misspelled_word[j-1]
                   ):
                    best = min(best, (memo[i-2][j-2][0], i-2, j-2))
                memo[i][j] = 1 + best[0], best[1], best[2]
    # К концу цикла расстояние по Левенштейну между исходными словами     # хранится в memo[m][n][0]. 

    # Теперь восстанавливаем оптимальное расположение букв.
    s, t = [], []
    i, j = m, n
    while i >= 1 or j >= 1:
        _, pi, pj = memo[i][j]
        di, dj = i - pi, j - pj
        if di == dj == 1:
            s.append(intended_word[i-1])
            t.append(misspelled_word[j-1])
        if di == dj == 2:
            s.append(intended_word[i-1])
            s.append(intended_word[i-2])
            t.append(misspelled_word[j-1])
            t.append(misspelled_word[j-2])
        if 1 == di > dj == 0:
            s.append(intended_word[i-1])
            t.append("")
        if 1 == dj > di == 0:
            s.append("")
            t.append(misspelled_word[j-1])
        i, j = pi, pj
    s.reverse()
    t.reverse()
    
    # Генерируем модификации длины не выше заданной.
    for i, _ in enumerate(s):
        ss = ts = ""
        while len(ss) < max_l and i < len(s):
            ss += s[i]
            ts += t[i]
            yield ss, ts
            i += 1

Сам подсчёт правок выглядит элементарно, хотя длиться может долго.

Применяем модель ошибок

Эта часть реализована в виде микросервиса на Go, связанного с основным бэкендом через gRPC. Реализован алгоритм, описанный самими Бриллом и Муром [2], с небольшими оптимизациями. Работает он у меня в итоге примерно вдвое медленнее, чем заявляли авторы; не берусь судить, дело в Go или во мне. Но по ходу профилировки я узнал о Go немного нового.

  • Не используйте math.Max, чтобы считать максимум. Это примерно в три раза медленнее, чем if a > b { b = a }! Только взгляните на реализацию этой функции:
    // Max returns the larger of x or y.
    //
    // Special cases are:
    //	Max(x, +Inf) = Max(+Inf, x) = +Inf
    //	Max(x, NaN) = Max(NaN, x) = NaN
    //	Max(+0, ±0) = Max(±0, +0) = +0
    //	Max(-0, -0) = -0
    func Max(x, y float64) float64
    
    
    func max(x, y float64) float64 {
    	// special cases
    	switch {
    	case IsInf(x, 1) || IsInf(y, 1):
    		return Inf(1)
    	case IsNaN(x) || IsNaN(y):
    		return NaN()
    	case x == 0 && x == y:
    		if Signbit(x) {
    			return y
    		}
    		return x
    	}
    	if x > y {
    		return x
    	}
    	return y
    }

    Если только вам ВДРУГ не нужно, чтобы +0 обязательно был больше -0, не используйте math.Max.

  • Не используйте хэш-таблицу, если можете использовать массив. Это, конечно, довольно очевидный совет. Мне пришлось перенумеровывать символы юникода в числа в начале программы так, чтобы использовать их как индексы в массиве потомков узла trie (такой lookup был очень частой операцией).
  • Коллбеки в Go недёшевы. В ходе рефакторинга во время код ревью некоторые мои потуги сделать decoupling ощутимо замедлили программу при том, что формально алгоритм не изменился. С тех пор я остаюсь при мнении, что оптимизирующему компилятору Go есть, куда расти.

Применяем модель языка

Здесь без особых сюрпризов был реализован алгоритм динамического программирования, описанный в разделе выше. На этот компонент пришлось меньше всего работы — самой медленной частью остаётся применение модели ошибок. Поэтому между этими двумя слоями было дополнительно прикручено кэширование результатов модели ошибок в Redis.

Результаты

По итогам этой работы (занявшей примерно человекомесяц) мы провели A/B тест опечаточника на наших пользователях. Вместо 10% пустых выдач среди всех поисковых запросов, которые мы имели до внедрения опечаточника, их стало 5%; в основном оставшиеся запросы приходятся на товары, которых просто нет у нас на платформе. Также увеличилось количество сессий без второго поискового запроса (и ещё несколько метрик такого рода, связанных с UX). Метрики, связанные с деньгами, впрочем, значимо не изменились — это было неожиданно и сподвигло нас к тщательному анализу и перепроверке других метрик.

Заключение

Стивену Хокингу как-то сказали, что каждая включенная им в книгу формула вдвое уменьшит число читателей. Что ж, в этой статье их порядка полусотни — поздравляю тебя, один из примерно

$10^{-10}$ читателей, добравшихся до этого места!

Бонус

Ссылки

[1] M. D. Kernighan, K. W. Church, W. A. Gale. A Spelling Correction Program Based on a Noisy Channel Model. Proceedings of the 13th conference on Computational linguistics — Volume 2, 1990.

[2] E.Brill, R. C. Moore. An Improved Error Model for Noisy Channel Spelling Correction. Proceedings of the 38th Annual Meeting on Association for Computational Linguistics, 2000.

[3] T. Brants, A. C. Popat, P. Xu, F. J. Och, J. Dean. Large Language Models in Machine Translation. Proceedings of the 2007 Conference on Empirical Methods in Natural Language Processing.

[4] C. Whitelaw, B. Hutchinson, G. Y. Chung, G. Ellis. Using the Web for Language Independent Spellchecking and Autocorrection. Proceedings of the 2009 Conference on Empirical Methods in Natural Language Processing: Volume 2.

Богдан Василенко

Богдан Василенко


SEO-специалист SE Ranking

По каждому запросу поисковая система подбирает релевантные результаты — страницы, подходящие по тематике, и ранжирует их, отображая в виде списка. Согласно исследованиям, 99 % пользователей находят информацию, отвечающую запросу, уже на первой странице выдачи и не пролистывают дальше. И чем выше позиция сайта в топ-10, тем больше посетителей она привлекает.

Перед тем, как распределить ресурсы в определенном порядке, поисковики оценивают их по ряду параметров. Это позволяет улучшить выдачу для пользователя, предоставляя наиболее полезные, удобные и авторитетные варианты.

В чём заключается оптимизация сайта?

Оптимизация сайта или SEO (Search Engine Optimization) представляет собой комплекс действий, цель которых — улучшить качество ресурса и адаптировать его с учётом рекомендаций поисковых систем.

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

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

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

Как обнаружить проблемы SEO на сайте?

Процесс оптимизации стоит начать с SEO-аудита — анализа сайта по самым разным критериям. Есть инструменты, выполняющие оценку определенных показателей, например, статус страниц, скорость загрузки, адаптивность для мобильных устройств и так далее. Альтернативный вариант — аудит сайта на платформе для SEO-специалистов.

Один из примеров — сервис SE Ranking, объединяющий в себе разные аналитические инструменты. Результатом SEO-анализа будет комплексный отчёт. Для запуска анализа сайта онлайн нужно создать проект, указать в настройках домен своего ресурса, и перейти в раздел «Анализ сайта». Одна из вкладок — «Отчёт об ошибках», где отображаются выявленные проблемы оптимизации.

Все параметры сайта разделены на блоки: «Безопасность», «Дублирование контента», «Скорость загрузки» и другие. При нажатии на любую из проблем появится её описание и рекомендации по исправлению. После технической SEO оптимизации и внесения корректировок следует повторно запустить аудит сайта. Увидеть, были ли устранены ошибки, можно колонке «Исправленные».

Ошибки технической оптимизации и способы их устранения

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

Отсутствие протокола HTTPS

Расширение HTTPS (HyperText Transfer Protocol Secure), которое является частью доменного имени, — это более надежная альтернатива протоколу соединения HTTP. Оно обеспечивает шифрование и сохранность данных пользователей. Сегодня многие браузеры блокируют переход по ссылке, начинающейся на HTTP, и отображают предупреждение на экране.

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

Как исправить

Чтобы перевести ресурс на HTTPS, необходимо приобрести специальный сертификат и затем своевременно продлевать срок его действия. Настроить автоматическое перенаправление с HTTP-версии (редирект) можно в файле конфигурации .htaccess.

После перехода на безопасный протокол будет полезно выполнить аудит сайта и убедиться, что всё сделано правильно, а также при необходимости заменить неактуальные URL с HTTP среди внутренних ссылок (смешанный контент).

У сайта нет файла robots.txt

Документ robots размещают в корневой папке сайта. Его содержимое доступно по ссылке website.com/robots.txt. Этот файл представляет собой инструкцию для поисковых систем, какое содержимое ресурса следует сканировать, а какое нет. К нему роботы обращаются в первую очередь и затем начинают обход сайта.

Ограничение сканирования файлов и папок особенно актуально для экономии краулингового бюджета — общего количества URL, которое может просканировать робот на данном сайте. Если инструкция для краулеров отсутствует или составлена неправильно, это может привести к проблемам с отображением страниц в выдаче.

Как исправить

Создайте текстовый документ с названием robots в корневой папке сайта и с помощью директив пропишите внутри рекомендации по сканированию содержимого страниц и каталогов. В файле могут быть указаны виды роботов (user-agent), для которых действуют правила; ограничивающие и разрешающие команды (disallow, allow), а также ссылка на карту сайта (sitemap).

Проблемы с файлом Sitemap.xml

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

Обработка XML Sitemap может быть затруднительна, если ее размер превышает 50 МБ или 50000 URL. Другая проблема — присутствие в карте страниц, закрытых для индексации метатегом noindex. При использовании канонических ссылок на сайте, выделяющих их похожих страниц основную, в файле sitemap должны быть указаны только приоритетные для индексации URL.

Как исправить

Если в карте сайта очень много URL и её объем превышает лимит, разделите файл на несколько меньших по размеру. XML Sitemap можно создавать не только для страниц, но и для изображений или видео. В файле robots.txt укажите ссылки на все карты сайта.

В случае, когда SEO-аудит выявил противоречия, — страницы в карте сайта, имеющие  запрет индексации noindex в коде, их необходимо устранить. Также проследите, чтобы в Sitemap были указаны только канонические URL.

Дубли контента

Один из важных факторов, влияющих на ранжирование, — уникальность контента. Недопустимо не только  копирование текстов у конкурентов, но и дублирование их внутри своего сайта. Это проблема особенно актуальна для больших ресурсов, например, интернет-магазинов, где описания к товарам имеют минимальные отличия.

Причиной, почему дубли страниц попадают в индекс, может быть отсутствие или неправильная настройка «зеркала» — редиректа между именем сайта с www и без. В этом случае поисковая система индексирует две идентичные страницы, например, www.website.com и website.com.

Также к проблеме дублей приводит копирование контента внутри сайта без настройки канонических ссылок, определяющих приоритетную для индексации страницу из похожих.

Как исправить

Настройте www-редиректы и проверьте с помощью SEO-аудита, не осталось ли на сайте дублей. При создании страниц с минимальными отличиями используйте канонические ссылки, чтобы указать роботу, какие из них индексировать. Чтобы не ввести в заблуждение поисковые системы, неканоническая страница должна содержать тег rel=”canonical” только для одного URL.

Страницы, отдающие код ошибки

Перед тем, как отобразить страницу на экране, браузер отправляет запрос серверу. Если URL доступен, у него будет успешный статус HTTP-состояния — 200 ОК. При возникновении проблем, когда сервер не может выполнить задачу, страница возвращает код ошибки 4ХХ или 5ХХ. Это приводит к таким негативным последствиям для сайта, как:

  • Ухудшение поведенческих факторов. Если вместо запрошенной страницы пользователь видит сообщение об ошибке, например, «Page Not Found» или «Internal Server Error», он не может получить нужную информацию или завершить целевое действие.
  • Исключение контента из индекса. Когда роботу долго не удается просканировать страницу, она  может быть удалена из индекса поисковой системы.
  • Расход краулингового бюджета. Роботы делают попытку просканировать URL, независимо от его статуса. Если на сайте много страниц с ошибками, происходит бессмысленный расход краулингового лимита.

Как исправить

После анализа сайта найдите страницы в статусе 4ХХ и 5ХХ и установите, в чём причина ошибки. Если страница была удалена, поисковая система через время исключит её из индекса. Ускорить этот процесс поможет инструмент удаления URL. Чтобы своевременно находить проблемные страницы, периодически повторяйте поиск проблем на сайте.

Некорректная настройка редиректов

Редирект — это переадресация в браузере с запрошенного URL на другой. Обычно его настраивают при смене адреса страницы и её удалении, перенаправляя пользователя на актуальную версию.

Преимущества редиректов в том, что они происходят автоматически и быстро. Их использование может быть полезно для SEO, когда нужно передать наработанный авторитет от исходной страницы к новой.

Но при настройке переадресаций нередко возникают такие проблемы, как:

  • слишком длинная цепочка редиректов — чем больше в ней URL, тем позже отображается конечная страница;
  • зацикленная (циклическая) переадресация, когда страница ссылается на себя или конечный URL содержит редирект на одно из предыдущих звеньев цепочки;
  • в цепочке переадресаций есть неработающий, отдающий код ошибки URL;
  • страниц с редиректами слишком много — это уменьшает краулинговый бюджет.

Как исправить

Проведите SEO-аудит сайта и найдите страницы со статусом 3ХХ.  Если среди них есть цепочки редиректов, состоящие из трех и более URL, их нужно сократить до двух адресов — исходного и актуального. При выявлении зацикленных переадресаций необходимо откорректировать их последовательность. Страницы, имеющие статус ошибки 4ХХ или 5ХХ, нужно сделать доступными или удалить из цепочки.

Низкая скорость загрузки

Скорость отображения страниц — важный критерий удобства сайта, который поисковые системы учитывают при ранжировании. Если контент загружается слишком долго, пользователь может не дождаться и покинуть ресурс.

Google использует специальные показатели Core Web Vitals для оценки сайта, где о скорости говорят значения LCP (Largest Contentful Paint) и FID (First Input Delay). Рекомендуемая скорость загрузки основного контента (LCP) — до 2,5 секунд. Время отклика на взаимодействие с элементами страницы (FID) не должно превышать 0,1.

К распространённым факторам, негативно влияющим на скорость загрузки, относятся:

  • объёмные по весу и размеру изображения;
  • несжатый текстовый контент;
  • большой вес HTML-кода и файлов, которые добавлены в него в виде ссылок.

Как исправить

Стремитесь к тому, чтобы вес HTML-страниц не превышал 2 МБ. Особое внимание стоит уделить изображениям сайта: выбирать правильное расширение файлов, сжимать их вес без потери качества с помощью специальных инструментов, уменьшать слишком крупные по размеру фотографии в графическом редакторе или через панель управления сайтом.

Также будет полезно настроить сжатие текстов. Благодаря заголовку Content-Encoding, сервер будет уменьшать размер передаваемых данных, и контент будет загружаться в браузере быстрее. Также полезно оптимизировать объем страницы, используя архивирование GZIP.

Не оптимизированы элементы JavaScript и CSS

Код JavaScript и CSS отвечает за внешний сайта. С помощью стилей CSS (Cascading Style Sheets) задают фон, размер и цвета блоков страницы, шрифты текста. Сценарии на языке JavaScript делают дизайн сайта динамичным.

Элементы CSS/JS важны для ресурса, но в то же время они увеличивают общий объём страниц. Файлы CSS, превышающие по размеру 150 KB, а JavaScript — 2 MB, могут негативно влиять на скорость загрузки.

Как исправить

Чтобы уменьшить размер и вес кода CSS и JavaScript, используют такие технологии, как сжатие, кэширование, минификация. SEO-аудит помогает определить, влияют ли CSS/JS-файлы на скорость сайта и какие методы оптимизации использованы.

Кэширование CSS/JS-элементов снижает нагрузку на сервер, поскольку в этом случае браузер загружает сохранённые в кэше копии контента и не воспроизводит страницы с нуля. Минификация кода, то есть удаление из него ненужных символов и комментариев, уменьшает исходный размер. Ещё один способ оптимизации таблиц стилей и скриптов — объединение нескольких файлов CSS и JavaScript в один.

Отсутствие мобильной оптимизации

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

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

О проблемах с настройками мобильной версии говорит отсутствие метатега viewport, отвечающего за адаптивность страницы под экраны разного формата, или его неправильное заполнение. Также о нестабильности элементов страницы во время загрузки информирует еще показатель производительности сайта Core Web Vitals — CLS (Cumulative Layout Shift). Его норма: 0,1.

Как исправить

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

Обратите внимание, чтобы в HTML-коде страниц были метатеги viewport. При этом значение device-width не должно быть фиксированным, чтобы ширина страницы адаптировалась под размер ПК, планшета, смартфона.

Отсутствие alt-текста к изображениям

В HTML-коде страницы за визуальный контент отвечают теги <img>. Кроме ссылки на сам файл, тег может содержать альтернативный текст с описанием изображения и ключевыми словами.

Если атрибут alt — пустой, поисковику сложнее определить тематику фото. В итоге сайт не сможет привлекать дополнительный трафик из раздела «Картинки», где поисковая система отображает релевантные запросу изображения. Также текст alt отображается вместо фото, когда браузер не может его загрузить. Это особенно актуально для пользователей голосовыми помощниками и программами для чтения экрана.

Как исправить

Пропишите альтернативный текст к изображениям сайта. Это можно сделать после установки SEO-плагина к CMS, после чего в настройках к изображениям появятся специальные поля. Рекомендуем заполнить атрибут alt, используя несколько слов. Добавление ключевых фраз допустимо, но не стоит перегружать описание ими.

Заключение

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

К частым проблемам оптимизации можно отнести:

  • имя сайта с HTTP вместо безопасного расширения HTTPS;
  • отсутствие или неправильное содержимое файлов robots.txt и sitemap.xml;
  • медленная загрузка страниц;
  • некорректное отображение сайта на смартфонах;
  • большой вес файлов HTML, CSS, JS;
  • дублированный контент;
  • страницы с кодом ошибки 4ХХ, 5ХХ;
  • неправильно настроенные редиректы;
  • изображения без alt-текста.

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

Запрос = Новый Запрос;       //поступившие ОС
Запрос.Текст=   
  “ВЫБРАТЬ
  |   ХозрасчетныйОборотыДтКт.СуммаОборот
  |ИЗ
  |   РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(&НачПериода, &КонПериода, СчетКт = &СчетКт, Организация = &Организация) КАК ХозрасчетныйОборотыДтКт,
  |   (ВЫБРАТЬ
  |      ХозрасчетныйОборотыДтКт.СуммаОборот КАК СуммаОборот
  |   ИЗ
  |      РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт(&НачПериода, &КонПериода, , СчетДт = &СчетДт, , , , Организация = &Организация) КАК ХозрасчетныйОборотыДтКт) КАК ВложенныйЗапрос”;

    Запрос.УстановитьПараметр(“НачПериода”,КонецДня(НачПериода));
  Запрос.УстановитьПараметр(“КонПериода”, КонецДня(КонПериода));
  Запрос.УстановитьПараметр(“СчетДт “, ПланыСчетов.Хозрасчетный.ВложенияВоВнеоборотныеАктивы);
  Запрос.УстановитьПараметр(“СчетКт”, ПланыСчетов.Хозрасчетный.ВложенияВоВнеоборотныеАктивы);
  Запрос.УстановитьПараметр(“Организация”, ПолеВвода29);

    результат= Запрос.Выполнить().Выбрать();

    пока результат.Следующий() цикл
      ПолеВвода12=результат.СуммаОборот;
   КонецЦикла;  //поступившие ОС

Ошибка:
{Отчет.Отчет2.Форма.ФормаОтчета.Форма(176)}: Ошибка при вызове метода контекста (Выполнить)
  результат= Запрос.Выполнить().Выбрать();

����� 1. ��������� �������.

����� 1. ������.

�������� ������ �� ��� �����, ���� �� �� ��������� ����� ����������, ��� ����� ������� ������ ����� ���� ������� � ������ �������. ��� �� ��ɣ� ����� ��������� � � ��������� SQL ����������.

��� ������ ��������� ������ � ������� ��������. ������ � ���� ������ ��������� ����� ������.


select * fro t1 where f1 in (1,2,1);

� �����, ����������� ��������� ��� �������� � ޣ� ��������. ���� ��������� ���� ������ � mysql cli ������ ������ �ݣ ��������:


mysql> select * fro t1 where f1 in (1,2,1);
ERROR 1064 (42000): You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version
for the right syntax to use near 'fro t1 where f1 in (1,2,1)' at line 1

�� ���� ��� �������������� ������: ������� �������� ����� o � ��������� from. ������, �� ������ ��?

����ͣ� ������ ���������. ��� ��� �� PHP. (��� ��������� ��������� �������, ��� ��� ��� ����������� ��� PHP ����������� � ������. ����������� �������� ����� �������� � �� ����� ������ ����� ������� ����� ����������������.)


$query = 'SELECT * FROM t4 WHERE f1 IN(';
for ($i = 1; $i < 101; $i ++)
$query .= "'row$i,";
$query = rtrim($query, ',');
$query .= ')';
$result = mysql_query($query);

� ������ ������ ��������� ������ ��� �������. � ��� ����� � ������ �ݣ ����� �������� �������?

� ������ � PHP ��� ������� ������� ������� echo, ������� ������������ �����:


$query = 'SELECT * FROM t4 WHERE f1 IN(';
for ($i = 1; $i < 101; $i ++)
$query .= "'row$i,";
$query = rtrim($query, ',');
$query .= ')';
echo $query;
//$result = mysql_query($query);

�������� ������:


$php phpconf2009_1.php
SELECT * FROM t4 WHERE f1 IN('row1,'row2,'row3,'row4,'row5,'row6,'row7,'row8,'row9,'row10,'row11,
'row12,'row13,'row14,'row15,'row16,'row17,'row18,'row19,'row20)

� ��� ��� ������ ���������� ����� ���������: �������� ����������� �������� � ���������


$query .= "'row$i,";

���������� �������� ��� ����������


$query .= "'row$i',";

� ������ ����� ����������� �����.

�������� ��������, ��� �� �������� ������ ������ � ��� ����, � ������� ��� �������� ����. ������� ����� ����� ������ � ������� �������, ��� � ������, ������� ���������� �� ������ ������ ��� �������� ����������.

������������� ��������� ������ ��� ��������� ����������� ������� ����������, �� ����������� ��ɣ�.

��ɣ� 1: ����������� �������� ������ ��� ������ ������� � ��� ����, � ������� ��� �������� ����.

� ��������� �� �� ������ ����� ������������ echo. �������� � ������ ��������� ������������� �������� ����������.

��������� �� ��������� ������.

�������� �������� �� web-�������� �� �������� �����������:


��������� �������

    * �������
    * ����
    * test2
    * test2
    * test2
    * test2
    * test2

������� �������� ����� �������:

<>

��������:

<>

<Go!>

�������� � ���, ��� � ��� �����-�� ������� ������������ ��������� ������ � ���������� ������.

��������� �� ���, ������� �� ��� ��������:


$system = System::factory()
->setName($this->form->get(Field::NAME))
->setDescription(
$this->form->get(Field::DESCRIPTION)
);
DAO::system()->take($system);

���-������ �������? � ����� ������� ������� ����� ������������ � ޣ� ��������, �� � ����� ������ ��� ������ ��������, ������� ���������� ����������� ��� ������������. �ݣ ����� ������� ��� � ���� ������� �������� ����� � ����� ���� ������: � ��� ��� �� ��������� � ����, �� �������.

� PHP ���������� ����� �������� ��� ����������, ������� �������� �� ���������� �������, ����� �� ������� ��� (������) � ���� ��� �� stderr ����� ���, ��� ��������� ����, �� � ������ � �������������� �������, ��������, � Java, ���������� ���ģ��� �����������������. �� � �� ������ ��� ���������� ������.

��� �� ������? � ������ � MySQL �� ����� ��������� general query log:


mysql> set global log_output='table';
Query OK, 0 rows affected (0.00 sec)


mysql> select * from mysql.general_log;
Empty set (0.09 sec)


mysql> set global general_log='on';
Query OK, 0 rows affected (0.00 sec)

��������� ����������.


mysql> select * from mysql.general_log order by event_time desc limit 25G
*************************** 1. row ***************************
  event_time: 2009-10-19 13:00:00
   user_host: root[root] @ localhost []
   thread_id: 10323
   server_id: 60
command_type: Query
    argument: select * from mysql.general_log order by event_time desc limit 25
...
*************************** 22. row ***************************
  event_time: 2009-10-19 12:58:20
   user_host: root[root] @ localhost [127.0.0.1]
   thread_id: 10332
   server_id: 60
command_type: Query
    argument: INSERT INTO `system` (`id`, `name`, `description`) VALUES ('', 'test2', '')
...
mysql> set global general_log='off';
Query OK, 0 rows affected (0.08 sec)

� 22 ������ �� ����� ��� ������. �� �� �������� ����������: ������� INSERT.

��������� ��� �� ����� � ������� system:


mysql> select * from system;
+----+---------+------------------------------------------+
| id | name    | description                              |
+----+---------+------------------------------------------+
|  1 | �������  | ��������������� ������� � ������� ������      |
|  2 | ����     | �������� �������������� �����                 |
|  3 | test2   | New test                                 |
|  4 | test2   | foobar                                   |
|  8 | test2   |                                          |
+----+---------+------------------------------------------+
5 rows in set (0.00 sec)

��������� ţ �����������:


mysql> show create table systemG
*************************** 1. row ***************************
       Table: system
Create Table: CREATE TABLE `system` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `description` tinytext NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
1 row in set (0.09 sec)

���� name �� �������� ����������. ������� �� ����� ������ �������� � ����������� ����� ���������: ����� ���� ������� ��� ���� ����������


(alter table system add unique(name))

���� �������� ���������� ����� �������, ����� ��� �������� �������������� �� �� ������ SQL.

�� ������ ��� ����������� ��������� ��ɣ�: ����������� general query log ��� ��������� �������, ������� �������� ������������ ���������.

��ɣ� 2: ����������� general query log, ���� ��� ����� ���������� ����� ������ ������ �������� ������������ ��������� ������ ����������.

�� ���������, ��� ����������� ������ ����� �������� ������, ��������� �������� � ����������. � ����������� ������� ����� ����������, ����� ����� � ��������� ������. ��� ���������, ������ ������� ����� ������� ������ � ������ ������ ��� ������ ����������� �������. �������, ���������� ��������, ����� ����������� ���������� ������� �� ����������. ��� �������� ����� ����������� ��������.
� ������ �� �������� ���� �������� �� ��������� �������:


mysql> select * from mysql.general_log;
Empty set (0.09 sec)


mysql> set global general_log='on';
Query OK, 0 rows affected (0.00 sec)

...

mysql> set global general_log='off';
Query OK, 0 rows affected (0.08 sec)

����� ��� ����� �������� �������� ���������� ����������, ���� �� ����� �������� general query log � ���������������� �����?

���� � ���, ��� general query log ��� �� ���� ������ �����������: �� ����������� �������� �� ������ � ��� ��� �� �������� ��� �������, �� ��� ����� ���ģ��� ������� �� ������ �� �����. ������� �� � ������ 5.1 ��� ����� ��������-��������� � ������ ��������� ������� ��� ����� ����� �������������� �������� �� MySQL ������ � ��������.

��������� ������ �������� ����� �� � ����, � � �������. ����� � ������� ����� ��� ��������� ����� ���������� �������� ��� � ��������� 2-�� ��������, ��� ��� ������� ����� �������������: �� ����������� � ��� ����� ��� � ����� ������ �������.


mysql> set global log_output='table';
Query OK, 0 rows affected (0.00 sec)

Добавить комментарий