Mysql query left join access to variable - mysql

If someone can help me with this one I've got these tables And I would like to get all bookings between two dates, and to know whether any of it is done by someone else or it is fail.
Table:Bookings
id | date | date_end | repeat | day_of_week | user_id | price
---------------------------------------------------------------------
1 2013-07-01 0000-00-00 1 3 1 20
2 2013-07-16 0000-00-00 1 -1 1 10
Table:Bookings_done_by_other
id | date | user_id | booking_id
---------------------------------
1 2013-07-01 2 2
2 2013-07-13 2 2
Table:Bookings_fail
id | date | booking_id
---------------------------
1 2013-07-01 2
2 2013-07-11 2
Table:Tally
id
--
1
....
500
Here is my query which is working well, but I can't manage with it to give me all what I want.
SELECT f.date as fail, b.id, b.date, a.showdate, DATEDIFF(b.date, a.showdate) AS diff
FROM bookings as b
LEFT JOIN bookings_fail f ON (b.id = f.booking_id and DATE(f.date) = DATE(a.showdate) )
,
(
SELECT DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) <= '2013-07-20 00:00:00')
ORDER BY Id ASC
) a
WHERE
MOD(DATEDIFF(b.date, a.showdate), b.repeat) = 0
AND
DATE(a.showdate)>= DATE(b.date)
when is like this is throw error that cant see a.showdate in left join, when I move the left join next to "a" table is throw error that cant see b.id so Is there any way to get this join working?
I would like to get result something like this
id | showdate | fail | by_other |
-----------------------------
1 2013-07-01 NULL 2
2 2013-07-01 1 NULL //Or date
1 2013-07-03 NULL NULL
1 2013-07-04 NULL NULL
1 2013-07-05 NULL NULL
1 2013-07-06 NULL NULL
.............................
1 2013-07-20 NULL NULL
Sorry if My English is not too good, and Thanks in advance to everyone who can help.
Here some pictures hope is getting clear I cant explain what I want properly
This is bookings
Booking table
This is fail
Fail table
I cant put more than 2 links so that's why these two are just like text
so what's happening when I want to get my bookings for some date I do this
s16.postimg.org/l00k1math/image.jpg
What I want to have done is
s12.postimg.org/5o5hbnmdp/image.jpg
But not only for booking with id 1 I wont to be b.id = un.booking_id and then if there is record for fail to appear how is on the last picture
Thanks for your patience

I think I finally understand what you are trying to do. My suggestion is to format your query like this:
SELECT b.id, a.showdate, f.id as fail, o.id as other
FROM (
SELECT DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) <= '2013-07-20 00:00:00')
ORDER BY Id ASC
) a
LEFT JOIN Bookings b ON (
MOD(DATEDIFF(b.date, a.showdate), b.repeat) = 0 AND
DATE(a.showdate)>= DATE(b.date)
)
LEFT JOIN Bookings_fail f ON (
b.id = f.booking_id AND
DATE(f.date) = DATE(a.showdate)
)
LEFT JOIN Bookings_done_by_other o ON (
b.id = o.booking_id AND
DATE(o.date) = DATE(a.showdate)
)
You start by selecting from the generated a table, and join that with the Bookings table. Once you have those two joined it's easy to join any other tables based on the a.showdate and the b.id fields.
I've created a SQL Fiddle example, more or less based on your data, so you can see how it works. http://sqlfiddle.com/#!2/7e151/1

Related

How to get data from a table even when count(row) is zero? please see the description for more details and the query?

