mysql within Joomla next and previous SQL statement within orderby - php

I know things are escaped incorrectly, but I am trying to write a previous/next query to get the next id and previous id within an order, but my output does not match the phpmyadmin sorting, which it should. I just have the 'next' written, and would write the previous with reverse conditions. This query seems to give me the next highest ID value number within the order...I need the next or previous in the order index...can anyone help?
$db = JFactory::getDBO();
$query = "SELECT image_id FROM #__jxgallery_images WHERE image_id >".$currentid."' ORDER BY '".$ordering." ".$direction." LIMIT 1";
// Executes the current SQL query string.
$db->setQuery($query);
// returns the array of database objects
$list = $db->loadObjectList();
// create the list of ids
foreach ($list as $item) {
$next = $item->image_id;
echo $next.'<br/>';
}
echo 'The next ID in this sort state is '.$next.'<br />';
?>
This is phpmyadmin and it is right...
SELECT * FROM `jos_jxgallery_images`
ORDER BY `jos_jxgallery_images`.`hits` DESC
LIMIT 0 , 30
I have now matched this query in my code to get the same results. My variables fill in the ($ordering) 'hits' field and the 'desc' ($direction) within my clause. That works fine. The image_ids and hits aren't special...just numbers. When hits are ordered, the image_ids are resored to match. I don't need the next value of image_id as to what is in the field. I need the next row or previous row, regardless of value, based on the current image_id I plugin.
These are actual image_ids LIMIT 5, and these are Ordered by the hits field Descending:
52791
52801
52781
52771
52581`
Now if the current image I'm looking at has an id of 52791, then previous should be nothing and next should be 52801. What my query is doing I think is giving me an image_id of a higher valued number as 'next' because that is the next highest VALUED image_id, not the next row. I can see why in the query, I am asking for greater than...but I just need the next row

I think the problem is with your WHERE condition: WHERE image_id >".$currentid."'
If I understand what you're trying to do, one way to do it is this:
$query = "SELECT image_id FROM `#__jxgallery_images` ORDER BY ".$ordering." ".$direction." LIMIT 2";
$db->setQuery($query);
$list = $db->loadObjectList();
$next_item = $list[1]->image_id;
Notice that the WHERE condition is removed from the query, and I also changed LIMIT 1 to LIMIT 2. This way, the query basically returns your top value and the one with the next highest "hits" value.
I hope this helps.

Related

Get next and previous in sql with a loop

A topic has images attached. I use these two queries to be able to open the previous and next image with a link. When you get to the last picture, you only have the option to get to the previous one and when you get to the first, you can only go to the next one. However, it wants to have a loop. This means when you get to the last one and you click on next, the first one will be loaded and when you get to the first and keep clicking on before, the last one will be loaded. I think a "loop" described it very well.
Is this possible with SQL in a simple query?
previous SQL:
SELECT * FROM attachments WHERE
attach_id < 9580
AND topic_id = 109331
ORDER BY attach_id DESC LIMIT 1
previous PHP:
while ($prevrow = $db->sql_fetchrow($result))
{
$previd = $prevrow['attach_id'];
}
next SQL:
SELECT * FROM attachments WHERE
attach_id > 9580
AND topic_id = 109331
ORDER BY attach_id DESC LIMIT 1
next PHP:
while ($nextrow = $db->sql_fetchrow($result))
{
$nextid = $nextrow['attach_id'];
}
Thank you very much.

It is possible in cursor-based pagination to get the prev and next cursor in the same query

