суббота, 6 июля 2019 г.

Перенос данных нескольких полей в единое поле hstore

Сегодня опишу вам как решить следующую сверхспецифическую задачу: у нас в БД PostgreSQL есть таблица некоторой структуры:

id | name | attr_1 |  attr_2 |  attr_3 |
---------------------------------------|
 1 | NAME |     v1 |      v2 |      v3 |


Вследствие каких-то причин в проекте решено соединить данные полей attr_1, attr_2, attr_3 в одно поле attrs типа hstore.

Таким образом, новая таблица и запись в ней выглядит так:

id | name |                                        attrs |
---------------------------------------------------------|
 1 | NAME |     attr_1 => v1, attr_2 => v2, attr_3 => v3 |


Естественно, будем все максимально автоматизировать. Для работы нам пригодятся некоторые функции для работы с hstore и немного php, просто чтобы сформировать общий текст запроса. Общий текст запроса выглядит вот так:

UPDATE tableName
SET attrs =
    hstore(
        string_to_array(
            rtrim(
                (CASE WHEN (attr_1 IS NOT NULL) THEN ('attr_1' || '~~~' || attr_1 || '~~~') ELSE '' END)
                ||
                (CASE WHEN (attr_2 IS NOT NULL) THEN ('attr_2' || '~~~' || attr_2 || '~~~') ELSE '' END)
                ||
                (CASE WHEN (attr_3 IS NOT NULL) THEN ('attr_3' || '~~~' || attr_3 || '~~~') ELSE '' END),
                '~'
            ),
            '~~~'
        )
    )


Итак, что же здесь происходит? Начнем с внутренней части.

1. Для значения каждого из полей attr_1, attr_2, attr_3 мы создаем строку вида НазваниеПоля~~~ЗначениеПоля~~~ или просто пустую строку, если значение поля NULL. Все эти строки объединяем в одну результрующую.

2. Далее нам требуется избавиться от ~ в конце объединенной строки. В этом нам помогает rtrim.

3. Потом из объединенной строки мы создаем массив, разбивая строку по разделителю ~~~.

4. И, наконец, полученный массив передаем в метод hstore. Готово.

Отдельно замечу, что разделителем выбран ~~~ потому, что встретить его в значениях полей attr_1, attr_2, attr_3 невозможно. Если в ваших данных может встречаться такой набор символов - используйте другой разделитель из более "странных" символов.

С использованием php можно создать такой скрипт генерации и выполнения запроса:

$fields = [
    'attr_1',
    'attr_2',
    'attr_3',
    // еще поля
];
$glue = '~~~';
$selectPattern = "(CASE WHEN (%s IS NOT NULL) THEN ('%s' || '$glue' || %s || '$glue') ELSE '' END)";
$select = [];
foreach ($fields as $field) {
    $select[] = vsprintf($selectPattern, array_fill(0, 3, $field);
}
$select = implode(' || ', $select);

$this->runSql("
    UPDATE {$table}
    SET attrs = hstore(string_to_array(rtrim(({$select}), '~'), '{$glue}'))
");


По ссылке - улучшенный гист с кодом, обрабатывающим даже поле типа datetime.

среда, 27 марта 2019 г.

Трансформация DateTime в DateInterval

Недавно в вопросе на stackoverflow.com автор пытался добавить к объекту \DateTime некоторый временной интервал. Интервал, однако, также являлся объектом \DateTime. Так как метод add ожидает на вход объект \DateInterval, то ничего не работало. Возможно, есть другие решения этой проблемы, но мне пришло в голову такое:

$dateAppointment = (new \DateTime());
$dtDuration = (new \DateTime())->setTime(1, 15, 0);
// Для примера получаем интервал только с учетом часов, минут и секунд
$duration = $dtDuration->format('\P\TH\Hi\Ms\S');
$dateAppointment->add(new \DateInterval($duration));

воскресенье, 10 февраля 2019 г.

Строковые представления типов данных, часть 1

Всем привет. Сегодня будем разбираться в том, как php преобразует различные типы данных к строковому виду.

Приведение к строке используется в нескольких случаях. Главный из них - это вывод с помощью конструкции echo. Обращаю внимание, что функции var_dump и print_r также выводят данные, однако, руководствуются своими алгоритмами для вывода.

Второй случай, где работает приведение к строке это когда вы конкатенируете или подставляете в строку какую-либо переменную, например:

$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";


Начнем издалека. Для полноты картины я добавлю работу с переменными типа строка, но понятно что здесь нет никаких подводных камней)

$var = 'foo';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"foo"
"Эту строку я конкатенирую с переменной foo"
"В эту строку я подставляю переменную foo"


Перейдем с булевым переменным. Булевая переменная принимает одно из двух значений - true или false. Каждое из них приводится к разному строковому представлению:

$var = false;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка
"Эту строку я конкатенирую с переменной "
"В эту строку я подставляю переменную "


$var = true;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"1"
"Эту строку я конкатенирую с переменной 1"
"В эту строку я подставляю переменную 1"


Следующий, отдельный тип данных - null. Здесь все просто:

$var = null;
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo $strConcat;
echo $strEval;
// вывод:
"" // на самом деле здесь ничего не выводится, я просто отмечаю что это пустая строка
"Эту строку я конкатенирую с переменной "
"В эту строку я подставляю переменную "


Таким образом, если вы выводите переменную, а получаете пустую строку, то не факт, что в переменной хранится пустая строка. В переменной с таким же успехом может храниться false или она вообще может быть не определена (null). Поэтому следует использовать var_dump, чтобы увидеть реальное значение переменной.

В следующей части рассмотрим преобразование к строке числовых типов данных - int и float.

пятница, 11 января 2019 г.

Локальный .gitignore

При работе над проектом в своем уютненьком локальном репозитории иногда возникает необходимость игнорить какие-то файлы и папки, причем эти файлы/папки есть только у вас локально, и в ремоут-ориджине они никогда не появятся. Потому нет смысла изменять общий .gitignore проекта и дописывать туда эти вещи. Поэтому нужен какой-то аналог файла .gitignore, распространяющийся только на локальный репозиторий.

И такой файл есть. Это файл .git/info/exclude. В нем, в формате аналогичном .gitignore, можно указать все свои исключения и, тем самым, почистить вывод git status от лишних файлов. А так как папка .git не связана с удаленным репозиторием, то эта информация никуда не уедет. Единственно, не забудьте этот файл, если вдруг будете переезжать, например, на другой компьютер или разворачивать репозиторий заново где-нибудь по другому пути.

Больше про игноры в оф-доке.