SELECT (обучающий этап) задачи по SQL запросам 120 штук, DML 10 шт. Дистанционное обучение языку баз данных SQL. Интерактивные упражнения и тестирование по операторам SELECT,INSERT,UPDATE,DELETE языка SQL. SQL remote education. SQL statements exercises. Подзапросы, Соединение таблиц, Функции SQL, Введение в SQL, Скачать книги по SQL. Команды SQL,CREATE SEQUENCE,CREATE SYNONYM,CREATE USER,CREATE VIEW,Create Table,DROP,GRANT,INSERT,REVOKE,SET ROLE,SET TRANSACTION,SQL ALTER TABLE,SQL команды.
Статья предназначена для читателей, имеющих некоторый опыт использования PHP и MySQL. Она предполагает, что читатель понимает основные принципы работы с базами данных и программирования и может использовать сценарий PHP для отправки запроса серверу MySQL.
Обратите внимание на то, что в конце статьи имеются сноски для разъяснения некоторых утверждений и словарь терминов.
Инструкции по установке PHP и MySQL выходят за рамки данной статьи;
Начиная с середины 90х, ext/mysql служило основным мостом между PHP и MySQL. Хотя в нем имелись недостатки и проблемы росли с годами, в общем, ext/mysql делал свое дело неплохо и шел в ногу с изменениями как в PHP, так и в MySQL.
Однако с появлением PHP 5 и MySQL 4.1 все изменилось - начали образовываться несколько достаточно обширных трещин.
В ext/mysql имелись "достоинства, оказавшиеся недостатками": в первую очередь это mysql_pconnect()[1] , подключение по умолчанию и автоматическое подключение[2]. Кроме того, проявились несовместимости между функциями ext/mysql и теми, что поддерживались клиентской библиотекой MySQL, на которой основаны и ext/mysql, и ext/mysqli.
В попытке исправить эти расхождения, Георг Рихтер создал очередное расширение PHP 5, которое поддерживает новые возможности MySQL 4.1+. Это расширение получило название ext/mysqli, где 'i' заменяет одно из слов: improved(улучшенное), interface(интерфейс), ingenious(изобретательное), incompatible(несовместимое) or incomplete(неполное).[3]
2.1 Основные цели
Некоторыми из основных целей создания нового расширения были:
Простота использования. Код ext/mysql стал очень сложным и беспорядочным. Значительная модернизация функциональности MySQL потребовала возможности подключения и отключения тех или иных частей в зависимости от версии клиентской библиотеки. Другие проблемы требовали изменения функциональности в зависимости от операционной системы.
Лучшая совместимость. Расширение должно было более аккуратно использовать клиентскую библиотеку MySQL, чтобы будущие усовершенствования библиотеки проще поддерживались в PHP.
Обратная совместимость. Хотя совместимость между ext/mysql и ext/mysqli не идеальна, были приложены значительные усилия для облегчения портирования приложений с ext/mysql на ext/mysqli.
2.2 Основные возможности
Ext/mysqli поддерживает новые возможности, появившиеся в последних версиях MySQL, и предлагает новые функции.
Основные возможности расширения:
Процедурный интерфейс, очень похожий на интерфейс ext/mysql.
Объектно-ориентированный интерфейс, который позволяет использовать стиль, более простой и расширяемый, нежели процедурный интерфейс.
Поддержка нового бинарного протокола MySQL, введенного в версии 4.1. (Новый протокол более эффективен, чем старый, и поддерживает более широкий набор возможностей, например подготовленные выражения).
Поддержка полного набора возможностей клиентской библиотеки MySQL C, в том числе установки сложных параметров соединения с помощью mysqli_init() и других функций. Кроме того, расширение имеет поддержку дополнительных функций мониторинга, отлова ошибок, управления загрузкой и репликации.
2.3 Зачем переходить?
Кроме получения доступа к дополнительному функционалу MySQL 4.1+, зачем же стоит переходить на использование ext/mysqli?
В дополнение к упомянутому функционалу, ext/mysqli имеет несколько существенных преимуществ:
Заметно большая скорость. Усовершенствования, как в расширении, так и в MySQL, ускорили большинство операций, иногда достигая 40-кратного увеличения производительности по сравнению с ext/mysql.
Усиленная безопасность. В ранних версиях MySQL RDBMS (см. Словарь терминов в конце статьи - прим. переводчика), существовала возможность отловить хэш слабого пароля в сети и затем воссоздать пароль пользователя. Новая процедура аутентификации гораздо прочнее и повторяет устойчивые к атакам механизмы авторизации таких инструментов как SSH.
2.4 Предупреждения и неожиданности
Некоторые аспекты ext/mysqli сильно отличаются от старого расширения. С целью исправления определенных изъянов в дизайне и поведения, склонного к ошибкам, некоторые возможности были убраны:
Подключение к базе данных по умолчанию. Если вы явно не подключитесь к ней, ext/mysqli не сделает этого за вас.
Соединение по умолчанию(link). Необходимо явно обращаться к соединению с сервером базы данных, которое вы хотите использовать, если вы работаете с ext/mysqli через процедурный интерфейс, например mysqli_query($link, $query);
3. Покажите мне код!
Теперь, когда вы знаете, что изменилось, мы начнем анализировать код, который демонстрирует, как выглядит и работает новое расширение. Весь самостоятельный код, приведенный в этой статье, использует базу данных "world", которая бесплатно доступна на сайте http://www.mysql.com/documentation/index.html.
3.1 Базовое использование
Вот простой скрипт, который соединяется с сервером MySQL, посылает запрос серверу с помощью этого соединения, выводит результаты запроса и затем освобождает результирующее множество запроса и закрывает соединение.
<?php /* Подключение к серверу MySQL */ $link = mysqli_connect( 'localhost', /* Хост, к которому мы подключаемся */ 'user', /* Имя пользователя */ 'password', /* Используемый пароль */ 'world'); /* База данных для запросов по умолчанию */ if (!$link) { printf("Невозможно подключиться к базе данных. Код ошибки: %s\n", mysqli_connect_error()); exit; } /* Посылаем запрос серверу */ if ($result = mysqli_query($link, 'SELECT Name, Population FROM City ORDER BY Population DESC LIMIT 5')) {
/* Освобождаем используемую память */ mysqli_free_result($result); } /* Закрываем соединение */ mysqli_close($link); ?>
Приведенный сценарий должен вывести что-то вроде:
Очень крупные города: Mumbai (Bombay) (10500000)Seoul (9981619)Sao Paulo (9968485)Shanghai (9696300)Jakarta (9604900)
Как видно из кода, ext/mysqli и ext/mysql могут быть очень похожи. Единственным существенным различием является то, что процедурный стиль ext/mysqli несколько более "многословен".
Заметьте, что без проверки на ошибки приведенный скрипт мог бы дать сбой в любом месте и вывести пользователю мерзкое сообщение об ошибке.
3.2 Использование объектно-ориентированного интерфейса
Объектно-ориентированный интерфейс предоставляет немного более лаконичный и менее восприимчивый к ошибкам метод использования ext/mysqli. Код, приведенный ниже, производит те же действия, что и предыдущий, однако, имеются несколько ключевых отличий, на которые стоит обратить внимание:
Нам не нужно явно задавать соединение, используемое в наших командах. Информация о подключении содержится в наших объектах $mysqli и $result и доступна при вызове соответствующих методов.
Когда производится выборка из результирующего набора данных запроса с использованием fetch_assoc(), не нужно явно задавать идентификатор используемого результирующего набора. Также как и информация о подключении, он содержится в объекте $result.
<?php /* Подключение к серверу MySQL */ $mysqli = new mysqli('localhost', 'user', 'password', 'world');
if (mysqli_connect_errno()) { printf("Подключение к серверу MySQL невозможно. Код ошибки: %s\n", mysqli_connect_error()); exit; } /* Посылаем запрос серверу */ if ($result = $mysqli->query('SELECT Name, Population FROM City ORDER BY Population DESC LIMIT 5')) {
/* Освобождаем память */ $result->close(); } /* Закрываем соединение */ $mysqli->close(); ?>
4. Подготовленные выражения
Теперь, когда мы разобрали азы использования расширения, рассмотрим несколько новых возможностей.
Подготовленные выражения предоставляют разработчикам возможность создавать запросы, которые являются более безопасными, имеют более высокую производительность и более удобны в написании.
Подготовленные выражения можно использовать двумя способами: с заданными параметрами и с заданными результатами.
4.1 С заданными параметрами
Подготовленные выражения с заданными параметрами позволяют создавать шаблоны запросов и хранить их на сервере MySQL. Когда нужно создать запрос, данные, заполняющие шаблон, отправляются серверу MySQL, где полностью сформированный запрос и выполняется.
Основной процесс создания и использования подготовленных выражений с заданными параметрами прост.
Создается шаблон запроса и посылается серверу MySQL. Сервер его получает, проверяет его корректность, чтобы убедиться, что он имеет смысл, и сохраняет его в специальном буфере. Затем сервер возвращает идентификатор, который может быть в дальнейшем использован для обращения к подготовленному выражению.
Когда нужно создать запрос, данные, заполняющие шаблон, отправляются серверу MySQL и полностью сформированный запрос выполняется.
В этом процессе заключено несколько очень важных деталей.
Тело шаблона отсылается серверу MySQL только один раз. Для выполнения выражения посылаются только данные, необходимые для заполнения шаблона.
Большая часть работы по проверке и обработке запроса проделывается только один раз, вместо того, чтобы делать это каждый раз.
Кроме того, для запросов, которые содержат небольшое количество дынных, расходы сильно уменьшены. Например, если у вас есть запрос типа:
INSERT INTO City (ID, Name) VALUES (NULL, 'Calgary');
то каждый раз при выполнении запроса нужно отослать лишь около 16 байт вместо обычных 60 или более байт. (Эти приближенные числа включают расходы на все данные вроде идентификатора подготовленного выражения, длины данных запроса - для безопасности бинарных данных - и т.д., но не включают расходы на строку запроса.)
Данные запроса не должны проходить через функции вроде mysql_real_escape_string(), чтобы убедиться, что нет угрозы атаки "SQL-впрыска"[4] Вместо этого, клиент и сервер MySQL работают так, чтобы убедиться, что посланные данные безопасно обработаны при их комбинировании с подготовленным выражением.
Шаблон запроса выглядит как-то так:
INSERT INTO City (ID, Name) VALUES (?, ?);
Знак '?' можно использовать в большинстве мест, где используются символьные данные, например запрос может быть переделан из
SELECT Name FROM City WHERE Name = 'Calgary';
в
SELECT Name FROM City WHERE name = ?;
Вот более полный пример, демонстрирующий весь процесс:
<?php $mysqli = new mysqli('localhost', 'user', 'password', 'world'); /* Проверка соединения */ if (mysqli_connect_errno()) { printf("Подключение невозможно: %s\n", mysqli_connect_error()); exit(); } $stmt = $mysqli->prepare("INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)"); $stmt->bind_param('sssd', $code, $language, $official, $percent); $code = 'DEU'; $language = 'Bavarian'; $official = "F"; $percent = 11.2; /* выполнение подготовленного выражения */ $stmt->execute(); printf("%d Row inserted.\n", $stmt->affected_rows); /* Закрытие соединения и выражения*/ $stmt->close(); /* Очистить таблицу CountryLanguage */ $mysqli->query("DELETE FROM CountryLanguage WHERE Language='Bavarian'"); printf("%d Row deleted.\n", $mysqli->affected_rows); /* Закрыть подключение */ $mysqli->close(); ?>
Обратите внимание на то, что первым параметром bind_param() является короткая строка. Это строка формата, используемая для определения того, как объявленные параметры должны быть интерпретированы.
В случае вышеприведенного сценария 'sssd' означает, что значения первых трех параметров $code, $language и $official будут посланы как строки, а четвертый параметр $percent будет содержать значения типа double с плавающей запятой.
Для каждой заявленной переменной в bind_param(), должна быть своя буква в строке формата, которая означает, как переменная будет отправлена. Например
Объявление типов обеспечивает то, что расширение mysqli знает, как зашифровать данные для большей эффективности.
Определения типов очень просты: данные в заданных переменных будут обрабатываться как целочисленные, рациональные числа (double) или как строки.
Также имеется специальный тип, позволяющий отправлять блобы (большие бинарные объекты) порциями.
Следующая таблица иллюстрирует типы и возможности использования:
Идентификатор типа
Тип столбца
i
Все INT типы
d
DOUBLE и FLOAT
b
BLOB'ы
s
Остальные типы
4.2 С заданными результатами
Подготовленные выражения с объявленными результатами позволяют привязывать переменные PHP-скрипта к значениям полей данных в результирующем множестве запроса.
Процесс объявления таков:
Создать запрос.
Попросить сервер MySQL заготовить запрос.
Привязать переменные PHP к столбцам в заготовке запроса.
Заставить сервер MySQL выполнить запрос.
Запросить добавление нового ряда данных в привязанные переменные.
Вот простой фрагмент кода, иллюстрирующий процесс:
<?php $mysqli = new mysqli("localhost", "user", "password", "world");
if (mysqli_connect_errno()) { printf("Подключение невозможно: %s\n", mysqli_connect_error()); exit(); } /* Подготовленное выражение */ if ($stmt = $mysqli->prepare("SELECT Code, Name FROM Country ORDER BY Name LIMIT 5")) { $stmt->execute();
/* Привязывание переменных к заготовке */ $stmt->bind_result($col1, $col2);
4.3 Использование заданных параметров и результатов вместе
Вот более полный пример, демонстрирующий использование и заданных параметров, и заданных результатов одновременно:
<?php $mysqli = new mysqli("localhost", "user", "password", "world");
if (mysqli_connect_errno()) { printf("Подключение невозможно: %s\n", mysqli_connect_error()); exit(); } /* Подготовленное выражение */ if ($stmt = $mysqli->prepare("SELECT Code, Name FROM Country WHERE Code LIKE ? LIMIT 5")) {
$stmt->bind_param("s", $code); $code = "C%";
$stmt->execute();
/* Объявление переменных для заготовленного выражения*/ $stmt->bind_result($col1, $col2);
В этой статье мы привели обзор возможностей и архитектуры ext/mysqli, а также краткое изложение истории его развития. К этому моменту вы должны понимать, как использовать и получать выгоду от подготовленных выражений MySQL и должны чувствовать удобство использования объектно-ориентированного интерфейса к ext/mysqli.
6. Словарь терминов
ext/mysql - старое расширение PHP для работы с MySQL. Не поддерживает всех возможностей MySQL версий 4.1 и выше.
ext/mysqli - новое расширение PHP 5 для работы с MySQL. Поддерживает возможности MySQL версий от 3.22 до 5.0
Клиентская библиотека MySQL - Компонент MySQL RDBMS (MySQL Relational DataBase Management System - Система управления реляционной базой данных MySQL - прим. переводчика), который позволяет программам общаться с RDBMS.
Сервер MySQL - Компонент MySQL RDBMS, который обрабатывает и отвечает на запросы, управляет файловым представлением данных внутри базы и т.д.
[1] - Функция mysql_pconnect() была создана для предоставления механизма уменьшения затрат на установление и разрыв соединений с сервером MySQL. К сожалению, из-за взаимодействия между архитектурами сервера Apache и PHP, большой трафик на сайте, использующем pconnect, мог быстро загрязнить сервер MySQL большим количеством неиспользуемых соединений, которые мешали активным соединениям получать доступ к базе данных.
[2] - Возможности автоматического соединения позволяли определенным вызовам функций автоматически соединяться с базой данных (если правильная информация о соединении находилась в конфигурационном файле php.ini). Возможность соединения по умолчанию работала по следующему принципу. Последнее открытое соединение с базой MySQL становится используемым соединением, если параметр соединения не был явно указан в аргументах функции.
[3] - Это расширение все еще находится в стадии разработки. В то время как набор возможностей ядра должен быть действительно стабильным, ни MySQL 4.1, ни PHP 5.0 не имеют стабильных релизов (статья появилась до выхода PHP 5.0.0 - прим. переводчика). Также, дополнительный набор возможностей, который не очень аккуратно использует клиентскую библиотеку MySQL, все еще дорабатывается.
[4] - Атаки типа "SQL-впрыск" возможны, когда данные входят в запрос, заставляя его совершать неожиданные и/или злонамеренные действия. Пусть, для примера, дан простой запрос в PHP скрипте типа "DELETE FROM grades WHERE class_name='test_$class'". Атакующий может получить контроль над переменной $class и получить возможность an attacker who can gain control over the value of $class can force unintended deletes to occur by changing the value of $class to something like "oops' or class_name LIKE '%'".
7. Об авторах
Зак Грэнт (Zak Greant) - профессиональный защитник концепции Open Source, писатель и программист. Он работает в MySQL AB пропагандистом Сообщества. Зак поддерживает оба расширения PHP для работы с MySQL и является соавтором PHP Functions Essential Reference.
Георг Рихтер (Georg Richter) - создатель расширения mysqli. Он также поддерживает расширения mysql и ncurses. Он работает в MySQL AB Старшим Разработчиком и является членом Apache Software Foundation.
Мы все привыкли к MySQL. Мы любим его за простоту, легкость освоения и множество программ для облегчения работы (phpMyAdmin). Мы делаем скрипты и задумываемся, а вдруг у заказчика стоит другая SQL-база? И вот тут мы начинаем выдумывать или скачивать классы для работы с различными базами. А не проще ли использовать готовое, да ещё к тому же включенное по умолчанию в дистрибутив php?
И вот тут мы начинаем выдумывать или скачивать классы для работы с различными базами. А зачем морочить голову и думать, как проверить, работает ли на остальных SQL базах то, что мы написали? А не проще ли использовать готовое, да ещё к тому же включенное по умолчанию в дистрибутив php? Это прекрасное дополнение к php, о котором вы больше сможете узнать посетив их сайт.
Сегодня попробуем разобраться с их классом DB, отвечающим за доступ к различным базам данных (MySQL, PostgreSQL, Oracle 7/8/8i, Microsoft SQL, InterBase, MiniSQL, SyBase, Informix, FrontBase и универсальный ODBC).
Как ни стандартно, при разработке проектов мы сделаем один файл connect.php, который будет подключатся к нашей базе в каждом скрипте, где это необходимо:
------------- connect.php ------------
<? PHP
require_once ( "DB.php");
#подключаем класс DB PEAR
$ TypeSQL = "mysql";
$ Host = "локальный";
$ User = "YourPasswordSQL"
$ Name = "YourNameSQL";
$ Dbase = "MyDBase";
//Все так же, как и делали раньше, только добавилась переменная $TypeSQL, в которой мы
//и указываем, какая у нас База Данных.
# прописываем источник DSN http://pear.sourceforge.net/manual/core.db.tut_dsn.php
$ DSN = "$ TypeSQL: // $ Пользователь: $ Пароль @ Хост / $ Dbase"
$ db = DB :: connect ($ dsn, true);
#Что соответствует знакомому по MySQL: mysql_db_connect() и mysql_db_select
$ Db-> setFetchMode (DB_FETCHMODE_ASSOC);
//выбор формата выбираемых строк. В нашем случае мы используем
// fetchRow().А вообще, кому что нравится
//http://pear.sourceforge.net/manual/core.db.setfetchmode.php.
?>
Вот, в принципе, и весь наш connect. И если мы захотим использовать другую базу данных, нам в этом скрипте необходимо заменить значение переменной $TypeSQL, к примеру, на "pgsql" (PostgreSQL).
Теперь приступим, собственно, к созданию запросов и работой с нашей базой данных.
<? PHP
require_once ( "connect.php");
#Подключаемся к Базе Данных.
$ sql = "SELECT * FROM MyTable";
$ res = $ db-> query ($ sql);
#Выполняем наш запрос (аналогия mysql_query() )
$ Num = $ res-> NumRows ();
#Определяем количество строк (аналогия mysql_num_rows() )
while ($ row = $ res-> fetchRow ())
{
$ id = $ row ["id"];
$ name = $ row ["name"];
}
#Получаем необходимые нам данные (аналогия mysql_fetch_array() )
#Или другим удобным для Вас методом подробнее http://pear.sourceforge.net/manual/
$ Res-> свободный ();
#освобождаем память (аналогия mysql_free_result() )
?>
Ничего сложного нет. Если вы работали с другими базами кроме MySQL то вы возмутитесь, сказав, что у MySQL есть такой замечательный метод, как auto_increment, а в других базах это реализовано по другому. Попытаюсь объяснить, как НЕ ИСПОЛЬЗОВАТЬ сразу auto_increment с MySQL и потом не иметь головной боли при переходе на другую базу данных.
В PEAR есть замечательная штука Sequence, которая создает новую последовательность с помощью createSequence()
Что бы понять, я объясню проще: в базе данных создается таблица с одним полем, в котором и будет находиться увеличивающееся значение счетчика записей. Сразу скажу, что если вы удалите в середине запись, то последовательность не уменьшится! Т.е. если было 1,2,3,4,5 и вы удалите запись 3 то будет 1,2,4,5.., и следующая последовательность будет 6, а не 3! Последовательность нам необходима, когда мы делаем вставку в базу данных, т.е. INSERT
<? PHP
require_once ( "connect.php");
$ id = $ db-> nextID (w_, MyTable);
# Эта функция возвращает следующее число, находящееся в таблице последовательности.
# Если таблица последовательности еще не была создана, то она создается.
# В скобках пишется имя таблицы последовательности.
# для удобства используется имя таблицы данных и приставка w_ для того, что бы все
# таблицы последовательностей находились внизу при просмотре всей базы.
# Не забудь! При создании таблицы НЕ УКАЗЫВАТЬ auto_increment в MySQL или другие
# увеличения в других базах. PEAR сделает все сам.
$ sql = "INSERT INTO MyTable VALUES ('$ id', '$ name')";
$ res = $ db-> query ($ sql);
$ Res-> свободный ();
?>
Внимательный читатель сделает замечание: "А обработка ошибок?!" И будет прав. Мы здесь её нигде не делали. Для обработки ошибок будем использовать PEAR. Подробнее можно почитать здесь.
Мы же здесь используем обработчик ошибок (режим PEAR_ERROR_CALLBACK) на основании нашей функции, который будет вызываться и останавливать работу в любом месте программы, где вызвана ошибка, с подробным описанием. Для этого используем наш файл connect.php
<? PHP
define (DEBUG_ENV, правда);
#Устанавливаем именованную константу. И когда наш сайт готов и мы можем его
#использовать, устанавливаем в FALSE, что бы нашим пользователем не выдавался код и
#сообщение, а просто стандартное сообщение на ошибку.
функция handle_pear_error ($ error_obj)
{
если (DEBUG_ENV)
{
die ($ error_obj-> getMessage (). " ". $ error_obj-> getDebugInfo ());
}
еще
{
die("Ваш запрос не выполнен. Повторите попытку позже.");
}
}
# функция обработки ошибок
PEAR :: setErrorHandling (PEAR_ERROR_CALLBACK, 'handle_pear_error');
#Устанавливаем, что при возникновении ошибки использовать нашу функцию.
?>
Цель данной статьи - не конкуренция мануалу, а возможность дать понять, что иногда не стоит изобретать велосипед, а "стоит посмотреть под ноги" и увидеть, что многое есть уже готовое, да к тому же официально включено в PHP по умолчанию. А это много значит.
Во избежание путаницы полей (если встречаются поля с одинаковыми названиями) используйте в запросах оператор AS: "SELECT table1.id as id1, table2.id as id2". Это поможет избежать ошибок в запросе (например, если не указана таблица, а поле с таким названием есть в нескольких запрашиваемых таблицах, mysql выдаёт ошибку), а так же вы избежите недоразумений при работе с полученными данными (echo $row["id1"] писать гораздо проще, чем $row[$x]).
Данные типа DATE, TIME, DATETIME и TIMESTAMP можно форматировать с помощью функции date_format (см. руководство по mysql). Используйте его, и не форматируйте данные через php - это не просто "самодеятельность", а ещё и растрата системных ресурсов.
По возможности минимально используйте LEFT JOIN для объединения таблиц. Это весьма трудоёмкая операция для базы данных.
Там, где можно, используйте идентификаторы - выборка данных при указании ключевого поля происходит быстрее, чем при указании обычного.
Вместо "WHERE id=1 OR id=3 OR id=232" можно использовать встроенную функцию IN: "WHERE id IN (1,3,232)".
Если нужен текстовый поиск, осторожней со знаком "%". Во всяком случае, запросы типа somefield LIKE '%a%' лучше не делать - опять же слишком трудоёмкая операция. По крайней мере, надо фильтровать слова и отрезать те, которые короче 3 символов.
Используйте минимум необходимых полей в запросе. "SELECT * FROM sometable" выполняется медленнее, чем "SELECT id FROM sometable", тем более если в таблице много данных. Для подсчёта количества строк в таблице вообще (или подпадающих под некоторое условие) достаточно одного поля.
Разбивайте данные на страницы, используя оператор LIMIT. Это экономит время выполнения запроса и уменьшает объем страницы, которую получает пользователь.
Даже если вам не грозит "падение" от наплыва посетителей, лучше взять себе в привычку, чтобы потом не было проблем с адаптацией к новым задачам. Теперь о безопасности работы.
Старайтесь не допускать внесения в базу данных символа одинарной кавычки ("'"), поскольку это служебный символ запросов БД. Перед внесением в базу поле можно обработать функцией str_replace: $somefield = str_replace("'", "'", $somefield);
К тому же это лишний барьер на пути взломщиков вашего сайта. Пример "взлома" простой:
Если кавычку не обработать на входе, злоумышленник может в качестве логина сунуть строку "vasya_pupkin' OR login LIKE '%". В базу данных залетит запрос: mysql_query("UPDATE users SET password=PASSWORD('$passwd') WHERE login='vasya_pupkin' OR login LIKE '%'"); То есть все пароли будут одинаковые. Это только один пример. Итак,
Обрабатывайте данные, получаемые из адресной строки или из формы, и приводите их к нужному типу во избежание ошибок и "взломов" сайта. (ещё пример: если требуется идентификатор, то есть целое число, надо обработать его с помощью intval: $id = intval($id)).
2. Запросы на вставку строки (INSERT)
Поле идентификатора вставлять не нужно. На это есть свойство поля AUTO_INCREMENT.
Забавно читать, как в форуме пишут:
- Как мне быть с генератором случайных чисел?! неправильно работает!
- А зачем тебе?
- Да в базе id использовать...
В общем, не надо самодеятельности
Если в поле формата DATE, TIME, DATETIME или TIMESTAMP надо вставить текущее время, используйте встроенную в mysql функцию NOW: "INSERT INTO vote (ip, date) VALUES ($REMOTE_ADDR, NOW())"
Хранимые в базе пароли лучше прикрыть функцией php md5: "INSERT INTO user (login, pass) VALUES ('$login', ". md5($pass). ")" "SELECT * FROM user WHERE login='$login' AND pass=". md5($pass)
Советы, кажется, уже исчерпаны. Напоследок. С недавних пор я стал думать, что при написании скриптов, работающих с БД, надо ориентироваться не только на глупого и шаловливого посетителя, но и на криворукого администратора. Даже если мы внимательно будем следить за текстом, который вставляем в текстовое поле (одинарные кавычки не писать, делать их автозамену в Word-е, белое не носить), вероятность попадания служебных символов в запрос ненулевая.
3. Постраничный вывод
Регулярно в форуме задают один и тот же вопрос: как сделать постраничный вывод. И каждый раз человеку отвечают: "Легко! m строк, начиная с n-ной: Select запрос Limit $n,$m". На самом деле не так всё просто.
Я уже писал про синтаксис параметра LIMIT, однако, без толку. Для полноценного постраничного вывода строк из базы требуется большее. Требуется
Обработать номер страницы (в том числе проверить, не больше ли он общего количества страниц)
Нарисовать навигационную строку (чтобы не просто "вперед-назад", а с ссылками на несколько соседних страниц)
Тут-то и начинаются главные проблемы.
Недавно я работал над сайтом, в котором эти постраничные выводы в статистике были в каждом списке (а списков было много!). Тут-то и созрело решение, как свести все эти штучки к простому и единому решению. Получились четыре функции, которые я теперь использую везде, где нужен постраничный вывод данных, и не напрягаю попусту голову проблемой (как же я делал это там, как бы вынуть этот код оттуда?).
Первая функция — для внутреннего пользования двумя следующими. Берёт номер страницы, общее количество строк и количество строк на странице и выдаёт номер страницы, уже проверенный. Вторая берёт то же самое, проверяет номер страницы и выдаёт парамерт LIMIT либо полный (LIMIT n,m), либо краткий (LIMIT m), если это первая страница, либо ничего не выдаёт. Третья функция из тех же трёх параметров и адреса для ссылки делает навигационную строку. Ещё одна функция выдаёт число для нумерованного списка.
Этого достаточно для нормальной работы с постраничным выводом данных. Посмотрим, что получается в коде программы:
<?php // кол-во строк в страницах $in_page = 10; // получаем количество строк $amount = @mysql_result(mysql_query("SELECT count(id) as goods_total FROM goods"),0); // рисуем навигационную строку и пишем начало таблицы print("<div align=center>". <b>draw_bar($page, $amount, $in_page, "goods.php?page=")</b>. "</div>\n<table>"); // формируем запрос к базе $goods_result = mysql_query("SELECT id, name, description, price FROM goods ORDER BY name, price ". <b>get_limit($page, $amount, $in_page)</b>); // получаем номер для нумерованного списка $count = <b>get_count_limit($page, $amount, $in_page)</b>; // выводим строки while ($good_row = mysql_fetch_array($goods_result)) { $count++; print ("<tr");
// фон каждой второй строки — серым цветом if ($count/2==intval($count/2)) print (" bgcolor=#e1e1e1"); print ("><td align=right>$count.</td><td>${good_row[name]} <br>${good_row[description]}</td><td align=right>${good_row[price]}</td></tr>\n"); }; // конец таблицы и нижняя навигационная строка print("</table><div align=center>". <b>draw_bar($page, $amount, $in_page, "goods.php?page=")</b>. "</div>\n");
Это ВСЁ, что нужно для постраничного вывода! Больше напрягаться не надо!
Одно только пояснение — в качестве параметра функции draw_bar указывается адрес этого скрипта со всеми параметрами так, чтобы он туда только дописывал номер страницы. Если сложная выборка, надо будет ручками формировать этот адрес (всё-таки упрощение жизни вышло относительное: упрощаем одно — усложняем другое).
Навигационная панель сделана в виде номеров страниц (" 1 | 2 | 3 "). Но привести к виду "0-10 | 11-20 | 21-30" не проблема.
4. Функции mySQL обработки данных
Должен признаться, много времени на изучение руководства по MySQL я не уделял, а брал его только когда было совсем необходимо что-то узнать. Как выяснилось, я многое потерял — вещи, над которыми иногда задумывался, оказывается, уже в базах данных MySQL реализованы.
Посмотрев в перечень математических функций, я несколько переделал подсчёт данных в своей игре. Данные вынимались из базы запросом, проводились вычисления, затем возвращались обратно. Теперь количество запросов для этой операции сведено к одному — отправляется сразу UPDATE-запрос, внутри которого указываются все вычисления и сопутствующие данные.
Пока что расскажу про функции (не все, конечно), которые нужно знать каждому, кто много работает с базой.
Функции условий
IFNULL(x,y) — если x не NULL, тогда выдаёт x, иначе — y.
NULLIF(x,y) — если x и y равны, выдаёт NULL, если не равны — x.
IF(x,y,z) — если x = true (вернее, если x не равен 0 и не NULL), выдаёт y, если нет — z.
К примеру, в форуме хранится информация о пользователях и есть возможность не показывать другим пользователям свой Email. Делается поле show_email, в котором лежит 0, если пользователь не хочет показывать адрес, и 1, если разрешает.
SELECT ..., IF (show_email,CONCAT('<a href=mailto:',email,'>написать письмо</a>'),'адрес не указан') AS email, ...
Математические функции
MOD(N,M) или "%" — остаток от деления N на M.
FLOOR и CEILING — округление до целого вниз и вверх.
ROUND — округление до целого или до определенной десятичной дроби.
LEAST (X,Y,...) и GREATEST(X,Y,...) — минимальное и максимальное числа из указанных.
Не упоминаю стандартные математические функции взятия модуля, знака, работы с углами.
Строковые функции
CONV(N,система_из,система_в) — конвертация числа из одной системы исчисления в другую: select CONV("ff",16,10); => 255. Кстати, конвертировать можно не только в стандартных системах (2,8,10,16), но и в любых других от 2 до 36 — насколько хватает букв латинского алфавита.
CONCAT(X,Y,...) — объединение строк и чисел в одну строку (пример приведён выше).
CONCAT_WS(разделитель,X,Y,...) — аналог функции implode.
TRIM() — удаление лишних символов из начала и конца строки. В отличие от функции php trim позволяет не только пробелы, а любые символы и даже комбинации символов.
REPLACE (строка, X, Y) — заменяет в строке X на Y (не перепутайте порядок с порядком параметров в str_replace).
Дата и время
Функций много, отмечу только некоторые самые важные: неправда, что MySQL считает дни недели только с воскресенья, как принято в Америке. Нужно использовать не функцию DAYOFWEEK, а WEEKDAY, тогда понедельнику будет соответствовать номер 0, вторнику — 1, воскресенью — 6.
Для сложного форматирования даты (например, для вывода даты в виде 18.08.01), есть функции DATE_FORMAT (для даты и времени) и TIME_FORMAT (только для времени). Работа с этими функциями удобнее, чем использование своих собственных (потому что это средство стандартное и универсальное, чего в самопальном приспособлении добиться очень сложно), а так же быстрее (используются встроенные функции mysql-сервера, которые уже сидят в памяти, вместо компиляции при каждом запуске скрипта собственного кода).
Юниксовский timestamp MySQL тоже поддерживает — переводы в него и из него через функции UNIX_TIMESTAMP и FROM_UNIXTIME:
FROM_UNIXTIME(дата [, формат]) — выдаёт дату в обычном формате (во втором аргументе может быть указан формат по правилам как в DATE_FORMAT).
Кроме того, основные параметры даты — число, день недели и месяц (возможно и словом), год, квартал (!), неделя и многое другое доступно не только через общую функцию DATE_FORMAT, но и через отдельные специальные функции.
Остальные функции
LAST_INSERT_ID() — как и mysql_insert_id(), выдаёт последний идентификатор, который сгенерировала база данных по запросу с данного соединения.
MD5(строка) — поскольку говорят, что зашифрованный функцией PASSWORD() пароль легко расшифровать, я храню хэш md5 от пароля.
FORMAT(X, D) — форматировать число X в виде "#,###,###.##", округлённое до D знаков после запятой. Подумал, что неплохо бы в моей игре сделать форматированные для удобного чтения числа, глянул в руководство, вот оно. Всё уже написано.
Ещё две функции, про которые я забыл, когда писал про оптимизацию работы логов.