MySQL Query Problem with INTERVAL, need 0 if no data provided - php

i have the following statement:
SELECT
count(rs.rsc_id) as counter
FROM shots as rs
where rsc_rs_id = 345354
AND YEAR(rs.timestamp) = YEAR(DATE_SUB(CURDATE(), INTERVAL 6 MONTH))
GROUP BY DATE_FORMAT(rs.timestamp,'%Y%m')
rs.timestamp is a unix timestamp
Output would be like for each row / month a numeric like '28'
It Works fine, but if i have inconsistent data, like only for the past three month (not for all six month), i get no return from my Database. I would like to have every time there is not data for this month, 0 returned...
any suggestion?
i thought about some case statements, but this seems not so good...
thanks!!

For only 6 months, a date table seems unnecessary, although this looks complicated (it really isn't!)
SELECT DATE_FORMAT(N.PivotDate,'%Y%m'), count(rs.rsc_id) as counter
FROM (
select ADDDATE(CURDATE(), INTERVAL N MONTH) PivotDate
FROM (
select 0 N union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6) N) N
LEFT JOIN shots as rs
ON rsc_rs_id = 345354
AND DATE_FORMAT(N.PivotDate,'%Y%m')=DATE_FORMAT(FROM_UNIXTIME(rs.timestamp),'%Y%m')
GROUP BY DATE_FORMAT(N.PivotDate,'%Y%m')

In such cases it's common to use a table of dates with all dates (e.g. from 1/1/1970 to 31/12/2999) and LEFT JOIN your data to that table.
See an example in the answer here: mysql joins tables creating missing dates
If you create a dates table you can use:
SELECT
DATE_FORMAT(d.date,'%Y%m') AS `month`, count(rs.rsc_id) AS `counter`
FROM dates d
LEFT JOIN shots as rs
ON d.date = FROM_UNIXTIME(rs.timestamp)
AND rs.rsc_rs_id = 345354
WHERE d.date > DATE_SUB(CURDATE(), INTERVAL 5 MONTH)
AND d.date < CURDATE()
GROUP BY DATE_FORMAT(d.date,'%Y%m');

Related

How to edit this query to get last 7 days visits even if there are no visits on specific days? [duplicate]

This question already has answers here:
MySQL how to fill missing dates in range?
(6 answers)
MySQL: Select All Dates In a Range Even If No Records Present
(6 answers)
Closed 2 years ago.
I have the following query that returns the dates first_visit starting from today and 7 days back, as well as the visitors hash per day:
SET time_zone= '{$company_timezone}';
SELECT DATE( first_visit ) AS day , COUNT( DISTINCT hash ) AS total
FROM table
WHERE company = 1 and first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day
The flaw with this is that if company = 1 have visitors only today and three days ago, I will get this:
day --------- total
2020-03-08 ----- 30
2020-03-05 ----- 40
leaving out all other dates inbetween.
What I want is to get all the past 7 days, even there are no visitors at all. If there are no visitors, then it should just show 0.
How to edit my query in order to achieve this?
Thank you
Perform an outer join with a derived table that contains desired dates:
select b.date as day, count(distinct hash) as total
from table
right join (select #now := #now - interval 1 day as date from (select #now := curdate()) a, table limit 7) b
on b.date = date(first_visit) and company = 1
group by b.date
This assumes that table has at least 7 rows.
Note: there are two occurrences of table.
If you have data for each day -- but not for that company -- then conditional aggregation is a pretty simply approach:
SELECT DATE( first_visit ) AS day ,
COUNT( DISTINCT CASE WHEN company = 1 THEN hash END ) AS total
FROM table
WHERE first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day;
This only works if all days are represented in your table for some company.
Some solutions involve a table of numbers.
Here is one way with a recursive query, available in MySQL 8.0:
with d as (select 0 n union all select n + 1 where n < 6)
select
current_date - interval n day myday,
count(distinct t.hash) total
from d
left join mytable t
on t.company = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n
In earlier version, you can enumerate the numbers as a derived table:
select
current_date - interval n day myday,
count(distinct t.hash) total
from (
select 0 n 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
) d
left join mytable t
on t.compatny = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n

Pivot query to display data for each day of the year

I have one database table as following:
id|start_date|end_date|sec
1|2018-08-01|2018-08-03|2500
2|2018-08-02|2018-08-13|100
3|2018-08-01|2018-08-05|500
So I want to display the report date wise so that user can know how many seconds available in specific days.
For example, I need below things:
Date /Day total
2018-08-01 (2500+500)=3000 //this date comes in 1&3 records
2018-08-02 (2500+100+500)=3100 //this date comes in all 1,2,&3 records
2018-08-03 (2500+100+500)=3100 //this date comes in all 1,2,&3 records
2018-08-04 (100+500)=600 //this date comes in 2&3 records
2018-08-05 (100+500)=600 //this date comes in 2&3 records
2018-08-06 (100)=100 //this date comes in 3 records
I am trying to use mysql and php but I don't know how to do.
There is no clean way to generate a table with each day of current year on the fly, I took the subtable code from generate days from date range (to give him credit) because it didn't use any loops etc and had a short execution time. That being said, you can go with :
SELECT
dpy.day as Day,
SUM(IFNULL(t.sec,0)) as total
FROM
(
select a.Date as day
from (
select DATE_FORMAT(CURRENT_DATE(), '%Y-12-31') - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
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 DATE_FORMAT(CURRENT_DATE(), '%Y-01-01') and DATE_FORMAT(CURRENT_DATE(), '%Y-12-31')
) dayPerYear dpy
LEFT JOIN
your_table t ON dpy.day > t.start_date AND dpy.day < t.end_date
GROUP BY dpy.day
Just replace your_table by your table name
Lets begin by assuming that your records are already fetched and are available in an associative array named $rows (after a query and a mysqli_fetch_array type call):
$ra=array();
$nr=count($rows);
for ($i=0; $i<$nr; $i++) {
$start=$rows['start_date'];
$end=$rows['end_date'];
$sec=$rows['sec'];
if (isset($ra[$start])) {
$ra[$start]+=$sec;
} else {
$ra[$start]=$sec;
}
if (isset($ra[$end])) {
$ra[$end]+=$sec;
} else {
$ra[$end]=$sec;
}
}
print_r($ra);
You have to loop through days
select sum(sec) from table where ('2018-08-01') between start_date and end_date ;
For generating the days With php, populate an array of days in current month

Min value from Database in MySQL

Am trying to find the min value from past 30 days, in my table there is one entry for every day, am using this query
SELECT MIN(low), date, low
FROM historical_data
WHERE name = 'bitcoin'
ORDER BY STR_TO_DATE(date,'%d-%m-%Y') DESC
LIMIT 7
But this value not returing the correct value. The structure of my table is
Table structure
And table data which is store is like this
Table data style
Now what i need is to get the minimum low value. But my query not working it give me wrong value which even did not exist in table as well.
Updates:
Here is my updated Table Structure.
enter image description here
And here is my data in this table which look like this
enter image description here
Now if you look at the data, i want to check the name of token omisego and fatch the low value from past 7 days which will be from 2017-12-25 to 2017-12-19
and in this cast the low value is 9.67, but my current query and the query suggested by my some member did not brings the right answer.
Update 2:
http://rextester.com/TDBSV28042
Here it is, basically i have more then 1400 coins and token historical data, which means that there will me more then 1400 entries for same date like 2017-12-25 but having different name, total i have more then 650000 records. so every date have many entries with different names.
To get the lowest row per group you could use following
SELECT a.*
FROM historical_data a
LEFT JOIN historical_data b ON a.name = b.name
AND a.low > b.low
WHERE b.name IS NULL
AND DATE(a.date) >= '2017-12-19' AND DATE(a.date) <= '2017-12-25'
AND a.name = 'omisego'
or
SELECT a.*
FROM historical_data a
JOIN (
SELECT name,MIN(low) low
FROM historical_data
GROUP BY name
) b USING(name,low)
WHERE DATE(a.date) >= '2017-12-19' AND DATE(a.date) <= '2017-12-25'
AND a.name = 'omisego'
DEMO
For last 30 day of 7 days or n days you could write above query as
SELECT a.*, DATE(a.`date`)
FROM historical_data2 a
LEFT JOIN historical_data2 b ON a.name = b.name
AND DATE(b.`date`) >= CURRENT_DATE() - INTERVAL 30 DAY
AND DATE(b.`date`) <= CURRENT_DATE()
AND a.low > b.low
WHERE b.name IS NULL
AND a.name = 'omisego'
AND DATE(a.`date`) >= CURRENT_DATE() - INTERVAL 30 DAY
AND DATE(a.`date`) <= CURRENT_DATE()
;
DEMO
But note it may return more than one records where low value is same, to choose 1 row among these you have specify another criteria to on different attribute
Consider grouping the same and running the clauses
SELECT name, date, MIN(low)
FROM historical_data
GROUP BY name
HAVING name = 'bitcoin'
AND STR_TO_DATE(date, '%M %d,%Y') > DATE_SUB(NOW(), INTERVAL 30 DAY);
Given the structure, the above query should get you your results.
// Try this code ..
SELECT MIN(`date`) AS date1,low
FROM historical_data
WHERE `date` BETWEEN now() - interval 1 month
AND now() ORDER by low ASC;

Select rows from MYSQL table where dates between two columns containing weekend

I want to select rows from a MYSQL table where dates between two columns start_date, end_date containing weekends
I tried found this but it is not helpful
My table columns are Name start_date end_date amount
I want to print name and amount if days between start_date and end_date containing Saturday and Sunday.
Use DAYNAME function. It gives Dayname of a particular date.
Try this,
select * from yourtable where
(DAYNAME(start_date) = 'Saturday' or DAYNAME(start_date) = 'Sunday') and
(DAYNAME(end_date) = 'Saturday' or DAYNAME(end_date) = 'Sunday');
Hope it will help.
P.S. I din't run this query.
From what I understand, you want to take two columns as start and end dates and count the number of weekend days between them. Is that correct?
This answer How to get list of dates between two dates in mysql select query has some SQL for getting a list of all dates in a range.
To build on that you could use that query as an inner join, selecting only the dates you need based on your start and end columns. Then count Sundays and Saturdays using DAYNAME.
Here's an example that gets close to what you should use. This is not exactly what you should use! This is only built for hard coding the dates (and very close to passing them in as parameters). You should adapt it by joining your table to it!. I didn't write it that way because: I'm lazy and didn't want to make a table for testing and demonstrating it, and (more importantly) I wanted to help you and not just hand over a solution.
select count(one_date), dayname(one_date) week_day from
(select * from
(select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) one_date from
(select 0 i 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) t0,
(select 0 i 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 i 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 i 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 0 i 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) t4) v ) as all_days
where one_date between '2016-10-01' and '2016-11-15' -- <---- DON'T HARDCODE THESE, JOIN YOUR TABLE TO THE all_days AND FILTER THE DAYS YOU WANT THAT WAY
group by week_day
having week_day in ('Sunday', 'Saturday');
This is a lengthy query, may slowdown the performance. But this can give you desired output
SELECT x.id,x.name,x.amount FROM
(SELECT name,amount,DATEDIFF(end_date,start_date)+1 AS totDays,
start_date AS day1,ADDDATE(start_date,1) AS day2,
ADDDATE(start_date,2) AS day3,ADDDATE(start_date,3) AS day4,
ADDDATE(start_date,4) AS day5,ADDDATE(start_date,5) AS day6,
end_date FROM your_table)X
WHERE x.totDays>5 OR (WEEKDAY(x.day1)>4 AND x.day1<=x.end_date) OR
(WEEKDAY(x.day2)>4 AND x.day2<=x.end_date) OR
(WEEKDAY(x.day3)>4 AND x.day3<=x.end_date) OR
(WEEKDAY(x.day4)>4 AND x.day4<=x.end_date) OR
(WEEKDAY(x.day5)>4 AND x.day5<=x.end_date) OR
(WEEKDAY(x.day6)>4 AND x.day6<=x.end_date);
Or try this,
SELECT name,amount FROM your_table WHERE
(DATEDIFF(end_date,start_date)+1) >5 OR
(WEEKDAY(start_date)>4 AND start_date<=end_date) OR
(WEEKDAY(ADDDATE(start_date,1))>4 AND ADDDATE(start_date,1)<=end_date) OR
(WEEKDAY(ADDDATE(start_date,2))>4 AND ADDDATE(start_date,2)<=end_date) OR
(WEEKDAY(ADDDATE(start_date,3))>4 AND ADDDATE(start_date,3)<=end_date) OR
(WEEKDAY(ADDDATE(start_date,4))>4 AND ADDDATE(start_date,4)<=end_date) OR
(WEEKDAY(ADDDATE(start_date,5))>4 AND ADDDATE(start_date,5)<=end_date);
Notes:
If DATEDIFF between start_date and end_date is greater than 5, then there will be weekends.
ADDDATE with start_date up to 5 and compare that with end_dateand if WEEKDAY of the output is greater than 4, then there will be weekends.
If you want to select rows where between start and end date there is a whole weekend (Saturday and Sunday together) you may use this query:
select name, amount FROM your_table WHERE
datediff(end_date,start_date) > 6
or
(datediff(end_date,start_date)+weekday(start_date)>=6
and weekday(start_date)<>6);
Assuming start_date and end_date inclusive datediff(end_date,start_date) for the whole week is equal 6, so if there is more days than one week we can be sure there is a weekend between. When there is less days between we have to check if the number of days starting from the given start weekday will cover the weekend but we can be sure that it's not true if the start day is Sunday (starting from Sunday six days later is Saturday but these two weekend days are not one after another).
In case you want to select rows where between start and end date there is at least one weekday (Saturday or Sunday) you can use this query
select name, amount FROM your_table WHERE
datediff(end_date,start_date)+weekday(start_date)>=5;

Filling Gaps in Dates Returned from Database - pure SQL solution possible?

I have this query:
SELECT COUNT(*) as clicks, DATE_FORMAT(FROM_UNIXTIME(click_date), '%w %M %Y') as point
FROM tracking
WHERE click_date < $end_date AND click_date > $start_date
GROUP BY DAY(FROM_UNIXTIME(click_date))
Where $start_date is two weeks ago and $end_date is today's date.
I am trying find all clicks made each day for a particular date range. I also want to include days where there has been no clicks. Since naturally there isn't an entry for these in my database I need to include them some how, how can I best do this whilst showing all dates from start date to end date. This what I currently have, lots of gaps for this two week date range.
Array
(
[0] => Array
(
[clicks] => 17
[point] => 0 February 2011
)
[1] => Array
(
[clicks] => 3
[point] => 1 February 2011
)
[2] => Array
(
[clicks] => 14
[point] => 5 February 2011
)
[3] => Array
(
[clicks] => 1
[point] => 1 February 2011
)
[4] => Array
(
[clicks] => 8
[point] => 2 February 2011
)
)
Can this possibly be done via a pure SQL query or do I have to use some php logic?
Btw, why do I have 0 February 2011 as my first date! Hmm, I also seem to have duplicate dates, that shouldn't happen, maybe my GROUP BY isn't working correctly?
Thanks all for any help.
Can this possibly be done via a pure SQL query or do I have to use some php logic?
Yes, it is better to create a Numbers table (single column N) that contains nothing but the numbers 0 to 999. It can be used for many things, not least a query like the below:
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(adddate($start_date, interval N day), '%d %M %Y') as point
FROM Numbers
LEFT JOIN tracking t
ON t.click_date >= adddate($start_date, interval N day)
and t.click_date < adddate($start_date, interval (N+1) day)
WHERE N between 0 and datediff($start_date, $end_date)
GROUP BY N
Btw, why do I have 0 February 2011 as my first date
You're using the wrong format. It's UPPER case W not lower for day-of-week, so '%W %M %Y' or '%d %M %Y' for day-of-month.
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format
maybe my GROUP BY isn't working correctly?
You are using GROUP BY DAY(FROM_UNIXTIME(click_date)) note "day" not weekday, but you are displaying (or trying to) "%W" (weekday) - pick one, don't mix them.
EDIT: If you prefer not to materialize (create as a real table) a Numbers sequence table, you can construct one on the fly. It won't be pretty.
Note: N1, N2 and N3 below combine to give a possible range of 0-999
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(adddate($start_date, interval N day), '%d %M %Y') as point
FROM (
select N1 * 100 + N2 * 10 + N3 as N
from (
select 0 N1 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) N1
cross join (
select 0 N2 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) N2
cross join (
select 0 N3 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) N3
) Numbers
LEFT JOIN tracking t
ON t.click_date >= adddate($start_date, interval N day)
and t.click_date < adddate($start_date, interval (N+1) day)
WHERE N between 0 and datediff($start_date, $end_date)
GROUP BY N
EDIT #2: A straight Dates table
Put this in a new window in phpMyAdmin or run it as a batch. It creates a table named Dates, with every single date from day 1900-01-01 (or change in the script) to 2300-01-01 (or change).
DROP PROCEDURE IF EXISTS FillDateTable;
delimiter //
CREATE PROCEDURE FillDateTable()
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
drop table if exists datetable;
create table datetable (thedate datetime primary key, isweekday smallint);
SET #x := date('1900-01-01');
REPEAT
insert into datetable (thedate, isweekday) SELECT #x, case when dayofweek(#x) in (1,7) then 0 else 1 end;
SET #x := date_add(#x, interval 1 day);
UNTIL #x > date('2300-01-01') END REPEAT;
END//
delimiter ;
CALL FillDateTable;
With such a utility table, your query can be just
SELECT COUNT(t.click_date) as clicks,
DATE_FORMAT(thedate, '%d %M %Y') as point
FROM Dates
LEFT JOIN tracking t
ON t.click_date >= thedate
and t.click_date < adddate(thedate, interval 1 day)
WHERE thedate between $start_date and $end_date
GROUP BY thedate
In my opinion you are better off doing this type of logic in your code. But if you wanted to do it in pure SQL you could construct a query to give you the results of all the days between one day and the next ... either by inserting into a temp table or an in memory table... then left join that into your results so that you get all the days regardless of if there were results for that day.
I'd stick to PHP logic, looping between the lowest date and the highest date, and incrementing one day at a time.
You could probably do it in SQL with some fancy joins, but I won't even start to consider that nastiness!
BTW, %w is the day of the week, starting 0=Sunday. You probably wanted %d.

Categories