MSSQL drivery – uprava applyLimit()

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
knedle
Člen | 34
+
+1
-

pokusil jsem si zprovoznit strankovani (limit, offset) u mssql driveru, tj. upravil cast fce applyLimit() v příslušném mssql driveru (u mne na windows tedy sqlsrv), kde je při definovaném offsetu Exception…

		if ($offset) {
			throw new Nette\NotSupportedException('Offset is not supported by this database.');
		}

vycházel jsem z:

https://forum.dibiphp.com/…-strankovani

má úprava

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{

		if ($offset > 0) {

		    preg_match("#^\s*SELECT\s(.*)\sFROM\s([^\s]+)#i", $sql, $matches);
		    $columns = $matches[1];
		    $table = $matches[2];

		    if (preg_match("#ORDER\sBY\s(.*)#i", $sql, $matches)) {
		    	$order = $matches[1];
		    } else {
				throw new Nette\NotSupportedException('SQL must have ORDER BY ...');
		    }

			$where = '';
		    if (preg_match("#WHERE\s(.+)\s(ORDER|GROUP|HAVING)#i", $sql, $matches)) {
		    	$where = $matches[1];
			}


	        $sql = 'SELECT '.$columns.'
	        FROM (
	        SELECT '.$columns.', ROW_NUMBER() OVER (ORDER BY '.$order.') AS RowNum
	        FROM '.$table. (!empty($where) ? ' WHERE '. $where : '') .'
	        ) AS tbl
	        WHERE tbl.RowNum BETWEEN '.$offset.'+1 AND '.$offset.'+'.$limit.'';

			//throw new Nette\NotSupportedException('Offset is not supported by this database.');
		} elseif ($limit >= 0) {
			$sql = preg_replace('#^\s*(SELECT|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
			if (!$count) {
				throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
			}
		}
	}

je nedokonalá v mnoha směrech:

  • neporadí s případmým join
  • regulární výraz nějaký guru může zvládnout napoprvé…
  • v REGEXP tuším i další problémy

nicméně pro jednoduchou tabulku to funguje a mně to zatím stačí

třeba se toho chytne někdo chytřejší a doladí to do lepšího stavu

svobodai
Člen | 136
+
0
-

Já jsem si udělal úpravu pouze pro MSSQL 2012, který už má implementovánu funkci pro offset.

	public function applyLimit(&$sql, $limit, $offset) {
		if ($offset > 0) {
			if ($limit = 0) {
				$limit = 20;
			}
			if (!preg_match("#ORDER\sBY\s(.*)#i", $sql)) {
	$sql .= ' ORDER BY 1';
	}
			$sql .= ' OFFSET ' . $offset . ' ROWS FETCH NEXT ' . $limit . ' ROWS ONLY';
		} elseif ($limit >= 0) {
			$sql = preg_replace('#^\s*(SELECT|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
			if (!$count) {
				throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
			}
		}
	}

jen nevím jak to udělat, aby to bylo jen pro ten MSSQL2012

Editoval svobodai (25. 10. 2014 11:20)

svobodai
Člen | 136
+
+1
-

knedle napsal(a):

pokusil jsem si zprovoznit strankovani (limit, offset) u mssql driveru, tj. upravil cast fce applyLimit() v příslušném mssql driveru (u mne na windows tedy sqlsrv), kde je při definovaném offsetu Exception…

		if ($offset) {
			throw new Nette\NotSupportedException('Offset is not supported by this database.');
		}

vycházel jsem z:

https://forum.dibiphp.com/…-strankovani

má úprava

	/**
	 * Injects LIMIT/OFFSET to the SQL query.
	 */
	public function applyLimit(& $sql, $limit, $offset)
	{

		if ($offset > 0) {

		    preg_match("#^\s*SELECT\s(.*)\sFROM\s([^\s]+)#i", $sql, $matches);
		    $columns = $matches[1];
		    $table = $matches[2];

		    if (preg_match("#ORDER\sBY\s(.*)#i", $sql, $matches)) {
		    	$order = $matches[1];
		    } else {
				throw new Nette\NotSupportedException('SQL must have ORDER BY ...');
		    }

			$where = '';
		    if (preg_match("#WHERE\s(.+)\s(ORDER|GROUP|HAVING)#i", $sql, $matches)) {
		    	$where = $matches[1];
			}


	        $sql = 'SELECT '.$columns.'
	        FROM (
	        SELECT '.$columns.', ROW_NUMBER() OVER (ORDER BY '.$order.') AS RowNum
	        FROM '.$table. (!empty($where) ? ' WHERE '. $where : '') .'
	        ) AS tbl
	        WHERE tbl.RowNum BETWEEN '.$offset.'+1 AND '.$offset.'+'.$limit.'';

			//throw new Nette\NotSupportedException('Offset is not supported by this database.');
		} elseif ($limit >= 0) {
			$sql = preg_replace('#^\s*(SELECT|UPDATE|DELETE)#i', '$0 TOP ' . (int) $limit, $sql, 1, $count);
			if (!$count) {
				throw new Nette\InvalidArgumentException('SQL query must begin with SELECT, UPDATE or DELETE command.');
			}
		}
	}

je nedokonalá v mnoha směrech:

  • neporadí s případmým join
  • regulární výraz nějaký guru může zvládnout napoprvé…
  • v REGEXP tuším i další problémy

nicméně pro jednoduchou tabulku to funguje a mně to zatím stačí

třeba se toho chytne někdo chytřejší a doladí to do lepšího stavu

Pokud není v dotazu Order by tak nemusíš vyhazovat exception, ale můžeš do tohot dotazu dát ORBER BY 1 což zajistí řazení podle prvního sloupce v SELECTu.

kralik
Člen | 230
+
0
-

Ahoj,
na MSSQL 2008 se mi ORDER BY 1 vyhazovala chyba.
V každé tabulce mám ID.
Tak jsem to upravil pro ID a jede to.

<?php
if (preg_match("#ORDER\sBY\s(.*)#i", $sql, $matches)) {
	$order = $matches[1];
} else {
	$order = 'Id';
}

?>