Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 126 · После таблиц и результатов X DevAPI

MySQL X DevAPI: правки таблиц, связи и готовые отчёты

От изменения одной цены к защите редактирования, ограниченным порциям, связанному экрану и общей транзакции Table/SQL.

От карточки товара к связанному экрану

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

Какие данные нужны экрану
  1. Таблица: строки и типизированные столбцы
  2. Условие и нужные поля задаём серверу
  3. Изменение или агрегирование выполняет MySQL
  4. PHP получает небольшой результат для показа

Table — объект для существующей реляционной таблицы, TableUpdate/TableDelete — подготовленные команды, Result — квитанция выполненного изменения. Само создание объекта не обновляет базу. Начальная работа с Table и чтение RowResult объяснены в предыдущей главе.

Связь хранится через значения ключей: orders.customer_id ссылается на customers.id. Внешний ключ проверяет допустимость ссылки, а JOIN собирает нужный ответ. Подробная модель таблиц и связей помогает выбрать структуру до написания запроса.

Окружение: отдельная учебная схема, расширение mysql_xdevapi и MySQL X Protocol. Каждый архив создаёт собственные таблицы со случайными именами, показывает реальные строки и затем удаляет только свои объекты. Подготовка подключения. Никакой готовый пример не очищает чужую схему.

В первой ступени новая операция показана коротким фрагментом; полный файл затем содержит создание таблиц, данные, обработку результата и очистку. Вспомогательный lab.php занимается окружением и отчётом. Предметные INSERT/UPDATE/DELETE/SELECT остаются видимыми в example.php.

Изменить цену лампы и прибавить остаток

В карточке лампы нужно исправить цену и принять две штуки на склад. После команды у LED цена 1200 копеек и остаток 5; кабель сохраняет цену 500 и остаток 7. Выбранные столбцы меняются прямо в MySQL, остальные значения строки остаются на месте.

Путь данных
  1. LED: 1000 коп., 3 шт.
  2. where id = 1 выбирает строку
  3. set: цена 1200; stock + 2
  4. execute → LED: 1200 коп., 5 шт.

Разобрать новую операцию

$products — уже открытая таблица. update() создаёт команду, set() задаёт столбец и новое значение, where() ограничивает строки, bind() передаёт значение вместо :id. Цепочка стрелок вызывает методы объекта команды. Только execute() отправляет изменение. Полный пример добавляет expression(): фиксированный текст stock + :received вычисляет сервер; строка с таким текстом сама по себе не является арифметикой.

$result = $products->update()
    ->set('price_cents', 1200)
    ->where('id = :id')->bind(['id' => 1])
    ->execute();

Результат на исходных данных

НаблюдениеРезультат
Изменено строк1
LED: цена, коп.1200
LED: остаток5
CABLE: цена, коп.500
CABLE: остаток7
Самостоятельная программа и архив
Изменить цену лампы и прибавить остатокСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'update_fields',
        '(id INT PRIMARY KEY, name VARCHAR(40) NOT NULL, price_cents INT NOT NULL, stock INT NOT NULL) ENGINE=InnoDB');
    $products = $schema->getTable($name);
    $products->insert(['id','name','price_cents','stock'])
        ->values([1,'LED',1000,3])->values([2,'CABLE',500,7])->execute();

    // Сначала все set, затем where, затем bind: порядок важен для выбранного расширения.
    $change = $products->update()->set('price_cents',1200)
        ->set('stock',mysql_xdevapi\expression('stock + :received'))
        ->where('id = :id')->bind(['id'=>1,'received'=>2])->execute();
    $saved = $products->select(['id','name','price_cents','stock'])->orderby('id ASC')->execute()->fetchAll();
    $rows = [
        ['Изменено строк',(int)$change->getAffectedItemsCount()],
        ['LED: цена, коп.',(int)$saved[0]['price_cents']],
        ['LED: остаток',(int)$saved[0]['stock']],
        ['CABLE: цена, коп.',(int)$saved[1]['price_cents']],
        ['CABLE: остаток',(int)$saved[1]['stock']],
    ];
    $checks = [
        'one_target_changed'=>$change->getAffectedItemsCount() === 1,
        'literal_and_expression'=>(int)$saved[0]['price_cents'] === 1200 && (int)$saved[0]['stock'] === 5,
        'neighbor_preserved'=>$saved[1]['name'] === 'CABLE' && (int)$saved[1]['price_cents'] === 500 && (int)$saved[1]['stock'] === 7,
    ];
    $observations = ['saved_rows'=>$saved,'execute_type'=>get_debug_type($change)];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-table-update-fields', 'Изменить цену лампы и прибавить остаток', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Изменено строк — 1
LED: цена, коп. — 1200
LED: остаток — 5
CABLE: цена, коп. — 500
CABLE: остаток — 7

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

Число 1200 хранится в копейках, чтобы не вводить округление денег. Имя столбца задаёт разработчик; bind передаёт значения, а не имена. В PECL 8.0.30 важен порядок: сначала все set(expression), затем where, затем bind. Иначе внутренний список параметров может рассогласоваться и привести даже к аварийному завершению процесса; это дефект выбранного расширения, а не правило SQL. Проверенный путь сохранён в полном файле. Проверка affected count подтверждает количество, а повторное чтение — конкретные поля. Для правки из формы дополнительно нужны проверка допустимой цены и права на товар.

Измените требование

Примите ещё три лампы и измените только цену кабеля на 550 копеек. Какие две независимые команды понадобятся?

Разбор следующего шага

В первой команде оставьте id = 1 и прибавьте 3: получится stock = 8 при цене 1200. Во второй используйте id = 2 и set(price_cents, 550); его stock останется 7. Если эти действия должны быть неделимыми, объедините их в транзакцию, как в последнем сценарии.

Не затереть сохранение другого редактора

Два сотрудника открыли одну карточку версии 1. Первый сохранил LED warm. Второй всё ещё редактирует прежнюю версию и не должен молча заменить её на LED cold. Программа воспроизводит этот порядок действий и оставляет в базе первое сохранение версии 2.

Путь данных
  1. Оба редактора получили version = 1
  2. Первый: WHERE id = 1 AND version = 1
  3. Имя изменено, version = 2
  4. Второй: старое условие не совпадает → 0

Разобрать новую операцию

version — обычный целочисленный столбец, который мы увеличиваем при каждом принятом изменении. AND требует одновременно нужный id и ту версию, которую видел пользователь. Проверка и изменение находятся в одной серверной команде: отдельная проверка версии в PHP перед безусловным UPDATE оставила бы промежуток для чужой записи.

$result = $products->update()
    ->set('name', 'LED warm')
    ->set('version', mysql_xdevapi\expression('version + 1'))
    ->where('id = :id AND version = :version')
    ->bind(['id' => 1, 'version' => 1])->execute();

Результат на исходных данных

