Join Table and Select Highest Date Value - mysql

Here is the query that I run
SELECT cl.cl_id, cc_rego, cc_model, cl_dateIn, cl_dateOut
FROM courtesycar cc LEFT JOIN courtesyloan cl
ON cc.cc_id = cl.cc_id
Results:
1 NXI955 Prado 2013-10-24 11:48:38 NULL
2 RJI603 Avalon 2013-10-24 11:48:42 2013-10-24 11:54:18
3 RJI603 Avalon 2013-10-24 12:01:40 NULL
The results that I wanted are to group by the cc_rego values and print the most recent cl_dateIn value. (Only Display Rows 1,3)
I've tried to use MAX on the date and group by clause, but it combines rows, 2 & 3 together showing both the highest value of dateIn and dateOut.

I resolved the problem.
Instead of using left join, I added a condition in the where clause which embeds to MAX of the dateIn
SELECT cll.cl_id, cc.cc_id, cc_rego, cc_model, cll.cl_dateIn, cll.cl_dateOut
FROM courtesycar cc, courtesyloan cll
WHERE cl_dateIn = (
SELECT MAX( cl.cl_dateIn )
FROM courtesyloan cl
WHERE cl.cc_id = cc.cc_id )
AND cc.cc_id = cll.cc_id

Related

How to get MAX date from table join that use UNION?

I have 3 table that need to be joined to get the max date.
table_grade_A
ID_GRADE GRADE NOTE SURVEYOR
1 70.7 PASS TOM
3 51.2 FAIL TOM
table_grade_B
ID_GRADE SUB_GRADE_I SUB_GRADE_II TOTAL_GRADE NOTE SURVEYOR
2 30.8 40.1 70.9 PASS MARVOLO
4 10.3 54.1 64.4 FAIL MARVOLO
5 41.7 20.9 62.6 FAIL RIDDLE
table_grade
ID_GRADE STUDENT TEST_DATE
1 MIYA 2018-12-20
2 LAYLA 2018-12-21
3 MIYA 2018-12-21
4 MIYA 2018-12-22
5 KARRIE 2018-12-28
Every student may get different test and different test stored in different table. I use UNION to populate the value from table_grade_a and table_grade_b and JOIN them to table_grade
My current query:
SELECT tg.STUDENT, MAX(tg.TEST_DATE) AS 'TEST_DATE', temp_grade.* FROM `table_grade` tg
INNER JOIN (
SELECT ID_GRADE,GRADE,NOTE
FROM table_grade_a 'tga'
UNION ALL
SELECT ID_GRADE,TOTAL_GRADE AS GRADE,NOTE
FROM table_grade_a 'tgb'
)temp_grade ON tg.ID_GRADE = temp_grade.ID_GRADE
WHERE tg.STUDENT = 'MIYA'
The result of above query is:
STUDENT TEST_DATE GRADE NOTE
MIYA 2018-12-22 70.7 PASS
The expected output should be:
STUDENT TEST_DATE GRADE NOTE
MIYA 2018-12-22 64.4 FAIL
For a result corresponding the the max date of each student:
The MIN or MAX of a column does not necessarily align to the other values of the wanted row(s), so you need to do more than just calculate the maximum date. In MySQL prior to version 8 you could do something like this, by calculating the maximum dates then using that as an inner join to limit the rows to those corresponding to the maximum values:
select
temp_grade .*
from table_grade tg
inner join (
select student, max(test_date) as test_date
from table_grade
group by student
) gd on tg.student = gd.student and tg.test_date = gd.test_date
INNER JOIN (
SELECT ID_GRADE,GRADE,NOTE
FROM table_grade_a 'tga'
UNION ALL
SELECT ID_GRADE,TOTAL_GRADE AS GRADE,NOTE
FROM table_grade_a 'tgb'
)temp_grade ON tg.ID_GRADE = temp_grade.ID_GRADE
# WHERE tg.STUDENT = 'MIYA'
In MySQL v8+ you could use row_number() over(...) instead:
select
temp_grade .*
from (
select *
, row_number() over(partition by student order by test_date DESC) as rn
from table_grade
) tg
INNER JOIN (
SELECT ID_GRADE,GRADE,NOTE
FROM table_grade_a 'tga'
UNION ALL
SELECT ID_GRADE,TOTAL_GRADE AS GRADE,NOTE
FROM table_grade_a 'tgb'
)temp_grade ON tg.ID_GRADE = temp_grade.ID_GRADE
where tg.rn = 1
# and tg.STUDENT = 'MIYA'
The problem with your current approach is that you are selecting the max date, a table level aggregate, while also asking for all individual records at the same time. This does make sense. One correct possibility would be to use LIMIT with ORDER BY:
SELECT tg1.STUDENT, tg1.TEST_DATE, tg2.*
FROM table_grade tg1
INNER JOIN
(
SELECT ID_GRADE, GRADE, NOTE
FROM table_grade_a
UNION ALL
SELECT ID_GRADE, TOTAL_GRADE, NOTE
FROM table_grade_b
) tg2
ON tg1.ID_GRADE = tg2.ID_GRADE
WHERE
tg1.STUDENT = 'MIYA'
ORDER BY
tg1.TEST_DATE DESC
LIMIT 1;

