Counting MySQL records with a LIMIT - php

As I am trying to count the number of records in a table, even when the SQL statement has a LIMIT into it, overall it works, however something weird happens, the code:
$sql = "SELECT COUNT(*) AS count FROM posts
ORDER BY post_date DESC
LIMIT 5";
// ... mysql_query, etc
while($row = mysql_fetch_array($result))
{
// ... HTML elements, etc
echo $row['post_title'];
// ... HTML elements, etc
echo $row['count']; // this displays the number of posts (which shows "12").
}
Although, when displaying through the while loop, it displays this:
Notice: Undefined index: post_title in /Applications/MAMP/htdocs/blog/index.php on line 55
If I remove the COUNT(*) AS count, everything will display perfectly... how come it's doing this?

Don't use COUNT(*) to count the number of rows (for a lot of reasons). Write out your full query, and add SQL_CALC_FOUND_ROWS right after SELECT:
SELECT SQL_CALC_FOUND_ROWS id, title FROM foo LIMIT 5;
Then, after that query executed (right after), run:
SELECT FOUND_ROWS();
That will return the number of rows the original SELECT would have returned if you didn't have the LIMIT on the end (accounting for all joins and where clauses).
It's not portable, but it's very efficient (and IMHO the right way of handling this type of problem).

LIMIT does not do anything here because you're selecting a single scalar. The error is shown because you are not selecting the post title, so it is not in the $row hash.

This is happening because COUNT() is an aggregate function. You will have to do two separate queries in order to get both the count of rows in the table and separate records.

I had the same problem and found this article: http://www.mysqldiary.com/limited-select-count/
best way (especially for big tables) is using it like this:
SELECT COUNT(*) AS count FROM (SELECT 1 FROM posts
ORDER BY post_date DESC
LIMIT 5) t
now it returns a number between 0 and 5

You're asking the SELECT statement to return only COUNT(*) AS count. If you want more columns returned, you have to specify them in the SELECT statement.

You're mixing up an aggregate function COUNT() with a standard selection. You can't get an accurate post title without a GROUP BY clause in your aggregate query. The easiest thing you should do is to do this two queries - one for your count, the other for your post information. Also, there's no sense in using LIMIT on an aggregate function with no other columns in your (omitted) GROUP BY - the current query will always return one row.

What everyone is trying to say is that you should opt to use 2 separate queries instead of 1 single one.
1) get row count of table
$sql = "SELECT COUNT(*) AS count FROM posts;
2) get last 5 rows of table
$sql = "SELECT * FROM posts
ORDER BY post_date DESC
LIMIT 5";

By including COUNT(*) AS count you've removed the actual columns that you want to get the information from. Try using
SELECT *, COUNT(*) AS count FROM posts ORDER BY post_date DESC LIMIT 5
That query will accomplish both things you're trying to do in one query, but really, these should be broken out into 2 separate queries. What is the overall purpose for having a COUNT in the query? It should be done a different way.

This works for me:
$sql = "
SELECT COUNT( po.id ) AS count, p.post_title
FROM posts p
JOIN posts po
GROUP BY p.post_title
ORDER BY p.post_date DESC
LIMIT 5
";
Important is to JOIN the same table, but name the table differently. Do not forget GROUP BY, must be used.

Related

Getting two different results by one SQL query

First I am new to SQL and PHP.
I have created a simple social networking web app so users can post and follow others to see new posts from them.
At home page a user can first see posts from all users he is followong.
but what i want is to make the user see some other random popular posts that will be ordered by Likes.
here what i have done to get posts from users i follow:
SELECT * FROM posts WHERE author_id in
(SELECT followedID FROM follows WHERE
followerID=:myID)
ORDER BY id DESC LIMIT 10
Now let's say you are following only 1 person. and that person has only one post. here you will see no more than a post!
That's why i want to show more posts when a user has already seen all posts.
i want some easy way to get other posts when the above query has done getting some specific posts.
This is the next query i'd like to execute.
SELECT * FROM posts ORDER BY post_likes DESC LIMIT 10
I wouldn't recommend union, because it incurs overhead for removing duplicates.
Instead, you can use a LEFT JOIN:
SELECT p.*
FROM posts p LEFT JOIN
follows f
ON p.author_id = f.follows_id AND
f.followerID = :myID
ORDER BY (f.follows_id IS NOT NULL) DESC,
(CASE WHEN f.follows_id IS NOT NULL THEN p.id END),
p.post_likes DESC
LIMIT 10;
The ORDER BY puts the followed posts first. The other two clauses order each of the groups by the criteria you want.
You may use UNION to do what you want
(SELECT * FROM posts WHERE author_id in
(SELECT followedID FROM follows WHERE
followerID=:myID)
ORDER BY id DESC limit 0,10)
union
(SELECT * FROM posts ORDER BY post_likes DESC limit 0,10)
LIMIT 0, 10
UNION will automatically append the 2nd query result to the 1st query result, and then show only the number of records specified by the LIMIT clause
Please note that union works only if the queries are of the same structure (which in this case is positive)
Please note that the use of parenthesis is mandatory if you use order by or limit or both
I have used 3 limit clauses (one for each query , and one for the final result of union) AND Both queries have ORDER BY clause. This is to make sure that the records extracted are what you want. (to show the followed posts first, and both are ordered properly)