НаблюдениеРезультат
Первая правка1
Устаревшая правка0
Имя после обеих попытокLED warm
Версия2
Повтор того же значения1
Правка отсутствующего ID0
Строка 1 существуетда
Строка 999 существуетнет
Самостоятельная программа и архив
Не затереть сохранение другого редактораСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'version',
        '(id INT PRIMARY KEY, name VARCHAR(40) NOT NULL, version INT NOT NULL) ENGINE=InnoDB');
    $products = $schema->getTable($name);
    $products->insert(['id','name','version'])->values([1,'LED',1])->execute();
    $firstEditor = $products->select(['id','name','version'])->where('id = 1')->execute()->fetchOne();
    $secondEditor = $products->select(['id','name','version'])->where('id = 1')->execute()->fetchOne();

    $first = $products->update()->set('name','LED warm')
        ->set('version',mysql_xdevapi\expression('version + 1'))
        ->where('id = :id AND version = :version')
        ->bind(['id'=>1,'version'=>$firstEditor['version']])->execute();
    $stale = $products->update()->set('name','LED cold')
        ->set('version',mysql_xdevapi\expression('version + 1'))
        ->where('id = :id AND version = :version')
        ->bind(['id'=>1,'version'=>$secondEditor['version']])->execute();
    $same = $products->update()->set('name','LED warm')->where('id = :id')->bind(['id'=>1])->execute();
    $missing = $products->update()->set('name','Missing')->where('id = :id')->bind(['id'=>999])->execute();
    $saved = $products->select(['id','name','version'])->where('id = 1')->execute()->fetchOne();
    $absent = $products->select(['id'])->where('id = 999')->execute()->fetchOne();
    $rows = [['Первая правка',(int)$first->getAffectedItemsCount()],
        ['Устаревшая правка',(int)$stale->getAffectedItemsCount()],
        ['Имя после обеих попыток',$saved['name']],['Версия',(int)$saved['version']],
        ['Повтор того же значения',(int)$same->getAffectedItemsCount()],
        ['Правка отсутствующего ID',(int)$missing->getAffectedItemsCount()],
        ['Строка 1 существует',$saved !== null],['Строка 999 существует',$absent !== null]];
    $checks = ['version_condition_atomic'=>$first->getAffectedItemsCount() === 1 && $stale->getAffectedItemsCount() === 0,
        'stale_data_not_saved'=>$saved['name'] === 'LED warm' && (int)$saved['version'] === 2,
        'no_change_distinct_from_missing'=>$same->getAffectedItemsCount() === 1 && $missing->getAffectedItemsCount() === 0 && $saved !== null && $absent === null];
    $observations = ['first_editor'=>$firstEditor,'second_editor'=>$secondEditor,'saved'=>$saved,'absent'=>$absent,
        'same_value_affected'=>$same->getAffectedItemsCount(),'missing_affected'=>$missing->getAffectedItemsCount()];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-table-version-conflict', 'Не затереть сохранение другого редактора', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Первая правка — 1
Устаревшая правка — 0
Имя после обеих попыток — LED warm
Версия — 2
Повтор того же значения — 1
Правка отсутствующего ID — 0
Строка 1 существует — да
Строка 999 существует — нет

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

Это последовательное воспроизведение устаревшего редактирования, а не тест одновременной нагрузки. Все пути записи должны соблюдать правило version. В выбранном X DevAPI даже повтор того же имени возвращает 1: счётчик сообщает совпавшие строки и не доказывает физического изменения значения. Ноль здесь означает отсутствие совпадения условия, например старую версию или отсутствующий ID. У других клиентских режимов правила affected rows могут отличаться. В versioned update успешная запись увеличивает version; повторное чтение помогает показать текущее состояние, но оно тоже может измениться позже. Не повторяйте устаревшую правку автоматически поверх чужой.

Практический вопрос: почему одной транзакции недостаточно для формы, которую два пользователя редактировали долго? Наш пример хранит увиденную версию и проверяет её в самом UPDATE. Обсуждение: Optimistic locking in MySQL.

Измените требование

Пусть второй редактор после отказа перечитает строку и осознанно сохранит LED cold с version = 2. Что нужно показать ему перед подтверждением?

Разбор следующего шага

Покажите прежнее и актуальное имя и предложите применить изменение к версии 2. Новый UPDATE с этой версией увеличит её до 3, если с момента чтения снова никто не сохранил карточку. Веб-приложение также проверяет полномочия пользователя, а не доверяет одному id/version.

Взять две записи на подготовку выгрузки

Подготовка выгрузки запускается порциями. Из ожидающих строк нужно пометить две с наименьшим приоритетом; при одинаковом приоритете раньше идёт меньший id. В базе меняются только ID 2 и 3, завершённая строка 4 не участвует.

Путь данных
  1. WHERE ready исключает done
  2. ORDER BY priority, id даёт 2, 3, 1
  3. LIMIT 2 оставляет 2 и 3
  4. SET state = preparing; readback подтверждает IDs

Разобрать новую операцию

orderby принимает правила порядка: ASC — от меньшего к большему, DESC — наоборот. Массив содержит два отдельных правила. id разрывает равенство приоритетов, поэтому выбранная порция определена однозначно. limit(2) задаёт максимум подходящих строк; эта команда не загружает весь набор для сортировки в PHP.

$result = $jobs->update()->set('state', 'preparing')
    ->where('state = :state')->bind(['state' => 'ready'])
    ->orderby(['priority ASC', 'id ASC'])->limit(2)->execute();

Результат на исходных данных

НаблюдениеРезультат
Изменено строк2
Выбраны ID2, 3
ID 1ready
ID 4done
Отрицательный лимит отклонёнда
Самостоятельная программа и архив
Взять две записи на подготовку выгрузкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'update_batch',
        '(id INT PRIMARY KEY, priority INT NOT NULL, state VARCHAR(20) NOT NULL) ENGINE=InnoDB');
    $jobs = $schema->getTable($name);
    $jobs->insert(['id','priority','state'])->values([1,2,'ready'])->values([2,1,'ready'])
        ->values([3,1,'ready'])->values([4,0,'done'])->execute();
    $result = $jobs->update()->set('state','preparing')->where('state = :state')
        ->bind(['state'=>'ready'])->orderby(['priority ASC','id ASC'])->limit(2)->execute();
    $saved = $jobs->select(['id','priority','state'])->orderby('id ASC')->execute()->fetchAll();
    $selected = $jobs->select(['id'])->where('state = :state')->bind(['state'=>'preparing'])
        ->orderby('id ASC')->execute()->fetchAll();
    $ids = array_map(static fn(array $row): int => (int)$row['id'],$selected);
    $badLimit = x141Failure(static fn() => $jobs->update()->set('state','bad')->limit(-1));
    $rows = [['Изменено строк',(int)$result->getAffectedItemsCount()],['Выбраны ID',implode(', ',$ids)],
        ['ID 1',$saved[0]['state']],['ID 4',$saved[3]['state']],['Отрицательный лимит отклонён',$badLimit !== null]];
    $checks = ['stable_two_selected'=>$ids === [2,3] && $result->getAffectedItemsCount() === 2,
        'outside_batch_preserved'=>$saved[0]['state'] === 'ready' && $saved[3]['state'] === 'done',
        'invalid_limit_rejected'=>$badLimit !== null];
    $observations = ['saved_rows'=>$saved,'selected_ids'=>$ids,'negative_limit_error'=>$badLimit];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-ordered-update-batch', 'Взять две записи на подготовку выгрузки', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Изменено строк — 2
Выбраны ID — 2, 3
ID 1 — ready
ID 4 — done
Отрицательный лимит отклонён — да

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

Для UPDATE лимит ограничивает совпавшие строки: запись с уже установленным значением тоже может занять место. Квитанция выбранного X DevAPI считает совпадения, поэтому фактическое изменение дополнительно подтверждаем чтением. Здесь WHERE ready и SET preparing делают каждое совпадение изменением. Это один запуск над учебным набором; надёжная конкурентная очередь дополнительно определяет владельца, повторную доставку и восстановление после сбоя. Предыдущая глава объясняет блокировки и SKIP_LOCKED.

Измените требование

Добавьте ready-строку ID 5 с priority = 1. Какие записи попадут в первую порцию и какие останутся?

Разбор следующего шага

Первыми остаются 2 и 3 благодаря второму правилу id ASC. ID 5 и затем ID 1 останутся ready. Не превращайте обработку в бесконечный цикл без политики остановки и восстановления; в реальной выгрузке сохраните понятный идентификатор порции.

Удалить два старых черновика и сохранить публикации

Очистка должна затронуть только старые черновики. Дата сама по себе недостаточна: публикация с той же датой обязана сохраниться. Получаем квитанцию о двух удалённых строках и список оставшихся ID 2, 4 и 5.

