Отсортировать массив можно разными способами; можно и с помощью базы данных.
Обычно программисты представляют себе базу данных лишь как хранилище. Код что-то смысловое записывает, код что-то (потом) читает. На самом деле хорошая база данных - это нечто гораздо большее, это мощный сервер, специально предназначенный и оптимизированный для работы с данными. Противоположный программистскому "ультрабазистский" подход состоит в том, что вся работа с данными делается в базе, а код занимается лишь созданием интерфейса. Oracle APEX льёт дополнительную воду на эту мельницу, поскольку интерфейс тоже создаётся в базе, уменьшая поле для традиционного программирования до области специальных задач. Истина, вероятно, где-то посередине. Вот пример, возможно, несколько необычного использования связки PHP/Oracle.
В пространстве PHP возникла задача отсортировать массив вида
$data[$id] = array(file => $file, type => $type);
по полю type.
В PHP для этого есть много способов, но мы всё равно постоянно шныряем в Oracle, а сортировки в базе данных - это сплошь и рядом. Мне было ловчее обратиться к базе.
Соединение с базой у нас описывается DB::$conn_c - воспримем это как данность и продолжим.
Для начала выполним commit на этом соединении с базой. Окончательно причина этого будет понятна чуть позже, сейчас же кратко отметим: у нас могли быть до этой сортировки какие-то модификации данных (DML), вероятно, они выполнялись в рамках политики commit on success, на всякий случай подтвердим это. Если ситуация иная и у нас есть осознанно подвешенные транзакции, то об этом следует подумать особо. Пока же сделаем
oci_commit(DB::$conn_c);
Далее запишем SQL выражение для занесения массива в служебную таблицу (вот здесь серьёзное различие в мышлении программиста и базиста: у программиста все таблицы - это реально ценные данные для хранения, базист без колебаний создаст таблицу для обмена, прокачки, иных служебных целей) и передадим его на разбор в Oracle:
$sql = "
insert into aux (act_file, act_type) select :f, :t from dual where :t is not null
";
$stmt = oci_parse(DB::$conn_c, $sql);
Далее пройдём циклом по всему подлежащему сортировке массиву и "загоним" его в служебную таблицу базы:
foreach($data as $row)
{
oci_bind_by_name($stmt, ":f", $row['file']);
oci_bind_by_name($stmt, ":t", $row['type']);
oci_execute($stmt, OCI_NO_AUTO_COMMIT);
}
Обратим внимание на два обстоятельства:
- Мы вынесли парсинг SQL запроса за пределы цикла;
- Мы запретили библиотеке OCI подтверждать транзакцию в соответствии с её политикой по умолчанию commit on success.
Первое мы сделали для экономии времени исполнения на повторном мягком разборе, второе мы обсудим в конце заметки.
Выполним формальные действия по освобождению курсора и очистке (модное слово: обнуление) исходного массива:
oci_free_statement($stmt);
unset($data);
$data = array();
Затем запишем смысловое сортирующее SQL выражение, отправим его в Oracle и выполним там:
$sql = "
select row_number() over (order by act_type) as id, act_file, act_type
from aux
order by id
";
$stmt = oci_parse(DB::$conn_c, $sql);
oci_execute($stmt, OCI_NO_AUTO_COMMIT);
Обратим внимание, что мы записали инструкцию отказа от commit'а для запроса (!). Это может показаться полной дичью для "ораклоида", но это правильно для PHP и библиотеки OCI. Дело в том, что OCI подтверждает ранее открытую транзакцию при каждом вызове функции oci_execute(), и совершенно не беспокоится, была ли это модификация данных (DML, предмет транзакции) или был ли это просто запрос (не предмет транзакции в смысле управления ей). Поэтому, если мы хотим, чтоб транзакция "висела", это надо подтверждать при каждом исполнении SQL выражения до явного commit'а или rollback'а. Как раз второе - наш случай, но об этом в конце заметки.
Далее выполним стандартные действия по извлечению результатов SQL запроса в (обнулённый) массив:
while(oci_fetch($stmt))
{
$id = oci_result($stmt, 1);
$file = oci_result($stmt, 2);
$type = oci_result($stmt, 3);
$data[$id] = array(file => $file, type => $type);
}
oci_free_statement($stmt);
Всё, наш массив отсортирован. Причём очень жёстко и надёжно.
А теперь выполним самое интересное - откатим транзакцию в Oracle:
oci_rollback(DB::$conn_c);
Действительно, мы использовали таблицу aux для крайне краткосрочного хранения данных - только для их сортировки. Мы могли бы использовать для их удаления перед следующим актом сортировки стандартную команду удаления delete. Могли бы, помня о понятии high water mark, использовать команду обновления таблицы truncate table. Но в первом случае нам надо было бы либо удалять из служебной таблицы все данные, либо как-то указать, какие данные относятся к нашему сеансу. Ведь возможно такое, что более одного пользователя одновременно выполняют описанные действия. Второй случай - это вообще полный разнос таблицы с последующим созданием. А вот откат транзакции - это ровно то, что нам нужно. Ведь Oracle для каждой модифицирующей данные сессии выполняет эту модификацию на отдельной копии данных; пока транзакция не подтверждена, другие сессии ничего не видят; если же транзакция откачена, то другие сессии вообще никогда ничего не увидят. Oracle автоматически предоставляет нам возможность проводить "частные вечеринки" на данных.
Вот такой, возможно не совсем обычный, подход к стандартной задаче.