Sorting in a query based on another querys AVG - mysql

This is my current query to grab my deals:
SELECT deals.*, SUM(orders.cost) AS 'revenue' FROM deals
LEFT JOIN orders ON (orders.deal_id = deals.id)
WHERE 1
AND (orders.status = 'new' OR orders.status = 'delivered')
GROUP BY deals.ID
ORDER BY revenue ASC
Now this works great and i can sort this query by (order by) whatever field I wish in orders or deals table.
Then when i loop through the deals and display them with a foreach(),
I have this query to grab average review stars from customers for a deal:
SELECT ROUND(AVG(stars)) as 'avg_stars' FROM deals_reviews WHERE deal_id = *THE DEAL_ID* AND active = '1'
Now I would like to sort all the deals ASC/DESC out from the average stars.
To do this it require for me to merge that query with the above one, and when i need to display the avg_stars I grab this value from the first and only query and not a second query.
How can I have that query 'merged' into that first one? So when thats done we can just do ORDER BY avg_stars DESC and it'll sort by this?

I am not sure if i understand right, but this should be the solution
SELECT deals.*, SUM(orders.cost) AS 'revenue' FROM deals
LEFT JOIN orders ON (orders.deal_id = deals.id)
LEFT JOIN deals_reviews ON (deals.id = deals_reviews.deal_id AND active = '1')
WHERE 1
AND (orders.status = 'new' OR orders.status = 'delivered')
GROUP BY deals.ID
ORDER BY ROUND(AVG(stars)) as 'avg_stars' ASC

Related

MySQL Join v. Subquery to get a conditional date while optimizing performance

I need your help on deciding which query to use since we are facing performance issue with MySQL joins and Subqueries.
The problem is that I'm trying to find out user's 'first order date' while they should fit certain conditions:
order_status = 1(completed) or order_status = 2(canceled)
The Tables are tb_order and tb_user; All the columns that contain a 'time' are using Unix Time Stamp.
The result I need looks like this:
order_id
user_id
user_1st_order_date
1
47
1666876594
2
982
1667095997
Option 1: JOIN
Select
o.id as 'order_id',
u.id as 'user_id',
ox.create_time as 'user_1st_order_date'
from
tb_order o
left join tb_user u on o.user_id = u.id
/* here I have about 10 joins */
left join
(
select
ux.id,
ox.create_time
from
tb_user u
left join tb_order ox on ox.user_id = u.id
where
( ox.order_status = 1 or ox.order_status = 2 )
/* Orders can be (completed) or (canceled) */
group by
ux.id
) x on x.id = u.id
/* The thought here is by using group by `ux.id` I will get the
user's earliest completed or canceled order and it's `create_time`
then this can be used to `join` the order info */
where
o.create_time != 0
and
( o.order_status = 1 or o.order_status = 2 )
group by
o.id
Option 2: Subquery
Select
o.id as 'order_id',
u.id as 'user_id',
(
select
ox.create_time
from
tb_order ox
where
(ox.order_status = 1 or ox.order_status = 2)
and
ox.user_id = u.id
order by
ox.id asc
limit 1
) as 'user_1st_order_date'
from
tb_order o
left join tb_user u on o.user_id = u.id
/* here I have about 10 joins */
where
o.create_time != 0
and
( o.order_status = 1 or o.order_status = 2 )
group by
o.id
/* Option 1 stopped working somehow yesterday and start to give me the latest order time instead, and I don't know why. Though I can get the correct date back by putting 'Min()' in front of the ox.create_time */
left join
(
select
ux.id,
Min(ox.create_time)
Both worked but I'm trying to find the most efficient one since I'll use this on a daily basis to update our data source for Tableau Online.
Many thanks in advance.
Just looking at query 1, you have set out a crazy set of table relationships.
Starting with the Select in parentheses, you have a Left Join that implies there are users without orders. That's OK, but your Where filter is based solely on order status, which is NULL when there is no order, so all such users will be filtered out. There is no useful purpose being served by joining the tb_user table and it can be omitted from that subquery.
In the outer query the Left join of tb_order to tb_user implies there are orders without users, but then joining the subquery using u.id instead of o.userid guarantees that nothing from the subquery will be usable in that case. Once again, there is no purpose served in bring tb_user in there either.
To get the desired result set you set out above, you can vastly simplify things by looking only at the tb_order table like Option 3 below:
Option 3
Select * From (
Select id as 'order_id', user_id as 'user_id'
,min(Case When order_status In (1,2) Then create_time End)
Over (Partition By user_id
Between unbounded preceding And unbounded following)
AS 'user_1st_order_date'
From tb_order
)
Where order_status in (1,2)
Order by order_id
This can be further simplified by moving the Where order_status in (1,2) inside the inner query and removing the Case statement around the created_date, but it's less adaptable to use within other queries.

Mysql SUM left join force show results with the sum of 0 zero results

So i am struggling with this for sometime now.
The employees just add start and end time on table clocktimes
I just want the result of all employees including the ZERO (the ones that didn’t show) also in that date interval. I also tried using COALESCE with no desired results.
The regular field is the amount of time already in units example : 3.5.
My problem is to show the ones that result is 0 (zero), no hours present in that task.
Query used:
SELECT
employees.employeeid,
employees.emp_external_id,
employees.status_id,
employees.firstname,
employees.middlename,
employees.lastname,
IFNULL(SUM(clocktimes.Regular), 0) AS sumoftime
FROM
employees
LEFT JOIN clocktimes ON employees.emp_external_id = clocktimes.EmployeeGUID
WHERE
employees.status_id <> 3 AND
date(clocktimes.StartDate) BETWEEN '2017-07-01' AND '2017-07-15'
GROUP BY
employees.employeeid
ORDER BY
employees.firstname ASC
http://sqlfiddle.com/#!9/d89534/1
SELECT
employees.employeeid,
employees.emp_external_id,
employees.status_id,
employees.firstname,
employees.middlename,
employees.lastname,
IFNULL(SUM(clocktimes.Regular), 0) AS sumoftime
FROM
employees
LEFT JOIN clocktimes ON employees.emp_external_id = clocktimes.EmployeeGUID
WHERE
employees.status_id <> 3 AND
(date(clocktimes.StartDate) BETWEEN '2017-07-01' AND '2017-07-15') or date(clocktimes.StartDate) is null )
GROUP BY
employees.employeeid
ORDER BY
employees.firstname ASC
what is haapening here is, since you are filtering via date, null columns are being filtered out. check the where clause