Путь данных
  1. Все строки → state = draft и дата до границы
  2. Старые черновики: 1, 3, 4
  3. ORDER BY дата, id; LIMIT 2
  4. Удалены 1, 3; публикация 2 остаётся

Разобрать новую операцию

delete() строит команду TableDelete. where() задаёт два обязательных условия; < означает строго раньше даты. Параметры передаются одним PHP-массивом, двоеточие используется в выражении, но не в ключе массива. execute() возвращает Result; его affected count — число удалённых строк. Удаление строк не удаляет саму таблицу.

$result = $drafts->delete()
    ->where('state = :state AND created_on < :cutoff')
    ->bind(['state' => 'draft', 'cutoff' => '2026-02-01'])
    ->orderby(['created_on ASC', 'id ASC'])->limit(2)->execute();

Результат на исходных данных

НаблюдениеРезультат
Удалено черновиков2
Остались ID2, 4, 5
ID 2: публикация сохраненада
Несуществующий ID: удалено0
Самостоятельная программа и архив
Удалить два старых черновика и сохранить публикацииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'delete_batch',
        '(id INT PRIMARY KEY, state VARCHAR(20) NOT NULL, created_on DATE NOT NULL) ENGINE=InnoDB');
    $drafts = $schema->getTable($name);
    $drafts->insert(['id','state','created_on'])->values([1,'draft','2026-01-01'])->values([2,'published','2026-01-01'])
        ->values([3,'draft','2026-01-02'])->values([4,'draft','2026-01-03'])->values([5,'draft','2026-12-01'])->execute();
    $deleted = $drafts->delete()->where('state = :state AND created_on < :cutoff')
        ->bind(['state'=>'draft','cutoff'=>'2026-02-01'])->orderby(['created_on ASC','id ASC'])->limit(2)->execute();
    $empty = $drafts->delete()->where('id = :id')->bind(['id'=>999])->limit(1)->execute();
    $saved = $drafts->select(['id','state','created_on'])->orderby('id ASC')->execute()->fetchAll();
    $ids = array_map(static fn(array $row): int => (int)$row['id'],$saved);
    $rows = [['Удалено черновиков',(int)$deleted->getAffectedItemsCount()],['Остались ID',implode(', ',$ids)],
        ['ID 2: публикация сохранена',$saved[0]['state'] === 'published'],['Несуществующий ID: удалено',(int)$empty->getAffectedItemsCount()]];
    $checks = ['bounded_old_drafts_deleted'=>$deleted->getAffectedItemsCount() === 2 && $ids === [2,4,5],
        'published_row_preserved'=>$saved[0]['state'] === 'published',
        'empty_delete_is_zero'=>$empty->getAffectedItemsCount() === 0];
    $observations = ['remaining_rows'=>$saved,'delete_result_type'=>get_debug_type($deleted)];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-bounded-table-delete', 'Удалить два старых черновика и сохранить публикации', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Удалено черновиков — 2
Остались ID — 2, 4, 5
ID 2: публикация сохранена — да
Несуществующий ID: удалено — 0

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

Без WHERE удаление может затронуть всю таблицу. LIMIT не заменяет условие: он ограничивает ущерб количеством, но не выбирает правильные данные. TableDelete не имеет offset; SQL DELETE LIMIT также не принимает смещение как SELECT. Для очистки по частям каждый раз удаляйте следующую подходящую порцию, а не пропускайте уже сместившиеся строки. В продукте срок хранения, права и необходимость архива согласуются отдельно.

Практический вопрос: попытка использовать DELETE LIMIT со смещением, как у SELECT, завершается ошибкой. Здесь очистка устроена через условие, стабильный порядок и размер очередной порции. Обсуждение: Mysql delete statement with limit.

Измените требование

Повторите тот же DELETE один раз: сколько строк он удалит и почему публикация всё ещё останется?

Разбор следующего шага

Останется один подходящий старый черновик ID 4, поэтому affected count будет 1. Публикация ID 2 не проходит state = draft, а ID 5 не проходит дату. Пустое совпадение в третьем запуске даст 0 и может быть условием остановки.

Посчитать оплаченные заказы на сервере

Менеджеру нужен список покупателей с оплатами от 3000 копеек. Четыре исходных заказа дают одну строку отчёта: покупатель 1, два оплаченных заказа, 3500 копеек. Черновик на 9000 копеек не должен увеличить сумму.

Путь данных
  1. WHERE paid: 1000, 2500, 700
  2. GROUP BY customer_id: группы 1 и 2
  3. SUM/COUNT: 3500 и 2; 700 и 1
  4. HAVING сумма ≥ 3000: только покупатель 1

Разобрать новую операцию

groupBy собирает строки с одинаковым customer_id в группы. SUM складывает суммы внутри каждой группы; COUNT(*) считает входящие строки. AS даёт понятное имя столбцу результата. WHERE отбирает исходные заказы до вычисления итогов; HAVING отбирает уже полученные группы. Это разные этапы, поэтому условие по общей сумме находится в having().

$report = $orders->select(['customer_id', 'SUM(amount_cents) AS paid_cents'])
    ->where('state = :state')->groupBy('customer_id')
    ->having('SUM(amount_cents) >= :minimum')
    ->bind(['state' => 'paid', 'minimum' => 3000])->execute()->fetchAll();

Результат на исходных данных

НаблюдениеРезультат
Групп выше порога1
Покупатель1
Оплаченных заказов2
Сумма, коп.3500
При пороге 4000 коп.: групп0
Самостоятельная программа и архив
Посчитать оплаченные заказы на сервереСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'group_report',
        '(id INT PRIMARY KEY, customer_id INT NOT NULL, state VARCHAR(20) NOT NULL, amount_cents INT NOT NULL) ENGINE=InnoDB');
    $orders = $schema->getTable($name);
    $orders->insert(['id','customer_id','state','amount_cents'])->values([1,1,'paid',1000])
        ->values([2,1,'paid',2500])->values([3,1,'draft',9000])->values([4,2,'paid',700])->execute();
    $report = $orders->select(['customer_id','COUNT(*) AS order_count','SUM(amount_cents) AS paid_cents'])
        ->where('state = :state')->groupBy('customer_id')->having('SUM(amount_cents) >= :minimum')
        ->bind(['state'=>'paid','minimum'=>3000])->orderby(['paid_cents DESC','customer_id ASC'])->execute()->fetchAll();
    $empty = $orders->select(['customer_id','SUM(amount_cents) AS paid_cents'])->where('state = :state')
        ->groupBy('customer_id')->having('SUM(amount_cents) >= :minimum')->bind(['state'=>'paid','minimum'=>4000])
        ->execute()->fetchAll();
    $rows = [['Групп выше порога',count($report)],['Покупатель',(int)$report[0]['customer_id']],
        ['Оплаченных заказов',(int)$report[0]['order_count']],['Сумма, коп.',(int)$report[0]['paid_cents']],
        ['При пороге 4000 коп.: групп',count($empty)]];
    $checks = ['one_customer_selected'=>count($report) === 1 && (int)$report[0]['customer_id'] === 1,
        'draft_excluded_before_grouping'=>(int)$report[0]['order_count'] === 2 && (int)$report[0]['paid_cents'] === 3500,
        'having_empty_result'=>$empty === []];
    $observations = ['report_rows'=>$report,'higher_threshold_rows'=>$empty,
        'sum_php_type'=>get_debug_type($report[0]['paid_cents'])];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-table-group-report', 'Посчитать оплаченные заказы на сервере', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Групп выше порога — 1
Покупатель — 1
Оплаченных заказов — 2
Сумма, коп. — 3500
При пороге 4000 коп.: групп — 0

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

