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

Дерево руководства
Глава 53 · После подключения SQLSRV, параметризованного запроса и простого цикла
SQLSRV: строки, поля, курсоры и несколько результатов команды
Последовательное чтение без потери первой строки, объекты с псевдонимами, небольшой STATIC-курсор и полная квитанция batch с диагностикой отложенной ошибки.
Что именно движется: строка или результат команды
Каталог должен показать каждый выбранный товар ровно один раз; накладная — понятные поля; составная SQL-команда — итоговую квитанцию и возможную ошибку. Эти задачи требуют различать три уровня: ресурс команды, текущий результат и строку внутри него. Указатель может двигаться по строкам, а next_result — между целыми результатами.
- query → statement с уже активным первым результатом
- fetch → строка; get_field → поле этой строки
- fetch снова → следующая строка; null → конец строк
- next_result → следующий набор или счётчик; null → конец всей команды
Сначала нужны подключение и диагностика SQLSRV и параметризованный SELECT и типы результата. $db — открытое соединение, $stmt — ресурс выполненной команды, $q — проверенное имя собственной учебной таблицы. Цикл, массив и аргументы функции разбираются отдельно; дополнительные функции helper в архивах отвечают за подключение, отчёт и уборку.
Самое короткое чтение одного поля
$stmt = sqlsrv_query($db, "SELECT 3 AS quantity");
if ($stmt === false) {
$errors = sqlsrv_errors();
} else {
$state = sqlsrv_fetch($stmt);
if ($state === true) {
$quantity = sqlsrv_get_field($stmt, 0); // 3.
}
sqlsrv_free_stmt($stmt);
}query выполняет SQL, но не выдаёт само число. fetch выбирает строку, get_field читает её первый столбец с индексом 0. Во второй раз fetch вернул бы null: единственная строка закончилась. Ни statement, ни true сами по себе не являются quantity. Самостоятельные программы ниже дополняют короткую схему полной диагностикой и видимым HTML/JSON-результатом.
Запрашивайте только нужные поля и строки, задавайте порядок в SQL. SELECT * с выгрузкой всей таблицы и последующей сортировкой большого PHP-массива передаёт лишнее по сети и занимает память. Для страницы выбирайте SQL-пагинацию; для общего числа — отдельный COUNT(*) по фильтру. Короткий STATIC-пример ниже нужен для изучения курсора, а не как способ показать огромный каталог.
Прочитать каталог по полям и сохранить первую строку
Каталогу нужны названия и остатки активных товаров. В базе есть и архивный товар; WHERE убирает его на сервере, ORDER BY определяет порядок. На экране ожидается «Крепёж:0 | Кабель:7». Ноль — допустимый остаток, NULL примечания означает отсутствие значения. Программа показывает их без потери первой строки.
- SQL выбирает title,qty,note только у active=1, по id
- has_rows → true; первая строка ещё не прочитана
- fetch → строка1; get_field(0,1,2) → Крепёж,0,NULL
- fetch → строка2; поля → Кабель,7,Полка 2
- fetch → null: закончить; поиск id999 → 0 строк и всё ещё 3 столбца
Сначала небольшой вызов
$state = sqlsrv_fetch($stmt);
if ($state === true) {
$title = sqlsrv_get_field($stmt, 0);
$qty = sqlsrv_get_field($stmt, 1);
// Для первой строки: title="Крепёж", qty=0.
}Указатель результата похож на закладку между строками: fetch передвигает её, get_field читает выбранное поле. Это модель чтения, не обещание, что вся таблица уже находится в памяти PHP. Индексы 0 и 1 означают первый и второй столбцы SELECT. Полный цикл отдельно обрабатывает null и false; после успешного fetch читает поля по возрастанию индекса. Массив items собирается только из двух нужных строк для учебного HTML/JSON.
Что получится
| Наблюдение | Результат |
|---|---|
| Каталог на экране | Крепёж:0 | Кабель:7 |
| Наличие строк до чтения | да |
| Столбцов результата | 3 |
| Первый товар не пропущен | да |
| NULL примечания сохранён | да |
| Пустой поиск без ошибки | да |
| Столбцов пустого поиска | 3 |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s150Runtime();
try {
$config=s150Config();$db=s150Connect($config);$runtime=s150Runtime($db,$config);
$name=s150OwnTable($db,$owned,'fields','(id int NOT NULL PRIMARY KEY, title nvarchar(40) NOT NULL, qty int NOT NULL, note nvarchar(40) NULL, active bit NOT NULL)');
$q=s150Qualified($name);
s150Command($db,"INSERT INTO $q (id,title,qty,note,active) VALUES (1,N'Крепёж',0,NULL,1),(2,N'Архивный товар',9,N'Архив',0),(3,N'Кабель',7,N'Полка 2',1)");
$stmt=s150Query($db,"SELECT title,qty,note FROM $q WHERE active = ? ORDER BY id",[1]);
try {
$hasRows=sqlsrv_has_rows($stmt);
if ($hasRows===false && sqlsrv_errors(SQLSRV_ERR_ERRORS)!==null) {s150Failure('Наличие строк каталога');}
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {s150Failure('Количество полей каталога');}
$items=[];
while (true) {
$state=sqlsrv_fetch($stmt);
if ($state===null) {break;}
if ($state===false) {s150Failure('Чтение строки каталога');}
// Read columns in increasing index order; SQL NULL is a valid field.
$title=sqlsrv_get_field($stmt,0);
if ($title===false) {s150Failure('Название');}
$qty=sqlsrv_get_field($stmt,1);
if ($qty===false) {s150Failure('Количество');}
$note=sqlsrv_get_field($stmt,2);
if ($note===false) {s150Failure('Примечание');}
$items[]=['title'=>$title,'qty'=>$qty,'note'=>$note];
}
} finally {sqlsrv_free_stmt($stmt);}
$empty=s150Query($db,"SELECT title,qty,note FROM $q WHERE id = ?",[999]);
try {
$emptyHasRows=sqlsrv_has_rows($empty);
$emptyErrors=$emptyHasRows===false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
$emptyFields=sqlsrv_num_fields($empty);
$emptyFetch=sqlsrv_fetch($empty);
} finally {sqlsrv_free_stmt($empty);}
$summary=implode(' | ',array_map(fn($item)=>$item['title'].':'.$item['qty'],$items));
$rows=[['Каталог на экране',$summary],['Наличие строк до чтения',$hasRows],['Столбцов результата',$fields],
['Первый товар не пропущен',$items[0]['title']==='Крепёж'],['NULL примечания сохранён',$items[0]['note']===null],
['Пустой поиск без ошибки',$emptyHasRows===false&&$emptyErrors===null&&$emptyFetch===null],['Столбцов пустого поиска',$emptyFields]];
$checks=['ordered_filtered_rows'=>$summary==='Крепёж:0 | Кабель:7','has_rows_before_fetch'=>$hasRows===true,
'field_count'=>$fields===3,'first_row_preserved'=>$items[0]['title']==='Крепёж','zero_is_value'=>$items[0]['qty']===0,
'sql_null_is_value'=>$items[0]['note']===null,'clean_end'=>$state===null,
'empty_not_error'=>$emptyHasRows===false&&$emptyErrors===null&&$emptyFetch===null,'empty_keeps_columns'=>$emptyFields===3];
$observations=['selected_items'=>$items,'empty_errors'=>$emptyErrors,'fetch_end'=>$state];
} finally {
if (is_resource($db)) {try {$removed=s150Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s150Report('sqlsrv150-forward-fields','Прочитать каталог по полям и сохранить первую строку',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Каталог на экране — Крепёж:0 | Кабель:7
Наличие строк до чтения — да
Столбцов результата — 3
Первый товар не пропущен — да
NULL примечания сохранён — да
Пустой поиск без ошибки — да
Столбцов пустого поиска — 3
Самостоятельная локальная программа: создаёт одну собственную таблицу SQL Server, выполняет сценарий, сохраняет result.html/result.json/metrics.json и удаляет свою таблицу. Настройка подключения описана в README.txt.
Таблица фактического запуска · Данные результата JSON.
Ошибки и ограничения
has_rows=false может означать ошибку; сразу сохраните диагностику. Значение поля null не завершает цикл — завершение относится к состоянию fetch. Не ставьте предварительный fetch в if перед while: так первая строка будет прочитана дважды по управлению потоком и один раз потеряна для вывода. Этот небольшой результат можно сохранить в массив; большие выгрузки обрабатывают последовательно, не накапливая весь каталог.
В списке пропала первая запись: разработчик вызвал fetch для проверки, затем начал цикл со следующего fetch. Разбор исходной ситуации на Stack Overflow. Первый вызов уже потребил строку. Здесь наличие проверяется has_rows, а единственный цикл обрабатывает каждую полученную строку.
Попробуйте изменить
Сделайте active=1 у архивного товара и задайте ему qty=0. Предскажите строку вывода и количество полей до запуска.
Как проверить решение
Появится третья позиция между Крепежом и Кабелем по id. Количество полей останется 3: новая строка не добавляет столбцов. Нулевой остаток должен выводиться, а не исчезнуть как ложное значение.
Получить объект накладной с понятными именами свойств
В представлении накладной удобно обращаться к invoiceId и quantity по именам. Сначала получим простой объект stdClass, затем ту же строку в собственном классе, который собирает подпись «Накладная #7: Кабель × 3». Объект здесь хранит прочитанные данные; само создание объекта не сохраняет изменения обратно в SQL Server.
- SQL id=7,title=Кабель,qty=3
- AS invoiceId и AS quantity задают имена свойств
- fetch_object без класса → stdClass
- fetch_object с InvoiceView150 → заполнение объявленных свойств → конструктор с аргументом Накладная
- caption → Накладная #7: Кабель × 3; следующий fetch простого результата → null
Сначала небольшой вызов
$item = sqlsrv_fetch_object($stmt);
if ($item !== null && $item !== false) {
echo $item->invoiceId, ": ", $item->quantity;
}
// Для строки примера: 7: 3.Объект — значение со свойствами; -> читает свойство, а AS в SQL задаёт его имя. class объявляет форму собственного объекта, public делает свойство доступным представлению, __construct вызывается при создании. ::class получает имя PHP-класса. Массив ["Накладная"] передаёт один аргумент конструктору. В проверенном SQLSRV поля заполняются до конструктора: ctorSawId фиксирует уже полученный 7. В полном примере все свойства объявлены явно и не имеют PHP-типов, чтобы изучать отображение данных отдельно от правил типизированной модели.
Что получится
| Наблюдение | Результат |
|---|---|
| Класс простого результата | stdClass |
| Псевдоним invoiceId | 7 |
| Количество | 3 |
| Подпись модели | Накладная #7: Кабель × 3 |
| ID, видимый конструктору | 7 |
| Конец после единственной строки | да |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s150Runtime();
try {
$config=s150Config();$db=s150Connect($config);$runtime=s150Runtime($db,$config);
class InvoiceView150 {
public $invoiceId;
public $title;
public $quantity;
public $ctorSawId;
public $caption;
public function __construct(string $prefix) {
$this->ctorSawId=$this->invoiceId;
$this->caption=$prefix.' #'.$this->invoiceId.': '.$this->title.' × '.$this->quantity;
}
}
$name=s150OwnTable($db,$owned,'objects','(id int NOT NULL PRIMARY KEY, title nvarchar(40) NOT NULL, qty int NOT NULL)');
$q=s150Qualified($name);
s150Command($db,"INSERT INTO $q (id,title,qty) VALUES (7,N'Кабель',3)");
$sql="SELECT id AS invoiceId,title,qty AS quantity FROM $q WHERE id = ?";
$plain=s150Query($db,$sql,[7]);
try {
$item=sqlsrv_fetch_object($plain);
if ($item===false) {s150Failure('Объект накладной');}
if ($item===null) {throw new RuntimeException('Учебная накладная не найдена.');}
$plainEnd=sqlsrv_fetch_object($plain);
if ($plainEnd===false) {s150Failure('Завершение накладной');}
} finally {sqlsrv_free_stmt($plain);}
$custom=s150Query($db,$sql,[7]);
try {
$view=sqlsrv_fetch_object($custom,InvoiceView150::class,['Накладная']);
if ($view===false) {s150Failure('Модель представления');}
if (!$view instanceof InvoiceView150) {throw new RuntimeException('Ожидался объект представления.');}
} finally {sqlsrv_free_stmt($custom);}
$rows=[['Класс простого результата',get_class($item)],['Псевдоним invoiceId',$item->invoiceId],['Количество',$item->quantity],
['Подпись модели',$view->caption],['ID, видимый конструктору',$view->ctorSawId],['Конец после единственной строки',$plainEnd===null]];
$checks=['stdclass_result'=>$item instanceof stdClass,'aliased_id'=>$item->invoiceId===7,
'quantity_value'=>$item->quantity===3,'declared_view'=>$view instanceof InvoiceView150,
'hydrated_before_constructor'=>$view->ctorSawId===7,'caption_value'=>$view->caption==='Накладная #7: Кабель × 3',
'clean_object_end'=>$plainEnd===null,'declared_fields'=>get_object_vars($item)===['invoiceId'=>7,'title'=>'Кабель','quantity'=>3]];
$observations=['plain'=>get_object_vars($item),'custom'=>get_object_vars($view),'constructor_args'=>['Накладная']];
} finally {
if (is_resource($db)) {try {$removed=s150Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s150Report('sqlsrv150-object-row','Получить объект накладной с понятными именами свойств',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Класс простого результата — stdClass
Псевдоним invoiceId — 7
Количество — 3
Подпись модели — Накладная #7: Кабель × 3
ID, видимый конструктору — 7
Конец после единственной строки — да
Самостоятельная локальная программа: создаёт одну собственную таблицу SQL Server, выполняет сценарий, сохраняет result.html/result.json/metrics.json и удаляет свою таблицу. Настройка подключения описана в README.txt.
Таблица фактического запуска · Данные результата JSON.
Ошибки и ограничения
Это не ORM: изменение $item->quantity не выполняет UPDATE. Конструктор может перезаписать прочитанное свойство; не используйте его для безусловного сброса полей. Драйверное заполнение свойств не гарантирует бизнес-проверки. Имена полей чувствительны к регистру, для JOIN нужны уникальные AS. Не полагайтесь на старое описание «типы свойств игнорируются» как на разрешение несовместимых типов в современном PHP. Для защищённой предметной модели сначала прочитайте массив, проверьте значения и вызовите её обычный конструктор явно.
Попробуйте изменить
Поменяйте SQL-псевдоним quantity на itemCount. Перед запуском согласуйте объявление свойства и обращение в конструкторе.
Как проверить решение
После согласованной замены подпись останется той же. Одного переименования в SELECT недостаточно: прежнее quantity перестанет получать данные. Тест полей результата тоже должен отражать новый контракт.
Посчитать короткий список и перейти к выбранной позиции
Оператор сверяет три выбранных артикула и переходит между первым, последним и предыдущим. Курсор — способ перемещения по результату запроса. Обычный FORWARD читает только вперёд и не предоставляет num_rows; STATIC позволяет прокрутку и счётчик. Программа сравнивает оба режима на одном коротком, ограниченном SQL-результате.
- SQL TOP(3) ORDER BY id → SKU-A,SKU-B,SKU-C
- FORWARD: num_rows=false, но строки читаются
- STATIC: num_rows=3
- FIRST→A, LAST→C, PRIOR→B, RELATIVE(-1)→A, ABSOLUTE(1)→B
- ABSOLUTE(9)→null: такой позиции нет
Сначала небольшой вызов
$stmt = sqlsrv_query($db, "SELECT TOP (3) sku FROM $q ORDER BY id", [],
["Scrollable" => SQLSRV_CURSOR_STATIC]);
// Проверить stmt !== false.
$count = sqlsrv_num_rows($stmt); // 3.
$state = sqlsrv_fetch($stmt, SQLSRV_SCROLL_ABSOLUTE, 1);
// При true get_field(0) вернёт SKU-B.Scrollable — настройка команды, а не ещё один SELECT. TOP (3) ограничивает строки на сервере; ORDER BY делает позиции определёнными. ABSOLUTE(1) означает вторую строку, независимо от её id=20. В полном коде moves — небольшой список указаний для foreach: название, направление, смещение. Он позволяет увидеть несколько переходов без повторения одинаковых проверок. Каждый переход проверяется до чтения поля.
Что получится
| Наблюдение | Результат |
|---|---|
| FORWARD: счётчик недоступен | да |
| FORWARD: прочитанные товары | SKU-A, SKU-B, SKU-C |
| STATIC: число строк | 3 |
| Переходы по результату | FIRST=SKU-A | LAST=SKU-C | PRIOR=SKU-B | RELATIVE(-1)=SKU-A | ABSOLUTE(1)=SKU-B |
| Позиция 9 отсутствует | да |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s150Runtime();
try {
$config=s150Config();$db=s150Connect($config);$runtime=s150Runtime($db,$config);
$name=s150OwnTable($db,$owned,'cursor','(id int NOT NULL PRIMARY KEY, sku nvarchar(20) NOT NULL)');
$q=s150Qualified($name);
s150Command($db,"INSERT INTO $q (id,sku) VALUES (10,N'SKU-A'),(20,N'SKU-B'),(30,N'SKU-C')");
$sql="SELECT TOP (3) sku FROM $q ORDER BY id";
$forward=s150Query($db,$sql);
try {
$forwardCount=sqlsrv_num_rows($forward);
$forwardErrors=$forwardCount===false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
$forwardRows=s150Rows($forward);
} finally {sqlsrv_free_stmt($forward);}
$stmt=s150Query($db,$sql,[],['Scrollable'=>SQLSRV_CURSOR_STATIC]);
try {
$count=sqlsrv_num_rows($stmt);
if ($count===false) {s150Failure('Счётчик STATIC');}
$moves=[['FIRST',SQLSRV_SCROLL_FIRST,0],['LAST',SQLSRV_SCROLL_LAST,0],
['PRIOR',SQLSRV_SCROLL_PRIOR,0],['RELATIVE(-1)',SQLSRV_SCROLL_RELATIVE,-1],['ABSOLUTE(1)',SQLSRV_SCROLL_ABSOLUTE,1]];
$positions=[];
foreach ($moves as [$label,$direction,$offset]) {
$state=sqlsrv_fetch($stmt,$direction,$offset);
if ($state===false) {s150Failure('Переход '.$label);}
if ($state===null) {throw new RuntimeException('В учебном списке ожидается строка '.$label);}
$sku=sqlsrv_get_field($stmt,0,SQLSRV_PHPTYPE_STRING('UTF-8'));
if ($sku===false) {s150Failure('Чтение SKU');}
$positions[$label]=$sku;
}
$outside=sqlsrv_fetch($stmt,SQLSRV_SCROLL_ABSOLUTE,9);
if ($outside===false) {s150Failure('Позиция за границей результата');}
} finally {sqlsrv_free_stmt($stmt);}
$parts=[];foreach ($positions as $label=>$sku) {$parts[]=$label.'='.$sku;}
$summary=implode(' | ',$parts);
$rows=[['FORWARD: счётчик недоступен',$forwardCount===false],['FORWARD: прочитанные товары',implode(', ',array_column($forwardRows,'sku'))],
['STATIC: число строк',$count],['Переходы по результату',$summary],['Позиция 9 отсутствует',$outside===null]];
$checks=['forward_count_unsupported'=>$forwardCount===false,'forward_diagnostic'=>is_array($forwardErrors)&&count($forwardErrors)>0,
'forward_still_readable'=>array_column($forwardRows,'sku')===['SKU-A','SKU-B','SKU-C'],'static_count'=>$count===3,
'position_contract'=>$positions===['FIRST'=>'SKU-A','LAST'=>'SKU-C','PRIOR'=>'SKU-B','RELATIVE(-1)'=>'SKU-A','ABSOLUTE(1)'=>'SKU-B'],
'outside_is_null'=>$outside===null];
$observations=['forward_count'=>$forwardCount,'forward_errors'=>s150Diagnostics($forwardErrors),'positions'=>$positions,'outside'=>$outside];
} finally {
if (is_resource($db)) {try {$removed=s150Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s150Report('sqlsrv150-static-cursor','Посчитать короткий список и перейти к выбранной позиции',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
FORWARD: счётчик недоступен — да
FORWARD: прочитанные товары — SKU-A, SKU-B, SKU-C
STATIC: число строк — 3
Переходы по результату — FIRST=SKU-A | LAST=SKU-C | PRIOR=SKU-B | RELATIVE(-1)=SKU-A | ABSOLUTE(1)=SKU-B
Позиция 9 отсутствует — да
Самостоятельная локальная программа: создаёт одну собственную таблицу SQL Server, выполняет сценарий, сохраняет result.html/result.json/metrics.json и удаляет свою таблицу. Настройка подключения описана в README.txt.
Таблица фактического запуска · Данные результата JSON.
Ошибки и ограничения
STATIC предоставляет снимок результата и имеет стоимость подготовки; CLIENT_BUFFERED дополнительно хранит выбранные данные у клиента. Для веб-страниц обычно нужен параметризованный SQL с WHERE, ORDER BY, OFFSET/FETCH или keyset-пагинацией, а не прокручиваемая копия всей таблицы в PHP. COUNT(*) по фильтру считает на сервере без передачи каждой записи. Позиции курсора не заменяют устойчивый идентификатор строки. После ошибочного num_rows сохранена диагностика, а затем на том же FORWARD явно проверено успешное чтение.
SELECT нашёл запись, но проверка через num_rows создала впечатление, что результата нет. Разбор исходной ситуации на Stack Overflow. Отличить false из-за FORWARD-курсора от нуля; выбрать подходящий курсор либо читать последовательный результат. Учебная задача использует каталог и параметризованный запрос, а не переносит небезопасный пример авторизации из вопроса.
Попробуйте изменить
Измените ORDER BY id на ORDER BY id DESC. Предскажите FIRST, LAST и ABSOLUTE(1).
Как проверить решение
FIRST станет SKU-C, LAST — SKU-A, ABSOLUTE(1) останется SKU-B для этих трёх строк. Число строк останется 3. При изменении самих фильтров средняя строка тоже может стать другой.
Прочитать квитанцию batch и заметить ошибку после первого SELECT
После изменения остатка приложение получает квитанцию. Одна команда SQL может вернуть несколько результатов: число изменённых строк, пустой табличный набор и заполненный набор. Нужно добраться до qty=5, сохранив смысл каждого шага. Затем отдельно воспроизведём ошибку после успешного SELECT: первые данные уже пришли, но вся операция не завершилась успешно.
- Начало: id1,qty2 → UPDATE qty+3
- Активный результат0: 0 полей, affected=1
- next_result=true → результат1: 2 поля, пусто
- next_result=true → результат2: id1,qty5; затем null
- Отдельная команда: SELECT qty5 → RAISERROR → next_result=false и код50000
Сначала небольшой вызов
// Уже активный результат сначала обработан.
$next = sqlsrv_next_result($stmt);
if ($next === true) {
// Есть следующий результат: проверить поля и прочитать.
} elseif ($next === false) {
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
} else {
// null — всё прочитано.
}Batch — несколько SQL-операторов в одной команде; это не обязательно транзакция. Набор строк — таблица результата SELECT, а счётчик UPDATE — отдельное сообщение без столбцов. num_fields помогает отличить их. Пустой SELECT имеет два столбца, но ни одной строки. next_result не читает строку, а меняет активный результат. В полном цикле сначала обрабатывается первый уже активный результат; только потом происходит переход. === не позволяет спутать false с null.
Что получится
| Наблюдение | Результат |
|---|---|
| Последовательность столбцов batch | 0, 2, 2 |
| UPDATE затронул строк | 1 |
| Строк в промежуточном SELECT | 0 |
| Количество в квитанции | 5 |
| NOCOUNT: сразу набор с двумя полями | да |
| До отложенной ошибки прочитано | 5 |
| Ошибка следующего результата обнаружена | да |
| Настройка NOCOUNT восстановлена | да |
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$db=$config=null;$owned=$removed=$rows=$checks=$observations=[];
$runtime=s150Runtime();
try {
$config=s150Config();$db=s150Connect($config);$runtime=s150Runtime($db,$config);
$name=s150OwnTable($db,$owned,'batch','(id int NOT NULL PRIMARY KEY, qty int NOT NULL)');
$q=s150Qualified($name);
s150Command($db,"INSERT INTO $q (id,qty) VALUES (1,2)");
$originalNoCount=(s150Read($db,'SELECT CASE WHEN (@@OPTIONS & 512) = 512 THEN 1 ELSE 0 END AS enabled')[0]['enabled']===1);
try {
s150Command($db,'SET NOCOUNT OFF');
$stmt=s150Query($db,"UPDATE $q SET qty = qty + 3 WHERE id = 1; SELECT id,qty FROM $q WHERE id = 999; SELECT id,qty FROM $q WHERE id = 1;");
try {
$results=[];$transitions=[];
while (true) {
// The first result is already active: do not advance before processing.
$fields=sqlsrv_num_fields($stmt);
if ($fields===false) {s150Failure('Описание результата batch');}
if ($fields===0) {
$affected=sqlsrv_rows_affected($stmt);
if ($affected===false) {s150Failure('Счётчик batch');}
$results[]=['fields'=>0,'affected'=>$affected,'rows'=>[]];
} else {
$hasRows=sqlsrv_has_rows($stmt);
$errors=$hasRows===false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
if ($errors!==null) {throw new RuntimeException('Ошибка проверки набора batch.');}
$results[]=['fields'=>$fields,'has_rows'=>$hasRows,'rows'=>s150Rows($stmt)];
}
$next=sqlsrv_next_result($stmt);
if ($next===false) {s150Failure('Следующий результат batch');}
$transitions[]=$next;
if ($next===null) {break;}
}
} finally {sqlsrv_free_stmt($stmt);}
// NOCOUNT removes the UPDATE count result, not its work or SELECT rows.
s150Command($db,'SET NOCOUNT ON');
$quiet=s150Query($db,"UPDATE $q SET qty = 5 WHERE id = 1; SELECT id,qty FROM $q WHERE id = 1;");
try {
$quietFields=sqlsrv_num_fields($quiet);
if ($quietFields===false) {s150Failure('NOCOUNT: поля');}
$quietRows=s150Rows($quiet);
$quietEnd=sqlsrv_next_result($quiet);
if ($quietEnd===false) {s150Failure('NOCOUNT: завершение');}
} finally {sqlsrv_free_stmt($quiet);}
$failing=s150Query($db,"SELECT qty FROM $q WHERE id = 1; RAISERROR(N'Controlled lesson error after SELECT',16,1);");
try {
$beforeError=s150Rows($failing);
$failedNext=sqlsrv_next_result($failing);
$lateErrors=$failedNext===false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
} finally {sqlsrv_free_stmt($failing);}
} finally {s150Command($db,$originalNoCount?'SET NOCOUNT ON':'SET NOCOUNT OFF');}
$restored=(s150Read($db,'SELECT CASE WHEN (@@OPTIONS & 512) = 512 THEN 1 ELSE 0 END AS enabled')[0]['enabled']===1)===$originalNoCount;
$errorCodes=is_array($lateErrors)?array_column($lateErrors,'code'):[];
$rows=[['Последовательность столбцов batch',implode(', ',array_column($results,'fields'))],['UPDATE затронул строк',$results[0]['affected']],
['Строк в промежуточном SELECT',count($results[1]['rows'])],['Количество в квитанции',$results[2]['rows'][0]['qty']],
['NOCOUNT: сразу набор с двумя полями',$quietFields===2&&$quietRows===[['id'=>1,'qty'=>5]]&&$quietEnd===null],
['До отложенной ошибки прочитано',$beforeError[0]['qty']],['Ошибка следующего результата обнаружена',$failedNext===false&&in_array(50000,$errorCodes,true)],['Настройка NOCOUNT восстановлена',$restored]];
$checks=['three_result_kinds'=>array_column($results,'fields')===[0,2,2],'update_count'=>$results[0]['affected']===1,
'empty_set_preserved'=>$results[1]['has_rows']===false&&$results[1]['rows']===[],
'receipt_value'=>$results[2]['rows']===[['id'=>1,'qty'=>5]],'strict_transitions'=>$transitions===[true,true,null],
'nocount_keeps_receipt'=>$quietFields===2&&$quietRows===[['id'=>1,'qty'=>5]]&&$quietEnd===null,
'first_select_not_whole_success'=>$beforeError===[['qty'=>5]]&&$failedNext===false,
'late_error_saved'=>in_array(50000,$errorCodes,true),'setting_restored'=>$restored];
$observations=['results'=>$results,'transitions'=>$transitions,'nocount_rows'=>$quietRows,'before_error'=>$beforeError,'late_errors'=>s150Diagnostics($lateErrors)];
} finally {
if (is_resource($db)) {try {$removed=s150Cleanup($db,$owned);} finally {sqlsrv_close($db);}}
}
s150Report('sqlsrv150-batch-results','Прочитать квитанцию batch и заметить ошибку после первого SELECT',$rows,$checks,$observations,$runtime,$owned,$removed);
Ожидаемый результат
Последовательность столбцов batch — 0, 2, 2
UPDATE затронул строк — 1
Строк в промежуточном SELECT — 0
Количество в квитанции — 5
NOCOUNT: сразу набор с двумя полями — да
До отложенной ошибки прочитано — 5
Ошибка следующего результата обнаружена — да
Настройка NOCOUNT восстановлена — да
Самостоятельная локальная программа: создаёт одну собственную таблицу SQL Server, выполняет сценарий, сохраняет result.html/result.json/metrics.json и удаляет свою таблицу. Настройка подключения описана в README.txt.
Таблица фактического запуска · Данные результата JSON.
Ошибки и ограничения
NOCOUNT ON убирает сообщения о количестве затронутых строк; сами изменения и SELECT остаются. Если процедура изменилась, жёсткое «перейти два раза» сломается. Всегда разбирайте полученные результаты и контракт процедуры. Успех первой части не гарантирует успех остальных; RAISERROR здесь намеренно создаёт контролируемый отказ, который сохраняется как наблюдение. Автоматического отката всех предыдущих изменений нет: для атомарности нужна транзакция. Пример возвращает настройку NOCOUNT и удаляет только свою таблицу. SQL-диагностику показывают в учебном локальном отчёте; посетителю рабочего сайта не раскрывают внутренние сообщения базы.
Процедура вернула строки, а последующая ошибка RAISERROR не была замечена при переходе между результатами. Разбор исходной ситуации на Stack Overflow. Разделить true, null и false; сразу после false сохранить диагностику. Полнота всей операции не определяется успехом первого SELECT.
Попробуйте изменить
Добавьте ещё один пустой SELECT перед квитанцией. Предскажите список полей и переходов; не добавляйте специальный «лишний next_result» вне общего цикла.
Как проверить решение
Появится ещё один элемент с двумя полями и пустым rows. Цикл сам его прочитает; будет ещё одно true перед конечным null. Квитанция по-прежнему содержит qty=5. Измените ожидаемый контракт учебного результата, чтобы проверка отражала новый batch.
Выбрать действие над результатом
| Функция | Когда нужна |
|---|---|
| sqlsrv_fetch | Передвинуть указатель на строку, чтобы затем читать её поля отдельно. |
| sqlsrv_get_field | Прочитать одно поле уже выбранной строки и при необходимости задать PHP-тип. |
| sqlsrv_fetch_object | Прочитать строку как объект с именованными свойствами. |
| sqlsrv_has_rows | Узнать, есть ли строки, не съедая первую строку чтением. |
| sqlsrv_num_rows | Получить число строк результата, когда выбранный тип курсора это поддерживает. |
| sqlsrv_num_fields | Узнать количество столбцов текущего результата, отдельно от количества строк. |
| sqlsrv_next_result | Перейти от одного результата команды к следующему и заметить отложенную ошибку. |
От чтения результата к завершённой операции
Для обычной выдачи достаточно одного последовательного чтения с различением конца и ошибки. Для отдельных полей нужен fetch вместе с get_field; для модели представления — понятные имена и проверяемое отображение свойств. Курсор выбирают под размер и задачу, а составной результат дочитывают до подтверждённого окончания.
Если несколько изменений должны сохраниться вместе или полностью откатиться, продолжите транзакциями SQLSRV. Для построения запросов к нужному экрану вернитесь к серверной фильтрации, сортировке и выбору полей; конкретный синтаксис пагинации SQL Server отличается от MySQL.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
sqlsrv_fetchПередвинуть указатель на строку, чтобы затем читать её поля отдельно.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_fetch(resource $stmt, int $row = SQLSRV_SCROLL_NEXT, int $offset = 0): bool|nullПередвинуть указатель на строку, чтобы затем читать её поля отдельно.
- Указатель перед первой строкой
- fetch → true: текущая строка «Крепёж»
- get_field(0) → «Крепёж»; следующий fetch передвигает указатель
| Параметр | Назначение |
|---|---|
| $stmt | Выполненный ресурс SQLSRV с текущим набором строк. |
| $row | Направление NEXT, PRIOR, FIRST, LAST, ABSOLUTE или RELATIVE. Переходы назад и по позиции требуют подходящего Scrollable-курсора. |
| $offset | Целочисленное смещение для ABSOLUTE или RELATIVE. Для ABSOLUTE первая строка имеет индекс 0; для остальных режимов смещение не определяет выбранную строку. |
true — текущая строка выбрана, false — ошибка, null — строки в этой позиции нет или чтение завершено. Функция не возвращает массив данных: значения читает get_field.
| Переход | Позиция |
|---|---|
| SQLSRV_SCROLL_NEXT | Следующая строка; обычный последовательный вызов. |
| SQLSRV_SCROLL_PRIOR | Предыдущая строка прокручиваемого результата. |
| SQLSRV_SCROLL_FIRST | Первая строка. |
| SQLSRV_SCROLL_LAST | Последняя строка. |
| SQLSRV_SCROLL_ABSOLUTE | Строка по абсолютному смещению: offset=0 означает первую. |
| SQLSRV_SCROLL_RELATIVE | Смещение относительно текущей позиции, например −1 назад. |
Прочитать название первой позиции
После true поле 0 относится к выбранной строке. В нашем каталоге это «Крепёж»; нулевое количество в следующем поле остаётся допустимым значением.
$status = sqlsrv_fetch($stmt);
if ($status === true) {
$title = sqlsrv_get_field($stmt, 0);
} elseif ($status === false) {
$errors = sqlsrv_errors();
} // null: строки нет.Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Вернуться к выбранному товару
В STATIC-результате ABSOLUTE с индексом 1 выбирает вторую строку в порядке SQL. Это позиция результата, а не первичный ключ товара.
$status = sqlsrv_fetch($stmt, SQLSRV_SCROLL_ABSOLUTE, 1);
if ($status === true) {
$sku = sqlsrv_get_field($stmt, 0);
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
Не делайте пробный fetch перед основным циклом, если эту строку затем не обработаете: указатель уже сдвинут. Для проверки наличия используйте has_rows, для обычного списка — цикл с отдельными ветвями null и false. fetch_array и fetch_object тоже продвигают указатель; смешивать их с fetch без понимания позиции нельзя.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_get_fieldПрочитать одно поле уже выбранной строки и при необходимости задать PHP-тип.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_get_field(resource $stmt, int $fieldIndex, int $getAsType = ?): mixedПрочитать одно поле уже выбранной строки и при необходимости задать PHP-тип.
- SELECT title,qty,note: индексы 0,1,2
- fetch выбрал строку «Крепёж»,0,NULL
- get_field последовательно → текст, число 0, null
| Параметр | Назначение |
|---|---|
| $stmt | Выполненный statement после успешного sqlsrv_fetch. |
| $fieldIndex | Индекс столбца от 0 в порядке SELECT, а не имя поля. Читайте индексы по возрастанию: после поля 1 нельзя вернуться к полю 0. |
| $getAsType | Необязательный SQLSRV_PHPTYPE_*: INT, FLOAT, STRING(encoding), STREAM(encoding), DATETIME или NULL, если преобразование допустимо. Если аргумент опущен, драйвер выбирает стандартный PHP-тип по SQL-типу. Символ ? в сигнатуре обозначает неуказанное документацией значение, не строку для передачи. |
Значение поля, включая 0, пустую строку и SQL NULL, либо false при ошибке. При стандартном чтении SQL NULL возвращается как null и не означает конец набора: конец определяет fetch. При явном SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY) на проверенном драйвере 5.13.3 значение SQL NULL читается как пустой поток, так же, как пустое бинарное значение. Чтобы сохранить различие, запросите отдельный признак payload IS NULL или DATALENGTH(payload) и прочитайте его до поля-потока. Это показано в примере пустого файла и отсутствующего вложения. Невозможное преобразование и нарушение порядка полей требуют диагностики.
Не потерять нулевой остаток
Поле qty равно 0 — товар найден, остаток нулевой. Строгое сравнение не принимает ноль за ошибку.
$title = sqlsrv_get_field($stmt, 0);
$qty = sqlsrv_get_field($stmt, 1);
if ($qty === false) { $errors = sqlsrv_errors(); }
else { /* qty может быть 0. */ }Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Получить код как UTF-8 строку
Явный тип пригодится текстовому экспорту. Сначала fetch выбирает строку, затем поле 0 читается в нужном представлении; неподдерживаемое преобразование нужно обработать.
$status = sqlsrv_fetch($stmt);
if ($status === true) {
$sku = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING("UTF-8"));
if ($sku === false) { $errors = sqlsrv_errors(); }
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
Не вызывайте get_field до успешного fetch и после null. Для повторного произвольного доступа к полям небольшой строки сначала получите массив fetch_array. Для большого бинарного поля можно запросить поток и читать частями; тип STREAM не превращает произвольный SQL-тип в поток. Явный PHP-тип не меняет SQL-тип столбца и правила его хранения.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_fetch_objectПрочитать строку как объект с именованными свойствами.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_fetch_object(resource $stmt, string $className = "stdClass", array $ctorParams = [], int $row = SQLSRV_SCROLL_NEXT, int $offset = 0): object|null|falseПрочитать строку как объект с именованными свойствами.
- SELECT id AS invoiceId,title,qty AS quantity
- Поля строки → одноимённые свойства объекта
- Конструктор модели представления → подпись «Накладная #7: Кабель × 3»
| Параметр | Назначение |
|---|---|
| $stmt | Выполненный statement с набором строк. |
| $className | Имя доступного PHP-класса; по умолчанию stdClass. |
| $ctorParams | Аргументы конструктора по порядку; пустой массив, если параметров нет. Обязательные параметры конструктора нужно передать. |
| $row | Режим перехода курсора. Для задания этого аргумента явно передайте также className и ctorParams. |
| $offset | Смещение ABSOLUTE/RELATIVE; индекс первой строки 0. |
Объект текущей строки, null при отсутствии следующей строки, false при ошибке драйвера, в том числе неизвестном имени класса. Ошибки PHP-конструктора, включая исключения, являются отдельным путём отказа и не превращаются автоматически в false.
| Переход | Позиция |
|---|---|
| SQLSRV_SCROLL_NEXT | Следующая строка; обычный последовательный вызов. |
| SQLSRV_SCROLL_PRIOR | Предыдущая строка прокручиваемого результата. |
| SQLSRV_SCROLL_FIRST | Первая строка. |
| SQLSRV_SCROLL_LAST | Последняя строка. |
| SQLSRV_SCROLL_ABSOLUTE | Строка по абсолютному смещению: offset=0 означает первую. |
| SQLSRV_SCROLL_RELATIVE | Смещение относительно текущей позиции, например −1 назад. |
Показать строку накладной
AS даёт полям уникальные имена. У объекта читают свойства через ->; значение quantity в примере равно 3.
$item = sqlsrv_fetch_object($stmt);
if ($item === false) { $errors = sqlsrv_errors(); }
elseif ($item !== null) {
echo $item->invoiceId, ": ", $item->quantity;
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Добавить подпись своей модели представления
В самостоятельном примере объявлены все свойства результата и конструктор с префиксом. Он получает уже заполненный invoiceId; это подтверждается наблюдением ctorSawId=7.
$item = sqlsrv_fetch_object($stmt, InvoiceView150::class, ["Накладная"]);
if ($item instanceof InvoiceView150) {
echo $item->caption;
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
Имена свойств чувствительны к регистру; title и Title — разные имена. Каждому вычислению и одноимённым столбцам JOIN задавайте AS. Безымянное поле игнорируется с предупреждением, дубликаты имён нельзя надёжно представить одним свойством. Драйвер способен заполнять и непубличные свойства: это не проверка бизнес-правил. Для обычного класса заранее объявляйте свойства, чтобы не создавать динамические свойства с устаревшим поведением в PHP 8.2+. Старую формулировку документации об игнорировании типов свойств нельзя использовать как обещание обхода ограничений современного PHP: здесь проверен класс с нетипизированными полями. Для строгой модели прочитайте массив и вызовите собственную фабрику с проверками.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_has_rowsУзнать, есть ли строки, не съедая первую строку чтением.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_has_rows(resource $stmt): boolУзнать, есть ли строки, не съедая первую строку чтением.
- SELECT двух активных товаров
- has_rows → true, указатель не потребляет товар
- Первый fetch → «Крепёж», а не второй товар
| Параметр | Назначение |
|---|---|
| $stmt | Ресурс выполненной команды с текущим результатом. |
true — набор содержит строки; false — строк нет либо возникла ошибка. Само false не является полным диагнозом: сразу сохраните sqlsrv_errors и различите пустой результат и отказ.
Показать пустое состояние каталога
Проверка перед циклом не делает лишний fetch. Первый обработанный товар остаётся «Крепёж».
$hasRows = sqlsrv_has_rows($stmt);
$errors = $hasRows === false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
if ($errors !== null) { /* Сообщить об отказе. */ }
elseif (!$hasRows) { /* Ничего не найдено. */ }
else { /* Начать чтение с первой строки. */ }Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Различить пустой набор и следующий результат
Пустой SELECT внутри batch всё равно является набором с описанными столбцами. После него надо вызвать next_result; пустота не означает конец всей команды.
$hasRows = sqlsrv_has_rows($stmt);
$errors = $hasRows === false ? sqlsrv_errors(SQLSRV_ERR_ERRORS) : null;
// При отсутствии ошибки показать пустой набор.
// Затем отдельно проверить sqlsrv_next_result($stmt).Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
has_rows не считает строки и не сообщает, сколько осталось после уже сделанных fetch. Он описывает наличие строк в наборе, а не текущий остаток чтения. Не передавайте false от неудачного query. Ошибки нужно сохранять до других вызовов драйвера. Исторический дефект старых драйверов с передвижением курсора не является текущим контрактом; самостоятельный пример проверяет сохранение первой строки.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_num_rowsПолучить число строк результата, когда выбранный тип курсора это поддерживает.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_num_rows(resource $stmt): int|falseПолучить число строк результата, когда выбранный тип курсора это поддерживает.
- SELECT TOP (3) sku ORDER BY id
- FORWARD → false: счётчик недоступен
- Тот же ограниченный SELECT с STATIC → 3
| Параметр | Назначение |
|---|---|
| $stmt | Выполненный ресурс с STATIC, KEYSET либо CLIENT_BUFFERED-курсором. У обычного FORWARD и DYNAMIC количество недоступно. |
Целое количество строк, включая 0, либо false. false у FORWARD не означает, что SELECT пустой: это неподдерживаемая операция для этого курсора.
| Курсор | Поведение | Граница |
|---|---|---|
| SQLSRV_CURSOR_FORWARD | По умолчанию; читать вперёд | num_rows возвращает false; счётчик не является проверкой наличия строк. |
| SQLSRV_CURSOR_STATIC | Прокрутка по снимку результата | num_rows доступен; последующие изменения исходных данных в снимке не отражаются. |
| SQLSRV_CURSOR_KEYSET | Прокрутка с фиксированным набором ключей | num_rows доступен; состав ключей фиксирован, изменения значений видны. Удалённая запись требует отдельной обработки. |
| SQLSRV_CURSOR_DYNAMIC | Прокрутка с отражением изменений | num_rows не поддерживается; результат может меняться. |
| SQLSRV_CURSOR_CLIENT_BUFFERED | Весь выбранный результат в памяти клиента | num_rows доступен; память ограничивают ClientBufferMaxKBSize. Большой каталог ради страницы так не загружают. |
Посчитать небольшой прокручиваемый список
Запрос ограничен TOP (3) и отсортирован SQL. STATIC позволяет вывести «3 позиции» и перейти к нужной позиции.
$stmt = sqlsrv_query($db, "SELECT TOP (3) sku FROM $q ORDER BY id", [],
["Scrollable" => SQLSRV_CURSOR_STATIC]);
if ($stmt !== false) {
$count = sqlsrv_num_rows($stmt);
if ($count === false) { $errors = sqlsrv_errors(); }
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Не принять отказ счётчика за отсутствие товаров
FORWARD подходит последовательной выдаче. В примере num_rows возвращает false, но чтение всё равно получает SKU-A, SKU-B и SKU-C.
$count = sqlsrv_num_rows($forward);
if ($count === false) {
$errors = sqlsrv_errors();
// Причина: курсор не предоставляет счётчик.
}
// Прочитать forward обычным fetch; не выводить «0 товаров».Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
Для общего количества совпадений на большой таблице выполните отдельный SELECT COUNT(*) с тем же WHERE; для страницы получите только нужные поля и диапазон. Не выбирайте CLIENT_BUFFERED лишь ради подсчёта сотен тысяч строк. num_rows считает текущий результат, не всю исходную таблицу и не число изменённых записей. Microsoft отдельно ограничивает подсчёт нескольких результатов batch клиентским буферизованным курсором; в примере STATIC используется только для одного SELECT.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_num_fieldsУзнать количество столбцов текущего результата, отдельно от количества строк.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_num_fields(resource $stmt): int|falseУзнать количество столбцов текущего результата, отдельно от количества строк.
- UPDATE → результат без столбцов → 0
- SELECT id,qty WHERE id=999 → 2 столбца, 0 строк
- SELECT id,qty WHERE id=1 → 2 столбца, 1 строка
| Параметр | Назначение |
|---|---|
| $stmt | Ресурс подготовленной либо выполненной команды; функция допустима до и после execute, если драйвер располагает описанием результата. |
Количество полей либо false. Для SELECT без найденных строк столбцы всё равно существуют. Результат-счётчик UPDATE не имеет полей: в проверенном batch num_fields возвращает 0.
Проверить форму строки каталога
SELECT title,qty,note даёт три поля. Это не три товара: количество записей определяется чтением.
$fieldCount = sqlsrv_num_fields($stmt);
if ($fieldCount === false) { $errors = sqlsrv_errors(); }
elseif ($fieldCount !== 3) { /* Контракт SELECT изменился. */ }Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Выбрать способ обработки результата batch
Нулевой fieldCount означает отсутствие табличных полей, а положительный — описанный набор, даже если тот пустой.
$fieldCount = sqlsrv_num_fields($stmt);
if ($fieldCount === false) { $errors = sqlsrv_errors(); }
elseif ($fieldCount === 0) { $affected = sqlsrv_rows_affected($stmt); }
else { /* Читать строки, различая null и false. */ }Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
Не путайте количество столбцов и строк, а также count массива SQLSRV_FETCH_BOTH: в нём один столбец представлен числовым и строковым ключом. Для названий, SQL-типов, размера и допуска NULL нужна field_metadata. После next_result описание относится уже к новому результату.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
sqlsrv_next_resultПерейти от одного результата команды к следующему и заметить отложенную ошибку.
SQLSRV: точная первая версия не указана в PHP Manual; профиль запуска указан в главе · sqlsrv
sqlsrv_next_result(resource $stmt): bool|nullПерейти от одного результата команды к следующему и заметить отложенную ошибку.
- Первый результат уже активен: UPDATE затронул 1 строку
- true → пустой SELECT; true → квитанция qty=5
- null → конец; false → ошибка, сохранить диагностику
| Параметр | Назначение |
|---|---|
| $stmt | Ресурс команды с возможными следующими результатами: наборы строк, счётчики изменений, выходные параметры. |
true — следующий результат активирован; null — результатов больше нет; false — ошибка перехода или выполнения следующей части команды. Первый результат активен сразу после query/execute: предварительный next_result его пропустит.
Получить квитанцию после изменения
В batch сначала приходит счётчик UPDATE, затем пустой SELECT, затем строка квитанции. Все три обрабатываются последовательно; пустой набор не обрывает чтение.
// Сначала обработать уже активный результат.
$next = sqlsrv_next_result($stmt);
if ($next === true) { /* Обработать следующий. */ }
elseif ($next === false) { $errors = sqlsrv_errors(); }
else { /* null: команда полностью прочитана. */ }Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Не объявить весь отчёт успешным после первого SELECT
Последующий оператор может вернуть ошибку SQL Server. Пример сначала читает quantity=5, затем получает false и сохраняет код 50000.
$next = sqlsrv_next_result($stmt);
if ($next === false) {
$errors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
// Уже полученные строки не доказывают успех всей команды.
}Фрагмент предполагает открытое соединение и выполненный SELECT; полный код, входные данные и результат: самостоятельный пример.
Ошибки и ограничения
while(sqlsrv_next_result($stmt)) смешивает false и null, поэтому теряет информацию об ошибке. Настройка SET NOCOUNT ON подавляет сообщения о числе изменённых строк, но не SELECT, не SQL-ошибки и не создаёт транзакцию. Не рассчитывайте на фиксированное число переходов для изменяемой процедуры. OUT/INOUT-параметры читают после завершения результатов. Переход дальше может отбросить непрочитанные строки текущего набора; сначала обработайте нужные данные.
Связанные действия: sqlsrv_fetch, sqlsrv_get_field, sqlsrv_next_result, query, fetch_array и освобождение statement, диагностика SQLSRV.
Источники и обсуждения
- PHP Manual — function.sqlsrv-fetch
- PHP Manual — function.sqlsrv-get-field
- PHP Manual — function.sqlsrv-fetch-object
- PHP Manual — function.sqlsrv-has-rows
- PHP Manual — function.sqlsrv-num-rows
- PHP Manual — function.sqlsrv-num-fields
- PHP Manual — function.sqlsrv-next-result
- Microsoft — sqlsrv-fetch-object
- Microsoft — sqlsrv-get-field
- Microsoft — sqlsrv-has-rows
- Microsoft — sqlsrv-num-rows
- Microsoft — sqlsrv-num-fields
- Microsoft — sqlsrv-next-result
- Microsoft — cursor-types-sqlsrv-driver
- Microsoft — SET NOCOUNT
- Microsoft SQLSRV — журнал изменений
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


