Help calculating average per day - mysql

The daily_average column is always returning zero. The default timestamp values are for the past week. Any thoughts on what I'm doing wrong here in getting the average order value per day?
SELECT
SUM(price+shipping_price) AS total_sales,
COUNT(id) AS total_orders,
AVG(price+shipping_price) AS order_total_average,
(SELECT
SUM(quantity)
FROM `order_product`
INNER JOIN `order` ON (
`order`.id = order_product.order_id AND
`order`.created >= '.$startTimestamp.' AND
`order`.created <= '.$endTimestamp.' AND
`order`.type_id = '.$type->getId().' AND
`order`.fraud = 0
)
) as total_units,
SUM(price+shipping_price)/DATEDIFF('.$endTimestamp.', '.$startTimestamp.') as daily_average
FROM `order`
WHERE created >= '.$startTimestamp.' AND
created <= '.$endTimestamp.' AND
fraud = 0 AND
type_id = '.$type->getId().'

You're using aggregate functions (SUM, COUNT, AVG) without an aggregate command (group by). I think your SQL is more complicated than it needs to be (no need for the inner select).
Here's a SQL command that should work (hard to test without test data ;))
SELECT
COUNT(id) total_orders,
SUM(finalprice) total_sales,
AVG(finalprice) order_average,
SUM(units) total_units,
SUM(finalprice)/DATEDIFF('.$endTimestamp.', '.$startTimestamp.') daily_average
FROM (
SELECT
o.id id,
o.price+o.shipping_price finalprice,
SUM(p.quantity) units
FROM order o INNER JOIN order_product p ON p.order_id=o.id
WHERE o.created>='.$startTimestamp.'
AND o.created<='.$endTimestamp.'
AND o.fraud=0
AND o.type_id='.$type->getId().'
GROUP BY p.order_id
) t;

Does casting one of the elements in the division work for you?
SELECT
SUM(price+shipping_price) AS total_sales,
COUNT(id) AS total_orders,
AVG(price+shipping_price) AS order_total_average,
(SELECT
SUM(quantity)
FROM `order_product`
INNER JOIN `order` ON (
`order`.id = order_product.order_id AND
`order`.created >= '.$startTimestamp.' AND
`order`.created <= '.$endTimestamp.' AND
`order`.type_id = '.$type->getId().' AND
`order`.fraud = 0
)
) as total_units,
CAST(SUM(price+shipping_price) AS float)/DATEDIFF('.$endTimestamp.', '.$startTimestamp.') as daily_average
FROM `order`
WHERE created >= '.$startTimestamp.' AND
created <= '.$endTimestamp.' AND
fraud = 0 AND
type_id = '.$type->getId().'

Related

SQL calculate new column based on other queries with inner join

