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

Дерево руководства
Глава 126 · После таблиц и результатов X DevAPI
MySQL X DevAPI: правки таблиц, связи и готовые отчёты
От изменения одной цены к защите редактирования, ограниченным порциям, связанному экрану и общей транзакции Table/SQL.
От карточки товара к связанному экрану
Каталог уже умеет читать таблицу. Теперь сотрудник меняет цену, другой сохраняет устаревшую форму, фоновая задача берёт ограниченную порцию, а менеджер смотрит клиентов с итогами оплат. Разберём эти действия по одному: от одной изменённой строки до согласованной операции над двумя таблицами.
- Таблица: строки и типизированные столбцы
- Условие и нужные поля задаём серверу
- Изменение или агрегирование выполняет MySQL
- 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, остальные значения строки остаются на месте.
- LED: 1000 коп., 3 шт.
- where id = 1 выбирает строку
- set: цена 1200; stock + 2
- 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.
- Оба редактора получили version = 1
- Первый: WHERE id = 1 AND version = 1
- Имя изменено, version = 2
- Второй: старое условие не совпадает → 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 |
| Правка отсутствующего ID | 0 |
| Строка 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 не участвует.
- WHERE ready исключает done
- ORDER BY priority, id даёт 2, 3, 1
- LIMIT 2 оставляет 2 и 3
- 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 |
| Выбраны ID | 2, 3 |
| ID 1 | ready |
| ID 4 | done |
| Отрицательный лимит отклонён | да |
Самостоятельная программа и архив
<?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.
- Все строки → state = draft и дата до границы
- Старые черновики: 1, 3, 4
- ORDER BY дата, id; LIMIT 2
- Удалены 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 |
| Остались ID | 2, 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 копеек не должен увеличить сумму.
- WHERE paid: 1000, 2500, 700
- GROUP BY customer_id: группы 1 и 2
- SUM/COUNT: 3500 и 2; 700 и 1
- 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 следующего сценария.
Показать покупателей, включая тех, кто ещё не оплатил заказ
На странице клиентов должны остаться Анна с двумя оплатами, Борис только с черновиком и Вера без заказов. Результат — три строки с понятными именами и суммами, а не повторяющиеся строки клиента по числу его заказов.
- Страница покупателей: Анна, Борис, Вера
- LEFT JOIN: оплаченные заказы по customer_id
- Нет оплаты → справа NULL; COUNT(o.id) = 0
- 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.
- Один Session → startTransaction
- TableUpdate списывает 2; SQL добавляет журнал
- commit сохраняет обе записи
- Следующая попытка: 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_values | PHP-массив имя → значение; имя соответствует 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.
Источники и обсуждения
- PHP Manual — TableUpdate
- PHP Manual — TableDelete
- PHP Manual — Table::update
- PHP Manual — TableUpdate::where
- PHP Manual — TableUpdate::bind
- PHP Manual — TableUpdate::set
- PHP Manual — TableUpdate::orderby
- PHP Manual — TableUpdate::limit
- PHP Manual — TableUpdate::execute
- PHP Manual — Table::delete
- PHP Manual — TableDelete::where
- PHP Manual — TableDelete::bind
- PHP Manual — TableDelete::orderby
- PHP Manual — TableDelete::limit
- PHP Manual — TableDelete::execute
- PHP Manual — TableSelect::groupBy
- PHP Manual — TableSelect::having
- MySQL 8.0 — UPDATE
- MySQL 8.0 — DELETE
- MySQL 8.0 — GROUP BY
- MySQL 8.0 — JOIN
- MySQL 8.0 — агрегатные функции
- MySQL 8.0 — неявный commit DDL
- Исходный код выбранного mysql_xdevapi 8.0.30
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