У каждой выбранной колонки должен быть определённый смысл для группы: ключ группировки или агрегат. Не отключайте ONLY_FULL_GROUP_BY ради случайного name из одной строки. GROUP BY не обещает порядок; задайте orderby отдельно. COUNT(*) здесь уместен: он считает реальные отфильтрованные заказы. Это не SELECT * и не выгрузка всех столбцов. Сумма из SQL может прийти строкой, в выбранном runtime встречается десятичная запись с точкой в конце. Исходный тип и текст сохранены в JSON. Только небольшие целые суммы этого примера приводим к int для показа: значение должно помещаться в диапазон PHP_INT_MIN…PHP_INT_MAX. Большие DECIMAL-суммы и дробные денежные значения сохраняйте десятичными строками и обрабатывайте подходящей десятичной арифметикой, без безусловного int/float-преобразования.

Измените требование

Измените порог на 500 копеек. Сколько групп появится и в каком порядке при paid_cents DESC?

Разбор следующего шага

Будут покупатель 1 с 3500 копейками и покупатель 2 с 700. Если требуется показать и покупателей без оплат, одной таблицы orders недостаточно: нужен список покупателей и LEFT JOIN следующего сценария.

Показать покупателей, включая тех, кто ещё не оплатил заказ

На странице клиентов должны остаться Анна с двумя оплатами, Борис только с черновиком и Вера без заказов. Результат — три строки с понятными именами и суммами, а не повторяющиеся строки клиента по числу его заказов.

Путь данных
  1. Страница покупателей: Анна, Борис, Вера
  2. LEFT JOIN: оплаченные заказы по customer_id
  3. Нет оплаты → справа NULL; COUNT(o.id) = 0
  4. GROUP BY клиент → одна строка экрана на клиента

Разобрать новую операцию

Это SQL-фрагмент для Session::sql(), а не PHP-код и не метод TableSelect::join: такого метода здесь нет. c и o — короткие имена двух таблиц. ON связывает order.customer_id с customer.id и оставляет только оплаченные заказы. LEFT JOIN сохраняет строку слева даже без совпадения справа. COUNT(o.id) не считает NULL; COALESCE в полном запросе заменяет отсутствующую сумму на 0.

SELECT c.id, c.name, COUNT(o.id) AS paid_count
FROM customers AS c
LEFT JOIN orders AS o
  ON o.customer_id = c.id AND o.state = ?
GROUP BY c.id, c.name
ORDER BY c.id

Результат на исходных данных

НаблюдениеРезультат
Анна2 оплат; 3500 коп.
Борис0 оплат; 0 коп.
Вера0 оплат; 0 коп.
COUNT(*) у Веры: ложное число заказов1
Самостоятельная программа и архив
Показать покупателей, включая тех, кто ещё не оплатил заказСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $customerName = x141OwnTable($session,$schema,$owned,'join_customers',
        '(id INT PRIMARY KEY, name VARCHAR(40) NOT NULL) ENGINE=InnoDB');
    $customersSql = x141Qualified($schema,$customerName);
    $orderName = x141OwnTable($session,$schema,$owned,'join_orders',
        '(id INT PRIMARY KEY, customer_id INT NOT NULL, state VARCHAR(20) NOT NULL, amount_cents INT NOT NULL, '
        .'FOREIGN KEY (customer_id) REFERENCES '.$customersSql.' (id)) ENGINE=InnoDB');
    $ordersSql = x141Qualified($schema,$orderName);
    $schema->getTable($customerName)->insert(['id','name'])->values([1,'Анна'])->values([2,'Борис'])->values([3,'Вера'])->execute();
    $schema->getTable($orderName)->insert(['id','customer_id','state','amount_cents'])
        ->values([101,1,'paid',1000])->values([102,1,'paid',2500])->values([103,1,'draft',9000])->values([201,2,'draft',600])->execute();
    $sql = 'SELECT c.id AS customer_id, c.name AS customer_name, COUNT(o.id) AS paid_count, '
        .'COALESCE(SUM(o.amount_cents),0) AS paid_cents FROM '
        .'(SELECT id,name FROM '.$customersSql.' ORDER BY id LIMIT 3) AS c '
        .'LEFT JOIN '.$ordersSql.' AS o ON o.customer_id = c.id AND o.state = ? '
        .'GROUP BY c.id,c.name ORDER BY c.id';
    $screen = $session->sql($sql)->bind('paid')->execute()->fetchAll();
    // Диагностическое сравнение: COUNT(*) считает также строку с NULL справа.
    $misleading = $session->sql('SELECT COUNT(*) AS row_count FROM '.$customersSql.' AS c '
        .'LEFT JOIN '.$ordersSql.' AS o ON o.customer_id = c.id WHERE c.id = ?')->bind(3)->execute()->fetchOne();
    $rows = [];
    foreach ($screen as $customer) {
        $rows[] = [$customer['customer_name'],(int)$customer['paid_count'].' оплат; '.(int)$customer['paid_cents'].' коп.'];
    }
    $rows[] = ['COUNT(*) у Веры: ложное число заказов',(int)$misleading['row_count']];
    $checks = ['all_customers_visible'=>array_column($screen,'customer_name') === ['Анна','Борис','Вера'],
        'paid_orders_only'=>(int)$screen[0]['paid_count'] === 2 && (int)$screen[0]['paid_cents'] === 3500,
        'no_paid_orders_are_zero'=>(int)$screen[1]['paid_count'] === 0 && (int)$screen[2]['paid_count'] === 0,
        'count_star_trap_observed'=>(int)$misleading['row_count'] === 1];
    $observations = ['customer_screen'=>$screen,'count_star_comparison'=>$misleading];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-joined-order-screen', 'Показать покупателей, включая тех, кто ещё не оплатил заказ', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Анна — 2 оплат; 3500 коп.
Борис — 0 оплат; 0 коп.
Вера — 0 оплат; 0 коп.
COUNT(*) у Веры: ложное число заказов — 1

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

Условие o.state = paid находится в ON: перенос в WHERE убрал бы клиентов без подходящего заказа. COUNT(*) считает строку результата соединения, поэтому у Веры получилось бы 1; диагностический запрос показывает эту ловушку отдельно. Ограничение страницы в подзапросе применяется к покупателям до соединения. FOREIGN KEY запрещает ссылку заказа на отсутствующего покупателя, а JOIN читает связанные данные — это разные задачи. В ответ передаются только нужные столбцы и понятные alias.

Практический вопрос: почему у человека без дочерних записей отчёт показывает один элемент? На собственном наборе клиентов сравниваем COUNT(o.id) с COUNT(*), не копируя чужой ответ. Обсуждение: Count zero when a LEFT JOIN has no matching rows.

Измените требование

У Бориса появился оплаченный заказ на 900 копеек. Изменится ли число строк экрана? А если у Анны появится ещё одна оплата?

Разбор следующего шага

Строк останется три: GROUP BY задаёт одну строку на клиента. У Бориса будут 1 оплата и 900 копеек, у Анны увеличатся сумма и счётчик. Не соединяйте одновременно несколько независимых наборов деталей до суммирования: перемножение строк может завысить итоги; сначала агрегируйте каждый набор.

Сохранить изменение остатка и запись журнала вместе

Резерв должен уменьшить остаток и оставить запись в журнале. Нельзя сохранить только половину операции. В первой попытке оба изменения подтверждаются; во второй предметный отказ отменяет и списание, и новую запись. На экране остаются 3 штуки и одна квитанция reserved 2.

Путь данных
  1. Один Session → startTransaction
  2. TableUpdate списывает 2; SQL добавляет журнал
  3. commit сохраняет обе записи
  4. Следующая попытка: rollback возвращает остаток и убирает новый журнал

Разобрать новую операцию