Let's see, I'm making a mess with the cursor pagination, based on an Id in my case ULID, I want to return an array with the results, next_cursor and prev_cursor.
To obtain the NextCursor is very easy, I only have to add one more to the Limit, that is to say, if I have a limit of 10, I request 11 records and if I get 11 records then the NextCursor is the result 11. But for the PrevCursor the only thing I can think of is to do an additional Query to the one I am already doing. Example:
$limit = 10;
$result = 'SELECT * FROM Table WHERE id <= $cursor ORDER BY id DESC LIMIT $limit+1'
$results = array_slice($result, 0, $limit);
$nextCursor = array_slice($result, $limit, 1);
And now to get the Prev Cursor, I do as I said before an additional query
$prevCursor = 'SELECT * FROM Table WHERE id > $cursor ORDER BY id ASC LIMIT 1'
That way my API can return the following array to the frontend
return [
'data' => $results,
'next_cursor' => $nextCursor,
'prev_cursor' => $prevCursor
];
Now I rephrase the same question again, is there any way to do this without having to do additional Mysql query to get the Prev Cursor, I mean in a same Query or in some other way, I don't know, it's the first time I do this, and I'm a bit lost.
Thanks very much!
Indexing column allows you to quickly find specific row by its ULID and scan nearest rows forward and backward, but obviously, scanning forward and backward internally are two different operations, so if you insist on having the result of the both, you are doomed to perform two different operations.
There's some SQL syntactic sugar to help you hide those two internally executed operations inside one query, but first let me clarify around your objective a little.
What you are trying to build here is actually a window, not a page.
Pagination is when you have an ordered list of rows split over in pages of some size and user references an index of a page which she wants to browse. E.g. page #0, page #1, ... etc. Last page might have less items than a page size, and also if the total number of rows is less than a page, then first page and last page would be the same page and it's OK.
LIMIT and OFFSET operators are here to support that use case. A link to previous page is simply min(0,current_page-1) and a link to next page is min(max_pages,current_page+1).
On the opposite side, windowing is when you have an ordered list of rows, and when user references some specific row by its ULID, you fetch him a few rows behind and/or after queried row. It's like grep -C 10 in bash.
You can emulate window using sub-selects and UNION.
$limit = 10;
// Fetch a limit+1 of results after and including id AND
// a limit of results before id
$result = 'SELECT * FROM (
(
SELECT * from Table
WHERE id >= $cursor ORDER BY id ASC LIMIT $limit+1
) UNION (
SELECT * from Table
WHERE id < $cursor ORDER BY id DESC LIMIT $limit
)
) TableAlias ORDER by id;'
// as we actually fetching some rows before cursor
// we should find its position in the result set ...
$cursor_index = array_search($cursor, array_column($result, 'id'));
// ... and throw away the rest rows
$results = array_slice($result, $cursor_index, $limit);
// here cursors are always first and last items of the result set
$prevCursor = array_slice($result, 0, 1)['id'];
$nextCursor = array_slice($result, -1, 1)['id'];
return [
'data' => $results,
'prev_cursor' => $prevCursor,
'next_cursor' => $nextCursor
];
Since MySQL 8.0 you have a whole set of windowing functions, for your case, LEAD() and LAG() can help you move away all cursor calculations and slicing to your MySQL server.
$limit = 10;
// Wrap same query as above into sub-select, because LAG/LEAD work after WHERE, so we still need UNION to fetch previous cursor
$result = '
WITH
tab1 AS (SELECT * FROM Table where id >= $cursor ORDER BY id ASC LIMIT $limit+1),
tab2 AS (SELECT * FROM Table WHERE id < $cursor ORDER BY id DESC LIMIT $limit),
tab3 AS (SELECT * FROM tab1
UNION ALL
SELECT * FROM tab2 ORDER BY id),
tab4 AS (SELECT
*,
LAG(id, 4) OVER (order by id) as prevcursor,
LEAD(id, 4) OVER (order by id) as nextcursor
FROM tab3)
SELECT * FROM tab4
WHERE id >= $cursor LIMIT $limit'
// here calculated cursor ids are always on first row of result set
$prevCursor = $result[0]['prevcursor'];
$nextCursor = $result[0]['nextcursor'];
// (optionally) strip unwanted columns from result
$results = array_map(function ($a) { return array_diff_key($a, array_flip(array('prevcursor', 'nextcursor'))); }, $result);
return [
'data' => $results,
'prev_cursor' => $prevCursor,
'next_cursor' => $nextCursor
];
That should work well if you don't ever delete rows.
Now, consider following. Both pagination and windowing do not work well with unstable lists.
E.g. if new rows are sequentially adding to the end of the set, then the last page is constantly moving forward. So when one user is opening 'last' page and sees, say, three items there, another user might add another bunch of items and his view of what 'last' page would be different.
What's worse is that if your table usage allows deleting rows, the whole set of pages after and including the page where deleted row was is now rearranged. This leads to very nasty user experience when user is clicking 'next' page and accidentally skips some items, or is clicking 'previous' and sees some of the items he has already seen before.
To overcome those deficiencies you might want to redesign your API such that querying 'previous' page and 'next' page be semantically clearly different from querying 'current' page.
That is, you would need three API endpoints:
query a row by ULID (and a set of up to N rows after it) - initial user entry point from, e.g. search or catalog tree.
query a set of N rows before specific ULID. You pass ULID of the first row in the window user is currently looking at. If there are no rows before given one, result set is empty, you might either display a notification message to user or silently redirect them to first endpoint.
query a set of N rows after specific ULID. You pass ULID of the last row in the window user is currently looking at. If there are no rows after given one, result set is empty, you might either display a notification message to user or silently redirect them back.
If you design your API that way, you would have following benefits:
all three API implemented by only simple ORDER and LIMIT
no need in second query neither explicit nor implicit
your next/previous window results would not ever have any misses or duplicates comparing to previously seen windows.
The only drawback here is that original row user is referring to can be deleted as well. To overcome this, you might want to add a boolean deleted flag to your table schema and set it to false instead of actual row deletion.
After reading #shomeax 's comment and thinking a little more I can suggest to encode cursor in base-64 and make it to contain prev cursor additionally. For example:
[$prevCursor, $curCursor] = explode(':', base64_decode($request['cursor']));
$limit = 10;
$prevPlusCurPagesLimit = $limit * 2;
$ulids = 'SELECT ulid FROM Table WHERE ulid <= $prevCursor ORDER BY ulid DESC LIMIT $prevPlusCurPagesLimit+1';
$resultUlids = array_slice($ulids, $limit, $limit);
$nextCursor = array_slice($ulids, $prevPlusCurPagesLimit, 1);
$prevPrevCursor = reset($ulids);
$response = [
'data' => $resultUlids,
'prevCursor' => base64_encode("$curCursor:$nextCursor"),
'nextCursor' => base64_encode("$prevPrevCursor:$prevCursor"),
];
I didn't try such approach myself but it is partially based on this article https://slack.engineering/evolving-api-pagination-at-slack/ and looks like working
You don’t need to request more, only use < and > rather than <= and >=.
Then you can use the last id in $results for next and the first id for previous.
Assuming that
the "cursor" is the ID from which the next portion starts
and IDs are sequential and without gaps,
and limit is not changed from one query to another
you could get the prev cursor by substracting/adding (depending on sorting) $limit from/to current cursor: $prevCursor = $results[0]['id'] - $limit
And if the ID column has gaps, I suppose there is no reasonable way to implement ability to get prev cursor without additional query. You could only turn it into sub-query or use UNION, but this does not make a big difference.
Consider this...
You fetch the 5 rows for the current page. Then the "previous" page ends before the first id on this page and the "next" page starts after the last id on the current page.
Example: The current page contains ids 65, 67, 71, 82, 91. This finds the 5 rows for the previous page:
SELECT ...
WHERE id < 65
ORDER BY id DESC
LIMIT 5;
(They will be in reverse order, but that is easy to fix.) For the "next" page (in proper order):
SELECT ...
WHERE id > 91
ORDER BY id ASC
LIMIT 5;
As another tip: fetching an extra row (6 instead of 5), lets you cheaply discover whether you are at the "end", thereby being able to suppress the [Next] or [Prev] button.
More: Pagination
Granted, this technique does not deliver the 5 rows for the next/previous page, but do you really need that? Since the Selects should be quite efficient, I don't necessarily see a drawback of doing more than one Select or combining selects with UNION.
I am going to delete my previous answer, despite its upvote, because it is clearly the wrong approach.
Update
Given that you are retrieving on a column, id, that is presumably unique and indexed, then when a "page" of N rows is returned (where N is 10), you need to pass up either the id of the first row (the one with the greater value since we are sorting by descending id) or the last row as query parameter lastId along with a direction flag parameter directionFlag that is either F for forward or B for backward to give the direction of "paging." It should then be possible to directly seek to the correct rows as follows (I am assuming that we are using PDO for MySql Access):
define(PAGE_SIZE, 10);
$limit = PAGE_SIZE;
// lastId parameter specified? It will not be present on the initial request:
if (isset($_REQUEST('lastId')) {
$lastId = $_REQUEST['lastId'];
$direction = $_REQUEST['direction']; 'F'orward or 'B'ackward
if ($direction == 'F') {
$sql = "SELECT * FROM Table ORDER BY id DESC where id < :id LIMIT $limit";
}
else {
// paging backward:
$sql = "SELECT * from (SELECT * FROM Table ORDER BY id ASC where id > :id LIMIT $limit) sq ORDER BY id DESC";
}
$params = [':id' => $lastId];
}
else {
// this is the initial request:
$sql = "SELECT * FROM Table ORDER BY id DESC LIMIT $limit";
$params = [];
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// The id from $rows[0] will be passed back as lastId with direction flag 'B' for paging backward
// And the id from $rows[PAGE_SIZE-1] will be passed back as lastId with direction flag 'F' for paging forward

How to select one row from multiple which are with same column name in mysql?

I have a table with with columns name:
id
message
sender
chat_id
I want to get one row from number of rows with same chat_id ,during while loop fetch.
What Will be the MySQL Query
If you only want one row, one of those columns needs to have unique data so you can add it to the WHERE clause
SELECT id,message,sender,chat_id from information WHERE id=2 for example.
SELECT id,message,sender,chat_id from information WHERE chat_id=5 AND sender='mark'
OR during the while loop, match the column and grab whatever you want from it.
But again, it needs to be a unique combination of data otherwise it'll be overwritten by the previous.
while($row = $result->fetch_assoc()){
if($row['sender'] == 'mark' && $row['chat_id'] == '5'){
$thisIsMyRow = $row['id'];
}
Please could you explain more? What is it you're trying to achieve?
Say this is your query:
SELECT id,message,sender,chat_id from messages WHERE chat_id=1
You may have 200 rows that match that query. To return just one result, you can do a couple of things.
If you want the latest message, you can do this:
SELECT id,message,sender,chat_id from messages WHERE chat_id=1 ORDER BY id DESC LIMIT 1
another example, latest 5 messages
SELECT id,message,sender,chat_id from messages WHERE chat_id=1 ORDER BY id DESC LIMIT 0,5
In addition when you take it back into PHP, rather than going through the loop, you can JUST do this:
$row = $result->fetch_assoc()
As our query is only returning 1 row, you don't need to loop through. You can do this when your query returns multiple results - but it'd be better to optimise your query.
I can advise better if you are able to elaborate.
while($row = $result->fetch_assoc()){
$query = "SELECT id, message, sender,chat_id FROM messages WHERE Y = X AND Z = $K LIMIT 1";
}
replace the WHERE part with whatever column you want

PHP While: prevent looping rows where one field is identical to another

I'm using a while loop to get some data from my database, however the problem I have is that it's looping everything inside it (as expected from the start) but now I wonder if it's possible for it to search for duplicated, and skip those? Depending on if a field is identical.
Let's say 3 rows are being looped, everyone have the field "number" but only the first 2 rows has the same value in the field "number". I want it to only loop the first one, and then skip the 2nd one as it already has the same value in the "number" field as the previous one. Is it possible?
$q = $database->query("SELECT * FROM table1 ORDER BY id DESC LIMIT 7");
while($f = $q->fetch_array()) {
echo $f["number"] . "<br />";
}
you can avoid duplicated elements on mysql by adding the DISTINCT keyword after select
SELECT DISTINCT id,nom,prenom FROM matable

How do I fetch the last record in a MySQL database table using PHP?

I want to fetch the last result in MySQL database table using PHP. How would I go about doing this?
I have 2 Columns in the Table, MessageID(auto) & Message.
I already know how to connect to the database.
Use mysql_query:
<?php
$result = mysql_query('SELECT t.messageid, t.message
FROM TABLE t
ORDER BY t.messageid DESC
LIMIT 1') or die('Invalid query: ' . mysql_error());
//print values to screen
while ($row = mysql_fetch_assoc($result)) {
echo $row['messageid'];
echo $row['message'];
}
// Free the resources associated with the result set
// This is done automatically at the end of the script
mysql_free_result($result);
?>
The SQL query:
SELECT t.messageid, t.message
FROM TABLE t
ORDER BY t.messageid DESC
LIMIT 1
...uses the ORDER BY to set the values so the highest value is the first row in the resultset. The LIMIT says that of all those rows, only the first is actually returned in the resultset. Because messageid is auto-increment, the highest value is the most recent one...
Records in a relational database do not have an intrinsic "order" so you cannot fetch the "last" record without some kind of ORDER BY clause.
Therefore, in order to fetch the "last" record, simply reverse the ORDER BY clause (change ASC to DESC or vice versa) then select the first result.
If you have an auto-increment field and you just want to find the last value that was inserted, you can use the fact that the auto-increment fields are ever-increasing (therefore the "last" one will be the one with the highest value) and do something like this:
SELECT *
FROM my_table
ORDER BY id_field DESC
LIMIT 1
Of course you can select the last row by sorting DESC in your query. But what if you want to select the first row and then the last. You can run a new query, but you can also use the function mysql_data_seek. check code below:
$result = mysql_query('YOUR QUERY') or die('Invalid query: ' . mysql_error());
$row_first = mysql_fetch_array($result);
mysql_data_seek($result , (mysql_num_rows($result)-1));
$row_last = mysql_fetch_array($result);
Hope this helps
The MySql query would look like this:
select MessageID, Message
from Table
order by MessageID desc
limit 1;
I am too rusty with PHP to give you the right syntax for executing this.
This query works because you have an auto-incrementing identifying field (MessageID). By ordering the results by that field in descending (largest to smallest) order we are effectively returning the records in the table in reverse order. The limit 1 clause simply limits the result set to one record - the last one in the table.
What do you mean by "the last result"? You need to precise a bit more.
Do you mean "the last entry I registered"?
In this case you should use the appropriate method (depending on the extension you are using) mysqli->insert_id OR mysql_insert_id.
If you mean "the latest entry in the table", an SQL query such as Andrew Hare's is just what you need.
Do you mean the last record or do you need the id of the most recently inserted record? For that you would use the PHP mysql_insert_id() function. Or if you are using the myusqli extension use $mysqli->insert_id.
for some reason (which I don't know why), my boss force me to get the data in this way:
$message_arr = array();
while ($row = mysql_fetch_assoc($result)) {
$message_arr['messageid']= $row['messageid'];
$message_arr['message']= $row['message'];
}
return $message_arr;
Of course, you need everything from OMG Ponies's answer I'm just telling you another way to do it =)
I hope this help.
You should use SELECT query. How SELECT works.
SELECT * FROM table - selects everything in a table (id, row 1, row 2,...)
SELECT id FROM table - selects only particular row from table.
If you now know, how to select, you can use additional logic.
SELECT * FROM table ORDER by id DESC LIMIT 1;
selects everything from table table, orders it by id - orders it DESCENDING and limits the query to only one result.
If you would do it like this:
SELECT * FROM table ORDER by id ASC limit 1; - you would get 1 entry into database.
You can order it by any row you want.
Hope it helps.
One thing to remember is that data does not get saved in the insertion order in any MYSQL database. So in order to get the last entered record u will have to have an auto increment field. Since there is an auto increment field in this table we are good to go.
The below script will help to get the last entered record
<?php
$sql = "SELECT * FROM table_name ORDER BY MessageID DESC LIMIT 2";
$result_set = mysql_query($sql);
if($result_set){
while($row = mysql_fetch_array($result_set)) {
echo "Message Id: ".$row['MessageID']."<br>";
echo "Message: ".$row['Message']."<br>";
}
//creating alert
echo "<script type=\"text/javascript\">alert('Data was Retrieved
successfully');</script>";
}
else{
//creating alert
echo "<script type=\"text/javascript\">alert('ERROR! Could Not Retrieve
Data');</script>";
}
?>
The query selects all the records in the table and orders them according to the descending order of the MessageID (as it is the auto increment field) and limits the returned result to only one record. So since the table is ordered according to the descending order of the MessageID only the last entered record will be returned.
NOTE: if you are using a newer version you will have to use mysqli_query($connection_variable,$sql); instead of mysql_query($sql); and mysqli_fetch_array($result_set) instead of mysql_fetch_array($result_set)
$result = mysql_query('select max(id) from your_table ') or die('Invalid query: ' . mysql_error());
while ($row = mysql_fetch_assoc($result)) {
echo $row['id'];
echo $row['message'];
}
//
//
mysql_free_result($result);
simple like that
this code of php works fine
SELECT t.messageid, t.message
FROM TABLE t
ORDER BY t.messageid DESC
LIMIT 1
if you don't have concurrent entries going into some table.b'cause concurrent entries may not go in accordance of their insertion order.
$statement = $PDO->prepare("
SELECT MessageID,
Message
FROM myTable
ORDER BY MessageID DESC
LIMIT 1;
");
$statement->execute();
$result = $statement->fetch(\PDO::FETCH_ASSOC);
echo $result['MessageID']." and ".$result['Message'];

Categories