I'm building up a large SQL query to calculate everything in one go based on a table. I need a column called total_revenue, which should be the sum of total_commission and total_profit together.
Right now, I'm constructing a new inner join to do this, but this inner join that calculates total_revenue is an overhead.
How can I, instead, run a inner join on the result of my upper query to then do a sum of total_commission + total_profit without doing another inner join on another table, here's my current SQL:
SELECT
# Affilaite
Conversion.aff_id,
# Revenue
JoinedRevenue.total_revenue,
# Commission
JoinedCommission.total_commission,
# Profit
JoinedProfit.total_profit
FROM
tlp_conversions AS Conversion
INNER JOIN
(
SELECT
Commission.aff_id,
Commission.created,
SUM(Commission.amount) AS total_commission
FROM
tlp_commissions AS Commission
WHERE
Commission.created >= '2022-10-03 00:00:00'
AND
Commission.created <= '2022-10-03 23:59:59'
GROUP BY
Commission.aff_id
) AS JoinedCommission
ON JoinedCommission.aff_id = Conversion.aff_id
INNER JOIN
(
SELECT
Application.tlp_aff_id,
ApplicationResponse.application_id,
ApplicationResponse.result,
Commission.seller_code,
Commission.application_response_id,
Commission.created,
SUM(Commission.amount) AS total_profit
FROM
tlp_commissions AS Commission
INNER JOIN tlp_application_responses AS ApplicationResponse
ON ApplicationResponse.id = Commission.application_response_id
INNER JOIN tlp_applications AS Application
ON Application.id = ApplicationResponse.application_id
WHERE
Commission.created >= '2022-10-03 00:00:00'
AND
Commission.created <= '2022-10-03 23:59:59'
AND
ApplicationResponse.result = 'Accepted'
AND
Commission.seller_code = 44
GROUP BY
Application.tlp_aff_id
) AS JoinedProfit
ON JoinedProfit.tlp_aff_id = Conversion.aff_id
INNER JOIN
(
SELECT
Application.tlp_aff_id,
ApplicationResponse.application_id,
Commission.application_response_id,
Commission.created,
SUM(Commission.amount) AS total_revenue
FROM
tlp_commissions AS Commission
INNER JOIN tlp_application_responses AS ApplicationResponse
ON ApplicationResponse.id = Commission.application_response_id
INNER JOIN tlp_applications AS Application
ON Application.id = ApplicationResponse.application_id
WHERE
Commission.created >= '2022-10-03 00:00:00'
AND
Commission.created <= '2022-10-03 23:59:59'
GROUP BY
Application.tlp_aff_id
) AS JoinedRevenue
ON JoinedRevenue.tlp_aff_id = Conversion.aff_id
WHERE
Conversion.conversion_time >= '2022-10-03 00:00:00'
AND
Conversion.conversion_time <= '2022-10-03 23:59:59'
AND
Conversion.aff_id IS NOT NULL
GROUP BY
Conversion.aff_id
I was hoping I could just do another SQL join that loops over each returned result in my query and appends total_revenue based on the row column of each?
INNER JOIN
(
SELECT SUM(JoinedProfit.total_profit + JoinedCommission.total_commission) AS total_revenue
) AS JoinedRevenue
But this isn't the right syntax here.

MySQL: Grouped by hour, need to show all hours, null where no data

Here's the query:
SELECT h.idhour, h.`hour`, outnumber, count(*) as `count`, sum(talktime) as `duration`
FROM (
SELECT
`cdrs`.`dcustomer` AS `dcustomer`,
(CASE
WHEN (`cdrs`.`cnumber` like "02%") THEN '02'
WHEN (`cdrs`.`cnumber` like "05%") THEN '05'
END) AS `outnumber`,
FROM_UNIXTIME(`cdrs`.`start`) AS `start`,
(`cdrs`.`end` - `cdrs`.`start`) AS `duration`,
`cdrs`.`talktime` AS `talktime`
FROM `cdrs`
WHERE `cdrs`.`start` >= #_START and `cdrs`.`start` < #_END
AND `cdrs`.`dtype` = _LATIN1'external'
GROUP BY callid
) cdr
JOIN customers c ON c.id = cdr.dcustomer
LEFT JOIN hub.hours h ON HOUR(cdr.`start`) = h.idhour
WHERE (c.parent = _ID or cdr.dcustomer = _ID or c.parent IN
(SELECT id FROM customers WHERE parent = _ID))
GROUP BY h.idhour, cdr.outnumber
ORDER BY h.idhour;
The above query results skips the hours where there is no data, but I need to show all hours (00:00 to 23:00) with null or 0 values. How can I do this?
SELECT h.idhour
, h.hour
,IFNULL(outnumber,'') AS outnumber
,IFNULL(cdr2.duration,0) AS duration
,IFNULL(output_count,0) AS output_count
FROM hub.hours h
LEFT JOIN (
SELECT HOUR(start) AS start,outnumber, SUM(talktime) as duration ,COUNT(1) AS output_count
FROM
(
SELECT cdrs.dcustomer AS dcustomer
, (CASE WHEN (cdrs.cnumber like "02%") THEN '02' WHEN (cdrs.cnumber like "05%") THEN '05' END) AS outnumber
, FROM_UNIXTIME(cdrs.start) AS start
, (cdrs.end - cdrs.start) AS duration
, cdrs.talktime AS talktime
FROM cdrs cdrs
INNER JOIN customers c ON c.id = cdrs.dcustomer
WHERE cdrs.start >= #_START and cdrs.start < #_END AND cdrs.dtype = _LATIN1'external'
AND
(c.parent = _ID or cdrs.dcustomer = _ID or c.parent IN (SELECT id FROM customers WHERE parent = _ID))
GROUP BY callid
) cdr
GROUP BY HOUR(start),outnumber
) cdr2
ON cdr2.start = h.idhour
ORDER BY h.idhour
You need a table with all hours, nothing else.
Then use LEFT JOIN with the hours table on the "left" and your current query on the "right":
SELECT b.*
FROM hours h
LEFT JOIN ( ... ) b ON b.hr = h.hr
WHERE h.hr BETWEEN ... AND ...
ORDER BY hr;
Any missing hours will be NULLs in b.*.

