SQL query joining a few tables (MySQL) - mysql

I need a "little" help with an SQL query (MySQL).
I have the following tables:
COURIERS table:
+------------+
| COURIER_ID |
+------------+
DELIVERIES table:
+-------------+------------+------------+
| DELIVERY_ID | COURIER_ID | START_DATE |
+-------------+------------+------------+
ORDERS table:
+----------+-------------+-------------+
| ORDER_ID | DELIVERY_ID | FINISH_DATE |
+----------+-------------+-------------+
COORDINATES table:
+-------------+-----+-----+------+
| DELIVERY_ID | LAT | LNG | DATE |
+-------------+-----+-----+------+
In the real database I have more columns in each table, but for this example the above columns are enough.
What do I need?
An SQL query that returns all couriers [COURIER_ID], their last
delivery [DELIVERY_ID] (based on last START_DATE), the
delivery's last coordinate [LAT and LNG] (based on last DATE) and the remaining orders count (total of orders of the last delivery that have no FINISH_DATE).
A courier can have no deliveries, in this case I want DELIVERY_ID =
NULL, LAT = NULL and LNG = NULL in the result.
A delivery can have no coordinates, in this case I want LAT = NULL
and LNG = NULL in the result.
What was I able to do?
SELECT c.`COURIER_ID`,
d.`DELIVERY_ID`,
r.`LAT`,
r.`LNG`,
(SELECT COUNT(DISTINCT `ORDER_ID`)
FROM `ORDERS`
WHERE `DELIVERY_ID` = d.`DELIVERY_ID`
AND `FINISH_DATE` IS NULL) AS REMAINING_ORDERS
FROM `COURIERS` AS c
LEFT JOIN `DELIVERIES` AS d USING (`COURIER_ID`)
LEFT JOIN `COORDINATES` AS r ON r.`DELIVERY_ID` = d.`DELIVERY_ID`
WHERE (CASE WHEN
(SELECT MAX(`START_DATE`)
FROM `DELIVERIES`
WHERE `COURIER_ID` = c.`COURIER_ID`) IS NULL THEN d.`START_DATE` IS NULL ELSE d.`START_DATE` =
(SELECT MAX(`START_DATE`)
FROM `DELIVERIES`
WHERE `COURIER_ID` = c.`COURIER_ID`) END)
AND (CASE WHEN
(SELECT MAX(`DATE`)
FROM `COORDINATES`
WHERE `DELIVERY_ID` = d.`DELIVERY_ID`) IS NULL THEN r.`DATE` IS NULL ELSE r.`DATE` =
(SELECT MAX(`DATE`)
FROM `COORDINATES`
WHERE `DELIVERY_ID` = d.`DELIVERY_ID`) END)
GROUP BY c.`COURIER_ID`
ORDER BY d.`START_DATE` DESC
The problem is that this query is very slow (from 5 to 20 seconds) when I have over 5k COORDINATES and it does not returns all couriers sometimes.
Thank you so much for any solution.

Try this:
SELECT C.COURIER_ID, D.DELIVERY_ID, D.START_DATE, D.FINISH_DATE,
B.LAT, B.LNG, B.DATE, C.NoOfOrders
FROM COURIERS C
LEFT JOIN ( SELECT *
FROM (SELECT *
FROM DELIVERIES D
ORDER BY D.COURIER_ID, D.START_DATE DESC
) A
GROUP BY COURIER_ID
) AS A ON C.COURIER_ID = A.COURIER_ID
LEFT JOIN ( SELECT *
FROM (SELECT *
FROM COORDINATES CO
ORDER BY CO.DELIVERY_ID, CO.DATE DESC
) B
GROUP BY CO.DELIVERY_ID
) AS B ON A.DELIVERY_ID = B.DELIVERY_ID
LEFT JOIN ( SELECT O.DELIVERY_ID, COUNT(1) NoOfOrders
FROM ORDERS O WHERE FINISH_DATE IS NULL
GROUP BY O.DELIVERY_ID
) AS C ON A.DELIVERY_ID = C.DELIVERY_ID;

