Correct my MySQL query? - mysql

I have table like
CREATE TABLE `survey` (
`id` int(11) NOT NULL auto_increment,
`submitdate` datetime default NULL,
`answer` varchar(5) collate utf8_unicode_ci default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=499 ;
now to get values like
c t Clicks
2012-10-29 2012-10-22 10
2012-11-04 2012-10-30 20
2012-11-11 2012-11-05 30
2012-11-19 2012-11-12 34
I am using this query
SELECT uq.timespan, COALESCE(tsq.TotalClicks, 0) as Clicks FROM (
SELECT DATE( DATE_ADD( NOW( ) , INTERVAL -21
DAY ) ) c, DATE( DATE_ADD( NOW( ) , INTERVAL -28
DAY ) ) l
union SELECT DATE( DATE_ADD( NOW( ) , INTERVAL -15
DAY ) ) c, DATE( DATE_ADD( NOW( ) , INTERVAL -20
DAY ) ) l
union SELECT DATE( DATE_ADD( NOW( ) , INTERVAL -8
DAY ) ) c, DATE( DATE_ADD( NOW( ) , INTERVAL -14
DAY ) ) l
union SELECT curdate() c,DATE( DATE_ADD( NOW( ) , INTERVAL -7
DAY ) ) l
)uq LEFT JOIN (
SELECT CASE
WHEN submitdate >= NOW() - INTERVAL 4 WEEK
AND submitdate < NOW() - INTERVAL 3 WEEK THEN c 'to' l
DAY ) )
WHEN submitdate >= NOW() - INTERVAL 3 WEEK
AND submitdate < NOW() - INTERVAL 2 WEEK THEN c 'to' l
WHEN submitdate >= NOW() - INTERVAL 2 WEEK
AND submitdate < NOW() - INTERVAL 1 WEEK THEN c 'to' l
DAY ) )
WHEN submitdate >= NOW() - INTERVAL 1 WEEK THEN c 'to' l
END Weeksubmitdate,
count(id) TotalClicks
FROM survey
WHERE submitdate >= NOW() - INTERVAL 4 WEEK
GROUP BY Weeksubmitdate
)tsq ON uq.timespan = tsq.Weeksubmitdate";
problem is with 16th line c to l.
I am getting the following error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''to' l
DAY ) )
WHEN submitdate >= NOW() - INTERVAL 3 WEEK
' at line 16

CASE is supposed to evaluate to a scalar expression. That means its THEN clauses must evaluate to scalar expressions too. Now, what does this c 'to' l thing stand for? Is it a scalar expression? It doesn't seem one to me, however I may be unaware of some things in MySQL, so it's more important whether MySQL itself recognises that as a scalar expression. And apparently it doesn't.
There is another issue. You are trying to reference a derived table's columns inside another derived table. More specifically, you seem to be trying to reference the columns c and l of uq inside the tsq subselect, and that is illegal. If uq was a normal table, it would be fine, but since it is a virtual table, the query doesn't know about its existence at that point, i.e. at the time of parsing the tsq subquery.
Anyway, what you seem to be doing with your query could probably be rewritten more simply, like this, for instance:
SELECT
MIN(submitdate) AS startdate,
MAX(submitdate) AS enddate,
COUNT(*) AS clicks
FROM (
SELECT
CASE
WHEN submitdate >= NOW() - INTERVAL 1 WEEK THEN 1
WHEN submitdate >= NOW() - INTERVAL 2 WEEK THEN 2
WHEN submitdate >= NOW() - INTERVAL 3 WEEK THEN 3
WHEN submitdate >= NOW() - INTERVAL 4 WEEK THEN 4
END AS weekid,
*
FROM survey
) s
GROUP BY
weekid
ORDER BY
startdate
;
The subquery assigns surrogate week IDs to every row of survey. The main query groups the results by those IDs and produces the counts as well as starting & ending dates for every group.

Related

SELECT specific date with JOIN

