MySQL/PHP - Pagination, sorting - php

I'm trying to paginate user submitted information into a catalog. At first I had something like this:
/?page=3&count=20&sort=date
$floor = ($page-1)*$count;
$ceiling = $count;
SELECT * FROM catalog ORDER BY date ASC LIMIT $floor, $ceiling
This, as I have read, is bad since it will count all the results, not stopping at the limit (floor+ceiling).
Now, I'm trying to make it faster by paging with respect to the last item on the page
/?last_date=2012&count=20&sort=date
$ceiling = $count;
SELECT * FROM catalog WHERE date>$last_date ORDER BY date ASC LIMIT $ceiling
However, this won't work right? Some dates will be the same. For the sake of argument, let's assume that I cannot use a more precise timestamp. For instance sorting by price would only go to 2 decimal places and there would definitely be overlap.
Is there anything that I can do to make this improvement work, or should i revert back to my previous query?

In order to use this, you'd have to constrain by date in both directions. What you could do is multipage each date range if it is too long, otherwise, select the whole set of dates. The disadvantage is that page sizes will be somewhat arbitrary and it might take longer to page through results in some cases.
This would mean you'd also have to first do a count() on that date range so you'd know if the next page would be in this or the next range of dates.

I ended up using my first pagination example.

If i understand it good, i think the LIKE operator in WHERE Clause in you SQL Statement should help... But i can be wrong, can you more specify what's exactly wrong?

Related

MySQL sorting date then by part number

Essentially I want these parts (below) grouped then the groups place in order of time, starting from the latest time being at the top of the list.
ID Parts Time
1 SMH_2010 08:59:18
2 JJK_0101 08:59:26
3 FTD_0002 08:59:24
4 JJK_0102 08:59:27
5 FTD_0001 08:59:22
6 SMH_2010 08:59:20
7 FTD_0003 08:59:25
So, the results would look like:
ID Parts Time
1 JJK_0101 08:59:26
2 JJK_0102 08:59:27
3 FTD_0001 08:59:22
4 FTD_0002 08:59:24
5 FTD_0003 08:59:25
6 SMH_2010 08:59:20
7 SMH_2010 08:59:18
Please, I would be grateful for any help.
What you are asking is not sorting in the traditional meaning. Your first attempt orders the result by time, and then by part if multiple timestamps occur at the same time.
What you want neither sorts the result in alphabetically by Parts name, nor ascending/descending on timestamp. What you are asking for can't be accomplished by the sort operation in SQL. Having the parts in sequence is not ordering.
I finally found a solution to this. Not my ideal solution but, never the less it works.
I added another field called max_date which by default is ‘now()’ as every new part is inserted.
I create a prefix from the current part being inserted, something like “SMH_” as a variable called $prefix = “SMH_”;
I have another query that directly follows the insert, which updates the max_date again, by ‘now()’ where the prefix is like $prefix.
UPDATE parts SET max_date = now() WHERE prefix LIKE '%$prefix%'
To display the results I use something along the line of :
SELECT * FROM parts ORDER BY parts.max_date DESC, parts.part ASC

How can I find the range of a field covered by each page of a paginated set?

I have a model Task(id, start_date, end_date, description). I use Paginator like
$this->Paginator->settings = array(
'Task'=>array(
'contain'=>$contain,
'limit'=> $limit,
'conditions'=>$conditions,
'order'=>'Task.start_date ASC',
'page'=> $page,
));
What I'm after is to be able to know the range of start_date covered by each page of the paged set. Instead of page numbers (i.e. in view generated by $this->Paginator->numbers()) I'd like to create links like "2 weeks ago" and "Today" that jump to the page containing the first Task with start_date > NOW()-14Days, for example.
I fully understand I could alter my $conditions and set a range on the start_date, but I want the whole set.
Open to other ideas on how to achieve the same result, or any pointers in the right direction.
You need to calculate the records before and after the given start date of your condition and then divide it by the number of records per page to get the page it is on.
However, this is not very user friendly, instead I would do this:
Pass your start date and range in the URL and based on the passed values build your query that you then use within your pagination settings.
/something/index?range=last-two-weeks
And check for the range value and do a switch for that value to set the right conditions. This will directly filter only the records the user is really interested in by a clear to understand URL.
I ended up running my search twice, once paginated, once not. I then went through the unpaged results
$pgStarts = array();
$pgCounter = 1;
foreach($tasks as $k => $task){
if(($k % $limit) == 0){
$pgStarts[$pgCounter] = $task['Task']['start_time'];
$pgCounter++;
}
}
Once I had the array of start dates for each page I could easily change the way the paging numbers were labeled. I was even able to break down the key event day by hour (most tasks are on this day).
I'm happy with the result, as I think it gives the paging more context. I'll continue to look for a better way of doing it, but this will do for now.

MySQL Query Between Two Ranges