I haven't been able to test this query since I don't have a mysql database set up right now, much less with this schema and sample data. But I think this will work for you:
select
c.courier_id
, d.delivery_id
, co.lat
, co.lng
, oc.cnt as remaining_orders
from
couriers c
left join (
select
d.delivery_id
, d.courier_id
from
deliveries d
inner join (
select
d.delivery_id
, max(d.start_date) as start_date
from
deliveries d
group by
d.delivery_id
) dmax on dmax.delivery_id = d.delivery_id and dmax.start_date = d.start_date
) d on d.courier_id = c.courier_id
left join (
select
c.delivery_id
, c.lat
, c.lng
from
coordinates c
inner join (
select
c.delivery_id
, max(c.date) as date
from
coordinates c
group by
c.delivery_id
) cmax on cmax.delivery_id = c.delivery_id and cmax.date = c.date
) co on co.delivery_id = d.delivery_id
left join (
select
o.delivery_id
, count(o.order_id) as cnt
from
orders o
where
o.finish_date is null
group by
o.delivery_id
) oc on oc.delivery_id = d.delivery_id

Related

mysql - add row wise different incentive

I am executing one query which is giving me output as I want. I am stuck at the last step of the solution.
Query
SELECT sum(`purchase`.`purchase_ammount`) as Purchase, `franchise`.`district_id`
FROM `purchase`
LEFT JOIN `franchise`
ON `purchase`.`f_id` = `franchise`.`id`
WHERE `franchise`.`district_id` in (
select `district`.`id`
from `district`
LEFT JOIN `region`
ON `district`.`region_id` = `region`.`id`
LEFT JOIN `regional_owner`
ON `regional_owner`.`r_id` = `region`.`id`
WHERE `regional_owner`.`email_id` = 'balajibarhate10#gmail.com'
)
AND purchase.purchase_date
BETWEEN Date_format(Curdate(), "%Y-%m-01") AND Curdate()
GROUP BY `franchise`.`district_id`
Output
+----------+-------------+
| Purchase | district_id |
+----------+-------------+
| 18151 | 4 |
| 24603 | 6 |
+----------+-------------+
What I want
I would like to add 5% in Purchase of the first row if district_id is 4 and 2% in Purchase of the second row if district_id is not 4. I want it in the query itself. How can I achieve this?
This should work
SELECT (CASE WHEN district_id = 4 THEN Purchase * 1.05
ELSE Purchase * 1.02 END) PURCHASE_AMT, district_id
FROM
(
SELECT sum(`purchase`.`purchase_ammount`) as Purchase, `franchise`.`district_id`
FROM `purchase`
LEFT JOIN `franchise`
ON `purchase`.`f_id` = `franchise`.`id`
WHERE `franchise`.`district_id` in (
select `district`.`id`
from `district`
LEFT JOIN `region`
ON `district`.`region_id` = `region`.`id`
LEFT JOIN `regional_owner`
ON `regional_owner`.`r_id` = `region`.`id`
WHERE `regional_owner`.`email_id` = 'balajibarhate10#gmail.com'
)
AND purchase.purchase_date
BETWEEN Date_format(Curdate(), "%Y-%m-01") AND Curdate()
GROUP BY `franchise`.`district_id`
) A
You can use a case statement.
CASE
WHEN district_id = 4 THEN purchase_amount * 1.05
ELSE purchase_amount * 1.02
END AS AdjustedPrice
Of course, you can adjust the assigned name of the calculated field as need be.

ID associated with max date in group