table one
id mandal_name
1 mandal1
2 mandal2
3 mandal3
table address
id mandal_name date
1 mandal1 2017-07-11 12:34:11
2 mandal1 2017-07-11 12:54:45
3 mandal1 2017-07-11 12:23:23
SELECT count(id) as yesterday_count, mandal FROM address WHERE date(date) = '2017-07-11'
Result obviously
3 , mandal1
Expecting result
3 , mandal1
0 , mandal2
0 , mandal3
...
The key is to use an OUTER JOIN - LEFT JOIN in this case.
You can either do
SELECT m.mandal_name, COUNT(a.id) AS yesterday_count
FROM table_one m LEFT JOIN address a
ON m.mandal_name = a.mandal_name
AND a.date >= '2017-07-11'
AND a.date < '2017-07-12'
GROUP BY m.mandal_name;
or
SELECT m.mandal_name, COALESCE(count, 0) AS yesterday_count
FROM table_one m LEFT JOIN (
SELECT mandal_name, COUNT(*) AS count
FROM address
WHERE date >= '2017-07-11'
AND date < '2017-07-12'
) a
ON m.mandal_name = a.mandal_name;
Here is a SQLFiddle demo
Output
| mandal_name | yesterday_count |
|-------------|-----------------|
| mandal1 | 3 |
| mandal2 | 0 |
| mandal3 | 0 |
Further reading - A Visual Explanation of SQL Joins
On a side note - don't use DATE(date) as it makes it impossible to use an index on date column effectively causing a full table scan.
you can query it like this:
SELECT A.mandal_name,IFNULL(COUNT(*),0)
FROM one A
LEFT JOIN address B ON A.mandal_name = B.mandal_name
WHERE DATE(B.date) = '2017-07-11'
GROUP BY A.mandal_name
just substitute your table name and columns to get the result

How to return results that relate across 3 tables efficiently MySQL

Howdie do,
I have the following 3 tables: order, manifest and tracking_updates. Now, each order has foreign key called manifest_id that references the manifest table. Several orders can be in a manifest. The tracking_updates table has a foreign key called order_id that references the order table.
Now, the manifest table contains a column named upload_date. That column, upload_date is the column I need to use in order to determine if an order was uploaded in the last 30 days.
The tracking_update table can contain many updates for each order and so, I must return the most recent tracking update status for each order that matches the criteria below:
1. orders < 30 days, any delivery status
2. orders > 30 days, not delivered
Please see tables below
**Order**
ID | manifest_id
1 | 123
2 | 123
3 | 456
**Manifest**:
ID | upload_date
123 | 2015-12-15 09:31:12
456 | 2015-10-13 09:31:12
**Tracking Update**:
order_id | status_type | last_updated
1 | M | 2015-12-15 00:00:00
1 | I | 2015-12-16 07:20:00
1 | D | 2015-12-17 15:20:00
2 | M | 2015-12-15 00:00:00
2 | D | 2015-12-16 15:20:00
3 | M | 2015-10-13 00:00:00
3 | I | 2015-10-14 12:00:00
3 | E | 2015-10-15 13:50:00
This is what the result set would look like for the orders above
**Result Set**
order_id | manifest_id | latest_tracking_update_status
1 | 123 | D
2 | 123 | D
3 | 456 | E
As you can see, order 1, 2 are assigned to manifest 123 and the manifest was uploaded within the last 30 days and their latest tracking update shows a 'D' for delivered. So those two orders should be included in the result set.
The order 3 is older then 30 days, but hasn't been delivered based off the latest tracking_update status_type, so it should show up in the result set.
Now, the tracking_update table as well over 1 million updates across all orders. So I'm really going for efficiency here
Currently, I have the following queries.
Query #1 returns orders that have been uploaded within the last 30 days and their corresponding latest tracking update
SELECT
fgw247.order.id as order_id,
(SELECT
status_type
FROM
tracking_update as tu
WHERE
tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order, manifest
WHERE
fgw247.order.manifest_id = manifest.id
AND
upload_date >= '2015-12-12 00:00:00'
Query #2 returns the order_id and latest tracking update for every order in the tracking_update table:
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1)
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id
I'm just not sure how to combine these queries to get my orders that match the criteria:
orders < 30 days, any delivery status
orders > 30 days, not delivered
Any ideas would be GREATLY appreciated.
* UPDATE *
This is the current query thanks to answer selected:
select
o.id, t.maxudate, tu.status_type, m.upload_date
from
(select order_id, max(last_updated) as maxudate from tracking_update group by order_id) t
inner join
tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.last_updated
right join
fgw247.order o on t.order_id=o.id
left join
manifest m on o.manifest_id=m.id
where
(tu.status_type != 'D' and tu.status_type != 'XD' and m.upload_date <='2015-12-12 00:00:00') or m.upload_date >= '2015-12-12 00:00:00'
LIMIT 10
UPDATE
This is the current query that joins the three tables rather efficiently
SELECT
o.*, tu.*
FROM
fgw247.`order` o
JOIN
manifest m
ON
o.`manifest_id` = m.`id`
JOIN
`tracking_update` tu
ON
tu.`order_id` = o.`id` and tu.`ship_update_date` = (select max(last_updated) as last_updated from tracking_update where order_id = o.`id` group by order_id)
WHERE
m.`upload_date` >= '2015-12-14 11:50:12'
OR
(o.`delivery_date` IS NULL AND m.`upload_date` < '2015-12-14 11:50:12')
LIMIT 100
Have a subquery that returns the latest update date from the tracking table for each order. Join this subquery on the tracking, orders, and manifests tables to get the details and filter based on the upload date in the where clause:
select o.order_id, t.maxudate, tu.status_type, m.upload_date
from (select order_id, max(update_date) as maxudate from tracking_update group by order_id) t
inner join tracking_update tu on t.order_id=tu.order_id and t.maxudate=tu.update_date
right join orders o on t.order_id=o.order_id
left join manifests m on o.manifest_id=m.manifest_id
where (tu.status_type<>'D' and curdate()-m.upload_date>30) or curdate()-m.upload_date<=30
It may be more efficient to use a union query instead of the or criteria in the where clause.
You can perform a JOIN with the 2nd query result like
SELECT
fgw247.order.id as order_id,
xx.some_column,
(SELECT
status_type
FROM
tracking_update as tu
WHERE tu.order_id = order_id
ORDER BY
tu.ship_update_date DESC
LIMIT
1
) as latestTrackingUpdate
FROM
fgw247.order JOIN manifest
ON fgw247.order.manifest_id = manifest.id
JOIN (
SELECT tracking_update.order_id,
substring_index(group_concat(tracking_update.status_type order by tracking_update.last_updated), ',', -1) AS some_column
FROM
tracking_update
WHERE
tracking_update.order_id is not NULL
GROUP BY tracking_update.order_id ) xx ON xx.order_id = fgw247.order.id
WHERE upload_date >= '2015-12-12 00:00:00'

