ZF2 Union + Pagination

2019-09-02 21:27发布

问题:

I'm using ZF2 and SQL Server 2012. I'm trying to write a union query with pagination.

It seems like support for unions in ZF2 is not fully there. There is no union() method, but I see that there is a combine() method. It creates the query with the union, but it puts the ORDER BY on the first query instead of on the entire unioned query. So when the ZF2 paginator tries to add the limit/offset parts, SQL Server chokes because it has to have an ORDER BY on the whole query.

Here is my code:

public function fetchPage($fkADUser, array $orderBy, $page = 1, $limit = 20) {

    $inboxSelect = $this->inboxTableGateway->getSql()
        ->select()
        ->columns([
            'pkid',
        ])
        ->join(['f' => 'Fax'], 'FaxInbound.fkFax = f.pkid', [
            'PageCount',
            'CreateTime',
        ])
        ->where(['f.fkADUser = ?' => $fkADUser])
        ->where('FaxInbound.DeleteTime IS NOT NULL');

    $sentSelect = $this->sentTableGateway->getSql()
        ->select()
        ->columns([
            'pkid',
        ])
        ->join(['f' => 'Fax'], 'FaxOutbound.fkFax = f.pkid', [
            'PageCount',
            'CreateTime',
        ])
        ->where(['f.fkADUser = ?' => $fkADUser])
        ->where('FaxOutbound.DeleteTime IS NOT NULL');

    $inboxSelect->combine($sentSelect)->order($orderBy);

    return $this->inboxTableGateway->paginate($inboxSelect, $page, $limit, $this->resultSetPrototype);
}

And here is the query generated (from SQL Profiler):

(
    SELECT
        [FaxInbound].[pkid] AS [pkid],
        [f].[PageCount] AS [PageCount],
        [f].[CreateTime] AS [CreateTime]
    FROM
        [FaxInbound]
        INNER JOIN [Fax] AS [f]
            ON [FaxInbound].[fkFax] = [f].[pkid]
    WHERE
        f.fkADUser = @P1
        AND FaxInbound.DeleteTime IS NOT NULL
    ORDER BY
        [CreateTime] DESC
) UNION (
    SELECT
        [FaxOutbound].[pkid] AS [pkid],
        [f].[PageCount] AS [PageCount],
        [f].[CreateTime] AS [CreateTime]
    FROM
        [FaxOutbound]
        INNER JOIN [Fax] AS [f]
            ON [FaxOutbound].[fkFax] = [f].[pkid]
    WHERE
        f.fkADUser = @P2
        AND FaxOutbound.DeleteTime IS NOT NULL
)
OFFSET 0 ROWS
FETCH NEXT 20 ROWS ONLY

Notice that the ORDER BY is on the first query instead of the entire unioned query. So the error messages I get are:

Msg 156, Level 15, State 1, Line 13
Incorrect syntax near the keyword 'ORDER'.
Msg 102, Level 15, State 1, Line 28
Incorrect syntax near 'OFFSET'.
Msg 153, Level 15, State 2, Line 29
Invalid usage of the option NEXT in the FETCH statement.

But if I move the ORDER BY outside, just before the OFFSET, the query works.

So the question is, does anyone know how to put the ORDER BY on the whole query instead of the first one? Or, Is there a way for me to use the ZF2 Paginator with a manually written query?

回答1:

Looks like there is a bug in ZF2. I added it to the ZF2 issue tracker.

I fixed the issue by reordering the entries in Zend\Db\Sql\Select::$specifications.

Original:

protected $specifications = array(
    'statementStart' => '%1$s',
    self::SELECT => array(...),
    self::JOINS  => array(...),
    self::WHERE  => 'WHERE %1$s',
    self::GROUP  => array(...),
    self::HAVING => 'HAVING %1$s',
    self::ORDER  => array(...),
    self::LIMIT  => 'LIMIT %1$s',
    self::OFFSET => 'OFFSET %1$s',
    'statementEnd' => '%1$s',
    self::COMBINE => '%1$s ( %2$s )',
);

New:

protected $specifications = array(
    'statementStart' => '%1$s',
    self::SELECT => array(...),
    self::JOINS  => array(...),
    self::WHERE  => 'WHERE %1$s',
    self::GROUP  => array(...),
    self::HAVING => 'HAVING %1$s',
    'statementEnd' => '%1$s',
    self::COMBINE => '%1$s ( %2$s )',
    self::ORDER  => array(...),
    self::LIMIT  => 'LIMIT %1$s',
    self::OFFSET => 'OFFSET %1$s',
);

I just moved ORDER, LIMIT, and OFFSET to the end. This fixed the query for me.