there are a ton of other related questions but I couldn't quite apply them to my situation. I am using sequelize so I'm trying to do this with as few hits to the DB as possible.
My (simplified) table is basically:
id dateAcquired staffId skillId
44 2017-04-27 25 2
341 2018-02-01 28 2
4787 2018-04-04 25 2
8852 2020-01-31 28 2
I am looking for just the id of the most recent dateAcquired per staffId. (note that the most recent one might be a lower id, otherwise I would have had a solution)
4787
8852
Just in SQL using this query I get the correct date but not the correct id:
SELECT id, staffId, max(`dateAcquired`)
FROM `histories` AS `history`
WHERE `history`.`skillId` = '2'
GROUP BY `staffId`, id
Results in:
44 2018-04-04
341 2020-01-31
Although I know it needs tweaking once I get the query right, my sequelize code is:
models.history.findAll(
{
attributes: [sequelize.fn("max", sequelize.col('dateAcquired')), 'id'],
where: {skillId:id},
group: ["id"]
}
).then(maxIds => {
let ids = maxIds.map(result =>{return result.id;});
models.history.findAll({
include: [
{
model:models.staff
}
],
where: {
id: {
[Op.in]: [ids]
}
}
}).then(results =>
{
res.json(results);
})
})
Thanks for your help!
With NOT EXISTS:
select h.* from histories h
where h.skillid = 2
and not exists (
select 1 from histories
where skillid = h.skillid and staffid = h.staffid and dateAcquired > h.dateAcquired
)
See the demo.
Results:
| id | dateAcquired | staffId | skillId |
| ---- | ------------------- | ------- | ------- |
| 4787 | 2018-04-04 00:00:00 | 25 | 2 |
| 8852 | 2020-01-31 00:00:00 | 28 | 2 |
you can try
Create table #test
(id int,
dateAcquired date,
staffid int,
skillid int
)
Insert into #test values (44 , '2017-04-27' , 25 , 2)
Insert into #test values (341 , '2018-02-01' , 28 , 2)
Insert into #test values (4787 , '2018-04-04' , 25 , 2)
Insert into #test values (8852 , '2020-01-31' , 28 , 2)
select id,dateacquired
from
(
select id,dateacquired,
--ROW_NUMBER()over(partition by dateacquired order by dateacquired )
ROW_NUMBER()over( order by dateacquired desc )rn
from #test
)a where rn<=2
Query
SELECT t.*
FROM my_table t
LEFT JOIN my_table t2 ON t2.staffId = t.staffId AND t2.dateAcquired > t.dateAcquired
WHERE t2.id IS NULL
AND t.skillId = 2;
Explanation
What happens is that each row from t joins onto any rows where the staffId matches and dateAcquired is greater. The only rows that don't join are the ones with the highest values in dateAcquired. We then filter out everything that does join in the WHERE clause.
Several possible query patterns to return the specified result
Using an inline view (derived table) to get the latest (maximum) date_acquired for each staffid, and then a join to the base table to get the row(s) that have that latest date_acquired.
SELECT MAX(h.`id`) AS `id`
, h.`staffId`
, h.`dateacquired`
FROM
JOIN ( SELECT l.`staffid`
, MAX(l.`dateacquired`) AS `max_dateacquired`
FROM `histories` l
WHERE l.`skillId` = '2'
GROUP BY l.`staffid`
) m
JOIN `histories` h
ON h.`dateacquired` = m.`max_dateacquired`
ON h.`staffid` = m.`staffid`
AND h.`skillId` = '2'
GROUP
BY h.`staffid`
, h.`dateacquired`
MySQL 8.0 introduced Window Functions, which gives us another way to get the specified result:
WITH w AS
( SELECT h.id
, h.staffid
, h.dateacquired
, ROW_NUMBER() OVER(PARTITION BY h.staffid ORDER BY h.dateacquired DESC, h.id DESC) AS _rn
FROM `histories` h
WHERE h.skillid = '2'
)
SELECT w.id
, w.staffid
, w.dateacquired
FROM w
WHERE _rn = 1
ORDER
BY w.staffid
We could also use an anti-join pattern, to retrieve rows where there isn't a later dateacquired... assuming id is unique in histories (or at least the (staffid,dateacquired,id) tuple is unique)
SELECT h.id
, h.staffid
, h.dateacquired
FROM `histories` h
-- anti-join
LEFT
JOIN `histories` l
ON l.skillid = '2'
AND l.staffid = h.staffid
AND l.dateacquired >= h.dateacquired
AND ( l.datecquired > h.dateacquired OR l.id > h.id )
WHERE l.staffid IS NULL
AND h.skillid = '2'
ORDER
BY h.staffid
We could accomplish the same thing, re-writing the anti-join as a NOT EXISTS
SELECT h.id
, h.staffid
, h.dateacquired
FROM `histories` h
WHERE h.skillid = '2'
AND NOT EXISTS
( SELECT 1
FROM `histories` l
WHERE l.skillid = '2'
AND l.staffid = h.staffid
AND l.dateacquired >= h.dateacquired
AND ( l.datecquired > h.dateacquired OR l.id > h.id )
)
(Note that some of these queries could be simplified a tiny bit if we have a guarantee that (staffid,skillid,dateacquired) tuple is unique. All of the queries above do not assume such a guarantee.)