Conditional calculation when using group by

I have 3 tables order, orderitem,shipment. I have to get the count of all these and MRP of items
here is the below query
SELECT
DATE_FORMAT(order.orderdate,"%Y-%m%-%d") AS i_orderdt,
COUNT(DISTINCT order.orderid) AS i_orderscount,
COUNT(DISTINCT orderitem.orderitemid) as i_orderitemcount,
COUNT(DISTINCT shipment.shipmentid) AS i_shipmentcount,
ROUND(SUM(ifnull(orderitem.unitprice,0) * orderitem.quantity),0) AS i_mrp,
FROM order
LEFT JOIN shipment
ON shipment.orderid = order.orderid
JOIN orderitem
ON orderitem.orderid = order.orderid
WHERE order.orderdate >= "2014-01-01 00:00:00" AND order.orderdate <= "2014-03-31 23:59:59" ) GROUP BY DATE_FORMAT(`order`.`orderdate`,"%y-%m%-%d")
but MRP comes wrong as order to shipment will be 1 to many relationship. How can i write a query so that the calculation happens only for distinct orderitemid ??
One solution is to move the shipment count into a subquery:
SELECT
DATE_FORMAT(order.orderdate,"%Y-%m%-%d") AS i_orderdt,
COUNT(DISTINCT order.orderid) AS i_orderscount,
COUNT(DISTINCT orderitem.orderitemid) as i_orderitemcount,
COALESCE(
(
SELECT COUNT(DISTINCT shipmentid)
FROM shipment
WHERE shipment.orderid = order.orderid
), 0) AS i_shipmentcount,
ROUND(SUM(ifnull(orderitem.unitprice,0) * orderitem.quantity),0) AS i_mrp,
FROM order
JOIN orderitem ON orderitem.orderid = order.orderid
WHERE order.orderdate >= "2014-01-01 00:00:00"
AND order.orderdate <= "2014-03-31 23:59:59" )
GROUP BY DATE_FORMAT(order.orderdate,"%y-%m%-%d");

How do I group SQL Server rows in Table A by date in Table B

