пʼятниця, 8 квітня 2016 р.

Сортировка MYSQL, нулевые значения - в конец.

Итак, мы решили хранить в базе данных некоторые сущности, для которых хотим вручную задавать очередность сортировки при выборке. Ну, например, список меню.
Структура БД (я сознательно перепутал все, для наглядности.):
menuID  |  menuName  |  menuTitle  | menuArrange
    1     |   menu-3   |   Третий    |     30
    2     |   menu-2   |   Второй    |     20
    3     |   menu-1   |   Первый    |     10
    4     |   menu-4   |  Четвертый  |     40
Если во всех записях `menuArrange` заполнено, то всё замечательно, делаем запрос и получаем предсказуемый порядок:
SELECT menuArrange, menuTitle 
FROM table_menu 
ORDER BY menuArrange
/*
 * 10 | Первый
 * 20 | Второй
 * 30 | Третий
 * 40 | Четвертый
 */
Но зачем же заполнять вручную `menuArrange`? В большинстве случаев список будет заполняться сразу в нужном порядке, а вручную очередность указать нужно будет лишь иногда:
menuID  |  menuName  |  menuTitle  | menuArrange
    1     |   menu-1   |   Первый    |    
    2     |   menu-2   |   Второй    |    
    3     |   menu-3   |   Третий    |    
    4     |   menu-4   |  Четвертый  |    
 И тут мы решили добавить два пункта в самое начало:
    5     |   menu-0   |   Нулевой   |     20
    6     |   menu--1  |   Минус 1й  |     10
И вот тут уже получается что для того, чтобы соблюсти правильную последовательность, нужно будет указать menuArrange для всех пунктов меню, иначе первые 4 будут болтаться в начале. Или же сделать так, чтобы все меню с пустым `menuArrange` прыгнули в самый конец списка. Этот вариант мне нравится намного больше:
SELECT menuArrange, menuTitle 
FROM table_menu 
ORDER BY 
  CASE WHEN menuArrange IS NULL THEN 1 ELSE 0 END,
  menuArrange
/*
 * 10 | Минус 1й
 * 20 | Нулевой
 *    | Первый
 *    | Второй
 *    | Третий
 *    | Четвертый
 */

середа, 6 квітня 2016 р.

Сравнение ассоциативных массивов по нескольким ключам

Ну, раз решил что блог будет про говнокод, тогда начнём.
Допустим ситуацию. Есть два вложенных ассоциативных массива данных. Нужно узнать, есть ли у них "похожие" записи, учитывая при сравнении похожести значения только некоторые из ключей. Я проиллюстрирую:
// при сравнении обращаем внимание только на ключи массива `param1` и `param2`
// то есть $data[0] похожа по значимым ключам $data_src[2]
// а так же $data[1] похожа $data_src[3]
$data_src = array(
  0 => array('id' => 1, 'param1' => 0, 'param2' => 0, 'value' => 0.01),
  1 => array('id' => 2, 'param1' => 1, 'param2' => 0, 'value' => 0.02),
  2 => array('id' => 3, 'param1' => 0, 'param2' => 1, 'value' => 0.03),
  3 => array('id' => 4, 'param1' => 1, 'param2' => 1, 'value' => 0.04) 
);
$data = array(
  0 => array('param1' => 0, 'param2' => 1, 'value' => 0.11),
  1 => array('param1' => 1, 'param2' => 1, 'value' => 0.12)
);
В моём случае я столкнулся с необходимостью такого анализа, когда писал парсер (распознавалку) файлов с прайс-листами. Там цена зависела сразу от нескольких параметров и мне нужно было запросить все цены прайса, уже существующие в базе данных и сравнить их с новыми ценами. Потом по результатам разобраться, какие вставлять, какие обновлять, какие даже удалять (если они существовали в БД, а в импортируемом файле цен с таким набором параметров уже не было).

Самым очевидным (для меня) было "проиндексировать" значения этих значимых ключей, и потом искать совпадение по этой индексированной строке:
// индексируем исходный массив
$index_src = array();
foreach ($data_src as $i => $row) {
  $index_src[$i] = serialize(array(
    'param1' => $row['param1'],
    'param2' => $row['param2']
  ));
}
$index_src = array_flip($index_src);

// ищем совпадения
$similar = array();
foreach ($data as $i => $row) {
  $str = serialize(array(
    'param1' => $row['param1'],
    'param2' => $row['param2']
  ));
  if (array_key_exists($str, $index_src)) {
    $similar[$i] = $index_src[$str];
  }
}
// получаем массив с индексами похожих массивов 
// $similar = array(
//    0 => 2,
//    1 => 3
// );
Цель достигнута конечно, но меня не отпускает ощущение, что это не самое оптимальное решение. Хотя не спорю, по рукам нужно бить линейкой за поиск оптимального решения в задаче, выполняемой в лучшем случае раз в месяц.