MYSQL query needs improvement - mysql

I have a query below that produces a simple list of plants (the field named "thriller")
Problem:
Query below considers only the first sequential set of matching items from the table, then displays them in random order. The query is not considering other matching items that are found later in the table.
Solution Needed:
I want the query to choose random results from ALL items in the table, not only the first set of matching results.
$row_object = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}personal_creations_assistant
WHERE pot_sun_exposure = %s
AND pot_height = %s
AND pot_size = %s
AND pot_shape = %s
AND pot_placement = %s
GROUP BY thriller
ORDER BY RAND() LIMIT 0,3
",
$sun_exposure,
$height,
$size,
$shape,
$placement
)
);

If i get it right, you want to get some random rows from database. Random 3 rows.
You could do it with php. First make a COUNT to find how many rows you have total. Then say you call your variable $rowstotal; you make another one $limit_s = rand(0, $rowstotal); Then you add it to your query:
... LIMIT $limit_s, 3

Related

Select a random row with where statement is taking to long

I want to select a random row with a specific where statement but the query is taking to long (around 2.7 seconds)
SELECT * FROM PIN WHERE available = '1' ORDER BY RAND() LIMIT 1
The database contains around 900k rows
Thanks
SELECT * FROM PIN WHERE available = '1' ORDER BY RAND() LIMIT 1
means, that you are going to generate a random number for EVERY row, then sort the whole result-set and finally retrieve one row.
That's a lot of work for querying a single row.
Assuming you have id's without gaps - or only little of them - you better use the programming language you are using to generate ONE random number - and fetch that id:
Pseudo-Example:
result = null;
min_id = queryMinId();
max_id = queryMaxId();
while (result == null){
random_number = random_beetween(min_id, max_id);
result = queryById(randomNumber);
}
If you have a lot of gaps, you could retrieve the whole id-set, and then pick ONE random number from that result prior:
id_set = queryAllIds();
random_number = random_beetween(0, size(id_set)-1);
result = queryById(id_set[random_number])
The first example will work without additional constraints. In your case, you should use option 2. This ensures, that all IDs with available=1 are pre-selected into an 0 to count() -1 array, hence ignoring all invalid ids.
Then you can generate a random number between 0 and count() -1 to get an index within that result-set, which you can translate to an actual ID, which you are going to fetch finally.
id_set = queryAllIdsWithAvailableEqualsOne(); //"Condition"
random_number = random_beetween(0, size(id_set)-1);
result = queryById(id_set[random_number])

Show each records of groupby sql query

