PDO для Microsoft SQL Server: подключение, запросы и оптимальные практики
Основы PDO и SQL Server
Расширение PDO (PHP Data Objects) предоставляет универсальный интерфейс для работы с различными базами данных. Для подключения к Microsoft SQL Server используется драйвер pdo_sqlsrv (или альтернативный pdo_dblib через FreeTDS). Данная статья рассматривает наиболее эффективное решение на основе официального драйвера Microsoft.
Основное решение: подключение с использованием PDO SQLSRV
Для стабильной работы рекомендуется использовать драйвер pdo_sqlsrv, устанавливаемый через PECL или менеджер расширений. Строка подключения включает имя сервера, имя базы данных и параметры аутентификации.
$dsn = 'sqlsrv:Server=localhost;Database=testdb';
$user = 'sa';
$password = 'secret';
try {
$pdo = new PDO($dsn, $user, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo 'Ошибка подключения: ' . $e->getMessage();
}Php pdo sqlsrv (pdo для sql server)
После подключения выполняются подготовленные запросы с параметрами, что защищает от SQL-инъекций и повышает производительность при повторных вызовах.
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([1]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);Php artisan db (команда artisan db в laravel)
Типичная ошибка: “could not find driver” означает, что расширение pdo_sqlsrv не установлено. Решение: установить через pecl install pdo_sqlsrv или включить в php.ini.
Проблема кодировки: SQL Server по умолчанию использует Latin1. Для работы с UTF-8 нужно указать параметры в DSN: CharacterSet=UTF-8 или настроить кодировку на стороне запроса.
Вариант 1: Как настроить аутентификацию Windows?
Для входа через текущего пользователя Windows указывается параметр Trusted_Connection=Yes.
$dsn = 'sqlsrv:Server=.;Database=mydb;Trusted_Connection=Yes';
$pdo = new PDO($dsn); // без имени и пароляPhp pdo pgsql (pdo для postgresql)
Вариант 2: Как выполнять простые запросы без параметров?
Метод query() подходит для однократных статических запросов, но не рекомендуется для данных из пользовательского ввода.
$rows = $pdo->query('SELECT * FROM products')->fetchAll(PDO::FETCH_OBJ);Create table php (создание таблицы в php)
Вариант 3: Как обрабатывать ошибки разными режимами?
PDO поддерживает три режима: ERRMODE_SILENT (по умолчанию), ERRMODE_WARNING и ERRMODE_EXCEPTION. Последний рекомендуется для надёжной обработки.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
try {
$pdo->query('SELECT invalid');
} catch (PDOException $e) {} // не сработает, только warning
// Лучше:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);Select from users php (sql запрос select from users в php)
Вариант 4: Как использовать именованные параметры в подготовленных запросах?
Именованные плейсхолдеры улучшают читаемость кода.
$stmt = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
$stmt->execute([':name' => 'Alice', ':id' => 10]);Table user php (таблица user в php)
Вариант 5: Как выполнять транзакции и откаты?
Транзакции гарантируют атомарность изменений.
$pdo->beginTransaction();
try {
$pdo->exec('INSERT INTO logs (message) VALUES ("start")');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollBack();
echo 'Транзакция откачена: ' . $e->getMessage();
}Admin db php (администрирование базы данных в php)
Вариант 6: Как получить ID последней вставленной записи?
Метод lastInsertId() работает для столбцов с типом IDENTITY или GENERATED ALWAYS.
$pdo->exec('INSERT INTO users (name) VALUES ("Bob")');
$id = $pdo->lastInsertId(); // возвращает последний автоинкрементPhp db query (выполнение запроса к базе данных в php)
Вариант 7: Как вызывать хранимые процедуры с выходными параметрами?
Для OUTPUT параметров нужно использовать bindParam() с указанием длины.
$stmt = $pdo->prepare('{CALL get_user_count(?)}');
$count = 0;
$stmt->bindParam(1, $count, PDO::PARAM_INT, 10);
$stmt->execute();
echo $count;Php pdo query (выполнение запросов pdo)
Вариант 8: Как работать с большими бинарными данными (BLOB)?
Для вставки изображений или файлов используется потоки PDO::PARAM_LOB.
$stmt = $pdo->prepare('INSERT INTO files (data) VALUES (?)');
$handle = fopen('image.png', 'rb');
$stmt->bindParam(1, $handle, PDO::PARAM_LOB);
$stmt->execute();
fclose($handle);Php mysqli fetch (функция mysqli_fetch в php)
Вариант 9: Как защититься от SQL-инъекций при построении динамических запросов?
Кроме подготовленных запросов, можно использовать quote() для экранирования строк, но это менее предпочтительно.
$safeValue = $pdo->quote($_GET['name']);
$pdo->exec('SELECT * FROM users WHERE name = ' . $safeValue);
// quote() добавляет кавычки и экранирует спецсимволыDb error php (ошибка базы данных в php)
Вариант 10: Как настроить время ожидания и другие опции соединения?
В DSN можно передавать дополнительные параметры, например, LoginTimeout=5 (сек) или ReturnDatesAsStrings=true.
$dsn = 'sqlsrv:Server=mssql:1433;Database=test;LoginTimeout=10;ReturnDatesAsStrings=TRUE';Расширенные примеры работы с PDO SQLSRV
Пример 1: Подключение с несколькими опциями и обработка ошибок
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::SQLSRV_ATTR_DIRECT_QUERY => false, // использование подготовленных запросов по умолчанию
PDO::SQLSRV_ATTR_QUERY_TIMEOUT => 30, // таймаут запроса в секундах
];
$dsn = 'sqlsrv:Server=mssql.example.com,1433;Database=testdb;CharacterSet=UTF-8;TrustServerCertificate=False';
$pdo = new PDO($dsn, 'user', 'pass', $options);
echo 'Подключение установлено';Подключение установлено
Пример 2: Подготовленный запрос с несколькими наборами результатов (MARS)
Для использования нескольких активных наборов результатов нужно включить MARS в DSN и явно переключаться между курсорами.
$dsn = 'sqlsrv:Server=.;Database=test;MultipleActiveResultSets=True';
$pdo = new PDO($dsn, 'sa', 'pass', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt1 = $pdo->query('SELECT 1 AS val');
$stmt2 = $pdo->query('SELECT 2 AS val');
while ($row1 = $stmt1->fetch()) {
while ($row2 = $stmt2->fetch()) {
echo $row1['val'] . ' - ' . $row2['val'] . '<br>';
}
// $stmt2->execute(); // можно повторно использовать после перемещения курсора
}1 - 2
Пример 3: Работа с хранимой процедурой, возвращающей результат и выходные параметры
$proc = 'CREATE OR ALTER PROCEDURE sp_get_user
@id INT,
@name NVARCHAR(100) OUTPUT
AS
BEGIN
SELECT @name = name FROM users WHERE id = @id;
RETURN @id;
END';
$pdo->exec($proc);
$stmt = $pdo->prepare('{CALL sp_get_user(?, ?)}');
$userId = 5;
$userName = '';
$stmt->bindParam(1, $userId, PDO::PARAM_INT);
$stmt->bindParam(2, $userName, PDO::PARAM_STR, 100);
$stmt->execute();
echo 'Имя: ' . $userName;
// Получение возвращаемого значен из процедуры через fetch()
$result = $stmt->fetch(PDO::FETCH_ASSOC);
echo 'Возвращённый ID: ' . $result['RETURN_VALUE']; // при RETURNИмя: Alice
Возвращённый ID: 5
Пример 4: Массовая вставка с использованием подготовленных запросов в цикле
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO orders (product_id, quantity) VALUES (?, ?)');
$data = [[1, 10], [2, 5], [3, 7]];
foreach ($data as $row) {
$stmt->execute($row);
}
$pdo->commit();
echo 'Вставлено ' . count($data) . ' записей';Вставлено 3 записей
Пример 5: Работа с потоками для вставки/извлечения больших объектов
$fileContent = file_get_contents('largefile.pdf');
$stmt = $pdo->prepare('INSERT INTO documents (name, content) VALUES (?, CONVERT(VARBINARY(MAX), ?))');
$stmt->execute(['document.pdf', $fileContent]);
// Извлечение с потоком
$stmt = $pdo->query('SELECT content FROM documents WHERE id = 1');
$stmt->bindColumn(1, $blob, PDO::PARAM_LOB);
$stmt->fetch();
// $blob теперь содержит поток, можно сохранить
file_put_contents('output.pdf', stream_get_contents($blob));Файл сохранён.
Пример 6: Использование PDO::errorInfo для диагностики ошибки
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $pdo->prepare('SELECT * FROM nonexistent');
$stmt->execute();
if ($stmt->errorCode() != '00000') {
$info = $stmt->errorInfo();
echo 'Код: ' . $info[0] . ', SQLSTATE: ' . $info[1] . ', Сообщение: ' . $info[2];
}Код: 42S02, SQLSTATE: 208, Сообщение: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid object name 'nonexistent'.
Пример 7: Асинхронное выполнение с использованием PDO::SQLSRV_ATTR_DIRECT_QUERY
В некоторых сценариях (например, выполнение множества запросов без подготовки) можно отключить предварительную подготовку для повышения производительности.
$pdo->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, true);
$pdo->exec('UPDATE stats SET counter = counter + 1');
$pdo->exec('UPDATE logs SET ts = GETDATE()');
// Внимание: такой режим не защищает от инъекций, если есть подставляемые значения.Выполнено.
Пример 8: Использование PDO::FETCH_CLASS для маппинга на объект
class User {
public $id;
public $name;
}
$stmt = $pdo->query('SELECT id, name FROM users');
$users = $stmt->fetchAll(PDO::FETCH_CLASS, 'User');
foreach ($users as $user) {
echo $user->id . ' ' . $user->name . '<br>';
}1 Alice
2 Bob
Пример 9: Изменение кодировки соединения через запрос
Если DSN не поддерживает CharacterSet, можно выполнить запрос после подключения.
$pdo->exec('SET NAMES "UTF-8"'); // эквивалент для некоторых версий SQLSRV драйвера не всегда работает; лучше указывать в DSN.Например, для драйвера ODBC задать кодировку можно через set_charset – отсутствует. Рекомендуется использовать параметр CharacterSet=UTF-8 в строке DSN.
$dsn = 'odbc:Driver={ODBC Driver 17 for SQL Server};Server=.;Database=test;CharSet=UTF-8';Подключение с UTF-8 установлено.
Пример 10: Комбинирование fetchAll с разными стилями
$stmt = $pdo->prepare('SELECT * FROM categories');
$stmt->execute();
$unique = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); // только первый столбец
print_r($unique);
$keyValue = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // если два столбца: ключ-значение
print_r($keyValue);Array ( [0] => 1 [1] => 2 )
Array ( [1] => Electronics [2] => Clothing )