I need help with a query. I am taking input from a user where they enter a range between 1-100. So it could be like 30-40 or 66-99. Then I need a query to pull data from a table that has a high_range and a low_range to find a match to any number in their range.
So if a user did 30-40 and the table had entries for 1-80, 21-33, 32-40, 40-41, 66-99, and 1-29 it would find all but the last two in the table.
What is the easiest why to do this?
Thanks
If I understood correctly (i.e. you want any range that overlaps the one entered by the user), I'd say:
SELECT * FROM table WHERE low <= $high AND high >= $low
What I understood is that the range is stored in this format low-high. If that is the case, then this is a poor design. I suggest splitting the values into two columns: low, and high.
If you already have the values split, you can use some statement like:
SELECT * FROM myTable WHERE low <= $needleHigherBound AND high >= $needleLowerBound
If you have the values stored in one column, and insist they stay so, You might find the SUBSTRING_INDEX function of MySQL useful. But in this case, you'll have to write a complicated query to parse all the values of all the rows, and then compare them to your search values. It seems like a lot of effort to cover up a design flaw.

Finding the nearest numeric match in a database to what a user has inputted in php

I need to find a way in which to find a field in a database which; numerically, is the closet match to what a user has inputted in a web form and submitted.
Deeper explanation:
In a database table there are two rows with a field entry of 80.1 and 80.7. If a site user enters 80 into an input element on a web form and submits that value, the handling script looks up that table and finds a row with 80.1 and chooses this as the closet match.
If it was the other way round, i.e. the user entering a floated integer (80.6), the table looking for the nearest rounded number i.e. (90) that would be easy. I'm obviously missing something and thought there would be an in built function in PHP for this.
Apologies if I have not explained myself well, if so please let me know.
SELECT ABS($user_value - numeric_field) as nearest, ...
FROM yourtable
ORDER BY nearest ASC
LIMIT 1
basically, take the difference of the number field you want and the user-provided value, then sort by the difference ascending and then return the first row.
So an exact match would have a difference of 0 and come out first. And then pick out whichever one is "closest" if there's no exact match.
try this
SELECT * FROM `table_test1` where `price` > 80 order by `price` asc limit 1
SELECT ABS(price - 80) AS nearest FROM table ORDER BY nearest ASC LIMT 1;
Difficult to give you an exact answer, however here is some pseudo SQL which might help you out:
SELECT number, abs((number * 100) - ({$search_number} * 100)) AS distance
FROM your_table
ORDER BY distance
LIMIT 1

How to perform page control?

I mean what the most efficient way to get information about the quantity of your page's items and make sql query with LIMIT that you need. or I should get all items and then crop array with php functions?
now I do 2 queries: first to count all items and second to get items that I need with LIMIT.
OK, I'll be more concrete. For example I need to show a question on my page and 20 answers to this question. At the bottom there shold be page control: links to the next, prev page and so on. I want to show proper number of links (number of answers/20) and when I go to any link I want to recieve proper answers (for example 41 to 60 on the 3d page). So what's the best way to get number of items (answers) to show proper number of links and to get proper answers for each link?
I guess your'e trying to say you want to know how many items/answers there is in the query but only read up to 20 items at at time, for pagination.
Firstly: You really should look for a pagination package; lots and lots of people have had the same problem before and there probably exists both free/opensource and proprietary solutions for your programming language and framework. (If you say what language you are using I'm sure someone can reccomend a solution for you.)
Anyway, I know I like to know how things work, so this is how it usually does:
As far as I know the pagination code calculates the pages by doing one query using select count(*) from tblX where something divide this number with the items-per-page number and use ceiling (e.g. 4.1 => 5).
For listing the results per page a new query is required; don't worry the count query is terribly much faster than getting every result discarding the ones you don't need DO NOT DO THAT (that's the recipie for becoming the top story on this page). Something like select * from tblX where something limit Y offset Z where Y is the number of items per page, and Z is the the (requested_page - 1)*Y; page 1 will have offset 0, page 2 have offset 20 (if thats what Y are) etc..
But do not try to implement this manually, it's unneccesary, tedious and error prone, much better to use your time customizing a readymade solution.
I'm assuming you want a count of the number of rows you'll be reading so as to do some pagination or similar? I don't understand your need for the LIMIT in the context of your question. However, if you just want a count of how many rows have been found, use one of the following.
You select the count of all rows such as:
select count(*) as counted, name, address
from contact
Or found rows:
SELECT SQL_CALC_FOUND_ROWS, name, address
from contact
This may be mysql specific I'm not sure.
Update:
For pagination you would do something like the following - (Psuedocode)
$rows = array($result)
$num_rows = sql_calc_found_rows
$per_page = 20
$pages = ceil($num_rows / $per_page)
page
$rows_this_page = array()
$rows_this_page = get_values($rows, (min index)$page_number * $per_page - $per_page, (max index)$page_number * $per_page - 1)

Categories