Транзакция принадлежит соединению Session. Table, полученный из его Schema, и команды Session::sql участвуют в одной транзакции, хотя записаны разным API. try содержит действия, catch обрабатывает отказ, rollback отменяет незавершённые изменения. Короткий фрагмент показывает границы; реальные запросы находятся в полном файле. DomainException ниже моделирует бизнес-отказ после обоих действий.

$session->startTransaction();
try {
    // Изменение таблицы и запись журнала через тот же Session.
    $session->commit();
} catch (Throwable $error) {
    $session->rollback();
    throw $error;
}

Результат на исходных данных

НаблюдениеРезультат
Остаток после успешной операции и отмены3
Сохранённый статусreserved
Записей в журнале1
Сохранённая записьreserved 2
Отмена обработанада
Самостоятельная программа и архив
Сохранить изменение остатка и запись журнала вместеСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';

$config = x141Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
    $schema = $session->getSchema($config['schema']);
    if (!$schema->existsInDatabase()) {
        throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
    }
    $runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
        'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
        'host'=>$config['host'], 'port'=>$config['port']];

    $name = x141OwnTable($session,$schema,$owned,'tx_products','(id INT PRIMARY KEY, stock INT NOT NULL, state VARCHAR(20) NOT NULL) ENGINE=InnoDB');
    $auditName = x141OwnTable($session,$schema,$owned,'tx_audit','(id INT PRIMARY KEY, message VARCHAR(80) NOT NULL) ENGINE=InnoDB');
    $products = $schema->getTable($name);
    $auditSql = x141Qualified($schema,$auditName);
    $products->insert(['id','stock','state'])->values([1,5,'available'])->execute();
    $qty = 2;
    $session->startTransaction();
    try {
        $change = $products->update()->set('state','reserved')
            ->set('stock',mysql_xdevapi\expression('stock - :decrement'))
            ->where('id = :id AND stock >= :minimum')
            ->bind(['id'=>1,'decrement'=>$qty,'minimum'=>$qty])->execute();
        if ($change === false) {throw new RuntimeException('Команда списания не выполнена.');}
        if ($change->getAffectedItemsCount() !== 1) {throw new RuntimeException('Недостаточно товара.');}
        $session->sql('INSERT INTO '.$auditSql.' (id,message) VALUES (?,?)')->bind(1)->bind('reserved 2')->execute();
        $session->commit();
    } catch (Throwable $error) {$session->rollback(); throw $error;}

    $cancelled = false;
    $qty = 1;
    $session->startTransaction();
    try {
        $change = $products->update()->set('state','cancelled_candidate')
            ->set('stock',mysql_xdevapi\expression('stock - :decrement'))
            ->where('id = :id AND stock >= :minimum')
            ->bind(['id'=>1,'decrement'=>$qty,'minimum'=>$qty])->execute();
        if ($change === false) {throw new RuntimeException('Команда списания не выполнена.');}
        if ($change->getAffectedItemsCount() !== 1) {throw new RuntimeException('Недостаточно товара.');}
        $session->sql('INSERT INTO '.$auditSql.' (id,message) VALUES (?,?)')->bind(2)->bind('reserved 1')->execute();
        throw new DomainException('Заказ отменён до подтверждения.');
    } catch (DomainException $error) {$session->rollback(); $cancelled = true;}
      catch (Throwable $error) {$session->rollback(); throw $error;}
    $saved = $products->select(['id','stock','state'])->where('id = 1')->execute()->fetchOne();
    $audit = $session->sql('SELECT id,message FROM '.$auditSql.' ORDER BY id')->execute()->fetchAll();
    $rows = [['Остаток после успешной операции и отмены',(int)$saved['stock']],
        ['Сохранённый статус',$saved['state']],['Записей в журнале',count($audit)],['Сохранённая запись',$audit[0]['message']],['Отмена обработана',$cancelled]];
    $checks = ['committed_change_survived'=>(int)$saved['stock'] === 3 && $saved['state'] === 'reserved',
        'rolled_back_audit_absent'=>count($audit) === 1 && (int)$audit[0]['id'] === 1 && $audit[0]['message'] === 'reserved 2',
        'business_refusal_caught'=>$cancelled];
    $observations = ['product_after_both_transactions'=>$saved,'audit_after_both_transactions'=>$audit];

} finally {
    try {
        if ($schema instanceof mysql_xdevapi\Schema) {
            $removed = x141Cleanup($schema, $owned);
        }
    } finally {
        $session->close();
    }
}

x141Report('mysql141-x-mixed-session-transaction', 'Сохранить изменение остатка и запись журнала вместе', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Остаток после успешной операции и отмены — 3
Сохранённый статус — reserved
Записей в журнале — 1
Сохранённая запись — reserved 2
Отмена обработана — да

CLI-программа создаёт собственные таблицы, выполняет запросы и читает сохранённые строки. result.html показывает итог, result.json сохраняет исходные типы и наблюдения; metrics.json подтверждает проверки и очистку. Это не HTTP-обработчик.

Таблица фактического запуска · Полный JSON строк и наблюдений.

Границы и типичные ошибки

DDL создаётся заранее: CREATE TABLE может неявно завершить транзакцию. Внутри используются InnoDB и короткие изменения. В PECL 8.0.30 повтор имени параметра в Expression и where оставляет одну позицию непривязанной: execute может вернуть false. Поэтому :decrement и :minimum имеют разные имена, но получают одно значение $qty. False означает отказ исполнения команды, а affected count 0 — отсутствие подходящей строки; оба случая отменяют транзакцию. Новое соединение с теми же учётными данными — другой Session и другая транзакция. Отправленное письмо, запрос платёжному сервису или запись на другом сервере этот rollback не отменит. Журнал примера — таблица той же базы; внешний журнал потребует отдельной схемы согласования.

Измените требование

Добавьте проверку максимального количества перед списанием. Какой отказ можно обнаружить без транзакции, а какой всё равно проверяется в UPDATE?

Разбор следующего шага

Некорректное количество, например 0 или отрицательное, отклоняется до начала. Доступный остаток меняется другими запросами, поэтому условие stock >= :minimum остаётся в серверном UPDATE. Обоим параметрам, :decrement и :minimum, передавайте одно проверенное количество. Если affected count не равен 1, не создавайте журнал успеха и откатите операцию.

Выбрать действие по задаче

ИнструментДля чего нужен
TableUpdateКласс описывает изменение записей одной таблицы; PHP Manual указывает PECL mysql_xdevapi >= 8.0.11.
TableDeleteКласс описывает удаление записей одной таблицы; доступность в PHP Manual — PECL mysql_xdevapi >= 8.0.11.
Table::updateСоздаёт команду обновления столбцов таблицы без параметров.
TableUpdate::whereЗадаёт условие выбора строк обновления.
TableUpdate::bindСвязывает значения с именованными параметрами команды.
TableUpdate::setДобавляет изменяемый столбец и его новое значение.
TableUpdate::orderbyЗадаёт порядок обновления.
TableUpdate::limitОграничивает размер обновляемой порции.
TableUpdate::executeОтправляет подготовленную команду обновления, параметров не принимает.
Table::deleteСоздаёт команду удаления строк таблицы.
TableDelete::whereОпределяет, какие строки разрешено удалить.
TableDelete::bindПередаёт значения отдельно от поискового выражения.
TableDelete::orderbyОпределяет, какие строки окажутся первыми при ограниченном удалении.
TableDelete::limitОграничивает число удалений за исполнение.
TableDelete::executeИсполняет подготовленное удаление, параметров нет.
TableSelect::groupByСобирает исходные строки в группы для агрегатов.
TableSelect::havingОтбирает группы по вычисленному результату.

Собрать экран с предсказуемыми изменениями

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

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

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

mysql_xdevapi\TableUpdateКласс описывает изменение записей одной таблицы; PHP Manual указывает PECL mysql_xdevapi >= 8.0.11.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

class mysql_xdevapi\TableUpdate implements mysql_xdevapi\Executable

Класс описывает изменение записей одной таблицы; PHP Manual указывает PECL mysql_xdevapi >= 8.0.11.

Запись, параметры и результат

class mysql_xdevapi\TableUpdate implements mysql_xdevapi\Executable

Объект получают из Table; пользовательский new не требуется.

Объект команды изменения, получаемый от Table::update(). После execute() нужен объект результата, а не продолжение настройки команды.

Класс описывает изменение записей одной таблицы; PHP Manual указывает PECL mysql_xdevapi >= 8.0.11. Конструктор закрыт: используйте фабрику Table::update(). Полный обзор методов: bind(array), execute(), limit(int), orderby(mixed, mixed ...), set(string, string в записи Manual), where(string). Конструктор команды ещё не отправляет UPDATE. Документационные неточности execute и set разобраны в собственных карточках ниже; это не основание скрывать опубликованную сигнатуру.

Два применения

Исправить цену выбранного товара

Получаем квитанцию о правке, после чего читаем цену товара.

$command = $products->update();
$command->set('price_cents', 1200)->where('id = :id');
$result = $command->bind(['id' => 1])->execute();

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

Сформировать ограниченную порцию выгрузки

Команда меняет только определённую порцию ожидающих строк.

$command = $jobs->update()->set('state', 'preparing');
$result = $command->where('state = :state')->bind(['state' => 'ready'])
    ->orderby(['priority ASC', 'id ASC'])->limit(2)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDeleteКласс описывает удаление записей одной таблицы; доступность в PHP Manual — PECL mysql_xdevapi >= 8.0.11.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

class mysql_xdevapi\TableDelete implements mysql_xdevapi\Executable

Класс описывает удаление записей одной таблицы; доступность в PHP Manual — PECL mysql_xdevapi >= 8.0.11.

Запись, параметры и результат

class mysql_xdevapi\TableDelete implements mysql_xdevapi\Executable

Объект получают из Table; пользовательский new не требуется.

Объект команды удаления строк, получаемый от Table::delete(); execute возвращает Result.

Класс описывает удаление записей одной таблицы; доступность в PHP Manual — PECL mysql_xdevapi >= 8.0.11. Конструктор закрыт. Обзор методов: bind(array), execute(): Result, limit(int), orderby(string в записи Manual), where(string). Удаляется строка, не таблица и не схема. Offset в этом API отсутствует. Поддерживаемая выбранным расширением форма orderby с массивом уточнена отдельно.

Два применения

Удалить только устаревшие черновики

Публикации не проходят условие и остаются в таблице.

$command = $drafts->delete();
$result = $command->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])->limit(2)->execute();

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