This is simple in theory, but difficult for me to figure out.
I have two SQL Server tables:
List of purchases with purchase date and total
select total, date from purchases
list of miles traveled for a specific job and date of travel
select travelDate, miles from trips
EDIT: To keep the answer/discussion on my question, I am rephrasing the requirement.
I need to figure out the total miles driven between each purchase.
I want more accuracy than an overall average.
I can manually get this by summing all miles from trips in between each purchases date. Now, I just want to automate the process.
The grouping should be such that all trips dates greater than purchases date A and less than purchases date B are part of the purchases date A group.
Curing my denseness, I see that your request is quite reasonable by treating the problem as "replacement fuel cost"—thus using the fuel cost of the next fueling rather than the previous cost to buy the fuel actually used (which gets really complicated, really fast). Volume then doesn't matter. Try this on for size.
SELECT
T.*,
P.*, -- from previous purchase
N.*, -- from next purchase (NULL if none yet)
TripCost = N.Total * T.Miles / M.MilesThisFill
FROM
dbo.Trips T
CROSS APPLY (
SELECT TOP 1 *
FROM dbo.Purchases P
WHERE P.[Date] < T.travelDate
ORDER BY P.[Date] DESC
) P
CROSS APPLY (
SELECT TOP 1 *
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
) N
CROSS APPLY (
SELECT Sum(miles) MilesThisFill
FROM dbo.Trips T2
WHERE
T2.[Date] > P.[Date]
AND T2.[Date] < N.[Date]
) M;
Or here's a version that thinks very differently about the problem but should give the same result. Let me know which one performs better, would ya? (SET STATISTICS IO ON; SET STATISTICS TIME ON;)
WITH PSeq AS (
SELECT
Seq = Row_Number() OVER (ORDER BY [Date]),
*
FROM dbo.Purchases
), Slices AS (
SELECT
FromDate = P.[Date],
ToDate = N.[Date],
N.Total
FROM
PSeq P
INNER JOIN PSeq N
ON P.Seq + 1 = N.Seq
), TotalMiles AS (
SELECT
S.FromDate,
Sum(T.Miles) MilesThisFill
FROM
Slices S
INNER JOIN dbo.Trips T
ON T.travelDate BETWEEN S.FromDate AND S.ToDate
GROUP BY
S.FromDate
)
SELECT
T.travelDate,
S.FromDate,
S.ToDate,
TripCost = S.Total * T.Miles / M.MilesThisFill
FROM
Slices S
INNER JOIN dbo.Trips T
ON T.travelDate BETWEEN S.FromDate AND S.ToDate
INNER JOIN dbo.TotalMiles M
ON S.FromDate = L.FromDate;
I apologize in advance for any typos or errors... I haven't tested the code.
And just for laughs, here's the first query transmogrified into a version that would work even on SQL Server 2000!
SELECT
T.travelDate,
T.Miles,
T.ToDate,
TripCost = P.Total * T.Miles / M.MilesThisFill
FROM
(
SELECT
T.travelDate,
T.Miles,
ToDate = (
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
)
FROM
dbo.Trips T
) T
INNER JOIN (
SELECT
ToDate = (
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T2.travelDate
ORDER BY P.[Date]
),
MilesThisFill = Sum(T2.Miles)
FROM dbo.Trips T2
GROUP BY
(
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T2.travelDate
ORDER BY P.[Date]
)
) M ON T.ToDate = M.ToDate
INNER JOIN dbo.Purchases P
ON T.ToDate = P.[Date];
This actually exposes that I might not need to look up the previous purchase date in my first query, if I do it right... so here's a final version:
WITH TripData AS (
SELECT
T.Miles,
T.travelDate,
ToDate = (
SELECT TOP 1 P.[Date]
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
)
FROM
dbo.Trips T
)
SELECT
T.*,
P.*,
TripCost = P.Total * T.Miles / M.MilesThisFill
FROM
TripData T
INNER JOIN dbo.Purchases P
ON T.ToDate = P.[Date]
INNER JOIN (
SELECT
T2.ToDate,
Sum(T2.Miles) MilesThisFill
FROM TripData T2
GROUP BY
T2.ToDate
) M ON T.ToDate = M.ToDate;
Note: the order of the TripCost expression is important, because Miles and TotalMiles are integers. If you put P.Total last, you will get wrong answers because Miles / TotalMiles will be converted to an integer.

MySQL: Using the dates in a between condition for the results