MySQL Join two tables and combine multiple rows into one

I have 2 tables:
sms_recipients
campaign_id | message | user_id | contact_number | status
.........................................................
1 something 12334 078237812719 1
1 something 12123 071231231232 1
2 other 12124 078123123126 0
2 other 12334 078234234212 0
2 other 42124 078124124415 1
sms_campaign
campaign_id | shop_id| campaign_type
.....................................
1 1123 marketing
2 2123 awareness
3 3231 something else
4 4432 bla bla
5 5244 last
campaign_id's are unique for the sms_campaign table, there are multiple user_id's related to the same campaign_id the message is the same for all unique champagne_id
I want to combine them so that every contact_number of the same campaign_id with a status = 0 appears in a single row and column like this:
campaign_id | shop_id| campaign_type | users_mobile_numbers | message
..........................................................................
1 1123 marketing something
2 2123 awareness 078123123126,078234234212 other
3 3231 something else
4 4432 078234234212
5 5244 078124124415
Here is my query so far:
SELECT c.campaign_id,
shop_id,
campaign_type,
contact_number AS users_mobile_numbers,
message FROM sms_campaign c
LEFT JOIN sms_recipients r
ON u.campaign_id = c.campaign_id
WHERE status = 0
In the LEFT JOIN, you will need to either move the filter status = 0 into a join condition, OR if you leave the filter in the WHERE clause, then status = 0 OR status IS NULL to avoid filtering out campaigns with no messages at all - I've done the first option.
As per the comment, you will need to GROUP the data by the campaign columns, and apply aggregate functions to all non-grouped columns, in order to guarantee just one row per group - GROUP_CONCAT will concatenate all text values in each GROUP. I've arbitrarily used MIN to resolve a value for shop and message, but you may need to adjust otherwise. (You can also do a DISTINCT in a GROUP CONCAT, if required).
SELECT
c.campaign_id,
MIN(shop_id) AS shop_id,
campaign_type,
GROUP_CONCAT(contact_number) AS users_mobile_numbers,
MIN(message) AS message
FROM sms_campaign c
LEFT JOIN sms_recipients r
ON u.campaign_id = c.campaign_id AND status = 0
GROUP BY c.campaign_id, campaign_type;
Move the condition on r.status to the ON clause of the outer join. (With the condition in the WHERE clause requiring r.status to be non-NULL, that will negate the outerness of the LEFT JOIN, making it equivalent to an INNER JOIN.)
Add a GROUP BY clause to collapse the rows.
Use a GROUP_CONCAT function to combine the values of contact_number. Column references which appear in the SELECT list but are not included in the GROUP BY clause should also be enclosed in aggregate expressions.)
SELECT c.campaign_id
, ...
, GROUP_CONCAT(DISTINCT r.contact_number ORDER BY r.contact_number) AS `c_numbers`
, MIN(r.message) AS `message`
FROM campaign c
LEFT
JOIN sms_recipients r
ON r.campaign_id = c.campaign_id
AND r.status = 0
GROUP BY c.campaign_id
The value returned by GROUP_CONCAT aggregate function is limited by max_group_concat_len variable. Longer values will be silently truncated to the maximum length.

Nested queries and Join