Удалить выбранный черновик по его ID

Ноль означает, что подходящей строки для удаления не было; таблица остаётся.

$result = $drafts->delete()->where('id = :id AND state = :s')
    ->bind(['id' => 4, 's' => 'draft'])->limit(1)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\Table::updateСоздаёт команду обновления столбцов таблицы без параметров.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\Table::update(): mysql_xdevapi\TableUpdate

Создаёт команду обновления столбцов таблицы без параметров.

Запись, параметры и результат

mysql_xdevapi\Table::update(): mysql_xdevapi\TableUpdate

Метод без параметров.

TableUpdate. Для исполнения вызовите execute() на настроенной команде.

Создаёт команду обновления столбцов таблицы без параметров. Следующие вызовы set и where определяют, что и где изменится; сама фабрика запись не меняет. В отличие от замены документа, set сохраняет остальные столбцы строки.

Два применения

Изменение цены

У товара меняется цена, но сохраняются имя и остаток.

$result = $products->update()->set('price_cents', 1200)
    ->where('id = :id')->bind(['id' => 1])->execute();

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

Сохранение формы с версией

Устаревшая версия не совпадёт и не затрёт новое имя.

$result = $products->update()->set('name', 'LED warm')
    ->set('version', mysql_xdevapi\expression('version + 1'))
    ->where('id = :id AND version = :v')->bind(['id' => 1, 'v' => 1])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::whereЗадаёт условие выбора строк обновления.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::where(string $where_expr): mysql_xdevapi\TableUpdate

Задаёт условие выбора строк обновления.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::where(string $where_expr): mysql_xdevapi\TableUpdate
ПараметрКак использовать
where_exprВыражение условия для отбора строк; именованные места значений записываются как :id.

TableUpdate для следующего звена цепочки.

Задаёт условие выбора строк обновления. PHP Manual описывает search filter для records/documents; здесь TableUpdate работает со строками SQL-таблицы. AND объединяет обязательные условия. Значения пользователя передаются через bind; текст выражения и имена столбцов задаёт программа. Без условия изменение может охватить всю таблицу.

Два применения

Нужная карточка товара

Обновление ограничено идентификатором.

$result = $products->update()->set('price_cents', 1200)
    ->where('id = :id')->bind(['id' => 1])->execute();

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

Отказ устаревшему редактору

Оба условия проверяются в одной команде сервера.

$result = $products->update()->set('name', 'LED cold')
    ->set('version', mysql_xdevapi\expression('version + 1'))
    ->where('id = :id AND version = :v')->bind(['id' => 1, 'v' => 1])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::bindСвязывает значения с именованными параметрами команды.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::bind(array $placeholder_values): mysql_xdevapi\TableUpdate

Связывает значения с именованными параметрами команды.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::bind(array $placeholder_values): mysql_xdevapi\TableUpdate
ПараметрКак использовать
placeholder_valuesАссоциативный PHP-массив: имя параметра без двоеточия → значение.

TableUpdate; значения привязаны, но запрос ещё не исполнен.

Связывает значения с именованными параметрами команды. На странице Manual параметр назван JSON array, но PHP-сигнатура и пример используют PHP array: json_encode не нужен. Bind не подставляет имя таблицы, направление сортировки или SQL-оператор. В выбранном PECL 8.0.30 сначала вызовите все set, затем where, затем bind. Where формирует внутренний список параметров; добавление Expression с новым параметром после него может аварийно завершить процесс. При повторении одного имени в Expression и where bind заполняет только первую позицию, и execute возвращает false. Используйте разные имена с одинаковым значением; это обход дефекта выбранного расширения, а не требование SQL. Не меняйте порядок по аналогии с другим построителем.

Два применения

Порог остатка при списании

Одно количество передано в две отдельные позиции: для проверки остатка и его уменьшения.

$qty = 2;
$result = $products->update()->set('stock', mysql_xdevapi\expression('stock - :decrement'))
    ->where('id = :id AND stock >= :minimum')
    ->bind(['id' => 1, 'decrement' => $qty, 'minimum' => $qty])->execute();

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

Прибавить поступление

Число передано отдельно от выражения, остаток вычисляет MySQL.

$result = $products->update()->set('stock', mysql_xdevapi\expression('stock + :received'))
    ->where('id = :id')->bind(['id' => 1, 'received' => 2])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::setДобавляет изменяемый столбец и его новое значение.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::set(string $table_field, string $expression_or_literal): mysql_xdevapi\TableUpdate

Добавляет изменяемый столбец и его новое значение.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::set(string $table_field, string $expression_or_literal): mysql_xdevapi\TableUpdate
ПараметрКак использовать
table_fieldИмя изменяемого столбца, выбранное программой.
expression_or_literalНовое значение или объект Expression для серверного вычисления; показана запись типа из PHP Manual.

TableUpdate, чтобы задать другие столбцы, условие и исполнение.

