воскресенье, 20 сентября 2020 г.

Размеры таблиц и индексов в БД PostgreSQL

Сегодня рассмотрим несколько команд для определения размеров различных сущностей в PostgreSQL.

Перед тем как начать - рассмотрим вспомогательную функцию pg_size_pretty(), которая:

Преобразует размер в байтах, представленный в 64-битном целом, в понятный человеку формат с единицами измерения

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

select pg_size_pretty(100250408::bigint);
-- Вывод:
pg_size_pretty
text
--------------
96 MB

Теперь, вооружившись этой функцией, переходим к основным функциям.

Первое и самое нужное - размер таблицы PostgreSQL без учета индексов и прочих деталей. Для этого есть функция pg_relation_size(), принимающая в качестве аргумента название таблицы. Комбинируем ее с pg_size_pretty() и получаем:

select pg_size_pretty(pg_relation_size('my_table'));
-- Вывод (небольшая тестовая таблица в моей БД):
pg_size_pretty
text
--------------
48 kB

Далее - определим сколько места занимают все индексы для таблицы. Для этого есть функция pg_indexes_size(), также принимающая в качестве аргумента название таблицы:

select pg_size_pretty(pg_indexes_size('my_table'));
-- Вывод (опять же для некоей тестовой таблицы):
pg_size_pretty
text
--------------
88 kB

Функция pg_total_relation_size() определяет сколько места суммарно занимает таблица, ее индексы и данные TOAST, так что ее результат будет отличаться от суммы результатов двух предыдущих запросов.

Ну и чтобы не складывать на калькуляторе размеры всех таблиц и индексов - размер базы данных PostgreSQL определим с помощью pg_database_size():

select pg_size_pretty(pg_indexes_size('my_table'));
-- Вывод (опять же для некоей тестовой базы данных):
pg_size_pretty
text
--------------
8137 kB

Больше информации про описанные функции - здесь: https://postgrespro.ru/docs/postgrespro/12/functions-admin#FUNCTIONS-ADMIN-DBOBJECT

понедельник, 1 июня 2020 г.

Неочевидное поведение функций конвертации даты

Продолжаем изучать неочевидное поведение функций php.

Рассмотрим такой код:

$var = strtotime(date('d.m.Y', time()));

Кажется, что здесь выполняется лишняя работа. Сначала мы берем таймштамп, возвращаемый функцией time(), и на его основе получаем строку времени определенного формата. Далее мы преобразуем полученную строку времени обратно в таймштамп. И логично, что таймштампы должны совпадать. Но нет.

Неочевидность в том, что строка времени в формате "d.m.Y" преобразуется в таймштамп начала дня, то есть "d.m.Y" аналогичен "d.m.Y 00:00:00".

Добавляем немного вывода и видим:

$ts = time();

$var = strtotime(date('d.m.Y', $ts));

var_dump(date('d.m.Y H:i:s', $ts));    // string(19) "01.06.2020 22:36:09"
var_dump(date('d.m.Y H:i:s', $var));   // string(19) "01.06.2020 00:00:00"

$varDayStart = strtotime(date('d.m.Y 00:00:00', $ts));
var_dump($var === $varDayStart);    // bool(true)


Таким образом, данный код можно считать одним из способов получения таймптампа начала текущего дня.

пятница, 10 апреля 2020 г.

Уникальные или неуникальные значения в массиве

В качестве разминки сегодня решим следующую задачу - предположим, есть массив, некоторые значения в нем повторяются. Требуется получить массив неуникальных (повторяющихся) значений или массив не повторяющихся (уникальных) значений.

Сразу к примеру:

$array = [1, 2, 3, 4, 3, 4, 5, 6, 6];

// Для любого из вариантов задачи нам потребуется знать
// сколько раз в массиве встречается каждое значение
$freqs = array_count_values($array);

// Массив уникальных значений - [1, 2, 5]
$unique = array_keys(
    array_filter(
        $freqs,
        function ($freq) { return 1 === $freq; }
    )
);

// Массив неуникальных значений - [3, 4, 6]
$nonunique = array_keys(
    array_filter(
        $freqs,
        function ($freq) { return 1 < $freq; }
    )
);

// Фильтрация исходного массива с оставлением только повторяющихся значений -[3, 4, 3, 4, 6, 6]
$allNonunique = array_filter(
    $array,
    function ($v) use ($freqs) { return 1 < $freqs[$v]; }
);