SQL: Return specified column of max() row inside SELECT

I want to return the date column for each of the rows where max() is used within the SELECT. Or maybe there is a better way of doing this?
This is how I imagine it:
SELECT
MAX(time) as time, [date column from max(time) row] as timedate,
MAX(distance) as distance, [date column from max(distance) row] as distancedate,
MAX(weight) as weight, [date column from max(weight) row] as weightdate
Here is my current SQL, this does not return the date for each of the MAX() rows.
$db->query("SELECT e.id as id, e.name, MAX(ue.time) as time, MAX(ue.weight) as weight, MAX(ue.distance) as distance
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
GROUP BY e.id
LIMIT 30");
id | exerciseid | date | weight | distance | time
----------------------------------------------------------
1 | 1 | 2014-06-14 | 100 | 33 | null
2 | 1 | 2013-03-03 | 500 | 11 | null
3 | 1 | 2014-11-11 | null | null | 41
Current Output:
Array
(
[id] => 1
[name] => run
[time] => 41
[weight] => 500
[distance] => 33
)
Expected Output:
Array
(
[id] => 1
[name] => run
[time] => 41
[time_date] => 2014-11-11
[weight] => 500
[weight_date] => 2013-03-03
[distance] => 33
[distance_date] => 2014-06-14
)
SQL Fiddle: http://sqlfiddle.com/#!2/75e53/1
SELECT e.id as id, e.name,
MAX(ue.time) as time,
(
select date
from `users exercises`
WHERE time = MAX(ue.time) AND ue.`userid` = $userid
LIMIT 1
) as time_date,
MAX(ue.weight) as weight,
(
select date
from `users exercises`
WHERE weight = MAX(ue.weight) AND ue.`userid` = $userid
LIMIT 1
) as weight_date,
MAX(ue.distance) as distance,
(
select date
from `users exercises`
WHERE distance = MAX(ue.distance) AND ue.`userid` = $userid
LIMIT 1
) as distance_date
FROM `users exercises` as ue
LEFT JOIN `exercises` as e ON exerciseid = e.id
WHERE ue.`userid` = $userid
GROUP BY e.id
LIMIT 30
There's probably a more efficient way to do this, but sadly my MySQL skills aren't that good; however the code below does what you want:
Solution 1
select
mx.time
, t.date as timedate
, mx.distance
, d.date as distancedate
, mx.weight
, w.date as weightdate
from
(
SELECT
MAX(`time`) as `time`
, MAX(`distance`) as `distance`
, MAX(`weight`) as `weight`
from `users exercises`
) as mx
inner join `users exercises` as t on t.time = mx.time
inner join `users exercises` as d on d.distance = mx.distance
inner join `users exercises` as w on w.weight = mx.weight;
Solution 2
select
mx.time
, (select date from `users exercises` as x where x.time = mx.time limit 1) as timedate
, mx.distance
, (select date from `users exercises` as y where y.distance = mx.distance limit 1) as distancedate
, mx.weight
, (select date from `users exercises` as z where z.weight = mx.weight limit 1) as weightdate
from
(
SELECT
MAX(`time`) as `time`
, MAX(`distance`) as `distance`
, MAX(`weight`) as `weight`
from `users exercises`
) as mx;
For anyone using a db which support partition by there is a better way of implementing this; sadly MySQL does not support that functionality currently.
SQL Fiddle: http://sqlfiddle.com/#!2/75e53/13

adding INNER JOIN to convert ID to a value from another table

i have an SQL Script which generates the following Output:
+------------+------------+--------------------+----------------------+---------------------+
| CUSTOMERID | TOTALCOUNT | VALIDWARRANTYCOUNT | EXPIREDWARRANTYCOUNT | LASTPURCHASED |
+------------+------------+--------------------+----------------------+---------------------+
| 1 | 5 | 5 | 0 | 2013-12-24 14:37:45 |
| 2 | 3 | 3 | 0 | 2013-12-24 14:37:45 |
| 3 | 6 | 6 | 0 | 2013-10-23 13:37:45 |
+------------+------------+--------------------+----------------------+---------------------+
I would like to see the Companyname of the Customer in a additional column. I have an extra table with customer id and company name but I don't know how to extend my query. All trys result in an empty table.
Here is my Query:
SELECT p2c.customerid
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT DISTINCT
p2c.customerid
FROM
products2customers p2c
INNER JOIN
products p
ON
p2c.customerid = p.id
) AS p2c
I made an SQL Fiddle with the normal query without a join, I hope someone could give me a hint.
SQLFiddle
Just add your JOIN in the very last line, and select the company after the customer id:
SELECT p2c.customerid,
c.company
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT DISTINCT
p2c.customerid
FROM
products2customers p2c
) AS p2c
JOIN customers c ON c.id = p2c.customerid; <--
Updated fiddle:
http://sqlfiddle.com/#!2/60396/5/0
just add another join to the customer table as below
SELECT p2c.customerid, company
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
) AS TotalCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) >= CURDATE()
) AS ValidWarrantyCount
, (
SELECT COUNT(*)
FROM products2customers
WHERE customerid = p2c.customerid
AND date_add(from_unixtime(purchased), INTERVAL 5 YEAR) < CURDATE()
) AS ExpiredWarrantyCount
, (
SELECT MAX( from_unixtime(purchased) )
FROM products2customers
WHERE customerid = p2c.customerid
) AS LastPurchased
FROM (
SELECT
p2c.customerid, c.company
FROM
products2customers p2c
INNER JOIN
products p
ON
p2c.customerid = p.id
inner join customers c
on c.id=p2c.customerid group by 1,2
) AS p2c