Table Client contains last_name and email column while table Product contain email and expiry_date column with 13 row of records. This syntax correctly select only 2 rows from Product table.
SELECT * FROM `Product` WHERE expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 + 1 DAY )
Now I want to join it with Client table to select last_name with matching email. Unfortunately my JOIN syntax below select all 13 records from Product table.
SELECT * FROM `Product` JOIN `Client`
ON product.email=client.email
AND (expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 + 1 DAY ))
This syntax also does not work. All 13 rows from Product table was selected:
SELECT * FROM
(
SELECT * FROM `Product` WHERE expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 +1 DAY )
) A
LEFT JOIN Client ON A.email=client.email
WHERE A.expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND A.expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 +1 DAY )
How do I select only 2 last_name and not all 13. I hope I explained it clearly. Thanks in advance.
The way you are specifying your JOIN conditions, everything matches. You should specify a WHERE clause instead
SELECT * FROM `Product` JOIN `Client`
ON product.email=client.email
WHERE expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 + 1 DAY )
You need to use GROUP BY to get only 2 rows instead of all rows in Product table. For example:
SELECT * FROM `Product` JOIN `Client`
ON product.email=client.email
AND (expiry_date > DATE_ADD( CURRENT_DATE, INTERVAL 24 DAY )
AND expiry_date < DATE_ADD( CURRENT_DATE, INTERVAL 24 + 1 DAY ))
GROUP BY expiry_date

MySql sum records daily from 2:00 am instead of midnight.

For a business closing at 2:00 am, daily accounting ends then. How do I write or simplify this query to sum daily traffic where the top of the clock is actually at 2:00.
SELECT date( CAST( Time - INTERVAL 2 HOUR AS DATETIME ) ) AS date,
CAST( (sum( `in_count` ) + sum( `out_count` ) ) /2 AS UNSIGNED) AS count
FROM establishment
LEFT JOIN `device` ON establishment.establishment_ID = device.establishment_ID
LEFT JOIN `device_state` ON device.device_id = device_state.device_ID
WHERE establishment.establishment_ID =1
AND date(CAST( Time - INTERVAL 2 HOUR AS DATETIME ))
BETWEEN '2013-02-01' AND '2013-03-01'
GROUP BY dayofmonth( CAST( Time - INTERVAL 2 HOUR AS DATETIME ))
ORDER BY Time

Mysql date_sub last 7 days result?