I have an ads table like this:
id adid approveby
1 10 MR A
2 11 MR A
3 12 MR B
I would like to count total record of MR A and echo each record out so I have this sql query:
$sql = "SELECT *, count(*) AS `totaladsapproved` FROM ads GROUP BY approveby";
$result = $conn->prepare($sql);
$result->execute();
foreach ($result as $row){
echo "Name: $row[approveby]"." - Total ads approved: $row[totaladsapproved] <br>";
for($i=0;$i<$row['totaladsapproved'];$i++){
echo "Ad ID: $row[adid] <br>"
}
}
But it only shows adid 10 twice. Here is the result i got:
Name: MR A - Total ads approved: 2
AD ID: 10
AD ID: 10
I would like to show AD ID 10 and then AD ID 11...What should I do to achieve this?
Many thanks
your query is grouping so you would not see each id
use following, this will give you count of total "aproveby" present for each row
SELECT id,
adid,
approveby,
(select count(*) from ads where approveby
= t1.approveby) AS `totaladsapproved`
FROM ads t1
if you want to restrict to MR A you can use where clause
The code has a lot of problems. They are addressed below one by one.
Undefined $i
Your code reads:
foreach ($result as $row){
echo "Name: $row[approveby]"." - Total ads approved: $row[totaladsapproved] <br>";
for ($i; $i < $row['totaladsapproved']; $i ++) {
echo "Ad ID: $row[adid] <br>";
}
}
First thing to notice is that $i is not initialized but you use it to iterate in a for() loop. PHP is nice and, besides showing you a notice you don't care of, because of the numeric context it uses 0 instead of NULL on the first loop when it handles $i to check the condition and to increment $i.
The correct way to do it is:
for ($i = 0; $i < $row['totaladsapproved']; $i ++) {
not only because of what I mentioned above but also because using your code, on the next foreach() iteration, $i starts with 2 and the for() loop skips the first two iterations.
The logic error in the loops
You get one row in variable $row but then you just print it 2 times (or as many times $row['totaladsapproved'] says) without getting the subsequent rows from the result set.
To fix it you could get new rows from the result set inside the for() loop. I don't provide code here because it doesn't matter, the query is wrong anyway.
General considerations about the queries that use GROUP BY
If you want to get the records then do not use GROUP BY. GROUP BY approveby produces a single row from all the rows that have the same value for approveby.
In the SELECT clause of a query that has a GROUP BY clause you can use only:
the columns that also appear in the GROUP BY clause;
other columns that are functionally dependent on the fields that appear in the GROUP BY clause; this means you can use any column of the table only if the table's PK or an UNIQUE INDEX of it appears in the GROUP BY clause;
any column of the table if it is used as an argument of aGROUP BY aggregate function.
Any column that appear in the SELECT clause and is not described in the list above renders the query invalid (according to the SQL standard).
MySQL extends the standard and allows such invalid queries (as long as they are syntactically correct, of course) but it does not guarantee anything about the values of these columns returned in the result set.
... a MySQL extension to the use of GROUP BY is to permit the SELECT list, HAVING condition, or ORDER BY list to refer to nonaggregated columns even if the columns are not functionally dependent on GROUP BY columns. This causes MySQL to accept the (...) query. In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
The query is incorrect
Back to your query, because the columns id and adid are not included in the list above, using * in the SELECT list renders the query invalid and the values returned for columns id and adid indeterminate.
In your case it happened that for approvedby == 'MR A', MySQL decided to return (1, 10) for (id, adid). It could return (2, 11) instead tomorrow, after you add or remove some rows from the table (or when the table is dumped and imported on a different server). It could even return (1, 11) and it is still right. It could even return -1 or NULL or whatever value it wants and you still cannot blame it because indeterminate is something that is not settled or decided.
The correct query
The correct query for your case is as simple as:
SELECT * FROM ads ORDER BY approveby
Using ORDER BY is important to get the rows having the same value for approveby one next to the other. Then, in the PHP code you run through the returned result set, count the rows for each value of approveby, store it in another list or do whatever you want with it.
$howMany = array(); // store the number of rows in each group
$current = '--fake--'; // the current group (use a value that does not appear in the data)
$count = 0; // the number of rows in the current group
foreach ($result as $row) {
// Check if a new group started
if ($row['approvedby'] != $current) {
// Store the value for the current 'approveby'
$howMany[$current] = $count;
// Start counting for the new value of 'approvedby'
$current = $row['approveby'];
$count = 0;
}
// Count the new occurrence
$count ++;
}
// Store the number of rows from the last group
$howMany[$current] = $count;
// Remove the fake value inserted on the first loop
unset($howMany['--fake--']); // Use the initial value of $current
If you don't need the number of rows in each group before enumerating the rows in the group then your code becomes even simpler: remove from the code above all the references to variable $howMany.

Mysql Really get rows from result?

Good day.
For page navigation useally need use two query:
1) $res = mysql_query("SELECT * FROM Table");
-- query which get all count rows for make links on previous and next pages, example <- 2 3 4 5 6 ->)
2) $res = mysql_query("SELECT * FROM Table LIMIT 20, $num"); // where $num - count rows for page
Tell me please really use only one query to database for make links on previous and next pages ( <- 2 3 4 5 6 -> ) and output rows from page (sql with limit) ?
p.s.: i know that can use two query and SELECT * FROM Table LIMIT 20 - it not answer.
If you want to know how many rows would have been returned from a query while still using LIMIT you can use SQL_CALC_FOUND_ROWS and 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:
$res = mysql_query("SELECT SQL_CALC_FOUND_ROWS, * FROM Table");
$count_result = mysql_query("SELECT FOUND_ROWS() AS found_rows");
$rows = mysql_fetch_assoc($rows);
$total_rows = $rows['found_rows'];
This is still two queries (which is inevitable) but is lighter on the DB as it doesn't actually have to run your main query twice.
Many database APIs don't actually grab all the rows of the result set until you access them.
For example, using Python's built-in sqlite:
q = cursor.execute("SELECT * FROM somehwere")
row1 = q.fetchone()
row2 = q.fetchone()
Of course the library is free to prefetch unknown number of rows to improve performance.

How can I "order by" only the LIMIT results in a mysql Query?

