I'm in over my head with this query. I have a table which looks something like this (simplified):
Date Weight kg
-------------------
2012-04-16 12.4
2012-04-17 9.6
2012-04-16 5.4
2012-04-18 2.8
2012-04-16 4.5
... ...
I want the query to return this result:
Week.no. <3kg 3-7kg >7kg
----------------------------
16 2.8 9.9 22.0
... ... ... ....
This is what I have so far:
SELECT *, CONCAT(WEEK(`Date`)) AS Week, SUM(`Weight`) AS TotalWeight,
(SELECT SUM(`Weight`) FROM tbl_fangster WHERE `Weight` < 3 AND
`Date` >= '2012-04-01 00:00:00' AND `Date` <= '2012-04-30 00:00:00'
AND `Species` = 'Salmon' ) AS SumSmall
FROM tbl_fangster
WHERE `Date` >= '2012-04-01 00:00:00'
AND `Date` <= '2012-04-30 00:00:00'
AND `Species` = 'Salmon'
GROUP BY CONCAT(WEEK(`Date`))
but SumSmall returns the same number on each row, namely the total SumSmall rather than the SumSmall for each week. I've tried to copypaste my GROUP clause into the subquery, but it didn't work.
Use a CASE statement to handle the various conditions.
SELECT *, CONCAT(WEEK(`Date`)) AS Week, SUM(`Weight`) AS TotalWeight,
SUM(CASE WHEN Weight < 3 THEN Weight ELSE 0 END) AS SumSmall,
SUM(CASE WHEN Weight >= 3 AND Weight <= 7 THEN Weight ELSE 0 END) AS SumMedium,
SUM(CASE WHEN Weight > 7 THEN Weight ELSE 0 END) AS SumLarge
FROM tbl_fangster
WHERE `Date` >= '2012-04-01 00:00:00'
AND `Date` <= '2012-04-30 00:00:00'
AND `Species` = 'Salmon'
GROUP BY CONCAT(WEEK(`Date`))
Related
I have two MYSQL queries that i am trying to merge into one, here is the first one -
SELECT
`Your_Name` as agentname,
sum(`Sale_Amount`) as todaysales,
COUNT(*) as NoSales
FROM mp_wp_sales_tracking
WHERE `created` BETWEEN CURDATE() AND NOW()
GROUP BY agentname
And the second one is the same query, just for the previous day
SELECT
`Your_Name` as agentname,
sum(`Sale_Amount`) as yesterdaysales,
COUNT(*) as YesterdayNoSales
FROM mp_wp_sales_tracking
WHERE `created` BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 DAY)
AND ADDTIME(DATE_SUB(CURDATE(),INTERVAL 1 DAY),'23:59:59.50')
GROUP BY agentname
Now what i am trying to do, is achieve something like below (Headers as per aliases in MYSQL)
| agentname | todaysales | nosales | yesterdaysales | yesterdaynosales |
I have already tried to use Inner Join, but keep getting SQL errors, i am not sure where to turn on this, being quite new to complex MYSQL queries.
I can think of one way to merge these 2 queries and use conditional aggregation for the results
SELECT `Your_Name` as agentname,
SUM( CASE WHEN
`created` BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 DAY) AND ADDTIME(DATE_SUB(CURDATE(),INTERVAL 1 DAY),'23:59:59.50')
THEN `Sale_Amount`
ELSE 0 END
) as yesterdaysales,
SUM(`created` BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 DAY) AND ADDTIME(DATE_SUB(CURDATE(),INTERVAL 1 DAY),'23:59:59.50') ) as YesterdayNoSales ,
SUM(CASE WHEN
`created` BETWEEN CURDATE() AND NOW()
THEN `Sale_Amount`
ELSE 0 END
) as todaysales,
SUM(`created` BETWEEN CURDATE() AND NOW() ) as NoSales
FROM mp_wp_sales_tracking
WHERE `created` BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 DAY) AND ADDTIME(DATE_SUB(CURDATE(),INTERVAL 1 DAY),'23:59:59.50')
OR `created` BETWEEN CURDATE() AND NOW()
GROUP BY agentname
Use conditional aggregation (i.e. boolean expressions inside an aggragtion function such as SUM).
select
your_name as agentname,
sum(case when date(created) = curdate() then sale_amount end) as todaysales,
sum(date(created) = curdate()) as todaynosales,
sum(case when date(created) = curdate() - interval 1 day then sale_amount end)
as yesterdaysales,
sum(date(created) = curdate() - interval 1 day) as yesterdaynosales
from mp_wp_sales_tracking
where created >= curdate() - interval 1 day
group by agentname
order by agentname;
this query works but pulls all results. I would like it to only pull results that are not 0.00 which is the totaldue. This is calculated within the query but I do not know how to exclude results with 0.00?
SELECT name,
SUM(IF(timeperiod='0',totalinv-paidtotal,0)) AS p0030,
SUM(IF(timeperiod='30',totalinv-paidtotal,0)) AS p3060,
SUM(IF(timeperiod='60',totalinv-paidtotal,0)) AS p6090,
SUM(IF(timeperiod='90',totalinv-paidtotal,0)) AS p9000,
SUM(totalinv)-SUM(paidtotal) AS totaldue
FROM
(
SELECT primary_key, name, timeperiod, totalinv, SUM(paidtotal) as paidtotal
FROM
(
SELECT
a.primary_key,
a_name AS name,
CAST(totalinv AS DECIMAL(10,2)) as totalinv,
CAST(IFNULL(amount,0) AS DECIMAL(10,2)) as paidtotal,
CASE
WHEN invoicedate > DATE_SUB(STR_TO_DATE($today,'%Y%m%d'),INTERVAL 29 DAY) THEN '0'
WHEN invoicedate > DATE_SUB(STR_TO_DATE($today,'%Y%m%d'),INTERVAL 59 DAY) AND invoicedate <= DATE_SUB(STR_TO_DATE($today,'%Y%m%d'),INTERVAL 29 DAY) THEN '30'
WHEN invoicedate > DATE_SUB(STR_TO_DATE($today,'%Y%m%d'),INTERVAL 89 DAY) AND invoicedate <= DATE_SUB(STR_TO_DATE($today,'%Y%m%d'),INTERVAL 29 DAY) THEN '60'
ELSE '90'
END AS timeperiod
FROM $mysql_billing a
LEFT OUTER JOIN $mysql_billing_dates b ON a.primary_key = b.id
WHERE $today >= invoicedate
AND $totaldue!='0.00'
AND void=''
) foo
GROUP BY primary_key, name, timeperiod
) bar
GROUP BY name
ORDER BY name ASC
You are just missing a HAVING at the very end:
....
GROUP BY name
HAVING totaldue != 0
ORDER BY name ASC
That will allow you to select on your calculated/grouped column.
I have created a SELECT statement where I fetch the time difference from now to a certain date in my database.
SELECT
`username`,
TIMESTAMPDIFF(HOUR, `a_date`, NOW()) AS `timediff`
FROM
`User`
... results in something like
username | timediff
-------------------
john | 441
henry | 1624
mike | 4
kyle | NULL
Now I would like to group them into pre defined groups, e.g.
NULL
less than 20
greater than 20
How can I JOIN the above result with a static set of predefined values and how can I use those values as my grouping identifier?
group | amount
--------------
NULL | 1
lt 20 | 1
gt 20 | 2
You can use case statement with group by:
SELECT (CASE WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) < 20 THEN 'lt 20'
WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) >= 20 THEN 'gt 20'
END) as grp, COUNT(*)
FROM `User`
GROUP BY grp;
As a side note: I usually repeat the case statement in the group by, because many databases don't allow column aliases there:
SELECT (CASE WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) < 20 THEN 'lt 20'
WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) >= 20 THEN 'gt 20'
END) as grp, COUNT(*)
FROM `User`
GROUP BY (CASE WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) < 20 THEN 'lt 20'
WHEN TIMESTAMPDIFF(HOUR, `a_date`, NOW()) >= 20 THEN 'gt 20'
END);
I have a table that looks like this:
ID t_stamp views uviews hits uhits
1 7/18/2012 19:00 105 11 0 0
5 7/18/2012 20:00 1 1 0 0
2 7/19/2012 9:00 118 4 0 0
1 7/19/2012 10:00 196 18 0 0
7 7/19/2012 11:00 2 1 0 0
2 7/19/2012 12:00 38 11 0 0
2 7/19/2012 13:00 20 5 0 0
2 7/19/2012 19:00 9 2 0 0
2 7/20/2012 15:00 85 6 0 0
1 7/20/2012 16:00 483 101 2 2
2 7/20/2012 17:00 1200 240 0 0
2 7/20/2012 18:00 1200 232 0 0
2 7/20/2012 19:00 1199 231 0 0
2 7/20/2012 20:00 1200 236 0 0
2 7/20/2012 21:00 1201 237 0 0
1 7/20/2012 22:00 1220 187 0 0
1 7/20/2012 23:00 869 165 0 0
And my method is to combine them by the day so I can get a SUM for each of the last four columns. The IDs do not really matter.
I am using this:
SELECT `bannerID` , DATE_FORMAT( `t_stamp` , '%m/%d/%Y' ) AS `date` ,
SUM( `views` ) AS `views` , SUM( `uviews` ) AS `uviews` , SUM( `hits` ) AS `hits` , SUM( `uhits` ) AS `uhits`
FROM test_bannerstats
WHERE DATE( t_stamp ) >= DATE( '2012-07-01' )
AND DATE( t_stamp ) <= DATE( '2012-08-24' )
GROUP BY `date`
ORDER BY `date` ASC
However that doesn't seem correct to me as that the numbers seem conflicting. In the end I want to get a daily tally the last four columns by day.
EDIT:
It is a problem with time zones it looks! I will show you why...
Look at the table above, now let's do the additions for the entire day...
1 07/18/2012 106 12 0 0
1 07/19/2012 383 41 0 0
1 07/20/2012 8657 1635 2 2
Above is correct. Below is wrong.
1 07/18/2012 105 11 0 0
1 07/19/2012 384 42 0 0
1 07/20/2012 4167 810 2 2
The problem? Anything after 8pm is going to the next day. It is a timezone issue that I have to sort out it seems.
Your query can be simplified as:
SELECT `bannerID`,
DATE_FORMAT( `t_stamp` , '%m/%d/%Y' ) AS `date`,
SUM( `views` ) AS `views`,
SUM( `uviews` ) AS `uviews`,
SUM( `hits` ) AS `hits`,
SUM( `uhits` ) AS `uhits`
FROM test_bannerstats
WHERE DATE( t_stamp ) BETWEEN '2012-07-01' AND '2012-08-24'
GROUP BY DATE(t_stamp)
ORDER BY DATE(t_stamp) ASC;
try this one,
SELECT DATE_FORMAT(DATE(t_stamp), '%m/%d/%Y') AS `date`,
SUM(views) totalViews,
SUM(uviews) totalUViews,
SUM(hits) totalHits,
SUM(uhits) totalUHits,
FROM tableName
WHERE DATE( t_stamp ) >= DATE( '2012-07-01' ) AND
DATE( t_stamp ) <= DATE( '2012-08-24' )
GROUP BY DATE(t_stamp)
ORDER BY `date` ASC
I think you're sql query wont really work, that is only applicable for date july 1 to august 24. What if the last 4 date is beyond that date? It wont pick up anything.
In my opinion the best way to do it is this:
SELECT SUM(column_name) FROM table_name order by id DESC limit 4;
Order by ID desc will sort the results in descending order. Limit 4, will only pick up the first 4 results.
Hope that helps.
If you want to give your indexes a chance to be used, avoid using functions on columns when you can. Replace the conditions:
WHERE DATE( t_stamp ) >= DATE( '2012-07-01' )
AND DATE( t_stamp ) <= DATE( '2012-08-24' )`
with:
WHERE t_stamp >= DATE( '2012-07-01' )
AND t_stamp < DATE( '2012-08-25' )
You define the alias date in the SELECT list. This alias cannot be used in the WHERE or GROUP BY clauses (to be honest, it can be used at the GROUP BY clause but I wouldn't recommend it). Instead of:
GROUP BY `date`
use:
GROUP BY DATE(t_stamp)
There is a proprietary MySQL syntax available (you can read it at the SELECT documenation) when the GROUP BY and ORDER BY are done on the same expression. Instead of:
GROUP BY DATE(t_stamp)
ORDER BY DATE(t_stamp) ASC;
you can use (for a slight efficiency gain):
GROUP BY DATE( t_stamp ) ASC ;
The query becomes now:
SELECT
bannerID
, DATE_FORMAT( DATE( t_stamp ), '%m/%d/%Y' ) AS `date`
, SUM( views ) AS views
, SUM( uviews ) AS uviews
, SUM( hits ) AS hits
, SUM( uhits ) AS uhits
FROM
test_bannerstats
WHERE
t_stamp >= DATE( '2012-07-01' )
AND
t_stamp < DATE( '2012-08-25' ) --- notice the `<` and the +1 date offset
GROUP BY
DATE( t_stamp ) ASC ;
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;