As a beginner with SQL, I’m ok to do simple tasks but I’m struggling right now with multiple nested queries.
My problem is that I have 3 tables like this:
a Case table:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
3 967 2015-10-09 22:35:40 UserA
4 967 2015-10-09 23:50:31 UserB
5 580 2017-02-09 10:19:43 UserA
a Value table:
case_id labelValue_id Value Type
-------------------------------------------------
1 3633 2731858342 X
1 124 ["864","862"] X
1 8981 -2.103 X
1 27 443 X
... ... ... ...
2 7890 232478 X
2 765 0.2334 X
... ... ... ...
and a Label table:
id label
----------------------
3633 Value of W
124 Value of X
8981 Value of Y
27 Value of Z
Obviously, I want to join these tables. So I can do something like this:
SELECT *
from Case, Value, Label
where Case.id= Value.case_id
and Label.id = Value.labelValue_id
but I get pretty much everything whereas I would like to be more specific.
What I want is to do some filtering on the Case table and then use the resulting id's to join the two other tables. I'd like to:
Filter the Case.nd's such that if there is serveral instances of the same nd, take the oldest one,
Limit the number of nd's in the query. For example, I want to be able to join the tables for just 2, 3, 4 etc... different nd.
Use this query to make a join on the Value and Label table.
For example, the output of the queries 1 and 2 would be:
id nd date username
--------------------------------------------
1 596 2016-02-09 16:50:03 UserA
2 967 2015-10-09 21:12:23 UserB
if I ask for 2 different nd. The nd 967 appears several times but we take the oldest one.
In fact, I think I found out how to do all these things but I can't/don't know how to merge them.
To select the oldest nd, I can do someting like:
select min((date)), nd,id
from Case
group by nd
Then, to limit the number of nd in the output, I found this (based on this and that) :
select *,
#num := if(#type <> t.nd, #num + 1, 1) as row_number,
#type := t.nd as dummy
from(
select min((date)), nd,id
from Case
group by nd
) as t
group by t.nd
having row_number <= 2 -- number of output
It works but I feel it's getting slow.
Finally, when I try to make a join with this subquery and with the two other tables, the processing keeps going on for ever.
During my research, I could find answers for every part of the problem but I can't merge them. Also, for the "counting" problem, where I want to limit the number of nd, I feel it's kind of far-fetch.
I realize this is a long question but I think I miss something and I wanted to give details as much as possible.
to filter the case table to eliminate all but oldest nds,
select * from [case] c
where date = (Select min(date) from case
where nd = c.nd)
then just join this to the other tables:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
to limit it to a certain number of records, there is a mysql specific command, I think it called Limit
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
Limit 4 -- <=== will limit return result set to 4 rows
if you only want records for the top N values of nd, then the Limit goes on a subquery restricting what values of nd to retrieve:
select * from [case] c
join value v on v.Case_id = c.Id
join label l on l.Id = v.labelValue_id
where date = (Select min(date) from [case]
where nd = c.nd)
and nd In (select distinct nd from [case]
order by nd desc Limit N)
So finally, here is what worked well for me:
select *
from (
select *
from Case
join (
select nd as T_ND, date as T_date
from Case
where nd in (select distinct nd from Case)
group by T_ND Limit 5 -- <========= Limit of nd's
) as t
on Case.nd = t.T_ND
where date = (select min(date)
from Case
where nd = t.T_ND)
) as subquery
join Value
on Value.context_id = subquery.id
join Label
on Label.id = Value.labelValue_id
Thank you #charlesbretana for leading me on the right track :).

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

Select Count of Rows with Joined Tables

I have two tables with a one to many relationship. I join the tables by an id column. My problem is that I need a count of all matching entries from the second (tablekey_id) table but I need the information from the row marked with the boolean is_basedomain. As a note there is only one row with is_basedomain = 1 per set of rows with the same tablekey_id.
Table: tablekey
id linkdata_id timestamp
22 9495028175 2013-03-10 01:13:46
23 8392740179 2013-03-10 21:23:25
Table: searched_domains.
NOTE: tablekey_id is the foreign key to the id in the tablekey table.
id tablekey_id domain is_basedomain
1 22 somesite.com 1
2 22 yahoo.com 0
3 23 red.com 1
4 23 blue.com 0
5 23 green.com 0
Heres the query Im working with. I was trying to use a sub query but I cant seem to select only the count for the current tablekey_id so this does not work.
SELECT `tablekey_id`, `linkdata_id`, `timestamp`, `domain`, `is_basedomain`,
(SELECT COUNT(1) AS other FROM `searched_domains` AS dd
ON dd.tablekey_id = d.tablekey_id GROUP BY `tablekey_id`) AS count
FROM `tablekey` AS k
JOIN `searched_domains` AS d
ON k.id = d.tablekey_id
WHERE `is_basedomain` = 1 GROUP BY `tablekey_id`
The result that I would like to get back is:
tablekey_id linkdata_id timestamp domain is_basedomain count
22 9495028175 2013-03-10 01:13:46 somesite.com 1 2
23 8392740179 2013-03-10 21:23:25 red.com 1 3
Can anyone help me get this into one query?
You can treat the searched_domains rows that have is_basedomain=1 as a separate table in the query and join it with another instance of searched_domains (to get the count):
SELECT
d.tablekey_id,
k.linkdata_id,
k.timestamp,
d.domain,
d.is_basedomain,
COUNT(*) as 'count'
FROM
tablekey AS k
join searched_domains AS d on d.tablekey_id=k.id
join searched_domains AS d2 on d2.tablekey_id=d.tablekey_id
WHERE
d.is_basedomain = 1
GROUP BY
d.tablekey_id,
k.linkdata_id,
k.timestamp,
d.domain,
d.is_basedomain
you have an error when using ON instead use WHERE
try this
SELECT `tablekey_id`, `linkdata_id`, `timestamp`, `domain`, `is_basedomain`,
(SELECT COUNT(1) AS other FROM `searched_domains` AS dd
where dd.tablekey_id = d.tablekey_id GROUP BY `tablekey_id`) AS count
FROM `tablekey` AS k
JOIN `searched_domains` AS d
ON k.id = d.tablekey_id
WHERE `is_basedomain` = 1 GROUP BY `tablekey_id`
DEMO HERE
There is no reason to use subquery, or what is your opinion?
SELECT
`tablekey_id`,
`linkdata_id`,
`timestamp`,
`domain`,
`is_basedomain`,
COUNT(*) as count
FROM
`tablekey` AS k ,
`searched_domains` AS d
WHERE
k.id = d.tablekey_id AND
`is_basedomain` = 1
GROUP BY
`tablekey_id`,
`linkdata_id`,
`timestamp`,
`domain`,
`is_basedomain`
If you want only latest timestamp use MAX(timestamp) as timestamp and remove it from group by.