Combining 2 SUMS in MySQL and optimising query

I have the below code which works:
SELECT admin_teams.name,
SUM(temp_orders.amount_paid) as amount,
SUM(instalments.amount) as amount2
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
(DATE(temp_orders.date_paid) = CURDATE()
OR DATE(instalments.date_paid) = CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
It produces a table that looks like:
+-------------+--------+---------+
| name | amount | amount2 |
+-------------+--------+---------+
| team name 1 | 100 | 150 |
| team name 2 | 200 | 250 |
| team name 3 | 300 | 175 |
+-------------+--------+---------+
I have two issues;
I actually only want one column which is the sum of amount and amount2.
The query is VERY slow - this took 190 sec to run.
I did have it almost working with a Union which was almost instant - I couldn't however get it fully working because the number of columns in my first select statement will not match those in the second - the table 'instalments' does not have a team column but the table temp_orders does.
Can anyone help with either problem?
Thanks.
SELECT admin_teams.name,
(SUM(temp_orders.amount_paid) + SUM(instalments.amount)) as amount,
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
temp_orders.date_paid >= CURDATE()
OR instalments.date_paid >= CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
And add these indexes
ALTER TABLE temp_orders ADD KEY (date_paid ,pay_status,team);
ALTER TABLE instalments ADD KEY (date_paid ,pay_status);

Mysql left join table not between dates

I have the following issue, I've got 3 tables the first one is called courses where I have
courses| id | start | end |
--------------------------------------
1 2012-10-12 | 2012-11-12 |
students| id | available_start | available_end |
-------------------------------------------------
1 2012-10-13 2012-11-11
2 2012-11-06 2012-11-08
students_to_courses | student_id | course_id |
-------------------------------------------------
1 1
So I'm trying to find which students are available for courses periods. So if the student is added to student_to_courses and dates are between the course dates I don't need it.
I've got the feeling that the query should be with a sub query but I really don't understand them. My query now is looking like this but doesn't work properly.
SELECT s.id
FROM (`students` s)
LEFT JOIN `student_to_course` s2c ON `s2c`.`student_id` = `s`.`id`
LEFT JOIN `courses` assigned_2_course ON `s2c`.`course_id` = `assigned_2_course`.`id`
LEFT JOIN `courses` c ON `c`.`id` = 1
WHERE
(
(s.available_start NOT BETWEEN assigned_2_course.start AND assigned_2_course.end
AND
s.aviailable_end NOT BETWEEN assigned_2_course.start AND assigned_2_course.end
) OR assigned_2_course.end IS NULL)
AND
`s`.`available_start` BETWEEN c.start AND c.end
AND `s`.`available_end` <= c.end
GROUP BY `s`.`id`
ORDER BY `s`.`id` desc
Here is http://sqlfiddle.com/#!2/49c11/1
now works, but doesn't remove the students which are assigned in other courses with same dates how you can see I'm trying to get available students for course 3 which starts 02-03 and ends 02-08, student 2 is in course 3 so is not shown, student 1 is in course 2 which starts 01-03 and ends 03-01 so shouldn't be available.
Any help will be appreciated.
I used your SQL fiddle (but added another student record) http://sqlfiddle.com/#!2/246645/1
try this to find all students that could attend course 3 because they are not in a class during that time:
SELECT student.*
FROM student
JOIN course
ON course.id = 3
AND student.available_start <= course.`start`
AND student.available_end >= course.`end`
WHERE NOT EXISTS
(SELECT *
FROM student_to_course
JOIN course AS c
ON student_to_course.course_id = c.id
WHERE student.id = student_to_course.student_id
AND (course.`start` BETWEEN c.`start` AND c.`end`
OR
course.`end` BETWEEN c.`start` AND c.`end`
OR
c.`start` BETWEEN course.`start` AND course.`end`));

Getting JOIN and WHERE to work together

Ok, I have an example table with the following information and query.
First up is the data, with the question following at the end.
Here's the SQL Dump:
http://pastie.org/private/o7zzajdpm6lzcbqrjolgg
Or you can use the included a visual below:
Purchases Table
| id | brand | date |
1 b1 2000-01-01
2 b1 2000-01-03
3 b2 2000-01-04
4 b3 2000-01-08
5 b4 2000-01-14
Owners Table
id | firstname | lastname | purchaseid | itemCoupon | itemReturned | Accessories
1 Jane Doe 1 yes no 4
2 Jane Doe 2 yes no 2
3 Jane Doe 3 no no 1
4 Jane Doe 4 no no 3
5 Jane Doe 5 no yes 6
The Query
SELECT brand, COALESCE( SUM( inTime.Accessories ) , 0 ) AS acessory_sum
FROM purchases
INNER JOIN owners AS person ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
LEFT JOIN owners AS inTime ON person.id = inTime.id
AND purchases.date
BETWEEN DATE( '2000-01-01' )
AND DATE( '2000-01-05' )
GROUP BY purchases.brand
This gives the following expected result:
| brand | accessory_sum
b1 6
b2 1
b3 0
b4 0
The question
Now, I would like to add to the query:
WHERE itemCoupon = 'yes' OR itemReturned = 'yes'
But this overrides the last join and when I do the same search above I get:
| brand | accessory_sum
b1 6
b2 1
Similarly I still want it to return No results found for 2000-01-04, 2000-01-08 using WHERE itemCoupon = 'yes' OR itemReturned = 'yes'. Removing the WHERE gives me zeros for all brands if I try to do it another way.
Basically I want to keep the way the WHERE behaves but also keep the format that I described in the first example of the expected output.
As it is now, using WHERE destroys the way the last LEFT JOIN works with COALESCE which fills the remaining brand rows with zeros.
Your WHERE turns the outer join into an inner join.
You need to move your additionally condition into the LEFT JOIN condition:
LEFT JOIN owners as inTime
ON person.id = inTime.id
AND purchases.date between purchases.date DATE ('2000-01-01') and DATE ('2000-01-05')
AND (inTime.itemCoupon = 'yes' or inTime.itemReturned = 'yes')
the ON clause when doing a JOIN is similar to the WHERE clause. So instead of trying to use WHERE, just add another AND to your query (and don't forget to use the parenthesis in the OR clause):
SELECT brand,
COALESCE(SUM(Time.purchasedAccessories),0) as acessory_sum
FROM purchases
INNER JOIN owners AS person
ON person.purchaseid = purchases.id
AND person.firstname = 'Jane'
AND person.lastname = 'Doe'
AND (person.itemCoupon = 'yes' OR person.itemReturned = 'yes')
LEFT JOIN owners AS inTime
ON person.id= inTime.id
AND purchases.date
BETWEEN purchases.date
DATE( '2000-01-01' )
AND
DATE( '2000-01-05' )
GROUP BY purchases.brand