Pagination with token in BigQuery + PHP client not working - php

I'm trying to implement a simple pagination mechanism with BigQuery.
this is my query function:
function query($sql, $max_results = null, $page_token = null) {
$request = new Google_Service_Bigquery_QueryRequest();
$request->setQuery($sql);
$response = $this->service->jobs->query(PROJECT_ID, $request);
$job_id = $response->getJobReference()->getJobId();
$optParams = ($max_results) ? array(
'pageToken' => $page_token,
'maxResults' => $max_results,
) : array();
$response = $this->service->jobs->getQueryResults(PROJECT_ID, $job_id, $optParams);
if (!$response->getJobComplete()) {
return null;
}
$rowsJson = $this->rowsJson($response->getRows());
if ($max_results) {
return array(
"rows" => $rowsJson,
"token" => $response->getPageToken()
);
}
return $rowsJson;
}
query("select url, CEIL(AVG(total)) as avg, count(id) as count from $table_id " .
"where created > $date_start and created < $date_end group by url order by $order_by desc",
10, $page_token
);
The first query seems like it's working. it returns only 10 results and a token (there are definitely more than 10 results). But when I call the function later with the token I got, the results are empty... I have no idea why. I see no error message in the logs or anything...

In order for BigQuery "pagination" to work you shold follow below steps (simplified but enough for this answer):
Execute query job
Retrieve jobid
Execute getQueryResults with that jobid and retrieve result and page_token
If page_token null - you done, otherwise continue with next step
Execute getQueryResults (still with jobid from #2) and retrieve result and page_token
Jump to step #4
Now, you most likely will see that your code doesn't follow this and each time executes query as new job - which totally breaks condition highlighted in #5 (still with jobid from #2).
The reason why first time it returns result is that first call actually meet above steps, but in consequitive calls you actually passing page_token and force execution of NEW job with some arbitrary (from that new job prospective) page_token
And also, this explains why your current code works with startIndex
Try to rewrite your code to follow above steps and it should work
Also,
P.S. page_token is alive and should be reusable for as long as underlying temp table (that holds your result) is alive - which is approximately 24 hours

Solved it by using startIndex instead of tokens.
Maybe tokens are short-lived and don't survive the 4-5 seconds delay, I'm not really sure.

Using a page token to get paged results are recommended because page token provides a view of the table at the first time it is retrieved. By just using startIndex, you may see different results as the table may change.
Response size is what may cause you get less than maxResults. We are limited by Apiary to return results of certain size.
https://cloud.google.com/bigquery/docs/data#paging
And we also have a max field counts to be less than 350,000 limit per call.
You should try to get the next results by using the page token it passed back to you.

function get_quote($page = 1){
global $database_connection;
$start = 0;
$limit = 5;
$start = ($page - 1) * $limit;
//select from database with limit $start, $limit
//display results
$sql_get_quote = "SELECT * FROM quote_table ORDER BY date_added ASC LIMIT $start, $limit";
$query_get_quote = mysqli_query($database_connection, $sql_get_quote);
while($fetch_quote = mysqli_fetch_array($query_get_quote)){
echo "<br>
<div class='w3-container w3-pale-green w3-bottombar w3-border-green w3-border'>
<p>".$fetch_quote['quote']."</p><hr><p class='w3-left-align'>".$fetch_quote['quote_by']."</p>
</div>";
}
//do another query, get the num_rows
$quote_rows= mysqli_num_rows (mysqli_query($database_connection ,"SELECT * FROM quote_table"));
$total = ceil($quote_rows / $limit);
if (isset($page)){
echo "<div class='w3-container'>";
echo "<ul class='pager'>";
if($page > 1) {
echo "<a href='?page=".($page - 1)."' style='float: left;' class=' w3-sand w3-btn w3-margin w3-round'>Previous</a>";
}
for($i = 1; $i <= $total; $i++) {
if($i == $page) { echo "<li class='active current'>".$i."</li>"; }
else { echo "<li><a href='?page=".$i."'>".$i."</a></li>"; }
}
if($page != $total) {
echo "<a href='?page=".($page + 1)."' class='w3-btn w3-margin w3-sand w3-round'>Next</a>";
}
echo "</ul></div>";
}
}
This works for me... Hope you can build around this

Related

Laravel LengthAwarePagination

I am working with laravel's LengthAwarePaginator Class. My problem is that I cannot use
$users = DB::table('users')->paginate(15);
or in other words,larvel's querybuilder or eloquent results. The reason is I have provided user with a frontend where they can create queries dynamically that can as simple as a select query and can be as complicated as queries with multiple joins etc. So I am left with only option of using LengthAwarePaginator. Here is what I have done do far
private function queryWithPagination($queryString,$path,$fieldName = '',$sortOrder = '',$perPage = 100){
$queryString = str_replace(''',"'",$queryString);
$queryString = str_replace('**',"",$queryString);
if(!empty($fieldName) && !empty($sortOrder)){
$queryString .= " ORDER BY '{$fieldName}' {$sortOrder}";
}
$currentPage = LengthAwarePaginator::resolveCurrentPage();
$limit = $perPage +1; // to have pagination links clickable i.e next,previous buttons
$queryString.= " LIMIT {$limit}";
if($currentPage == 1){
$queryString.= " OFFSET 0";
}
else{
$offset = ($currentPage-1)*$perPage;
$queryString.= " OFFSET {$offset}";
}
$path = preg_replace('/&page(=[^&]*)?|^page(=[^&]*)?&?/','', $path);
$result = DB::connection('database')->select($queryString);
$collection = new Collection($result);
//$currentPageSearchResults = $collection->slice(($currentPage - 1) * $perPage, $perPage)->all();
$entries = new LengthAwarePaginator($collection, count($collection), $perPage);
$entries->setPath($path);
//dd($queryString,$result);
dd($entries);
return $entries;
}
As you can see I append LIMIT and OFFSET in the query otherwise, for complex queries the load time was getting greater resulting in bad user experience. The problem with current set up is that the last page is always set to 2 and when I get to second page I cannot browse more results i.e cases where records returned were more than 500 I can still only browse upto page 2. How do I fix this issue where by using limit offset I can still keep browsing all the results until I get to the last page and then disable the pagination links?
made the second parameter to LengthAwarePaginator dynamic as follows in the given code,it solved my problem
$entries = new LengthAwarePaginator($collection, (count($collection)>=101)?$currentPage*$limit:count($collection), $perPage);

How to index a table with order by?

Using a while loop I'm able to return my table in the order I want, but after implementing pagination the variable I've created (counter) resets itself on each page, frustratingly. Example code:
$sql = ('SELECT id,name,logo FROM mytable ORDER BY name DESC LIMIT 25');
$query = mysqli_query($db_conx,$sql);
$counter = 0;
while ($row = $query->fetch_assoc()) {
$counter++;
echo "$counter, $row['id'], $row['name']";
echo "<br />";
}
I've tried many things and can't get this to work. Obviously my logic is flawed. The loop returns the correct results, but the $counter variable breaks on each page, resetting itself indefinitely.
What I am trying to do is get $counter to increase by 25 (representing results for each page) for each of the pages created by the pagination loop. Example code:
for ($i=1; $i<=$total_pages; $i++) {
echo "<a href='page.php?page=".$i."'>&nbsp[".$i."]</a> ";
$GLOBALS["counter"]+=25;
};
Obviously this was not working, so I am stumped at what I should try next. If anyone has any ideas I would love to hear them, I have heard great things about the SO community.
You seem to display only the first 25 results at any time.
You need to initialize $counter to zero if it's the first page, to 26 if it's the second page, and so on :
$counter = 0;
if(isset($_GET['counter'])){
$counter = intval($_GET['counter']);
}
You need to modify your query to fetch a different set of results for each page :
$sql = 'SELECT id,name,logo FROM mytable ORDER BY name DESC LIMIT ' . mysqli_real_escape_string($db_conx, $counter . ',25');
$query = mysqli_query($db_conx,$sql);
Then I assume you display a link to the other paginated pages, you need to pass it the value of $counter :
Next

How to hide the next button when no pages to dispaly

Can someone help me please? I am sure it is easy for you guys. I am battling to find a solution on how to hide the next link when there are no pages to display my code is as follows:
if (!isset($_GET['page']) or !is_numeric($_GET['page'])) {
$page = 0;
} else {
$page = (int)$_GET['page'];
}
$pages_query=mysql_query ("SELECT COUNT * FROM hardware");
$result = mysql_query("SELECT * FROM hardware LIMIT $page, 3");
echo 'Next<p>';
$prev = $page - 3;
//only print a "Previous" link if a "Next" was clicked
if ($prev >= 0) {
echo 'Previous';
}
You can use mysql_num_rows($result) to get the number of records in hardware:
$result = mysql_query("SELECT * FROM hardware LIMIT $page, 3");
$record_count = mysql_num_rows($result);
if ($record_count > 1)
echo 'Next';
in your if statement check if the $page is greater than 0 then according to the outcome of the value of $page write your code. you can use another if statement in the first if statement and make it detect the situation and decide what to do. The other thing is if the user clicked next then the user is on the second page so your previous should appear if $prev is higher than 1 it should make it
something along the lines of:
$itemsPerPage = 3;
$sql = "SELECT * FROM hardware";
$result = mysql_query($sql);
$count = mysql_num_rows($result);
$pageCount = $count/$itemsPerPage;
if($pageCount > $page) { //Are there more pages worth of items stored, than we're currently looking at?
echo 'next';
}
You want to be using OFFSET in your SQL syntax, as well as LIMIT.
LIMIT limits the number of rows returned.
OFFSET tells it to start a number of rows into the result set.
You need to limit to the number of items you want on a page. and offset by that number*page.
Hopes this helps.

How do I get the first and last results from a query?

I am creating a pagination script and I need to get the first and last results in the database query so that I can determine what results appear when the user clicks a page to go to. This is the code that I have at the minute:
// my database connection is opened
// this gets all of the entries in the database
$q = mysql_query("SELECT * FROM my_table ORDER BY id ASC");
$count = mysql_num_rows($q);
// this is how many results I want to display
$max = 2;
// this determines how many pages there will be
$pages = round($count/$max,0);
// this is where I think my script goes wrong
// I want to get the last result of the first page
// or the first result of the previous page
// so the query can start where the last query left off
// I've tried a few different things to get this script to work
// but I think that I need to get the first or last result of the previous page
// but I don't know how to.
$get = $_GET['p'];
$pn = $_GET['pn'];
$pq = mysql_query("SELECT * FROM my_table ORDER BY id ASC LIMIT $max OFFSET $get");
// my query results appear
if(!$pn) {
$pn = 1;
}
echo "</table><br />
Page $pn of $pages<br />";
for($p = 1;$p<=$pages;$p++) {
echo "<a href='javascript:void(0);' onclick='nextPage($max, $p);' title='Page $p'>Page $p</a> ";
}
I think you have few problems there, but I try to tackle them for you. First, as comments say above, you are using code that it vulnerable to SQL injection. Take care of that - you might want to use PDO, which is as easy use as MySQL extension, and will save you from many trouble (like injection).
But to your code, lets go through it:
You should ask DB to get count of the rows, not using mysql function, it's far more effective, so use SELECT count(*) FROM mytable.
For $pages use ceil() as you want all rows to be printed, if you have $max 5 and have 11 rows, round will make $pages 2, where you actually want 3 (last page just contains that last 11th row)
in LIMIT you want to LIMIT row_count OFFSET offset. You can calculate offset from page number, so: $max = row_count but $offset = ($max * $page) - $max. In your code if $get is directly the page, it means you get $get'th row (Not sure though what happens in your JS nextpage. Bare in mind that not all use JavaScript.)
I have prepared simple example here which uses PDO, maybe that gives you idea how simple it's use PDO.
The selecting rows shows example how to put parameters in SQL, it would be perfectly safe in this case state, 'SELECT * FROM pseudorows LIMIT '.$start.','.$max by I wanted to make an example how easy it is (and then safe):
// DB config
$DB_NAME = 'test';
$DB_USER = 'test';
$DB_PASSWD = 'test';
// make connection
try {
$DB_CONN = new PDO("mysql:host=localhost;dbname=".$DB_NAME, $DB_USER, $DB_PASSWD);
$DB_CONN->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die($e);
}
// lets say user param 'p' is page, we cast it int, just to be safe
$page = (int) (isset($_GET['p'])?$_GET['p']:1);
// max rows in page
$max = 20;
// first select count of all rows in the table
$stmt = $DB_CONN->prepare('SELECT count(*) FROM pseudorows');
$stmt->execute();
if($value = $stmt->fetch()) {
// now we know how many pages we must print in pagination
// it's $value/$max = pages
$pages = ceil($value[0]/$max);
// now let's print this page results, we are on $page page
// we start from position max_rows_in_page * page_we_are_in - max_rows_in_page
// (as first page is 1 not 0, and rows in DB start from 0 when LIMITing)
$start = ($page * $max) - $max;
$stmt = $DB_CONN->prepare('SELECT * FROM pseudorows LIMIT :start,:max');
$stmt->bindParam(':start',$start,PDO::PARAM_INT);
$stmt->bindParam(':max', $max,PDO::PARAM_INT);
$stmt->execute();
// simply just print rows
echo '<table>';
while($row = $stmt->fetch()) {
echo '<tr><td>#'.$row['id'].'</td><td>'.$row['title'].'</td></tr>';
}
echo '</table>';
// let's show pagination
for($i=1;$i<=$pages;$i++) {
echo '[ '.$i.' ]';
}
}
mysql_fetch_array returns an associative array
Which means you can use reset and end to get the first and last results:
$pqa = mysql_fetch_array($pq);
$first = reset($pqa);
$last = end($pqa);
I don't see how you plan to use the actual results, just page numbers should be sufficient for pagination.
Still, hope it helps. And yes, upgrade to mysqli, so your code doesn't get obsolete.

MySQL PHP Pagination

Is it possible to create pagination without getting all elements of table?
But with pages in GET like /1 /666…
It usually involves issuing two queries: one to get your "slice" of the result set, and one to get the total number of records. From there, you can work out how many pages you have and build pagination accordingly.
A simply example:
<?php
$where = ""; // your WHERE clause would go in here
$batch = 10; // how many results to show at any one time
$page = (intval($_GET['page']) > 0) ? intval($_GET['page']) : 1;
$start = $page-1/$batch;
$pages = ceil($total/$batch);
$sql = "SELECT COUNT(*) AS total FROM tbl $where";
$res = mysql_query($sql);
$row = mysql_fetch_assoc($res);
$total = $row['total'];
// start pagination
$paging = '<p class="paging">Pages:';
for ($i=1; $i <= $pages; $i++) {
if ($i==$page) {
$paging.= sprintf(' <span class="current">%d</a>', $i);
} else {
$paging.= sprintf(' %1$d', $i);
}
}
$paging.= sprintf' (%d total; showing %d to %d)', $total, $start+1, min($total, $start+$batch));
And then to see your pagination links:
...
// loop over result set here
// render pagination links
echo $paging;
I hope this helps.
Yes, using mySQL's LIMIT clause. Most pagination tutorials make good examples of how to use it.
See these questions for further links and information:
How do you implement pagination in PHP?
Searching for advanced php/mysql pagination script
more results
You can use LIMIT to paginate over your result set.
SELECT * FROM comments WHERE post_id = 1 LIMIT 5, 10
where LIMIT 5 means 5 comments and 10 is the offset. You can also use the longer syntax:
... LIMIT 5 OFFSET 10

Categories