首页 > 数据库 >解决高版本laravel/framework中SQLServer2008分页报错问题

解决高版本laravel/framework中SQLServer2008分页报错问题

时间:2024-11-13 10:18:42浏览次数:1  
标签:laravel function return limit framework 报错 components offset query

前提:laravel6.0后就明确了支持的SQL Server版本最低为2017,而SQL Server是在2012版本后,引入的offset语法来实现分页,在此之前只能使用ROW_NUMBER()函数来完成分页。

问题:生产环境的SQL Server由于历史原因,仍旧使用的2008版本,自然是不支持offset语法的,而新建项目使用的laravel版本为10,就不可避免遇到了分页报错问题

最终解决方案

  1. 自定义一个参数解析器

<?php

namespace App\Models\SqlServer;

use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\SqlServerGrammar;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;

// 引用了 laravel/framework 旧版本的参数解析相关代码
class SqlServerGrammarPolyfill extends SqlServerGrammar
{
    public function __construct()
    {
        Log::info('Using custom sqlserver2008 parameter parser');
    }

    public function compileSelect(Builder $query): string
    {
        if (! $query->offset) {
            return parent::compileSelect($query);
        }

        if (is_null($query->columns)) {
            $query->columns = ['*'];
        }

        $components = $this->compileComponents($query);

        // 这里注释掉了下面这个判断,否则当有排序时,仍然会使用OFFSET去做分页
        // if (! empty($components['orders'])) {
        //     return parent::compileSelect($query)." offset {$query->offset} rows fetch next {$query->limit} rows only";
        // }

        // If an offset is present on the query, we will need to wrap the query in
        // a big "ANSI" offset syntax block. This is very nasty compared to the
        // other database systems but is necessary for implementing features.
        return $this->compileAnsiOffset(
            $query, $components
        );
    }

    protected function compileAnsiOffset(Builder $query, $components): string
    {
        // An ORDER BY clause is required to make this offset query work, so if one does
        // not exist we'll just create a dummy clause to trick the database and so it
        // does not complain about the queries for not having an "order by" clause.
        if (empty($components['orders'])) {
            $components['orders'] = 'order by (select 0)';
        }

        // We need to add the row number to the query so we can compare it to the offset
        // and limit values given for the statements. So we will add an expression to
        // the "select" that will give back the row numbers on each of the records.
        $components['columns'] .= $this->compileOver($components['orders']);

        unset($components['orders']);

        if ($this->queryOrderContainsSubquery($query)) {
            $query->bindings = $this->sortBindingsForSubqueryOrderBy($query);
        }

        // Next we need to calculate the constraints that should be placed on the query
        // to get the right offset and limit from our query but if there is no limit
        // set we will just handle the offset only since that is all that matters.
        $sql = $this->concatenate($components);

        return $this->compileTableExpression($sql, $query);
    }

    protected function compileOver($orderings): string
    {
        return ", row_number() over ({$orderings}) as row_num";
    }

    protected function queryOrderContainsSubquery($query): bool
    {
        if (! is_array($query->orders)) {
            return false;
        }

        return Arr::first($query->orders, function ($value) {
                return $this->isExpression($value['column'] ?? null);
            }, false) !== false;
    }

    protected function sortBindingsForSubqueryOrderBy($query): array
    {
        return Arr::sort($query->bindings, function ($bindings, $key) {
            return array_search($key, ['select', 'order', 'from', 'join', 'where', 'groupBy', 'having', 'union', 'unionOrder']);
        });
    }

    protected function compileTableExpression($sql, $query): string
    {
        $constraint = $this->compileRowConstraint($query);

        return "select * from ({$sql}) as temp_table where row_num {$constraint} order by row_num";
    }

    protected function compileRowConstraint($query): string
    {
        $start = (int) $query->offset + 1;

        if ($query->limit > 0) {
            $finish = (int) $query->offset + (int) $query->limit;

            return "between {$start} and {$finish}";
        }

        return ">= {$start}";
    }

    /**
     * Compile the "limit" portions of the query.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @param  int  $limit
     * @return string
     */
    protected function compileLimit(Builder $query, $limit)
    {
        return '';
    }

    /**
     * Compile the "offset" portions of the query.
     *
     * @param  \Illuminate\Database\Query\Builder  $query
     * @param  int  $offset
     * @return string
     */
    protected function compileOffset(Builder $query, $offset)
    {
        return '';
    }
}	
  1. 让SQL Server链接使用这个参数解析器

<?php

namespace App\Models\SqlServer;

use Illuminate\Database\SqlServerConnection;

class SqlServerConnectionPolyfill extends SqlServerConnection
{
    protected function getDefaultQueryGrammar()
    {
        return $this->withTablePrefix(new SqlServerGrammarPolyfill());
    }
}
  1. 在AppServiceProvider中注册你自定义的解析器

<?php