В общем-то всё, на досуге можете расширить этот код и задать количество появлений в виде переменной, а не фиксированного значения 1.

понедельник, 6 апреля 2020 г.

Неявное поведение DateTime::createFromFormat

Данный пост есть результат недавнего обсуждения в одном из php-каналов некоторого странного (как кажется изначально) поведения функции DateTime::createFromFormat.

Рассмотрим простейший код, надо отметить, что исследуемое поведение отмечается только 31-го числа каждого месяца (ну и плюс 29-30 для февраля):

$months = [1, 2, 3, 4];

foreach ($months as $month) {
    $dt = '2019-' . $month;
    echo $dt . ': ' . (\DateTime::createFromFormat('Y-m', $dt))->format('Y-m-d') . PHP_EOL;
}


Обратите внимание, что при создании объекта не указывается день. Так как день не указан, то разумно предположить (это же подтверждается в комментариях), что php берет в качестве дня текущий день запуска скрипта из системных настроек.

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

// ожидаемо, в январе есть 31 число
2019-1: 31.01.2019
// в феврале-2019 нет 29 (и 30 и 31) числа, потому 31-му февраля
// соответствует третье марта (а 29-му февраля - первое марта)
2019-2: 03.03.2019
// ожидаемо, в марте есть 31 число
2019-3: 31.03.2019
// в апреле нет 31 числа, и следующим после 30 апреля идет 1 мая
2019-4: 01.05.2019


Как видим, происходит не то, что ожидается, хотя в другие дни - всё работает нормально, и даже имеющиеся тесты будут проходить.

Можно, конечно, рассуждать о том, что раз день не указан, то может надо кидать эксепшен при создании объекта, но так как такого поведения нет, то придерживаемся мудрого принципа "Явное лучше, чем неявное" и повнимательней пишем свой код.

Всем здоровья)

воскресенье, 8 марта 2020 г.

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

В предыдущих частях (часть 1, часть 2) мы рассмотрели как к строковому типу приводятся скалярные типы данных и null. Сейчас перейдем к таким типам данных как массив, объект и ресурс.

Массив, сразу смотрим пример:

$var = [1,2,3];
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Notice: Array to string conversion in file on line ..
Notice: Array to string conversion in file on line ..
Notice: Array to string conversion in file on line ..
Array
Эту строку я конкатенирую с переменной Array
В эту строку я подставляю переменную Array


Итак, строковое представление массива это просто слово Array. И так как это не ожидаемая операция над массивом, то выводится еще и Notice. Таким образом, просто взять и вывести массив на экран не получится. Можно обойти массив с помощью цикла и вывести элементы в нужном формате. Естественно, если элементы тоже массивы - их также требуется обойти циклом. И так далее. В случае если массив одномерный - можно воспользоваться функцией implode. Также, если у вас при вставке в БД в поле появляется слово Array - вы поняли, в чем ошибка.

Объект. С объектами ситуация следующая: не всякий объект можно вывести на экран. Например, возьмем такой класс и попытаемся вывести на экран объект данного класса:

class myStdClass
{
    public $intField;
    public $strField;
}

$var = new myStdClass();
$var->intField = 42;
$var->strField = 'Forty two';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Recoverable fatal error: Object of class stdClass could not be converted to string in ... on line ...


Получаем фатальную ошибку, так как php не представляет как привести данный объект к строковому представлению. Но ситуация поправима. Для приведения к объекта к строке требуется определить в классе "магический" метод __toString. Метод, естественно, должен вернуть некую строку, которая и будет строковым представлением объекта.
Модифицируем класс из предыдущего примера:

class myStdClass
{
    public $intField;
    public $strField;

    public function __toString(): string
    {
        return 'intField: ' . $this->intField . '; strField: ' . $this->strField;
    }
}

$var = new myStdClass();
$var->intField = 42;
$var->strField = 'Forty two';
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
intField: 42; strField: Forty two
Эту строку я конкатенирую с переменной intField: 42; strField: Forty two
В эту строку я подставляю переменную intField: 42; strField: Forty two


Как видим, теперь наш объект прекрасно приводится к строковому представлению.

Я не знаю ни одного случая, когда требуется строковое представление ресурса. Но для полноты картины посмотрим на следующий код:

$var = fopen('/tmp/1.tmp', 'a');
$strConcat = 'Эту строку я конкатенирую с переменной ' . $var;
$strEval = "В эту строку я подставляю переменную $var";
echo $var;
echo PHP_EOL;
echo $strConcat;
echo PHP_EOL;
echo $strEval;
// вывод:
Resource id #5
Эту строку я конкатенирую с переменной Resource id #5
В эту строку я подставляю переменную Resource id #5


Как видим, толку в этом выводе мало, только убедиться что в переменной хранится ресурс. Надеяться на то, что ИД ресурса (в данном выводе - 5) не изменится при следующем запуске, также не стоит.

На этом я завершаю цикл статей по преобразованию типов данных в строки, всем спасибо за внимание.

воскресенье, 23 февраля 2020 г.

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

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

Рассмотрим тип данных int. Переменная типа int может быть задана в четырех системах счисления: десятичной, восьмеричной, шестнадцатеричной и двоичной. Независимо от системы счисления, вывод будет преобразован к десятичной системе счисления:

$a = 0;
$b = 42;
$c = 0b101011;   // 43 в десятичной
$d = 054;        // 44 в десятичной
$e = 0x2D;       // 45 в десятичной
$f = -42;

echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
echo $d . PHP_EOL;
echo $e . PHP_EOL;
echo $f . PHP_EOL;
// вывод:
0
42
43
44
45
-42


Числа, превышающие размер типа int (PHP_INT_MAX / PHP_INT_MIN), автоматически конвертируются в тип float и выводятся соответствующе:

$a = PHP_INT_MAX;
$b = 2 + PHP_INT_MAX;
$c = PHP_INT_MIN;
$d = PHP_INT_MIN -1;

echo $a . PHP_EOL;
echo $b . PHP_EOL;
echo $c . PHP_EOL;
echo $d . PHP_EOL;
// вывод:
9223372036854775807
9.2233720368548E+18
-9223372036854775808
-9.2233720368548E+18


Для того чтобы вывести int переменную не в десятичной, а другой системе счисления, нужно воспользоваться функциями форматирования, например, sprintf/printf:

$a = 42;

echo sprintf("%o", $a) . PHP_EOL;  // восьмеричная
echo sprintf("%x", $a) . PHP_EOL;  // шестнадцатиричная, буквы в нижнем регистре
echo sprintf("%X", $a) . PHP_EOL;  // шестнадцатиричная, буквы в верхнем регистре
echo sprintf("%b", $a) . PHP_EOL;  // двоичная
// вывод:
52
2a
2A
101010


Переходим к выводу переменных типа float.

$floatNums = [
    // Первый способ записи float-чисел
    4.2,
    4.2222,
    4.22222222,
    0.2,
    0.0002,
    0.00002,
    0.000002,
    // Второй способ записи float-чисел
    1.2e4,
    // Третий способ записи float-чисел
    1.2E4,
    5E-3,
    5E-4,
    5E-5,
    // int значения превышающие размер типа int конвертируются во float
    PHP_INT_MAX + 20,
];

foreach ($floatNums as $num) {
    echo $num . PHP_EOL;
}
// вывод:
4.2
4.2222
4.22222222
0.2
0.0002
2.0E-5    //числа с пятью и более знаками после запятой выводятся в нотации с основанием и мантиссой
2.0E-6
12000
12000
0.005
0.0005
5.0E-5
9.2233720368548E+18


Как видим - числа выводятся с тем же количеством знаков после запятой, что были указаны при их объявлении. Для указания нужного числа знаков после запятой - пользуйтесь функциями форматирования, например, теми же sprintf/printf или number_format:

$floatNum = 4.23456789;
echo sprintf('%f', $floatNum) . PHP_EOL;
echo sprintf('%.2f', $floatNum) . PHP_EOL;
echo sprintf('%.5f', $floatNum) . PHP_EOL;
echo sprintf('%.12f', $floatNum) . PHP_EOL;

echo number_format($floatNum) . PHP_EOL;
echo number_format($floatNum, 2) . PHP_EOL;
echo number_format($floatNum, 5) . PHP_EOL;
echo number_format($floatNum, 6) . PHP_EOL;

// вывод
4.234568   // по умолчанию выводится 6 знаков после запятой
4.23
4.23457    // можно подумать что это округление, но нет, такой вывод связан с представлением числа в памяти программы
4.234567890000     // оставшиеся позиции заменяются нулями
4
4.23
4.23457    // аналогично, это НЕ округление
4.234568


Оставшиеся типы данных - массив, объект и ресурс, рассмотрим в заключительном посте.