Добавляет изменяемый столбец и его новое значение. Сигнатура PHP Manual пишет string для второго параметра, но её же пример передаёт число. В исходном коде выбранного PECL 8.0.30 используется zval; программы наблюдают литерал и Expression. Проверенный порядок для PECL 8.0.30: все set → where → bind; добавление нового параметра Expression после where небезопасно из-за дефекта расширения. Обычная строка является значением, а expression() обозначает выражение сервера. Не превращайте произвольный пользовательский текст в Expression.

Два применения

Цена как буквальное значение

Столбец получает ровно 1200, соседние поля сохраняются.

$result = $products->update()->set('price_cents', 1200)
    ->where('id = :id')->bind(['id' => 1])->execute();

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

Счётчик версии как выражение

Имя и новая версия меняются в одной операции.

$result = $products->update()->set('name', 'LED warm')
    ->set('version', mysql_xdevapi\expression('version + 1'))
    ->where('id = :id AND version = :v')->bind(['id' => 1, 'v' => 1])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::orderbyЗадаёт порядок обновления.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::orderby(mixed $orderby_expr, mixed ...$orderby_exprs): mysql_xdevapi\TableUpdate

Задаёт порядок обновления.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::orderby(mixed $orderby_expr, mixed ...$orderby_exprs): mysql_xdevapi\TableUpdate
ПараметрКак использовать
orderby_exprСтрока критерия или массив одного/нескольких критериев.
orderby_exprsДополнительные критерии отдельными аргументами; в примерах используем явный массив.

TableUpdate с заданным порядком.

Задаёт порядок обновления. Критерии — выражения с ASC/DESC. Массив содержит отдельные строки, не одну строку JSON. При limit добавляйте уникальный второй критерий для равных значений первого. Порядок не заменяет WHERE: сначала определите допустимые строки. Метод принимает выражения разработчика, а не необработанный выбор сортировки из URL.

Два применения

Равные приоритеты

Сначала меньший приоритет, при равенстве — меньший ID.

$result = $jobs->update()->set('state', 'preparing')->where('state = :s')
    ->bind(['s' => 'ready'])->orderby(['priority ASC', 'id ASC'])->limit(2)->execute();

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

Последние добавленные ожидающие записи

Берётся наибольший ID среди ready; это порядок ID, не обещание реального времени создания.

$result = $jobs->update()->set('state', 'review')->where('state = :s')
    ->bind(['s' => 'ready'])->orderby('id DESC')->limit(1)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::limitОграничивает размер обновляемой порции.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::limit(int $rows): mysql_xdevapi\TableUpdate

Ограничивает размер обновляемой порции.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::limit(int $rows): mysql_xdevapi\TableUpdate
ПараметрКак использовать
rowsМаксимум подходящих строк. Выбранное расширение отвергает отрицательное значение.

TableUpdate; исполнение отдельно через execute().

Ограничивает размер обновляемой порции. PHP Manual описывает максимум records/documents; для SQL UPDATE это предел совпавших строк. Строка с тем же значением занимает место в порции. В выбранном X DevAPI она также учитывается квитанцией, хотя значение не изменилось; поэтому отдельно проверяем прочитанные поля. Для повторяемой порции сочетайте limit с where и orderby.

Два применения

Два задания за запуск

Размер шага ограничен двумя совпадениями.

$result = $jobs->update()->set('state', 'preparing')->where('state = :s')
    ->bind(['s' => 'ready'])->orderby(['priority ASC', 'id ASC'])->limit(2)->execute();

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

Ошибка отрицательного размера

Сценарий сохраняет фактическую цепочку ошибки; бизнес-строки не меняются.

try {
    $jobs->update()->set('state', 'preparing')->limit(-1);
} catch (mysql_xdevapi\Exception $error) {
    // Отклоните неверную настройку порции.
}

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableUpdate::executeОтправляет подготовленную команду обновления, параметров не принимает.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableUpdate::execute(): mysql_xdevapi\TableUpdate [запись PHP Manual]

Отправляет подготовленную команду обновления, параметров не принимает.

Запись, параметры и результат

mysql_xdevapi\TableUpdate::execute(): mysql_xdevapi\TableUpdate [запись PHP Manual]

Метод без параметров.

PHP Manual объявляет TableUpdate. Исходный код PECL 8.0.30 создаёт MYSQLX_RESULT; фактическое наблюдение на выбранном runtime подтвердило mysql_xdevapi\Result. Рассматриваем его как квитанцию, не продолжаем настройку TableUpdate на результате.

Отправляет подготовленную команду обновления, параметров не принимает. Счётчик результата и чтение строки отвечают на разные вопросы: сколько строк учтено квитанцией и что именно сохранилось. Выбранный X DevAPI считает совпавшие строки, включая повтор того же значения. Ошибка выполнения не является нулевым числом строк; её нужно обработать как исключение. Ноль сам по себе не доказывает отсутствие записи.

Два применения

Проверка конкретной цены

Квитанцию дополняет readback цены и остатка.

$result = $products->update()->set('price_cents', 1200)
    ->where('id = :id')->bind(['id' => 1])->execute();
$changed = $result->getAffectedItemsCount();

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

Различить одинаковое значение и отсутствующий ID

В выбранном X DevAPI повтор того же имени даёт 1, отсутствующий ID — 0; чтение доказывает сохранённое состояние.

$same = $products->update()->set('name', 'LED warm')->where('id = 1')->execute();
$missing = $products->update()->set('name', 'Missing')->where('id = 999')->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\Table::deleteСоздаёт команду удаления строк таблицы.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\Table::delete(): mysql_xdevapi\TableDelete

Создаёт команду удаления строк таблицы.

Запись, параметры и результат

mysql_xdevapi\Table::delete(): mysql_xdevapi\TableDelete

Метод без параметров.

TableDelete. Для реального удаления нужна настроенная команда и execute().

Создаёт команду удаления строк таблицы. Параметров нет; условие передаётся в where() следующего объекта. Таблица после удаления строк существует, её столбцы и индексы не исчезают. Не смешивайте эту операцию с DROP TABLE.

Два применения

Очистка по сроку и статусу

Удаляются только подходящие старые черновики.

$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])->limit(2)->execute();

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

Удалить один собственный черновик

Условие статуса защищает опубликованную запись от этой бизнес-команды.

$result = $drafts->delete()->where('id = :id AND state = :s')
    ->bind(['id' => 4, 's' => 'draft'])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDelete::whereОпределяет, какие строки разрешено удалить.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableDelete::where(string $where_expr): mysql_xdevapi\TableDelete

Определяет, какие строки разрешено удалить.

Запись, параметры и результат

mysql_xdevapi\TableDelete::where(string $where_expr): mysql_xdevapi\TableDelete
ПараметрКак использовать
where_exprУсловие отбора удаляемых строк; значения задавайте через параметры.

TableDelete для дальнейшей настройки.

Определяет, какие строки разрешено удалить. Отсутствие совпадений — допустимый результат с нулевым affected count. Проверка прав в приложении и условие в SQL дополняют друг друга: id из формы сам по себе не подтверждает право удаления.

Два применения

Статус и дата одновременно

Старая публикация не проходит условие статуса.

$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])->limit(2)->execute();

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

Несуществующий идентификатор

Ноль удалений, без изменения других строк.

$result = $drafts->delete()->where('id = :id')->bind(['id' => 999])->limit(1)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDelete::bindПередаёт значения отдельно от поискового выражения.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableDelete::bind(array $placeholder_values): mysql_xdevapi\TableDelete

Передаёт значения отдельно от поискового выражения.

Запись, параметры и результат

mysql_xdevapi\TableDelete::bind(array $placeholder_values): mysql_xdevapi\TableDelete
ПараметрКак использовать
placeholder_valuesPHP-массив имя → значение; имя соответствует placeholder в условии.

TableDelete с привязанными значениями.