namespace App\Providers;

use App\Models\SqlServer\SqlServerConnectionPolyfill;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
        Connection::resolverFor('sqlsrv', function ($connection, $database, $prefix, $config) {
            return new SqlServerConnectionPolyfill($connection, $database, $prefix, $config);
        });
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        ...
    }
}

参考链接: (感谢伟大的gayhub,感谢laracasts)
  1. [Pagination On DIfferent SQL Server Versions]

  2. pagination with sqlsrv driver · laravel/framework · Discussion #43549

  3. framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php at beea2aaffb8b2bc4c2a348abeee306904c6fd32c · laravel/framework

  4. [8.x] Add proper paging offset when possible to sql server (#39863) · laravel/framework@beea2aa

  5. [8.x] Add proper paging offset when possible to sql server by joelharkes · Pull Request #39863 · laravel/framework

标签:laravel,function,return,limit,framework,报错,components,offset,query
From: https://www.cnblogs.com/dust2/p/18543336

相关文章

  • laravel PhpOffice 读取表格数据
    /***更新安通船期*Description*AuthorAllen*Date2024-11-11*@paramRequest$request[description]*@return[type][description]*/publicfunctionupdateAntongShipDate(Request$request){......
  • 走进科学IT版:两个控制台窗口,一个python命令报错一个不报错
    真是碰到走进科学那样的灵异事件了,同一个目录下,一样的python环境,一样pyramid的服务,两个控制台窗口,一个终端可以启动,另一个终端就启动不了。都是这一条命令pythonpyramid_app.py不能启动的终端,报错:pythonpyramid_app.pyTraceback(mostrecentcalllast):File"/User......
  • 启动mysql报错“服务没有响应控制功能”
    启动mysql服务器报错,如图: 解决方案:1、查看path环境变量配置正确2、查看初始化配置文件正确[mysqld]#设置3306端口port=3306#设置mysql的安装目录basedir=D:\\mysql-8.0.40-winx64#设置mysql数据库的数据的存放目录datadir=D:\\mysql-8.0.40-winx64\\data#允......
  • 检查电脑的 .net framework 的版本(复制到powershell里执行)
    打开powershell,$release=Get-ItemPropertyValue-LiteralPath'HKLM:SOFTWARE\Microsoft\NETFrameworkSetup\NDP\v4\Full'-NameReleaseswitch($release){{$_-ge533320}{$version='4.8.1orlater';break}{$_-ge52......
  • 【转】[Java][Idea] 打开时报错 Internal error. Address already in use: bind
    方法一:netshwinsockreset以管理员身份运行cmd执行这个命令,然后重启电脑。 方法二:按报错提示,访问  https://jb.gg/ide/critical-startup-errors  按文章,可以参考 https://youtrack.jetbrains.com/issue/IDEA-238995解决问题 以下是文章摘抄:ReviseIDEdire......
  • 【MonitorUtil】java报错日志工具类
     自定义报警日志打印工具importorg.apache.commons.lang.StringUtils;importjava.io.PrintWriter;importjava.io.StringWriter;/***自定义报警日志打印工具**@authorAngel挤一挤*@date2022/12/13*/publicclassMonitorUtil{/***......
  • uniapp中uni.setTabBarItem方法在非tabbar页面使用会报错
    背景:uniapp在开发小程序多语言切换功能时,使用uni.setTabBarItem方法切换tabbar语言时报错,查阅文档发现微信小程序该功能只能在tabbar页面或其子页面中使用,不能在其他页面直接使用。解决:在tabbar页面中使用onShow方法监听切换语言设置,部分代码示例如下:<scriptsetup>import{......
  • SQLSever将csv文件中的数据导入数据库中的某个表中的操作以及可能会出现的报错
    注:导入数据前,检查一下数据类型是否与数据库中的数据类型相匹配第一种方法:首先打开SQLSever数据库,右击你的数据库找到任务后点开导入平面文件,如图 在弹开的窗口中点击下一步 接下来 填写完后点击下一步,跳转到预览数据的页面,会显示出表中的前20条数据,在检查没有错误后接......
  • vue3 antd 报错:please transfer a valid name path to form item
    在使用antd时多层嵌套循环表单时校验会报如下错误:pleasetransferavalidnamepathtoformitem原因:是循环体和字段无法绑定解决如下:<a-form><divv-for="(item,index)inform.List":key="index"><a-form-item:name="['List',index,&......
  • 电脑提示kernel32.dll动态链接库报错怎么解决?kernel32.dll修复方法
    在使用电脑的过程中,你是否遇到过kernel32.dll动态链接库报错的情况呢?这个问题可能会让你感到困扰,但别担心,今天我们就来一起探讨一下kernel32.dll动态链接库报错的解决方法。一、了解kernel32.dllkernel32.dll是Windows操作系统的核心动态链接库之一,它包含了许多重要......