MySQL的`datediff`函数在用于单元测试的SQLite中不可用。

huangapple go评论72阅读模式
英文:

MySQL datediff function not available in SQLite used for unit tests

问题

$statisticsInRangeDate = $myModel->selectRaw(
    "floor((julianday(created_at) - julianday('{$dateInfo['currentRangeDate']}')) / {$dateInfo['daysRange']}) * {$dateInfo['daysRange']} AS diff_days_range, count(*) as total_clicks, ".
    'min(created_at), max(created_at)'
)
    ->where('sdk_clicks.application_id', '=', 1)
    ->whereNotNull('sdk_clicks.application_id')
    ->where('created_at', '>=', '2023-04-01')
    ->where('created_at', '<', '2023-04-07')
    ->whereNull('sdk_clicks.deleted_at')
    ->groupByRaw('diff_days_range')
    ->orderByRaw('diff_days_range DESC')
    ->get();
英文:

I have used the following Raw query in my Laravel project.

$statisticsInRangeDate = $myModel-&gt;selectRaw(
    &quot;floor(datediff(created_at, &#39;{$dateInfo[&#39;currentRangeDate&#39;]}&#39;)&quot;.
    &quot; / {$dateInfo[&#39;daysRange&#39;]}) * {$dateInfo[&#39;daysRange&#39;]} AS diff_days_range, count(*) as total_clicks, &quot;.
    &#39;min(created_at) ,max(created_at)&#39;
)
    -&gt;groupByRaw(&#39;diff_days_range&#39;)
    -&gt;orderByRaw(&#39;diff_days_range DESC&#39;)
    -&gt;get();

The raw query is:

select
    floor(datediff(created_at, &#39;2023-04-04&#39;) / 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 &gt;= &#39;2023-04-01&#39;
    and created_at &lt; &#39;2023-04-07&#39;
    and sdk_clicks.deleted_at is null
group by diff_days_range
order by diff_days_range

The database of the project is mysql and I get the necessary output, but I get the following error when running the phpunit tests which use a sqlite memory database for speed.

> Illuminate\Database\QueryException : SQLSTATE[HY000]: General error: 1 no such function: datediff (SQL: select floor...

I have the knowledge that this function does not exist in sqlite and julianday(&#39;now&#39;) - julianday(DateCreated) should be used for the time difference, but I am looking for the best solution to solve this problem automatically by detecting the type of database and the appropriate function be used.


Note: Due to the need for high speed test, I want to use memory and sqlite database

答案1

得分: 3

Considering the possibility of using 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();

这种方法的优势在于它更清晰,而且最重要的是,如果项目需要使用另一种类型的数据库,代码更易维护

英文:

Considering the possibility of using Macro in laravel, it allows me to add new functions to Illuminate\Database\Eloquent\Builder.

On my AppServiceProvider.php I create the macro rangeDateDiff() :

Builder::macro(&#39;rangeDateDiff&#39;,
            function ($firstDate, $secondDate, $daysRange = 1, string $as = &#39;diff_days_range&#39;) {
            if ($this-&gt;getConnection()-&gt;getDriverName() == &#39;sqlite&#39;) {
                $this-&gt;addSelect(\DB::raw(
                    &quot;floor (cast (julianday(strftime(&#39;%Y-%m-%d&#39;, {$firstDate})) - julianday(strftime(&#39;%Y-%m-%d&#39;, {$secondDate})) As Integer)&quot;
                    . &quot; / {$daysRange}) * {$daysRange} AS {$as}&quot;)
                );
            } else {
                $this-&gt;addSelect(\DB::raw(
                    &quot;floor(datediff({$firstDate}, {$secondDate})&quot;
                    . &quot; / {$daysRange}) * {$daysRange} AS {$as}&quot;)
                );
            }

            return $this;
        });

then use this new function globaly in Eloquent model :

$sdkClicks = $this-&gt;application-&gt;sdkClicks()
            -&gt;where(&#39;created_at&#39;, &#39;&gt;=&#39;, $dateInfo[&#39;previousRangeDate&#39;])
            -&gt;where(&#39;created_at&#39;, &#39;&lt;&#39;, $dateInfo[&#39;toDate&#39;]-&gt;format(&#39;Y-m-d&#39;));

        $sdkClicks-&gt;rangeDateDiff(&#39;created_at&#39;, &quot;&#39;{$dateInfo[&#39;currentRangeDate&#39;]}&#39;&quot;, $dateInfo[&#39;daysRange&#39;])
            -&gt;groupByRaw(&#39;diff_days_range&#39;)
            -&gt;orderByRaw(&#39;diff_days_range DESC&#39;)-&gt;get();

The advantage of this method is that it's more clean, and most importantly, if another type of database needs to be used in the project, the code is more maintainable.

答案2

得分: 2

PHP提供了在sqlite中创建用户定义函数的能力。这是使用PDO::sqliteCreateFunction()函数完成的。

基本上,你在sqlite中定义一个函数,该函数将调用你提供的PHP代码。

你的代码将会是这样的:

$pdo = DB::connection()->getPdo();

// 仅当使用sqlite时执行以下操作。
if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) != 'sqlite') {
    return;
}

$pdo->sqliteCreateFunction(
    'datediff', // 要创建的sqlite函数的名称
    function ($first, $second) {
        $dateFirst = Carbon\Carbon::parse($first)->startOfDay();
        $dateSecond = Carbon\Carbon::parse($second)->startOfDay();
        
        return $dateSecond->diffInDays($dateFirst, false);
    }, // PHP回调函数的实现
    2 // sqlite函数所需的参数数量
);

在你的测试套件中的某个地方添加这段代码,然后在执行查询之前,你应该没问题了。

英文:

PHP provides the ability to create user defined functions in sqlite. This is done using the PDO::sqliteCreateFunction() function.

Basically, you're defining a function inside of sqlite that will call the PHP code you provide.

Your code would look something like:

$pdo = DB::connection()-&gt;getPdo();

// Only do this if using sqlite.
if ($pdo-&gt;getAttribute(PDO::ATTR_DRIVER_NAME) != &#39;sqlite&#39;) {
    return;
}

$pdo-&gt;sqliteCreateFunction(
    &#39;datediff&#39;, // name of the sqlite function to create
    function ($first, $second) {
        $dateFirst = Carbon\Carbon::parse($first)-&gt;startOfDay();
        $dateSecond = Carbon\Carbon::parse($second)-&gt;startOfDay();
        
        return $dateSecond-&gt;diffInDays($dateFirst, false);
    }, // PHP callback function implementation
    2 // number of parameters the sqlite function takes
);

Add this somewhere in your test suite before the query is executed, and you should be good.

huangapple
  • 本文由 发表于 2023年4月13日 20:07:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76005229.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定