How to merge the result of this 2 queries in mysql server

I am actually stuck in merging the result of this two queries:
first query:
SELECT c.code, c.name, pc.sku, pc.cat_code, pp.title
FROM `cat_parent` cp, cat c, prod_cat pc, products pp
WHERE c.code = cp.cat_code
AND cp.cat_code = pc.cat_code
AND pp.sku = pc.sku
AND cp.parent_code = 01110
AND hide =0
The result I get is:
Second query:
SELECT `sku` , `update_date` , `description` , count( * ) AS total_sold
FROM `orderline`
WHERE `update_date` >= ( DATE_ADD(CURDATE( ) , INTERVAL -14 DAY ) )
AND `update_date` <= ( DATE_ADD(CURDATE( ) , INTERVAL -7 DAY ) )
GROUP BY left( sku, 7 )
ORDER BY total_sold DESC
The result:
The question I want to ask that how can I get the result by filtering the sku available in both tables.
Just bit confused on that part....any ideas will be appreciated.
This is only part of the data. there is heaps of data. Yes, I want to merge the both tables and want to find the common sku available in both tables.
My expected result will be sku, title, total sold.
Thanks, anyway I managed to get around to get the result.
My final query:
SELECT * FROM (
SELECT sku , update_date , description FROM orderline WHERE
update_date >= '2012-03-06' AND update_date <= '2012-03-07' )g
JOIN (
SELECT c.code, c.name, pc.sku, pc.cat_code FROM cat_parent cp, cat
c, prod_cat pc, products pp WHERE c.code = cp.cat_code AND cp.cat_code
= pc.cat_code AND pp.sku = pc.sku AND cp.parent_code =01110 AND hide =0 )p ON left( g.sku, 7 ) = left( p.sku, 7 )
Something like this -
SELECT
`c`.`code`, `c`.`name`, `pc`.`sku`, `pc`.`cat_code`, `pp.title`,
`ol`.`sku`, `ol`.`update_date`, `ol`.`description`, COUNT(*) AS `total_sold`
FROM `cat_parent` `cp`
INNER JOIN `cat` `c`
ON `c`.`code` = `cp`.`cat_code`
INNER JOIN `prod_cat` `pc`
ON `cp`.`cat_code` = `pc`.`cat_code`
INNER JOIN `products` `pp`
ON `pp`.`sku` = `pc`.`sku`
INNER JOIN `orderline` `ol`
ON LEFT(`pc`.`sku`, 7) = LEFT(`ol`.`sku`, 7)
WHERE `cp`.`parent_code` = 01110
AND `hide` = 0
AND `ol`.`update_date` >= ( DATE_ADD(CURDATE( ) , INTERVAL -14 DAY ) )
AND `ol`.`update_date` <= ( DATE_ADD(CURDATE( ) , INTERVAL -7 DAY ) )
GROUP BY left( `ol`.`sku`, 7 )
ORDER BY `total_sold` DESC