I have a SQL statement in which I do this
... group by date having date between '2010-07-01' and '2010-07-10';
The result looks like:
sum(test) day
--------------------
20 2010-07-03
120 2010-07-07
33 2010-07-09
42 2010-07-10
So I have these results, but is it possible, that I can write a statement that returns me for every day in the "between" condition a result row in this kind:
sum(test) day
--------------------
0 2010-07-01
0 2010-07-02
20 2010-07-03
0 2010-07-04
0 2010-07-05
0 2010-07-06
120 2010-07-07
... ...
42 2010-07-10
Otherwise, if this is not possible, I have to do it in my program logic.
Thanks a lot in advance & Best Regards.
Update: Perhaps it will be better if I will show you the full SQL statement:
select COALESCE(sum(DUR), 0) AS "r", 0 AS "opt", DATE_FORMAT(date, '%d.%m.%Y') AS "day" from (
select a.id as ID, a.dur as DUR, DATE(FROM_UNIXTIME(REVTSTMP / 1000)) as date,
a_au.re as RE, a_au.stat as STAT from b_c
join c on b_c.c_id = c.id
join a on c.id = a.c_id
join a_au on a.id = a_au.id
join revi on a_au.re = revi.re
join (
select a.id as ID, DATE(FROM_UNIXTIME(REVTSTMP / 1000)) as date,
max(a_au.re) as MAX_RE from b_c
join c on b_c.c_id = c.id
join a on c.id = a.c_id
join a_au on a.id = a_au.id
join revi on a_au.re = revi.re
where b_c.b_id = 30 group by ID, date) x on
x.id = a.id and x.date = date and x.MAX_RE = a_au.rev
where a_au.stat != 7
group by ID, x.date)
AS SubSelTable where date between '2010-07-01' and '2010-07-15' group by date;
Update:
My new SQL statement (-> Dave Rix):
select coalesce(`theData`.`real`, 0) as 'real', 0 as 'opt', DATE_FORMAT(`DT`.`ddDate`, '%d.%m.%Y') as 'date'
from `dimdates` as DT
left join (
select coalesce(sum(DUR), 0) AS 'real', 0 AS 'opt', date
from (
select a.id as ID, a.dur as DUR, DATE(FROM_UNIXTIME(REVTSTMP / 1000)) as date, a_au.RE as RE, a_au.stat as STAT
from b_c
join c on b_c.c_id = c.id
join a on c.id = a.c_id
join a_au on a.id = a_au.id
join revi on a_au.RE = revi.RE
join (
select a.id as ID, DATE(FROM_UNIXTIME(REVTSTMP / 1000)) as date, max(a_au.RE) as MAX_RE
from b_c
join c on b_c.c_id = c.id
join a on c.id = a.c_id
join a_au on a.id = a_au.id
join revi on a_au.RE = revi.RE
where b_c.b_id = 30 GROUP BY ID, date
) x
on x.id = a.id and x.date = date and x.MAX_RE = a_au.RE
where a_au.stat != 20
group by ID, x.date
) AS SubTable
where date between '2010-07-01' and '2010-07-10' group by date) AS theData
ON `DT`.`ddDate` = `theData`.`date` where `DT`.`ddDate` between '2010-07-01' and '2010-07-15';
Put the Between Logic in a Where Clause
Select Sum(day), day
From Table
Where day Between date1 and date2
Group By day
EDIT:
Having should only be used to filter data in the aggregates... i.e.
Having Sum(day) > 10
Check out my answer to the following question;
Select all months within given date span, including the ones with 0 values
This may be just what you are looking for :)
You can modify your query above as follows (you could integrate this, but this way is simpler!);
SELECT COALESCE(`theData`.`opt`, 0), `DT`.`myDate`
FROM `dateTable` AS DT
LEFT JOIN (
... INSERT YOUR QUERY HERE ...
) AS theData
ON `DT`.`myDate` = `theData`.`date`
and you will also need to change the DATE_FORMAT(date, '%d.%m.%Y') AS "day" in your query to just date
E.g.
select COALESCE(sum(DUR), 0) AS "r", 0 AS "opt", `date` from
As for #OMG Ponies answer, you will need to pre-populate the dateTable with plenty of rows of data!
Does anyone know how I can post my SQL dump of this table as a file which can be attached? It's quite big, but can be useful...
Assuming that your date column is a DATETIME column, you need to use something to change time values to be the same for proper grouping to happen. IE:
SELECT SUM(t.test),
DATE_FORMAT(t.date, '%Y-%m-%d') AS day
FROM TABLE t
WHERE t.date BETWEEN #start AND #end
GROUP BY DATE_FORMAT(t.date, '%Y-%m-%d')
But if there's no record for a given date, the date will not appear in the result set. In other words, no dates with zero will appear in your output.
To solve that, you need to LEFT JOIN to a table of dates, which MySQL doesn't have the ability to generate. It can't even generate a list of numbers, so you have to create a table with a single column:
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;
...and populate it:
INSERT INTO numbers (id) VALUES (NULL)
...before you can use the number value to generate a list of dates using the DATE_ADD function:
SELECT COALESCE(SUM(t.test), 0),
x.the_date AS day
FROM (SELECT DATE_FORMAT(DATE_ADD(NOW(), INTERVAL n.id-1 DAY), '%Y-%m-%d') AS the_date
FROM NUMBERS n) x
LEFT JOIN your_table yt ON DATE_FORMAT(yt.date, '%Y-%m-%d') = x.the_date
WHERE x.the_date BETWEEN #start AND #end
GROUP BY x.the_date