finding change between records in MySQL

I have a table where I am storing the stored number of barrels inside of many tanks. I am storing values here every night at midnight, and at the beggining and end of any operator initiated transfer.
What I want to return is the number of barrels difference since the previous event record for that specific tank. I have the correct ID for the self join to get the previous record number, however the barrels is incorrect.
Here is what I currently have.
SELECT
inventory.id,
MAX(inventory2.id) AS id2,
inventory.tankname,
inventory.barrels,
inventory.eventstamp,
inventory2.barrels
FROM
inventory
LEFT JOIN
inventory inventory2 ON inventory2.tankname = inventory.tankname AND inventory2.eventstamp < inventory.eventstamp
GROUP BY
inventory.id,
inventory.tankname,
inventory.barrels,
inventory.eventstamp
ORDER BY
inventory.tankname,
inventory.eventstamp
That returns the following
Just use correlated subqueries:
SELECT i.*,
(SELECT i2.id
FROM inventory i2
WHERE i2.tankname = i.tankname AND
i2.eventstamp < i.eventstamp
ORDER BY i2.eventstamp DESC
LIMIT 1
) as prev_id,
(SELECT i2.barrels
FROM inventory i2
WHERE i2.tankname = i.tankname AND
i2.eventstamp < i.eventstamp
ORDER BY i2.eventstamp DESC
LIMIT 1
) as prev_barrels
FROM inventory i
ORDER BY i.tankname, i.eventstamp;
Your query doesn't work because you have columns in the SELECT that are not in the GROUP BY and are not aggregated. That shouldn't be allowed in any database; it is unfortunate that MySQL does allow it.

how to mysql group by date multiple left join

