I'm trying to build a month graphic using a MySQL query. I'm checking how many rows there are in a table for each month in a single query using the UNION command. Example with 3 months bellow:
$query =
"SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 1), 0) AS total UNION
SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 2), 0) AS total UNION
SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 3), 0) AS total";
$stats_query = mysqli_query ($db_connection, $query);
$result = "";
while ($row = mysqli_fetch_assoc($stats_query)) {
$result .= $row['total'].",";
}
echo ($result);
// OUTPUT: 0,176,68,
As you can see, I'm telling mysql to return me a "0" in case there are no rows for that month (which is the case for January).
There are a total of 12 SELECTS in that query (I copied just 3 to save space), one for each month. Some months will return a value, others won't (which the IFNULL should then convert to a "0").
My final output, for all the 12 months, should look like this:
// OUTPUT: 0,176,68,0,0,0,0,0,12,15,176,43,
BUT... if there is more than one SELECT that returns no rows, the query won't add another "0" to the result. My final result ends up being like this:
// OUTPUT: 0,176,68,12,15,176,43,
It's like the IFNULL is only executed once, even though he's present in all the 12 SELECTS...
Am I doing something wrong? Can anyone spot an error in my code or something?
Thank you!
Use UNION ALL instead of UNION to get all results:
SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 1), 0) AS total UNION ALL
SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 2), 0) AS total UNION ALL
SELECT IFNULL((SELECT SUM(score) FROM statistics WHERE MONTH(date) = 3), 0) AS total
UNION returns only DISTINCT rows.
From doc:
The default behavior for UNION is that duplicate rows are removed from
the result.
Related
I try to count how many were sold by the month and get one Array.
$q = SELECT COUNT(id) AS January FROM tableName WHERE status='sold' AND month = '1'
UNION
SELECT COUNT(id) AS February FROM tableName WHERE status='sold' AND month = '2'
$row = mysqli_fetch_array(mysqli_query($connection, $q));
print_r($row);
UNION don't work, when I'm trying print_r(array), I get result
[Jenuary] => 200
How do I get one array in both months?
I want result:
[January] => 200,
[February] => 221
You can try this query to get exact result you want.
select MONTHNAME(STR_TO_DATE(month, '%m')) as month, count(*) cnt
from tablename
where status = 'sold'
group by month
Only for specific month
select MONTHNAME(STR_TO_DATE(month, '%m')) as month, count(*) cnt
from tablename
where status = 'sold' and month in(1, 2)
group by month
I think you want conditional aggregation:
select sum(month = 1) as january, sum(month = 2) as february
from tablename
where status = 'sold' and month in (1, 2)
This generates just one row, with two columns called january and february, each containing the number of rows for that month.
Or maybe you want one row per month. In that case you can use simple aggregation:
select month, count(*) cnt
from tablename
where status = 'sold' and month in (1, 2)
group by month
I have a database with colums I am working on. What I am looking for is the date associated with the row where the SUM(#) reaches 6 in a query. The query I have now will give the date when the number in the colum is six but not the sum of the previous rows. example below
Date number
---- ------
6mar16 1
8mar16 4
10mar16 6
12mar16 2
I would like to get a query to get the 10mar16 date because on that date the number is now greater than 6. Earlier dates wont total up to six.
Here is an example of a query i have been working on:
SELECT max(date) FROM `numbers` WHERE `number` > 60
You could use this query, which tracks the accumulated sum and then returns the first one that meets the condition:
select date
from (select * from mytable order by date) as base,
(select #sum := 0) init
where (#sum := #sum + number) >= 6
limit 1
SQL Fiddle
Most databases support ANSI standard window functions. In this case, cumulative sum is your friend:
select t.*
from (select t.*, sum(number) over (order by date) as sumnumber
from t
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
In MySQL, you need variables:
select t.*
from (select t.*, (#sumn := #sumn + number) as sumnumber
from t cross join (select #sumn) params
order by date
) t
where sumnumber >= 10
order by sumnumber
fetch first 1 row only;
Awesome!!!! It seems to be working great. Here is the code that I used.
SELECT date, id, crewname
FROM (select * FROM flightrecord WHERE `crewname` = 'brayn'
ORDER BY dutyTimeArrive DESC) as base,
(select #sum := 0) init
WHERE (#sum := #sum + tankDropCount) >= 6
limit 1
I have multiple tables (same column structures) which I want to have the count rows between 2 date ranges. Right now it gives me all records from each table. Each table have around 700K records, which makes it also slow to search through.
I'm not getting the right query for this.
The below query is what I have so far.
$monday = '2018-01-15';
$tuesday = '2018-01-16';
SELECT count(datetime) FROM uptime_m1 WHERE datetime
BETWEEN '$monday' and'$tuesday' and status = 'idle'
UNION ALL
SELECT (datetime) FROM uptime_m2 WHERE datetime
BETWEEN '$monday' and'$tuesday' and status = 'idle';
PHP CODE:
if ($result = $mysqli->query("
SELECT count(datetime) FROM uptime_m1 WHERE datetime
BETWEEN '$monday' and'$tuesday' and status = 'idle'
UNION ALL
SELECT (datetime) FROM uptime_m2 WHERE datetime
BETWEEN '$monday' and'$tuesday' and status = 'idle';
")) {
$row_cnt = $result->num_rows;
$count_m1_idle_monday = $row_cnt;
$result->close();
}
You can use a subrequest to count each table and sum the result.
select
(select count(*) from uptime_m1
where datetime between '$monday' and '$tuesday' and status = 'idle')
+ (select count(*) from uptime_m2
where datetime between '$monday' and '$tuesday' and status = 'idle')
as nbrows;
Disclaimer: I am aware that I have in my code deprecated mysql functions. That is on my todo list.
I have a MySql select giving me seasons for diferent items in a house booking system.
Kind of:
Low season: 2010-01-01, 2010-03-01, 100 //meaning start,end,price
This comes in my first sql:
while($season_row=mysql_fetch_assoc($season_res)){
$seasonsArray[$season_row['id_item']][] = array(
$season_row['season_start'],
$season_row['season_end'],
$season_row['daily_price']
);
}
The dates are defined here (arriving to the function as YYYY-mm-dd):
function seasonPrice($from,$to,$requested_item){
$start = round(strtotime($from)/86400)*86400; // like 2008-01-01
$end = round(strtotime($to)/86400)*86400; // to 2015-01-01
$formattedStart = date('Y-m-d', $start);
$formattedEnd = date('Y-m-d', $end);
Now I need to loop between 2 dates, between the items of the $seasonsArray and then check the price of that item in that specific day.
I did this with:
foreach($seasonsArray as $item=>$value){
for( $thisDay = $start; $thisDay < $end; $thisDay = $thisDay + 86400){
foreach($value as $innerValue){
$season_start = roundToSeconds($innerValue[0]);
$season_end = roundToSeconds($innerValue[1]);
if($thisDay >= $season_start && $thisDay <= $season_end) {
$foundPrice[] = round($innerValue[2]);
}
}
$thisSerie[] = array($thisDay * 1000, isset($foundPrice) ? $foundPrice[0] : 0);
// security check to avoid double assigned seasons to same day
if(count($foundPrice) > 1){ die('There is double bookings in item: '.$item);}
unset($foundPrice);
}
$seasonPrices[] = array(
'data'=> $thisSerie,
'label'=> 'House ID: '.$item,
);
}
But I get: Fatal error: Allowed memory size of 100663296 bytes exhausted
Any suggestion on where my code can be improved to not need so much memory? Or is there a bug and I don't see it?
I'd generate a range of days and join against your seasons table, and use a single query to get the desired resulset, e.g.:
SELECT dates.Date,
coalesce(s.price, 0) AS price
FROM
(SELECT a.Date
FROM
( SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Date, '0' AS price
FROM
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS a
CROSS JOIN
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS b
CROSS JOIN
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS c) a
WHERE a.Date BETWEEN '$from' AND '$to'
ORDER BY a.Date) dates
LEFT JOIN seasons s ON dates.Date BETWEEN s.start AND s.END
The complicated inner query avoids the creation of a temp table (taken from generate days from date range) and works for up to 1000 days, but creating a temp table would be fine.
It looks to be like you are never leaving the for loop. Where do $start and $end get set. Verify their values by printing them out.
As far as optimization, there is no need to be looping through each day. Skip the for loop that counts days and use $season_start and $season_end to calculate the day in the second foreach loop.
In fact, there is a bug right now unless $session_start and $session_end are always more than a day apart, because sometime the event will happen in between the 24 hour periods you are looping by.
So, I am fetching some data from MySQL with that code:
<?
$query=mysql_query("SELECT date,COUNT(*) as num FROM downloads WHERE prjID='".$_GET['id']."' GROUP BY date ORDER BY date ASC");
$num=mysql_num_rows($query);
$res='';
$i=0;
while($row=mysql_fetch_array($query)){
$i++;
$date=date("d.m.Y", strtotime($row['date']));
$dan=date("d", strtotime($row['date']));
$mesec=date("m", strtotime($row['date']));
$leto=date("Y", strtotime($row['date']));
if($i=1){
$danPrvi=$leto.", ".($mesec-1).", ".$dan;
$dan1=date("d", strtotime(time()));
$mesec1=date("m", strtotime(time()));
$leto1=date("Y", strtotime(time()));
$danZadnji=$leto1.", ".($mesec1-1).", ".$dan1;
}
$numb=1;
if($row['num']!=1){
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$row['num']."], ";
}
else{
if($i!=$num){
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$numb."], ";
}
else{
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$numb."]";
}
}
}
?>
And I get results like that:
1.3.2013 - 1
6.3.2013 - 5
But I would like to get results like that:
1.3.2013 - 1
2.3.2013 - 0
3.3.2013 - 0
4.3.2013 - 0
5.3.2013 - 0
6.3.2013 - 1
I am also using Highcharts, so date values must be formatted like Date.UTC(year, month-1, day)
EDIT:
I don't have all dates in my databases. In my examle, there are only 1.3.2013 and 6.3.2013, so how would I detect and set 0 value for all dates between, that don't have a value >=1?
I imagine what you need to do is set up a construct to hold the dates you are interested in and either use that in your query or post processing the query data. For example:
<?php
$query=mysql_query("SELECT date,COUNT(*) as num FROM downloads WHERE prjID='".$_GET['id']."' GROUP BY date ORDER BY date ASC");
$num=mysql_num_rows($query);
// Get the first and last dates in the result set
$firstRow = mysql_result($query, 0);
$lastRow = mysql_result($query, $num-1);
// Now make thos the begin and end dates
$beginDate = new DateTime(strtotime($firstRow['date']));
$endDate = new DateTime(strtotime($lastRow['date']));
$currentDate = $beginDate;
$interestingDates = array();
// Populate our interestingDates array with all counts set to 0
while ($currentDate <= $endDate){
$interestingDates[$currentDate->format('d.m.Y')] = 0;
$currentDate->add(new DateInterval('P1D'));
}
// Reset the data result for looping over
mysql_data_seek($query,0);
while($row=mysql_fetch_array($query)){
// Go ahead and format the string
$formatedString = date("d.m.Y", strtotime($row['date']));
// If the string is in our interestingDates array, update the count
if (array_key_exists($formatedString, $interestingDates)){
$interestingDates[$formatedString] = $row['num'];
}
}
// Print it out
foreach ($interestingDates as $key=>$value){
print "$key - $value\n";
}
NOTE 1: mysql_query is deprecated as of PHP 5.5.0 and will be removed in the future. Pleasue use another API - I recommend pdo_mysql.
NOTE 2: The current query is not parameterized. Using PDO, this would perhaps look like:
$sth = $dbh->prepare('SELECT date,COUNT(*) as num FROM downloads WHERE prjID= :prjID GROUP BY date ORDER BY date ASC');
$sth->bindParam(':prjID', $_GET['id'], PDO::PARAM_INT);
$sth->execute();
Disclaimer - I haven't actually run this code, I just wrote it off the top of my head. You might need to test/debug it.
Assuming you have data for all dates, just not for a particular id, you can do a condition sum:
SELECT date, sum(case when prjID='".$_GET['id']."' then 1 else 0 end) as num
FROM downloads
GROUP BY date
ORDER BY date ASC
WARNING! It's a very bad style to do it in SQL, but if you want -- you can :)
http://sqlfiddle.com/#!2/b491a/1/0
SELECT dynamic_date AS date, COUNT(downloads.date) AS num FROM (
SELECT DATE_ADD(start, INTERVAL i - 1 DAY) AS dynamic_date, prjID FROM
(
SELECT #i := #i + 1 AS i FROM
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT #i:=0) init
) i_sequence,
(SELECT MIN(date) start, MAX(date) finish, prjID FROM downloads WHERE prjID=5 GROUP BY prjID) minmax
WHERE DATE_ADD(start, INTERVAL i - 1 DAY) <= finish
) date_sequence
LEFT JOIN downloads ON dynamic_date = downloads.date and downloads.prjID=date_sequence.prjID
GROUP BY dynamic_date
;