Hi I need to get the results and apply the order by only in the limited section. You know, when you apply order by you are ordering all the rows, what I want is to sort only the limited section, here is an example:
// all rows
SELECT * FROM users ORDER BY name
// partial 40 rows ordered "globally"
SELECT * FROM users ORDER BY name LIMIT 200,40
The solution is:
// partial 40 rows ordered "locally"
SELECT * FROM (SELECT * FROM users LIMIT 200,40) AS T ORDER BY name
This solution works well but there is a problem: I'm working with a Listview component that needs the TOTAL rows count in the table (using SQL_CALC_FOUND_ROWS). If I use this solution I cannot get this total count, I will get the limited section count (40).
I hope you will give me solution based on the query, for example something like: "ORDER BY LOCALLY"
Since you're using PHP, might as well make things simple, right? It is possible to do this in MySQL only, but why complicate things? (Also, placing less load on the MySQL server is always a good idea)
$result = db_query_function("SELECT SQL_CALC_FOUND_ROWS * FROM `users` LIMIT 200,40");
$users = array();
while($row = db_fetch_function($result)) $users[] = $row;
usort($users,function($a,$b) {return strnatcasecmp($a['name'],$b['name']);});
$totalcount = db_fetch_function(db_query_function("SELECT FOUND_ROWS() AS `count`"));
$totalcount = $totalcount['count'];
Note that I used made-up function names, to show that this is library-agnostic ;) Sub in your chosen functions.

Perl MySQL - How do I return a tables last row id without looping through whole table?

my $sth = $dbh->prepare("SELECT id
FROM user
WHERE group == '1'
ORDER BY id DESC
LIMIT 1");
I was trying to get the id of the last row in a table without reading the whole table.
I am already accessing via:
my $sth = $dbh->prepare("SELECT name,
group
FROM user
WHERE group == '1'
LIMIT $from, $thismany");
$sth->execute();
while(my ($name,$group) = $sth->fetchrow_array()) {
...and setting up a little pagination query as you can see.
But, I am trying to figure out how to detect when I am on the last (<= 500) rows so I can turn off my "next 500" link. Everything else is working fine. I figured out how to turn off the "previous 500" link when on first 500 page all by myself!
I thought I would set up a "switch" in the while loop so if ($id = $last_id) I can set the "switches" var.
Like:
if ($id = $last_id) {
$lastpage = 1; #the switch
}
So I can turn off next 500 link if ($lastpage == 1).
I am really new to this and keep getting stuck on these types of things.
Thanks for any assistance.
Try to grab an extra row and see how many rows you really got. Something like this:
my #results = ( );
my $total = 0;
my $sth = $dbh->prepare(qq{
SELECT name, group
FROM user
WHERE group = ?
LIMIT ?, ?
});
$sth->execute(1, $from, $thismany + 1);
while(my ($name, $group) = $sth->fetchrow_array()) {
push(#results, [$name, $group]); # Or something more interesting.
++$total;
}
$sth->finish();
my $has_next = 0;
if($total == $thismany + 1) {
pop(#results);
$has_next = 1;
}
And BTW, please use placeholders in all of your SQL, interpolation is fraught with danger.
Always asking for one more row than you are going to show, as suggested by mu is too short, is a good way.
But if you want to take the other suggested approach of doing two separate queries, one to get the desired rows, and one to get the total count if there had not been a limit clause, MySQL provides an easy way to do that while combining as much of the work as possible:
SELECT SQL_CALC_FOUND_ROWS name, group FROM user WHERE group = '1' LIMIT ..., ...;
then:
SELECT FOUND_ROWS();
The SQL_CALC_FOUND_ROWS qualifier changes what a following FOUND_ROWS() returns without requiring you to do a whole separate SELECT COUNT(*) from user WHERE group = '1' query.
SELECT COUNT(*) from tablename will give you the number of rows, so if you keep a running count of how many rows you have read so far, you'll know when you're on the last page of results.
You could generate that query with (untested; away from a good workstation at the moment):
my $sth = $dbh->prepare("select COUNT(*) FROM user WHERE group == '1'");
my #data = $sth->fetchrow_array;
my $count = $data->[0];
(PS. you should be aware of SQL injection issues -- see here for why.)
As Ether mentioned in the comments, pagination usually requires two queries. One to return your paged set, the other to return the total number of records (using a COUNT query).
Knowing the total number of records, your current offset and the number of records in each page is enough data to work out how many pages there are in total and how many before and after the current page.
Although your initial suggestion of SELECT id FROM table WHERE ... ORDER BY id DESC LIMIT 1 should work for finding the highest matching ID, the standard way of doing this is SELECT max(id) FROM table WHERE ...