In mysql i am trying to get last 7 days data so i tried with following query
date_sub(curdate(), interval 7 day) <= visited_time from visitor table gives date from 5/10/2013 to 5/16/2013
but here the same query date_sub(curdate(), interval 7 day) <= date_entered from order table gives date from 5/9/2013 to 5/15/2013
Today is 5/16/2013 so it should give 5/9/2013 to 5/15/2013 as last 7 days result for visitor table also.
whats wrong on this ? kindly advice
I have followed the post and tried with Numbers table trick
DROP TABLE IF EXISTS `example`.`numbers`;
CREATE TABLE `example`.`numbers` (
`id` int(10) unsigned NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Populate the table using: INSERT INTO NUMBERS (id) VALUES (NULL) ...for as many values as you need.
Then finally tried with below query using left join with number table.
SELECT x.ts AS TIMESTAMP
FROM (
SELECT DATE_ADD( CURDATE( ) , INTERVAL n.id -7
DAY ) AS ts
FROM numbers n
WHERE DATE_ADD( CURDATE( ) , INTERVAL n.id -7
DAY ) <= CURDATE( )
)x
LEFT JOIN orders y ON ( y.`date_entered` ) = x.ts
GROUP BY DATE( x.ts )
ORDER BY DATE( x.ts ) DESC

SQL View to show interval of 12 months and by year

Right now one of the other programmers wrote this view to show interval of 6months. How do i write this so that it shows interval of 12 months grouped by month but only for year 2011
I'd like to copy it for a separate view of 12 months grouped by month but only for year 2012
CREATE ALGORITHM=UNDEFINED DEFINER=`root`#`%`
SQL SECURITY DEFINER VIEW `vw_dash_bymonth`AS
select
month(from_unixtime(`tbl_services`.`datetime`)) AS` month1`,
date_format(from_unixtime(`tbl_services`.`datetime`),'%Y') AS` year1`,
date_format(from_unixtime(`tbl_services`.`datetime`),'%d') AS `day1`,
`tbl_services`.`datetime` AS `realdate`,sum(`tbl_services`.`gallons`) AS `gallons`,
count(0) AS `service`,
round(avg(`tbl_services`.`gallons`),1) AS `average`
from `tbl_services`
where (from_unixtime(`tbl_services`.`datetime`) > (now() - interval 6 month))
group by month(from_unixtime(`tbl_services`.`datetime`))
If you look at the where clause
where (from_unixtime(`tbl_services`.`datetime`) > (now() - interval 6 month))
I believe this is getting the dates from everything from 6 months ago until today. If you want 12 months in 2011 I think you could replace that line with something like:
where (from_unixtime(`tbl_services`.`datetime`) >= DATE('2011-01-01 00:00:00'))
AND (from_unixtime(`tbl_services`.`datetime`) < DATE('2012-01-01 00:00:00'))
Although I don't know MySQL (just SQLServer) so if this doesn't work, hopefully someone else can tell me where I went wrong.
It can be simplified to:
where (from_unixtime(`tbl_services`.`datetime`) >= '2011-01-01')
AND (from_unixtime(`tbl_services`.`datetime`) < '2012-01-01')
SELECT DISTINCT FROM_UNIXTIME('date','%m-%Y') AS month,
COUNT(`id`) AS count
FROM 'blog'
GROUP BY month
ORDER BY month DESC LIMIT 0,12
SELECT
MONTH( UNIX_TIMESTAMP( t.`datetime`) ) AS month1,
DATE_FORMAT( UNIX_TIMESTAMP( t.`datetime`), '%Y' ) AS year1,
DATE_FORMAT( UNIX_TIMESTAMP( t.`datetime`), '%d') AS day1,
t.`datetime` AS realdate,
SUM(t.gallons) AS gallons,
COUNT(*) AS `service`,
ROUND( AVG( t.gallons ), 1 ) AS `average`
FROM
tbl_services AS t
CROSS JOIN
( SELECT 2011 AS YearToCheck
) AS c
WHERE t.`datetime` >= UNIX_TIMESTAMP( MAKEDATE( YearToCheck, 1 ) )
AND t.`datetime` < UNIX_TIMESTAMP( MAKEDATE( YearToCheck+1, 1 ) )
GROUP BY MONTH( FROM_UNIXTIME( t.`datetime` ) )

Query for duration between two times within 1 day

Suppose I have a table that contain information on streaming media connections. In this table, I have a start time and end time for when the connection was initiated and then later closed.
Table: logs
id (INT, PK, AUTO_INCREMENT)
StartTime (DATETIME)
EndTime (DATETIME)
I want to be able to run a query that will add up the total time connections were established for a day. This is obvious for connections within a day:
SELECT
SUM(
TIME_TO_SEC(
TIMEDIFF(`EndTime`, `StartTime`)
)
)
WHERE (`StartTime` BETWEEN '2010-01-01' AND '2010-01-02);
However, suppose a StartTime begins one day, say around 11:00PM, and EndTime is some time the next day, maybe 3:00AM. In these situations, I want to allocate only the amount of time that occurred during the day, to that day. So, 1 hour would go towards the first day, and 3 hours would go to the next.
SUM(
TIME_TO_SEC(
TIMEDIFF(
IF(`EndTime`>DATE_ADD('2010-01-01', INTERVAL 1 DAY), DATE_ADD('2010-01-01', INTERVAL 1 DAY), `EndTime`),
IF(`StartTime`<'2010-01-01', '2010-01-01', `StartTime`)
)
)/60/60
)
The thinking with this is that if the EndTime is more than the end of the day, then we'll just use the end of the day instead. If the StartTime is less than the beginning of the day, then we'll just use the beginning of the day instead.
So, I then need to wrap this all up into something that will generate a table that looks like this:
date, total
2010-01-01, 0
2010-01-02, 1.53
2010-01-03, 5.33
I thought this query would work:
SELECT
`date`,
SUM(
TIME_TO_SEC(
TIMEDIFF(
IF(`EndTime`>DATE_ADD(`date`, INTERVAL 1 DAY), DATE_ADD(`date`, INTERVAL 1 DAY), `EndTime`),
IF(`StartTime`<`date`, `date`, `StartTime`)
)
)/60/60
) AS `total_hours`
FROM
(SELECT * FROM `logs` WHERE `StartTime` BETWEEN '2010-08-01' AND '2010-08-31') AS logs_small,
(SELECT DATE_ADD("2010-08-01", INTERVAL `number` DAY) AS `date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS `dates`
GROUP BY `date`;
Note the numbers table referenced is a table with just one column, number, with a series of integers, 0, 1, 2, 3, etc. I am using it here to generate a series of dates, which works fine.
The problem with this query is that I get inaccurate data. Specifically, rows in the logs table that have an EndDate that goes into the next day don't get any time counted in that next day. For example, if I had a row that started 2010-08-01 23:00:00 and ended 2010-08-02 01:00:00, then the resulting row for 2010-08-02 would add up to 0.
Is there a better way to do this? Ideally, I'd like to get 0 instead of null on days that don't have any records that match up to them as well.
Edit: To clarify, I want to turn this:
id, StartTime, EndTime
0, 2000-01-01 01:00:00, 2000-01-01 04:00:00
1, 2000-01-01 23:00:00, 2000-01-02 05:00:00
2, 2000-01-02 00:00:00, 2000-01-04 01:00:00
... into this:
date, total_hours
2000-01-01, 4
2000-01-02, 29
2000-01-03, 24
2000-01-04, 1
2000-01-05, 0
Solution
Thanks to jim31415 for coming up with the solution! I translated his answer over to the functions usable in MySQL and came up with this:
SELECT `d`.`Date`,
SUM(COALESCE(
(CASE WHEN t.StartTime >= d.Date AND t.EndTime < DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(t.EndTime, t.StartTime))
WHEN t.StartTime < d.Date AND t.EndTime <= DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(t.EndTime,d.Date))
WHEN t.StartTime >= d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY) THEN TIME_TO_SEC(TIMEDIFF(DATE_ADD(d.Date, INTERVAL 1 DAY),t.StartTime))
WHEN t.StartTime < d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY) THEN 24*60*60
END), 0)
)/60/60 ConnectionTime
FROM (SELECT DATE_ADD('2011-03-01', INTERVAL `number` DAY) AS `Date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS d
LEFT JOIN `logs` t ON (t.StartTime >= d.Date AND t.StartTime < DATE_ADD(d.Date, INTERVAL 1 DAY))
OR (t.EndTime >= d.Date AND t.EndTime < DATE_ADD(d.Date, INTERVAL 1 DAY))
OR (t.StartTime < d.Date AND t.EndTime > DATE_ADD(d.Date, INTERVAL 1 DAY))
GROUP BY d.Date
ORDER BY d.Date;
I should also note that the null values for EndTime weren't applicable in my situation, as I am reading from old log files in my application. If you need them though, Jim's post has them outlined quite well.
This is in MS SQL, but I think the logic applies and can be translated into MySQL.
I wasn't sure how you wanted to handle EndTime that are null, so I commented that out.
select d.Date,
sum(coalesce(
(case when t.StartTime >= d.Date and t.EndTime < dateadd(day,1,d.Date) then datediff(minute,t.StartTime,t.EndTime)
when t.StartTime < d.Date and t.EndTime <= dateadd(day,1,d.Date) then datediff(minute,d.Date,t.EndTime)
when t.StartTime >= d.Date and t.EndTime > dateadd(day,1,d.Date) then datediff(minute,t.StartTime,dateadd(day,1,d.Date))
when t.StartTime < d.Date and t.EndTime > dateadd(day,1,d.Date) then 24*60
--when t.StartTime >= d.Date and t.EndTime is null then datediff(minute,t.StartTime,getdate())
--when t.StartTime < d.Date and t.EndTime is null then datediff(minute,d.Date,getdate())
end), 0)
) ConnectionTime
from (select Date=dateadd(day, num, '2011-03-01') from #NUMBERS where num between 0 and 30) d
left join Logs t on (t.StartTime >= d.Date and t.StartTime < dateadd(day,1,d.Date))
or (t.EndTime >= d.Date and t.EndTime < dateadd(day,1,d.Date))
or (t.StartTime < d.Date and t.EndTime > dateadd(day,1,d.Date))
group by d.Date
order by d.Date
Use a union to make it easier for yourself
SELECT
`date`,
SUM(
TIME_TO_SEC(TIMEDIFF(`EndTime`,`StartTime`))/60/60
) AS `total_hours`
FROM
(SELECT id, starttime, if (endtime > date then date else endtime) FROM `logs` WHERE `StartTime` >= date AND `StartTime` < date
union all
SELECT id, date, endtime FROM `logs` WHERE `enddate` >= date AND `enddate` < date and !(`StartTime` >= date AND `StartTime` < date)
union all
SELECT id, date, date_add(date, 1) FROM `logs` WHERE `enddate` > date AND `startdate` < date
) as datedetails inner join
(SELECT DATE_ADD("2010-08-01", INTERVAL `number` DAY) AS `date` FROM `numbers` WHERE `number` BETWEEN 0 AND 30) AS `dates`
GROUP BY `date`;
Hope, I understood your question correctly
Edit: Forgot case when there is a multiday request that starts before the day asked for, and ended after
Use this
select startTime,duration as duration,time,TIME_TO_SEC(TIMEDIFF(time,startTime)) as diff from <idling> limit 25;
select startTime,duration DIV 60 as duration,time,TIMESTAMPDIFF(MINUTE,startTime,time) as diff from <idling> limit 25;