mysql merge two queries, get count of each value from two seperate columns?

$AcceptEventCount = mysql_query("SELECT COUNT(*) as count, tblDevices.name, tblEvents.sentdeviceid FROM tblDevices,tblEvents WHERE tblDevices.deviceid = tblEvents.sentdeviceid
GROUP BY tblEvents.sentdeviceid ORDER BY count DESC");
$DeclineEventCount = mysql_query("SELECT COUNT(*) as cnt, tblDevices.name, tblDeclinedEvents.deviceid FROM tblDevices,tblDeclinedEvents WHERE tblDevices.deviceid = tblDeclinedEvents.deviceid
GROUP BY tblDeclinedEvents.deviceid ORDER BY cnt DESC");
I'm new to merging two queries in php mysql. I tried unions with it but i'm not able to get the desired result.
Basically I want to get a count of the accepted events and the declined events of the devices from the table tbldevices. Three tables are involved here. can anyone help me with this?
this is what i tried!
SELECT COUNT() as count, tblDevices.deviceid,null,tblEvents.sentdeviceid
from tblEvents,tblDevices
WHERE tblEvents.sentdeviceid = tblDevices.deviceid
GROUP BY tblEvents.sentdeviceid
UNION
SELECT COUNT() as cnt,tblDevices.deviceid,tblDeclinedEvents.deviceid,null
from tblDevices,tblDeclinedEvents
WHERE tblDeclinedEvents.deviceid = tblDevices.deviceid
GROUP BY tblDeclinedEvents.deviceid
I think it is not possible (at least without using sub query). The reason is that group by needs to be done on fields, but accepted and declined events are not identified by a field but by tables.
If you can't modified the table structure, having 2 queries is a reasonable options. If you can change the table structure, I would recommend adding a column that store the validation status.

PHP MYSQL search based on rating and timestamp

My search query runs like:
select * from posts p where p.post like '%test%' ORDER BY p.upvotes DESC,
p.unix_timestamp DESC LIMIT 20
If there are more than 20 results for the searched keyword, i find out the minimum timestamp value, store it in a hidden element and run another query to Load More results like:
select * from posts p where p.post like '%test%' and p.unix_timestamp < 1360662045
ORDER BY p.upvotes DESC, p.unix_timestamp DESC LIMIT 20
Whats really happening is that my first query is ignoring (Obviously, my mistake) posts which haven't had any votes(meaning 0 votes) because of my ORDER BY p.upvotes DESC and as a result of this, i noticed that it fetched the first post in the table in the first 20 results, so the minimum timestamp becomes first post's timestamp. Now after this, if i try to fetch the next 20 results which is less than the minimum timestamp, it doesn't give anything.
Right now, i am simply using the upvotes ordering to fetch top records. Should i be using some algorithm like Bayesian Average or some other algorithm?
Please advise how i can improve the queries if i had to stay with current system of ordering or is there any viable and more efficient method i should be using?
P.S. If possible, please refer some resources about the Bayesian Average(it seems to be most used) or some other alternative?
Storing the timestamp when you first do a search and then using that for the next query you could use something like this:-
$sql = "SELECT *
FROM posts p
LEFT OUTER JOIN (SELECT post_id, COUNT(*) FROM post_ratings WHERE timestamp_rated <= $SomeTimeStoredBetweenPages GROUP BY post_id) pr ON p.id = pr.post_id
WHERE p.post like '%test%'
ORDER BY pr.post_ratings DESC, p.unix_timestamp
DESC LIMIT ".(($PageNo - 1) * 20)." 20";
This is very much an example as I have no real idea of you table structures. Also not sure if you just have a row for each up vote, or whether there are down votes to take account of as well.

Find total number of results in mySQL query with offset+limit

I'm doing a pagination feature using Codeigniter but I think this applies to PHP/mySQL coding in general.
I am retrieving directory listings using offset and limit depending on how many results I want per page. However to know the total number of pages required, I need to know (total number of results)/(limit). Right now I am thinking of running the SQL query a second time then count the number of rows required but without using LIMIT. But I think this seems to be a waste of computational resources.
Are there any better ways? Thanks!
EDIT: My SQL query uses WHERE as well to select all rows with a particular 'category_id'
Take a look at SQL_CALC_FOUND_ROWS
SELECT COUNT(*) FROM table_name WHERE column = 'value' will return the total number of records in a table matching that condition very quickly.
Database SELECT operations are usually "cheap" (resource-wise), so don't feel too bad about using them in a reasonable manner.
EDIT: Added WHERE after the OP mentioned that they need that feature.
The SQL_CALC_FOUND_ROWS query modifier and accompanying FOUND_ROWS()
function are deprecated as of MySQL 8.0.17 and will be removed in a
future MySQL version. As a replacement, considering executing your
query with LIMIT, and then a second query with COUNT(*) and without
LIMIT to determine whether there are additional rows. For example,
instead of these queries:
SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();
Use these queries instead:
SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;
COUNT(*) is subject to certain optimizations. SQL_CALC_FOUND_ROWS
causes some optimizations to be disabled.
Considering that SQL_CALC_FOUND_ROWS requires invoking FOUND_ROWS() afterwards, if you wanted to fetch the total count with the results returned from your limit without having to invoke a second SELECT, I would use JOIN results derived from a subquery:
SELECT * FROM `table` JOIN (SELECT COUNT(*) FROM `table` WHERE `category_id` = 9) t2 WHERE `category_id` = 9 LIMIT 50
Note: Every derived table must have its own alias, so be sure to name the joined table. In my example I used t2.
you can use 2 queries as below, One to fetch the data with limit and other to get the no of total matched rows.
Ex:
SELECT * FROM tbl_name WHERE id > 1000 LIMIT 10;
SELECT COUNT(*) FROM tbl_name WHERE id > 1000;
As described by Mysql guide , this is the most optimized way, and also SQL_CALC_FOUND_ROWS query modifier and FOUND_ROWS() function are deprecated as of MySQL 8.0.17
as of final of 2021, why not:
SELECT
t1.*,
COUNT(t1.*) OVER (PARTITION BY RowCounter) as TotalRecords
FROM (
SELECT a, b, c, 1 as RowCounter
FROM MyTable
) t1
LIMIT 120,10
using a subquery with a column marking every row with the same value, will give us the possibility to count all of the same values of the the resulted column with PARTITION BY window function's group
SELECT COUNT(id) FROM `table` WHERE `category_id` = 9
Gives you the number of rows for your specific category.

help with counting mysql results

I have to modify an existing PHP script so that it displays the total number of matches in the database before it shows the results for the current page. The query is:
SELECT products.features, products.make_id, products.model, products.price,
products.id, brands.name AS brandname, brands.id AS brandid
FROM products
LEFT JOIN brands ON products.make_id=brands.id
WHERE products.active=1 AND brands.active=1 AND (products.category=22)
ORDER BY id DESC LIMIT 0,12
How do I get the number of results? mysql_num_rows() obviously returns 12 but I don't know how to use count in this query, everything I try returns some error.
I could just execute the query once without the limit (clause?) but that seems a bit inefficient.
Thanks.
Use the MySQL function FOUND_ROWS():
A SELECT statement may include a LIMIT
clause to restrict the number of rows
the server returns to the client. In
some cases, it is desirable to know
how many rows the statement would have
returned without the LIMIT, but
without running the statement again.
To obtain this row count, include a
SQL_CALC_FOUND_ROWS option in the
SELECT statement, and then invoke
FOUND_ROWS() afterward:
so run the two following queries in PHP, the first statement gives the first 12 rows, the second one gives the total of rows:
$sql1 = "SELECT SQL_CALC_FOUND_ROWS
products.features
, products.make_id
, products.model
, products.price
, products.id
, brands.name AS brandname
, brands.id AS brandid
FROM products
LEFT JOIN brands ON products.make_id=brands.id
WHERE (products.active=1) AND (brands.active=1) AND (products.category=22)
ORDER BY id DESC LIMIT 0,12"
$sql2 = "SELECT FOUND_ROWS()"
This would return you the total number of rows the current select matches. The result will be one row, with one field and it will hold the total
SELECT COUNT(*) AS total
FROM products
LEFT JOIN brands ON products.make_id=brands.id
WHERE products.active=1 AND brands.active=1 AND products.category=22
You will have to run this as a separate query.
<?php
$q=mysql_query('above query');
$result=mysql_fetch_array($q);
echo $result['total']; // will print out the desired number of rows
You were probably trying to get the total and the limit in the same select and while that might be possible with a union, but since you were worrying about inefficiency before, this is possible worse.
Since in your OP you hint that this is for pagination. Let me tell you, LIMIT m,n may not be as fast as it sounds. Learn how to improve it and read more about Efficient Pagination Using MySQL

Categories