I have the following schema:
http://sqlfiddle.com/#!9/bd3a4/1
I would like to
group by date() and add where user_id = ?..
per day and count the results per day.
required result Day|TotalRequests|TotalOrders
Since you could have an order on Day 1, and a request on Day 8, you may have entries on one side but not the other. To qualifify your needs, I would do a UNION of all orders and requests individually by date. Then roll those values up. The inner Pre-Aggregate result query is where the WHERE clause per user would be applied. The pre-aggregate query also has a recSource column to indicate where the record originated from as 'O' from orders and 'R' from requests, so the roll-up knows which column to store the total count respectively.
select
preAgg.recDate,
SUM( case when preAgg.recSource = 'O' then preAgg.recCount else 0 end ) as OrderCount,
SUM( case when preAgg.recSource = 'R' then preAgg.recCount else 0 end ) as RequestCount
from
( select
date(o.created_at) recDate,
'O' as recSource,
count(*) as recCount
from
orders o
where
o.user_id = 3
group by
date(o.created_at)
UNION ALL
select
date(r.created_at) recDate,
'R' as recSource,
count(*) as recCount
from
requests r
where
r.user_id = 3
group by
date(r.created_at) ) preAgg
group by
preAgg.recDate
order by
preAgg.recDate
For query optimization, I would ensure your order and request table both have have an index on ( user_id, created_at ).
SQL Fiddle result
You can use the following query:
SELECT
DATE(o.created_at) AS Day
,COUNT(r.id) AS TotalRequests
,COUNT(o.id) AS TotalOrders
FROM orders o
LEFT JOIN
requests r ON
r.id = o.request_id
WHERE o.user_id = 3
GROUP BY DATE(r.created_at), DATE(o.created_at),o.user_id

MySQL GROUP BY grouping by lowest field value

I'm trying to fetch the lowest price per day per hotel, I get multiple results.
I first try to fetch the lowest amount with the MIN() function, then inner join.
When i later try to group by outside the subquery, it just groups by the lowest id.
The SQL itself:
SELECT mt.id, mt.amount, mt.fk_hotel, mt.start_date
FROM price mt
INNER JOIN
(
SELECT price.id, MIN(price.amount) minAmount
FROM price
WHERE 1=1 AND price.start_date >= '2014-10-08' AND price.start_date <= '2014-10-10' AND price.active = 1 AND price.max_people = 2
GROUP BY id
) t
ON mt.id = t.id AND mt.amount = t.minAmount
ORDER BY mt.fk_hotel, mt.amount;
And the results looks like this:
http://jsfiddle.net/63mg3b2j/
I want to group by the start date and fk_hotel so that it groups by the lowest amount value, can anybody help me? Am I being clear?
Edit: I also need a field fk_room from the corresponding row, so i can inner join
Try this:
SELECT MIN(mt.amount) AS min_amount, mt.fk_hotel, mt.start_date
FROM price mt
WHERE
mt.active = 1 AND
mt.max_people = 2 AND
mt.start_date >= '2014-10-08' AND mt.start_date <= '2014-10-10'
GROUP BY mt.fk_hotel, mt.start_date
ORDER BY mt.fk_hotel, min_amount;
Well first of all get a table with minimum value in top row using ORDER BY and then GROUP BY for your required result
SELECT mt.id, mt.amount, mt.fk_hotel, mt.start_date
FROM
(SELECT id, amount, fk_hotel, start_date
FROM price
WHERE start_date >= '2014-10-08' AND start_date <= '2014-10-10'
AND active = 1 AND max_people = 2
ORDER BY amount DESC) AS mt
GROUP BY mt.id
Well I had to still go with a subquery, cause i needed some additional foreign key fields from the corresponding row to inner join some other stuff. It isn't a great solution, cause it fetches too much stuff, the rest is filtered out programmatically.
The most annoying thing here, when I try to use MIN() or MAX() function and get the appropriate fields to that row, it fetches the first results from the DB, which are incorrect and so i have to use a subquery to inner join to get the other fields, I can use grouping, but I had too many fields to group. Maybe I'm missing something. The amount of data doesn't grow in time, so I guess it works for me. So this is the final SQL i came up with, for future reference..
SELECT mt.*, roomtype.name roomname, hotel.name hotelname
FROM booking.price mt
INNER JOIN roomtype ON roomtype.id = mt.fk_roomtype
INNER JOIN hotel ON hotel.id = mt.fk_hotel
INNER JOIN(
SELECT price.id, MIN(price.amount) minAmount
FROM booking.price WHERE 1=1 AND price.start_date >= '2014-10-22' AND price.start_date <= '2014-10-31' AND price.max_people = 2 AND price.active = 1
GROUP BY id
) t
ON mt.id = t.id AND mt.amount = t.minAmount
ORDER BY mt.start_date, mt.amount