Передаёт значения отдельно от поискового выражения. Ключ массива не содержит двоеточия. Не склеивайте дату/статус/ID с выражением. Формат даты и допустимость статуса приложение проверяет до запроса; bind не выполняет предметную валидацию.

Два применения

Настраиваемая дата очистки

Одна структура запроса работает для выбранной границы даты.

$cutoff = '2026-02-01';
$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => $cutoff])->limit(2)->execute();

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

Выбранный черновик

Числовой ID передаётся значением, не куском SQL.

$draftId = 4;
$result = $drafts->delete()->where('id = :id AND state = :s')
    ->bind(['id' => $draftId, 's' => 'draft'])->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDelete::orderbyОпределяет, какие строки окажутся первыми при ограниченном удалении.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableDelete::orderby(string $orderby_expr): mysql_xdevapi\TableDelete [запись PHP Manual]

Определяет, какие строки окажутся первыми при ограниченном удалении.

Запись, параметры и результат

mysql_xdevapi\TableDelete::orderby(string $orderby_expr): mysql_xdevapi\TableDelete [запись PHP Manual]
ПараметрКак использовать
orderby_exprКритерий удаления. Страница Manual показывает string; выбранный PECL также поддерживает массив критериев и несколько аргументов.

TableDelete с заданным порядком.

Определяет, какие строки окажутся первыми при ограниченном удалении. В исходном коде PECL 8.0.30 парсер принимает один или несколько аргументов строка/массив; используем массив из двух строк для однозначного порядка. ASC выбирает меньшие/более ранние значения, DESC — большие/поздние. Название столбца с направлением задаётся программой.

Два применения

Самые старые черновики

Дата определяет возраст, ID разрешает равенство дат.

$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])
    ->orderby(['created_on ASC', 'id ASC'])->limit(2)->execute();

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

Самый новый ID среди черновиков

Удаляется максимальный ID среди draft; выбор должен соответствовать явному требованию.

$result = $drafts->delete()->where('state = :s')->bind(['s' => 'draft'])
    ->orderby('id DESC')->limit(1)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDelete::limitОграничивает число удалений за исполнение.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableDelete::limit(int $rows): mysql_xdevapi\TableDelete

Ограничивает число удалений за исполнение.

Запись, параметры и результат

mysql_xdevapi\TableDelete::limit(int $rows): mysql_xdevapi\TableDelete
ПараметрКак использовать
rowsМаксимальное количество удаляемых записей; отрицательные числа недопустимы в выбранном расширении.

TableDelete для продолжения цепочки.

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

Два применения

Очистка небольшими шагами

Первый шаг удаляет два старых черновика, следующий — оставшийся.

$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])
    ->orderby(['created_on ASC', 'id ASC'])->limit(2)->execute();

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

Один ID

Лимит не заставляет удалить одну строку, когда условие ничего не нашло.

$result = $drafts->delete()->where('id = :id')->bind(['id' => 999])->limit(1)->execute();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableDelete::executeИсполняет подготовленное удаление, параметров нет.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableDelete::execute(): mysql_xdevapi\Result

Исполняет подготовленное удаление, параметров нет.

Запись, параметры и результат

mysql_xdevapi\TableDelete::execute(): mysql_xdevapi\Result

Метод без параметров.

Result — квитанция команды, в частности число удалённых строк через getAffectedItemsCount().

Исполняет подготовленное удаление, параметров нет. Метод возвращает результат команды, а не массив удалённых строк. Если нужен перечень для архива или подтверждения, продумайте получение и согласование этого перечня отдельно. После команды readback проверяет состояние таблицы; при ошибке возникает исключение, а не успешный ноль.

Два применения

Квитанция очистки

Число дополняется проверкой сохранённой публикации и оставшихся ID.

$result = $drafts->delete()->where('state = :s AND created_on < :d')
    ->bind(['s' => 'draft', 'd' => '2026-02-01'])->limit(2)->execute();
$deletedCount = $result->getAffectedItemsCount();

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

Повтор запроса без совпадений

Повторное отсутствие цели может быть нормальным исходом, не системной ошибкой.

$result = $drafts->delete()->where('id = :id')->bind(['id' => 999])->execute();
$deletedCount = $result->getAffectedItemsCount();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableSelect::groupByСобирает исходные строки в группы для агрегатов.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableSelect::groupBy(mixed $sort_expr): mysql_xdevapi\TableSelect

Собирает исходные строки в группы для агрегатов.

Запись, параметры и результат

mysql_xdevapi\TableSelect::groupBy(mixed $sort_expr): mysql_xdevapi\TableSelect
ПараметрКак использовать
sort_exprКритерий группировки; имя sort_expr в Manual не означает сортировку результата.

TableSelect с группировкой; execute вернёт RowResult.

Собирает исходные строки в группы для агрегатов. В примере PHP Manual группировка по возрасту сочетается с count; здесь это покупатель и оплаченные заказы. GROUP BY и ORDER BY решают разные задачи. Неагрегированный столбец должен быть ключом группы либо функционально зависеть от него; сохраняйте режим ONLY_FULL_GROUP_BY.

Два применения

Сумма оплат на покупателя

Одна строка на покупателя с оплатами.

$report = $orders->select(['customer_id', 'SUM(amount_cents) AS paid_cents'])
    ->where('state = :s')->bind(['s' => 'paid'])->groupBy('customer_id')
    ->orderby('customer_id ASC')->execute()->fetchAll();

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

Количество заказов каждого статуса

Отдельные группы draft и paid дают сводку процесса.

$report = $orders->select(['state', 'COUNT(*) AS order_count'])
    ->groupBy('state')->orderby('state ASC')->execute()->fetchAll();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

mysql_xdevapi\TableSelect::havingОтбирает группы по вычисленному результату.

Классы PECL mysql_xdevapi >= 8.0.11; страницы методов не указывают начальную версию. Учебное расширение 8.0.30. · mysql_xdevapi

mysql_xdevapi\TableSelect::having(string $sort_expr): mysql_xdevapi\TableSelect

Отбирает группы по вычисленному результату.

Запись, параметры и результат

mysql_xdevapi\TableSelect::having(string $sort_expr): mysql_xdevapi\TableSelect
ПараметрКак использовать
sort_exprУсловие над результатом группировки/агрегатами; параметр назван sort_expr в Manual.

TableSelect с условием по группам.

Отбирает группы по вычисленному результату. WHERE ограничивает строки до агрегирования, HAVING — группы после него. При реальном условии WHERE порог можно передать именованным параметром, вызвав bind после having. В выбранном PECL 8.0.30 binding HAVING без WHERE имеет дефект: для такой задачи используйте Session::sql с параметрами; не добавляйте выдуманное бизнес-условие ради обхода. Если ни одна группа не подходит, fetchAll возвращает пустой массив; отсутствие группы отличается от суммы ноль у существующего покупателя.

Два применения

Покупатели с оплатами от порога

В выборке остаётся покупатель с суммой 3500 копеек.

$report = $orders->select(['customer_id', 'SUM(amount_cents) AS paid_cents'])
    ->where('state = :s')->groupBy('customer_id')->having('SUM(amount_cents) >= :minimum')
    ->bind(['s' => 'paid', 'minimum' => 3000])->execute()->fetchAll();

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

Повторные оплаченные заказы

Список содержит только покупателей с минимум двумя оплатами.

$report = $orders->select(['customer_id', 'COUNT(*) AS order_count'])
    ->where('state = :s')->groupBy('customer_id')->having('COUNT(*) >= :minimum')
    ->bind(['s' => 'paid', 'minimum' => 2])->execute()->fetchAll();

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

Значения из запроса проверяют по правилам задачи и передают через bind. Ошибку соединения/SQL обрабатывают отдельно от успешного результата без изменений. SQL в том же Session нужен для JOIN и действий, которых нет в построителе Table.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.