I'm new here. So, be easy on me. I have a table called 'data' in which one of the columns is 'date' stored in YYYY-MM-DD format. Here is the code that I have been working on. Basically, what I want to do is compare if two dates stored in my table are equal. But, each time I run the code, I keep getting error: Undefined offset right where the code says $lisdate[$i+1]. How do I compare the dates stored in a table? Thank you.
My code
$sth2 = mysql_query("SELECT * FROM data WHERE dest_name='www.myren.net.my'");
while($rowstemp = mysql_fetch_assoc($sth2))
{
$lisdate[] = $rowstemp['date'];
$lisaverage[] = $rowstemp['avg_rtt'];
}
$rows = mysql_num_rows($sth2);
$addAverage[0] = $lisaverage[0];
$numbers = 0;
$j = 0;
for($i = 0; $i<$rows; $i++)
{
if($lisdate[$i] == $lisdate[$i+1])
{
$addAverage[$j] = $addAverage[$j] + $lisaverage[$i+1];
}
else
{
$j++;
$numbers = $numbers +1;
}
}
Gordon has it right. Your for loop is overrunning the end of your array because you're working with consecutive pairs of rows. Change it to read ; $i < $rows - 1; to correct this problem.
But, you have another problem. SQL rows in result sets have an unpredictable order unless your query includes an ORDER BY clause. If these rows, without that clause, appear in ascending order by date, it's dumb luck. Put ORDER BY date in your query.
It's not entirely clear what that code in the question is attempting to achieve.
(As Ollie pointed out in his answer, it bears repeating: the order that MySQL returns the rows is guaranteed ONLY if there's an ORDER BY clause on the query. The result from a "duplicate date" check that's being performed in the code is only going to detect a "duplicate" date value if it appears on contiguous rows.)
We see the code loading a couple of arrays, using the date and avg_rtt column values returned by the query. Then there's some manipulation on the array... the end result will be another it's not clear what the actual intent of that rigmarole is, what it's actually trying to achieve.
If there are any rows It looks like there's going to be another array... if there are no "duplicate" date values found, it's going to have a single element (with the value of rtt_avg from the first row), and $numbers will be set to the number of elements in the original array.
If there is a "duplicate" date values found, the results from the code seem very odd to me, a sparsely populated array. Why?
Personally, I'd be looking to get an actual statement of the specification, and have the database do that processing for me, rather than mucking through two arrays.
If what we want is set of values with no duplicated date values, I'd use a GROUP BY and some aggregation, for example:
SELECT d.dest_name
, d.date
, AVG(d.avg_rtt) AS avg_avg_rtt
, MAX(d.avg_rtt) AS max_avg_rtt
, MIN(d.avg_rtt) AS min_avg_rtt
, SUM(d.avg_rtt) AS sum_avg_rtt
, COUNT(1) AS cnt
, COUNT(d.avg_rtt) AS cnt_avg_rtt
, COUNT(DISTINCT d.avg_rtt) AS cnt_distinct_avg_rtt
FROM data d
WHERE d.dest_name = 'www.myren.net.my'
GROUP
BY d.dest_name
, d.date
ORDER
BY d.dest_name
, d.date
If I was looking for just a count of distinct date values, like what $numbers is going to contain, then just:
SELECT d.dest_name
, COUNT(DISTINCT d.date) AS cnt_distinct_date
FROM data d
WHERE d.dest_name = 'www.myren.net.my'
GROUP BY d.dest_name
ORDER BY d.dest_name
The "looping through array" that the original query just looks like an odd way to achieve a result, whatever that result is supposed to be.
Related
I've got a products table that I'm trying to get to work. The query brings back results; however, it isn't actually using the ORDER BY FIELD to sort the results. It's skipping it somehow. I even tried ORDER BY FIELD(sc.id,'4','5','6'), and that didn't work either.
Is it even possible to use table_name.column in an ORDER BY FIELD()? Is there an alternative or a better method of doing this query?
$product = $db1q->query("
SELECT p.id, p.name, p.image, p.url,p.subcat as subcat, sc.id as scid,sc.name as scname
FROM Product as p
JOIN Product_Sub_Category as sc ON p.subcat = sc.id
WHERE p.visibility='1' AND find_in_set(p.id,'". $sidr['products'] ."')
ORDER BY FIELD(p.subcat,'4','5','6'), sc.sort_order ASC, p.sort_order ASC")
or die ('Unable to execute query. '. mysqli_error($db1q));
I just dumbed the query down to the basic level....
$product = $db1q->query("
SELECT id, name, image, url,subcat
FROM Product WHERE visibility='1' AND id IN ({$sidr['products']}) ORDER BY FIELD(subcat,'5','4','6','22')") or die ('Unable to execute query. '. mysqli_error($db1q));
and for some reason the order of my subcats are as follows....
3,12,23,5,5,4,4,4,4,4,22
Why wouldn't they begin with 5, 4, 6(doesn't exist), and 22? Then display 3,12, and 23 after those are first....
Simple Rextester Demo
When datatype is numeric don't compare to 'string' values
eg visibility = '1' if visibility is numeric you really shouldn't have the apostrophes around it. same in the field function given subcat.
$product = $db1q->query("SELECT id, name, image, url,subcat
FROM Product
WHERE visibility='1'
AND id IN ({$sidr['products']})
ORDER BY case when subcat in (5,4,6,22) then 0 else 1 end,
FIELD(subcat,5,4,6,22)
") or die ('Unable to execute query. '. mysqli_error($db1q));
or something like:
order by case when field(sort,'5','4','22') = 0 then (select max(sort)+1+sort from Product)
else field(sort,'5','4','22') end;
The issue with the 2nd approach is that it has to run a subquery for every record. In addition if the size of subcat/sort exceed or approach the max of int we'll run into a problem adding the values together. This problem is negated by using the 2 column sort approach in the first method.
Again, my gut feeling is that the first approach with 2 sort columns would be faster; and in my opinion easier to follow/maintain. The downfall is if the sort order defined changes then we have to change code. So... why have the order defined here... what isn't the order defined in a table; or is the order passed in as a parameter by user?
My following query not return row when COUNT(*) result return 0 row
SELECT COUNT(id) test,
UNIX_TIMESTAMP(my_time)*1000 d
FROM chat_table
WHERE dept_id = 5
AND my_time >="2015-03-30"
GROUP BY DAY(my_time)
ORDER BY d ASC;
I need GROUP BY day even return 0 row. How can left join in same table? or any idea?
You can only have groups based on values that exist. If a value, for example, timestamp '2015-04-01 12:00:00' does not exist in your table, it cannot be part of the result.
You can work around this by:
handling the results of the query in a programming language, supplementing count 0 for all rows that are missing
OR creating a reference table that contains all timestamps
OR manually injecting all timestamps using a subquery and union.
The best option is the first: keep your query as it is, but write logic in the program that starts the query to automatically assume that the count is zero if the timestamp is not in the result set.
For example, in PHP:
$countsPerTimestamp = array();
foreach ($stmt->fetchAll() as $row) {
$countsPerTimestamp[$row['d']] = $row['test'];
}
$searchTimestamp = '2015-04-05 12:13:14';
if (isset($countsPerTimestamp[$searchTimestamp])) {
echo 'Count for now is: ' . $countsPerTimestamp[$searchTimestamp];
}
else {
echo 'Count for now is: 0';
}
If your table has data on every day but the where clause is filtering it out, then the easiest solution is to use conditional aggregation:
SELECT SUM(dept_id = 5) as test,
UNIX_TIMESTAMP(my_time)*1000 as d
FROM chat_table
WHERE my_time >= '2015-03-30'
GROUP BY DAY(my_time)
ORDER BY d ASC;
If those conditions are not true, then you need to start with a list of all the dates and use left join.
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.
I'm using this kind of queries with different parameters :
EXPLAIN SELECT SQL_NO_CACHE `ilan_genel`.`id` , `ilan_genel`.`durum` , `ilan_genel`.`kategori` , `ilan_genel`.`tip` , `ilan_genel`.`ozellik` , `ilan_genel`.`m2` , `ilan_genel`.`fiyat` , `ilan_genel`.`baslik` , `ilan_genel`.`ilce` , `ilan_genel`.`parabirimi` , `ilan_genel`.`tarih` , `kgsim_mahalleler`.`isim` AS mahalle, `kgsim_ilceler`.`isim` AS ilce, (
SELECT `ilanresimler`.`resimlink`
FROM `ilanresimler`
WHERE `ilanresimler`.`ilanid` = `ilan_genel`.`id`
LIMIT 1
) AS resim
FROM (
`ilan_genel`
)
LEFT JOIN `kgsim_ilceler` ON `kgsim_ilceler`.`id` = `ilan_genel`.`ilce`
LEFT JOIN `kgsim_mahalleler` ON `kgsim_mahalleler`.`id` = `ilan_genel`.`mahalle`
WHERE `ilan_genel`.`ilce` = '703'
AND `ilan_genel`.`durum` = '1'
AND `ilan_genel`.`kategori` = '1'
AND `ilan_genel`.`tip` = '9'
ORDER BY `ilan_genel`.`id` DESC
LIMIT 225 , 15
and this is what i get in explain section:
these are the indexes that i already tried to use:
any help will be deeply appreciated what kind of index will be the best option or should i use another table structure ?
You should first simplify your query to understand your problem better. As it appears your problem is constrained to the ilan_gen1 table, the following query would also show you the same symptoms.:
SELECT * from ilan_gene1 WHERE `ilan_genel`.`ilce` = '703'
AND `ilan_genel`.`durum` = '1'
AND `ilan_genel`.`kategori` = '1'
AND `ilan_genel`.`tip` = '9'
So the first thing to do is check that this is the case. If so, the simpler question is simply why does this query require a file sort on 3661 rows. Now the 'hepsi' index sort order is:
ilce->mahelle->durum->kategori->tip->ozelik
I've written it that way to emphasise that it is first sorted on 'ilce', then 'mahelle', then 'durum', etc. Note that your query does not specify the 'mahelle' value. So the best the index can do is lookup on 'ilce'. Now I don't know the heuristics of your data, but the next logical step in debugging this would be:
SELECT * from ilan_gene1 WHERE `ilan_genel`.`ilce` = '703'`
Does this return 3661 rows?
If so, you should be able to see what is happening. The database is using the hepsi index, to the best of it's ability, getting 3661 rows back then sorting those rows in order to eliminate values according to the other criteria (i.e. 'durum', 'kategori', 'tip').
The key point here is that if data is sorted by A, B, C in that order and B is not specified, then the best logical thing that can be done is: first a look up on A then a filter on the remaining values against C. In this case, that filter is performed via a file sort.
Possible solutions
Supply 'mahelle' (B) in your query.
Add a new index on 'ilan_gene1' that doesn't require 'mahelle', i.e. A->C->D...
Another tip
In case I have misdiagnosed your problem (easy to do when I don't have your system to test against), the important thing here is the approach to solving the problem. In particular, how to break a complicated query into a simpler query that produces the same behaviour, until you get to a very simple SELECT statement that demonstrates the problem. At this point, the answer is usually much clearer.
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 ...