Я использовал следующий необработанный запрос в своем проекте Laravel.
$statisticsInRangeDate = $myModel->selectRaw(
"floor(datediff(created_at, '{$dateInfo['currentRangeDate']}')".
" / {$dateInfo['daysRange']}) * {$dateInfo['daysRange']} AS diff_days_range, count(*) as total_clicks, ".
'min(created_at) ,max(created_at)'
)
->groupByRaw('diff_days_range')
->orderByRaw('diff_days_range DESC')
->get();
Необработанный запрос:
select
floor(datediff(created_at, '2023-04-04') / 3) * 3 AS diff_days_range,
count(*) as total_install,
min(created_at),
max(created_at)
from sdk_clicks
where sdk_clicks.application_id = 1
and sdk_clicks.application_id is not null
and created_at >= '2023-04-01'
and created_at < '2023-04-07'
and sdk_clicks.deleted_at is null
group by diff_days_range
order by diff_days_range
Базой данных проекта является mysql, и я получаю необходимый вывод, но я получаю следующую ошибку при запуске тестов phpunit, которые используют базу данных памяти sqlite для скорости.
Illuminate\Database\QueryException: SQLSTATE[HY000]: Общая ошибка: 1 нет такой функции: datediff (SQL: выберите пол...
Я знаю, что эта функция не существует в sqlite и julianday('now') - julianday(DateCreated) следует использовать для разницы во времени, но я ищу лучшее решение для автоматического решения этой проблемы путем определения типа базы данных и использования соответствующей функции.
Примечание. Из-за необходимости высокоскоростного теста я хочу использовать память и базу данных sqlite.
Вы также можете рассмотреть возможность использования MySQL, но в своей тестовой среде определите таблицы с помощью механизма хранения MEMORY.
Если вам нужно протестировать код, который в значительной степени зависит от неосновного материала sql, вам будет лучше создать отдельную базу данных mysql только для целей тестирования. Это будет медленнее, чем база данных sqlite, но вы будете уверены, что запросы ведут себя одинаково. Использование механизма хранения в памяти может смягчить это, но, по моему опыту, это не всегда работает должным образом.






PHP предоставляет возможность создавать пользовательские функции в sqlite. Это делается с помощью функции PDO::sqliteCreateFunction().
По сути, вы определяете функцию внутри sqlite, которая будет вызывать предоставленный вами PHP-код.
Ваш код будет выглядеть примерно так:
$pdo = DB::connection()->getPdo();
// Only do this if using sqlite.
if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) != 'sqlite') {
return;
}
$pdo->sqliteCreateFunction(
'datediff', // name of the sqlite function to create
function ($first, $second) {
$dateFirst = Carbon\Carbon::parse($first)->startOfDay();
$dateSecond = Carbon\Carbon::parse($second)->startOfDay();
return $dateSecond->diffInDays($dateFirst, false);
}, // PHP callback function implementation
2 // number of parameters the sqlite function takes
);
Добавьте это где-нибудь в свой набор тестов перед выполнением запроса, и все будет хорошо.
Это правда, но идея автора состоит в том, чтобы быть быстрым, создание собственного метода и использование PHP в середине побеждает цель тестов «производительности» / «быстрых» (БД против PHP), так что это решение, если пользователь не хочет использовать MySQL и по-прежнему хочет продолжить работу с SQLite. В этом случае я думаю, что это сумасшествие, добавляя этот материал, чтобы «тесты выполнялись быстро с SQLite», вы как бы разрабатываете метод для БД, что никому не нужно, но тем не менее, это правильный ответ для любой сумасшедший, который хочет по-прежнему следовать этому пути (не используя настройку производства, а для локальных тестов)
@matiaslauriti, база данных sqlite в памяти с UDF по-прежнему быстрее, чем база данных mysql, поэтому нет, она не противоречит цели OP - быстрому набору тестов. Кроме того, любое снижение производительности будет происходить только в тех тестах, которые запускают запрос с UDF. Остальная часть набора тестов не пострадает; в отличие от преобразования всего набора тестов для использования более медленной базы данных mysql.
Учитывая возможность использования макроса в laravel, это позволяет мне добавлять новые функции в Illuminate\Database\Eloquent\Builder.
В моем AppServiceProvider.php я создаю макрос rangeDateDiff() :
Builder::macro('rangeDateDiff',
function ($firstDate, $secondDate, $daysRange = 1, string $as = 'diff_days_range') {
if ($this->getConnection()->getDriverName() == 'sqlite') {
$this->addSelect(\DB::raw(
"floor (cast (julianday(strftime('%Y-%m-%d', {$firstDate})) - julianday(strftime('%Y-%m-%d', {$secondDate})) As Integer)"
. " / {$daysRange}) * {$daysRange} AS {$as}")
);
} else {
$this->addSelect(\DB::raw(
"floor(datediff({$firstDate}, {$secondDate})"
. " / {$daysRange}) * {$daysRange} AS {$as}")
);
}
return $this;
});
затем используйте эту новую функцию глобально в модели Eloquent:
$sdkClicks = $this->application->sdkClicks()
->where('created_at', '>=', $dateInfo['previousRangeDate'])
->where('created_at', '<', $dateInfo['toDate']->format('Y-m-d'));
$sdkClicks->rangeDateDiff('created_at', "'{$dateInfo['currentRangeDate']}'", $dateInfo['daysRange'])
->groupByRaw('diff_days_range')
->orderByRaw('diff_days_range DESC')->get();
Преимущество этого метода в том, что он более чистый, и, что наиболее важно, если в проекте необходимо использовать другой тип базы данных, код более удобен в сопровождении.
Вы можете принять свой ответ, чтобы будущие посетители сразу увидели, что проблема решена.
Спасибо @LajosArpad за комментарий и голосование, я ждал, когда другие люди проголосуют за мой ответ и подтвердят ответ с большей уверенностью.
Я скажу, что ответ довольно очевиден ... вы не можете использовать SQLite или просто не использовать RAW sql, а использовать фасад БД или модель и позволить этому УПРАВЛЯТЬ запросом по назначению ... Старайтесь избегать необработанного sql как сколько можно...