21 ошибка программиста PHP. Часть 2 - Непродуманная работа с данными: бд и sql
ОГЛАВЛЕНИЕ
13. Непродуманная работа с данными: бд и sql
Забавно иногда наблюдать, сколько разных уловок находят люди для организации доступа к базам данных и получения выборки результатов. Среди прочих особенно выделяются комбинации из веток if, циклов do..while, множественных запросов и вызовов функции sql_result() внутри цикла for.
Чем, на их взгляд, они занимаются?
Код, основанный на методе научного тыка, говорит о недостаточно ясно определённой организации работы с БД. Те, кто прилагают все свои усилия на написание кода, а не на написание правильного кода, рискуют больше потерять, чем заработать. Некорректная выборка данных - яркий тому пример. Некоторые программисты не уделяют достаточно времени на тщательное продумывание этого момента. Естественно, в реальной жизни может и не оказаться того "единственно верного" способа выборки данных, но всегда найдётся тысяча "неверных", это точно.
Ошибки в организации выборки данным можно разделить на три класса:
- неправильное использование функций обращения к БД
- ошибки SQL: запрашивается не то, что нужно
- обработка результатов выборки средствами PHP
Неправильное использование функций обращения к БД
Один из PHP-исходников предлагал следующий способ получения выборки из БД (приведённый ниже код в проекте находится после сгенерированных SQL-запросов):
<?php
if (!($row = sql_fetch_row ($result))) {
print "Ошибка: не найдено ни одного ряда";
exit;
}
do {
print "$row[0]: $row[1]\n<br>\n";
}
while ($row = sql_fetch_row ($result));
?>
Примечание: в данном и последующих примерах $result является дескриптором выборки или указателем на неё. Другими словами, был произведён запрос и получено определённое множество рядов. Примеры демонстрируют методы эффективной обработки этого множества.
В этом отрезке кода есть две ошибки:
- проверка на "ноль рядов" - это попытка получить хотя бы один.
- полученные данные не хранятся в ассоциативном массиве.
Проверка на "ноль рядов" ($result): неправильный подход
Задействовав функцию sql_fetch_row(), данный кусок кода предлагает косвенную проверку выборки на наличие хотя бы одного ряда данных. Но ведь существует прямой способ - это подсчёт количества рядов в выборке $result функцией sql_num_rows(), как показано ниже:
<?php
if (sql_num_rows ($result) <= 0) {
print "Ошибка: не найдено ни одного ряда";
exit;
}
while ($row = sql_fetch_row ($result)){
print "$row[0]: $row[1]\n<br>\n";
}
?>
Избавляемся от do..while
Прежде всего, исчезает необходимость в использовании давно уже поднадоевшего do..while, ибо для проверки на "ноль рядов" функция sql_num_row() не выдёргивает первый рядв $row, и указатель по-прежнему установлен на начало.
В PHP Source как-то был представлен подобный фрагмент кода. Если выборка не была нулевой, то функция sql_fetch_row() внутри условного блока доставляла первый ряд. Для получения остальных приходилось прибегать к do..while, потому что получение ряда из выборки ("to fetch" - принести, доставить// Прим. перев.) смещает указатель в ней. Таким образом, сначала вам придётся обработать уже полученный ряд ("do"), только потом получить второй ряд и так далее.
Так чем же do..while так провинился?
- в данном примере внутри цикла do..while помещён только один оператор: простой вывод. Теперь представим, что там может оказаться не один, а десять операторов. Тогда редактору кода придётся искать условие while после оператора do и целого блока действий внутри цикла. Занятие не из приятных.
- условие while обычно располагается в начале блока, а не в конце его. Поэтому редактору кода нужно будет уделять этому особое внимание при чтении, чтобы не спутать цикл do..while с предварительным условием while обычного цикла.
Делаем всё просто и понятно
В случае получения нулевой выборки, функция sql_num_row() в отличие от sql_fetch_row() делает именно то, что вам нужно сделать:
- действие sql_fetch_row(): "При попытке получить первый ряд не найдено ни одного ряда. Это может означать, что в данной выборке их нет".
- Действие sql_num_row(): "Количество рядов в выборке равно нулю".
Но как это отражается на написании кода?
Рассмотрим следующий пример, где операторы внутри условия записаны псевдокодом:
- if(!($row = sql_fetch_row($result))){Print Error}:
- Получаем первый ряд из выборки.
- Если выборка пустая, то переменной $row приписываем 0; ноль логически выражается False; отсюда !(0)=True; выводим сообщение об ошибке.
- Иначе, если выборка не пустая, получаем первый ряд, приписываем его переменной $row; $row не равно нулю, то есть True; !(True)=False; выходим на цикл do..while.
- If(sql_num_rows($result)<=0){Print Error}:
- Подсчёт рядов в выборке.
- Если их меньше или равно нулю, выводим сообщение об ошибке.
- Иначе - идём дальше.
Итак, какое из двух выражений проще и быстрее понять? Безусловно, подсчёт рядов - более прямой и короткий путь.
Каково всё же практическое преимущество второго способа? Невелика разница, что мы поместим внутри этого условия - многого тут не выиграть.
Однако на протяжении 10 000 строк вашего кода продуманные, а потому просто и ясно изложенные идеи сэкономят кучу времени редактору кода (вот и первое преимущество). Есть и другие преимущества: разработка скриптов заметно ускоряется и становится более размеренной.
Если ваша СУБД не поддерживает sql_num_row()
Действительно, некоторые СУБД могут не поддерживать эту функцию. Отнесёмся с сочувствием ко всем владельцам таких систем. Им придётся проверять выборки "на ноль рядов" путем запроса первого ряда. Однако и здесь, рекомендуем использовать булевские переменные:
<?php
$found = false;
while ($row = sql_fetch_array($result)){
$found = true;
}
if (!$found){
print "Ошибка";
}
?>