I'm trying to calculate the percentage between two sub queries, I have a solution which to me doesn't seem very elegant at all:
SET #tot := (select count(*) FROM shipment WHERE created < date_format(date_add(CURRENT_TIMESTAMP(), interval 1 day), '%Y%m%d000000') AND created >= date_format(CURRENT_TIMESTAMP(), '%Y%m%d000000'))
SELECT #tot AS Total, ((select count(*) from shipment where created < date_format(date_add(CURRENT_TIMESTAMP(), interval 1 day), '%Y%m%d000000') AND created >= date_format(CURRENT_TIMESTAMP(), '%Y%m%d000000') AND state = 'despatched') / #tot) * 100 AS Percentage
While this works at command line, it fails miserably in a bespoke platform i'm trying to create a report for, i'm wondering if there is a way to simplify this without the usage of a set variable?
Thanks in advance.
I don't see any reason you couldn't do it simply as a SELECT from a nested subquery. (Untested in MySQL, but works in SQL Server with the proper date functions.)
select total, dispatched / total * 100 as percentage from
(
select count(*) as Total,
sum(case when state = 'despatched' then 1.0 else 0.0 end) as dispatched
from
shipment
where
created >= date_format(CURRENT_TIMESTAMP(), '%Y%m%d000000')
and
created < date_format(date_add(CURRENT_TIMESTAMP(), interval 1 day), '%Y%m%d000000')
) calcs
Related
I'm currently trying to get the customers' retention rates using the following query:
set #DAY1 = 0;
set #DAY30 = 0;
set #DAY90 = 0;
SET #order_month = 1;
SET #order_year = 2020;
SELECT
#DAY1/COUNT(uid) AS D1,
#DAY30/COUNT(uid) AS D30,
#DAY90/COUNT(uid) AS D90
FROM
(
SELECT
uid,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 1 then #DAY1:=#DAY1+1 END AS DAY1,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 30 then #DAY30:=#DAY30+1 END AS DAY30,
case when DATEDIFF(date(lastConnection), date(accountCreation)) >= 90 then #DAY90:=#DAY90+1 END AS DAY90
from user
where month(accountCreation) = #order_month
and year(accountCreation) = #order_year
)
AS T1
This works as expected but what I would like to add is some sort of loop that would increment the #order_month variable by one (as well as the #order_year variable when we reach a new year). The increment would go as far as August 2022. I would like the result of each 'Select' to be displayed at once. Is that possible and if yes, how could I do that?
If I get your intention right, you just want to calculate the percentage of users whose account was created at least 1 day (30 days, 90 days) before their last login. And you want to calculate this for each year and month based on accountCreation, that is something like
SELECT year(accountCreation) year,
month(accountCreation) month,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 1 THEN 1 END) / count(*) AS D1,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 30 THEN 1 END) / count(*) AS D30,
sum(CASE WHEN DATEDIFF(date(lastConnection), date(accountCreation)) >= 90 THEN 1 END) / count(*) AS D90
FROM user
GROUP BY year, month;
In other words: there's no need for a loop.
I have a mySQL database with each row containing an activate and a deactivate date. This refers to the period of time when the object the row represents was active.
activate deactivate id
2015-03-01 2015-05-10 1
2013-02-04 2014-08-23 2
I want to find the number of rows that were active at any time during each month. Ex.
Jan: 4
Feb: 2
Mar: 1
etc...
I figured out how to do this for a single month, but I'm struggling with how to do it for all 12 months in a year in a single query. The reason I would like it in a single query is for performance, as information is used immediately and caching wouldn't make sense in this scenario. Here's the code I have for a month at a time. It checks if the activate date comes before the end of the month in question and that the deactivate date was not before the beginning of the period in question.
SELECT * from tblName WHERE activate <= DATE_SUB(NOW(), INTERVAL 1 MONTH)
AND deactivate >= DATE_SUB(NOW(), INTERVAL 2 MONTH)
If anybody has any idea how to change this and do grouping such that I can do this for an indefinite number of months I'd appreciate it. I'm at a loss as to how to group.
If you have a table of months that you care about, you can do:
select m.*,
(select count(*)
from table t
where t.activate_date <= m.month_end and
t.deactivate_date >= m.month_start
) as Actives
from months m;
If you don't have such a table handy, you can create one on the fly:
select m.*,
(select count(*)
from table t
where t.activate_date <= m.month_end and
t.deactivate_date >= m.month_start
) as Actives
from (select date('2015-01-01') as month_start, date('2015-01-31') as month_end union all
select date('2015-02-01') as month_start, date('2015-02-28') as month_end union all
select date('2015-03-01') as month_start, date('2015-03-31') as month_end union all
select date('2015-04-01') as month_start, date('2015-04-30') as month_end
) m;
EDIT:
A potentially faster way is to calculate a cumulative sum of activations and deactivations and then take the maximum per month:
select year(date), month(date), max(cumes)
from (select d, (#s := #s + inc) as cumes
from (select activate_date as d, 1 as inc from table t union all
select deactivate_date, -1 as inc from table t
) t cross join
(select #s := 0) param
order by d
) s
group by year(date), month(date);
I am using MySQL. Here is my schema:
bannerstatclick(idBannerStats: integer, Time: Timestamp, idCampaignBanner :char(36))
I am trying to write a query to select the total no of click month wise by using count on idCampaignBanner.
this will not work it will give an error invalid use of group function.
iwill also try this using having clause but it also not work...
SELECT count(idCampaignBanner) AS TotalClicks ,max(`Time`) AS maxdate,(min(`Time`) + INTERVAL 30 DAY)as monthly
FROM newradium.BannerStatsClick
WHERE Time BETWEEN max(`Time`) AND ( max(`Time`)- INTERVAL 30 DAY)
Something like this should work (you need group by clause if you do aggregation)
select count(idCampaignBanner), MONTH(`Time`) as m
from newradium.BannerStatsClick
group by m
SELECT
count(idCampaignBanner) AS TotalClicks
, max(`Time`) AS maxdate
, (min(`Time`) + INTERVAL 30 DAY)as monthly
FROM newradium.BannerStatsClick
WHERE Time <= (Select max(`Time`) FROM newradium.BannerStatsClick)
And Time >= (Select max(`Time`) - INTERVAL 30 DAY FROM newradium.BannerStatsClick)
Technically could get rid of "Time <= (Select max(Time) FROM newradium.BannerStatsClick)", doesn't really affect the selection. But left in in case you needed different range in future
In mysql, I am calculating averages of the same metric over different intervals (3 Day, 7 Day, 30 Day, 60 Day, etc...), and I need the results to be in a single line per id.
Currently, I am using a Join per each interval. Given that I have to compute this for many different stores, and over several different intervals, is there a cleaner and/or more efficient way of accomplishing this?
Below is the code I am currently using.
Thanks in advance for the help
SELECT T1.id, T1.DailySales_3DayAvg, T2.DailySales_7DayAvg
FROM(
SELECT id, avg(DailySales) as 'DailySales_3DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 3 DAY)
AND `Date` < '2012-07-28'
) AS T1
JOIN(
SELECT id, avg(DailySales) as 'DailySales_7DayAvg'
FROM `SalesTable`
WHERE `Store`=2
AND `Date` >= DATE_SUB('2012-07-28', INTERVAL 7 DAY)
AND `Date` < '2012-07-28'
) AS T2
ON T1.ArtistId = T2.ArtistId
Where the results are:
id DailySales_3DayAvg DailySales_7DayAvg
3752 1234.56 1114.78
...
You can use a query like this -
SELECT
id,
SUM(IF(date >= '2012-07-28' - INTERVAL 3 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 3 DAY, 1, NULL)) 'DailySales_3DayAvg',
SUM(IF(date >= '2012-07-28' - INTERVAL 7 DAY, DailySales, 0)) /
COUNT(IF(date >= '2012-07-28' - INTERVAL 7 DAY, 1, NULL)) 'DailySales_7DayAvg'
FROM
SalesTable
WHERE
Store = 2 AND Date < '2012-07-28'
GROUP BY
id
I don't think you can do this in any other way if you want to pull real-time data. However, if you can afford displaying slightly outdated data, you could pre-calculate these average (like once or twice a day) for each item.
You may want to look into the Event Scheduler, which allows you to keep everything inside MySQL.
I am trying to make a report. It is supposed to give me a list of the machines at a specific customer and the sum of hours and material that was put in to that machine.
In the following examples, I select the sum of materials and hours in different fields to make the problem clearer. But i really want to sum the material an hours, then group them by the machine field.
I can query the list of machine and cost of hours without problems.
SELECT CONCAT(`customer`.`PREFIX`, `wo`.`machine_id`) AS `machine`,
ROUND(COALESCE(SUM(`wohours`.`length` * `wohours`.`price`), 0), 2) AS `hours`
FROM `wo`
JOIN `customer` ON `customer`.`id`=`wo`.`customer_id`
LEFT JOIN `wohours` ON `wohours`.`wo_id`=`wo`.`id` AND `wohours`.`wo_customer_id`=`wo`.`customer_id`
AND `wohours`.`wo_machine_id`=`wo`.`machine_id` AND `wohours`.`date`>=(CURDATE() - INTERVAL DAY(CURDATE() - INTERVAL 1 DAY) DAY) - INTERVAL 11 MONTH
WHERE `wo`.`customer_id`=1
GROUP BY `wo`.`machine_id`;
This gives me the correct values for hours. But when I add the material like this:
SELECT CONCAT(`customer`.`PREFIX`, `wo`.`machine_id`) AS `machine`,
ROUND(COALESCE(SUM(`wohours`.`length` * `wohours`.`price`), 0), 2) AS `hours`,
ROUND(COALESCE(SUM(`womaterial`.`multiplier` * `womaterial`.`price`), 0), 2) AS `material`
FROM `wo`
JOIN `customer` ON `customer`.`id`=`wo`.`customer_id`
LEFT JOIN `wohours` ON `wohours`.`wo_id`=`wo`.`id` AND `wohours`.`wo_customer_id`=`wo`.`customer_id`
AND `wohours`.`wo_machine_id`=`wo`.`machine_id` AND `wohours`.`date`>=(CURDATE() - INTERVAL DAY(CURDATE() - INTERVAL 1 DAY) DAY) - INTERVAL 11 MONTH
LEFT JOIN `womaterial` ON `womaterial`.`wo_id`=`wo`.`id` AND `womaterial`.`wo_customer_id`=`wo`.`customer_id`
AND `womaterial`.`wo_machine_id`=`wo`.`machine_id` AND `wohours`.`date`>=(CURDATE() - INTERVAL DAY(CURDATE() - INTERVAL 1 DAY) DAY) - INTERVAL 11 MONTH
WHERE `wo`.`customer_id`=1
GROUP BY `wo`.`machine_id`;
then both hour and material values are incorrect.
I have read other threads where people with similar problems could solve this by splitting it in multiple queries or subqueries. But I don't think that is possible in this case.
Any help is appreciated.
//John
Your other reading is correct. You will need to put them into their own "subquery" for the join. The reason you are probably getting invalid values is that the materials table has multiple records per machine, thus causing a Cartesian result from your original based on hours. And you don't know which has many vs just one making it look incorrect.
So, I've written, and each inner-most query for pre-aggregating the woHours and woMaterial will produce a single record per "wo_id and machine_id" to join back to the wo table when finished. Each of these queries has the criteria on the single customer ID you are trying to run it for.
Then, as re-joined to the work order (wo) table, it grabs all records and applies the ROUND() and COALESCE() in case no such hours or materials present. So this is a return of something like
WO Machine ID Machine Hours Material
1 1 CustX 1 2 0
2 4 CustY 4 2.5 6.5
3 4 CustY 4 1.2 .5
4 1 CustX 1 1.5 1.2
Finally, you can now roll up the SUM() of all these entries into a single row per machine ID
Machine Hours Material
CustX 1 3.5 1.2
CustY 4 3.7 7.0
SELECT
AllWO.Machine,
SUM( AllWO.Hours ) Hours,
SUM( AllWO.Material ) Material
from
( SELECT
wo.wo_id,
wo.Machine_ID,
CONCAT(customer.PREFIX, wo.machine_id) AS machine,
ROUND( COALESCE( PreSumHours.MachineHours, 0), 2) AS hours,
ROUND( COALESCE( PreSumMaterial.materialHours, 0), 2) AS material
FROM
wo
JOIN customer
ON wo.customer_id = customer.id
LEFT JOIN ( select wohours.wo_id,
wohours.wo_machine_id,
SUM( wohours.length * wohours.price ) as machinehours
from
wohours
where
wohours.wo_customer_id = 1
AND wohours.date >= ( CURDATE() - INTERVAL DAY( CURDATE() - INTERVAL 1 DAY) DAY) - INTERVAL 11 MONTH
group by
wohours.wo_id,
wohours.wo_machine_id ) as PreSumHours
ON wo.id = PreSumHours.wo_id
AND wo.machine_id = PreSumHours.wo_machine_id
LEFT JOIN ( select womaterial.wo_id,
womaterial.wo_machine_id,
SUM( womaterial.length * womaterial.price ) as materialHours
from
womaterial
where
womaterial.wo_customer_id = 1
AND womaterial.date >= ( CURDATE() - INTERVAL DAY( CURDATE() - INTERVAL 1 DAY) DAY) - INTERVAL 11 MONTH
group by
womaterial.wo_id,
womaterial.wo_machine_id ) as PreSumMaterial
ON wo.id = PreSumMaterial.wo_id
AND wo.machine_id = PreSumMaterial.wo_machine_id
WHERE
wo.customer_id = 1 ) AllWO
group by
AllWO.Machine_ID