Join 2 tables and display duplicated row value into new column - mysql

I've been spending 2 days working on this and cannot find anything help.
I have 2 tables.
1. players
id
name
1
AA
2
BB
3
CC
4
DD
2. matching
id
player_id
match_id
date
has_opponent
1
1
1
2021-06-23
0
2
2
1
2021-06-23
0
3
3
2
2021-06-21
0
4
4
3
2021-06-22
1
Expected Result
match_id
date
first_opponent_name
second_opponent_name
1
2021-06-23
AA
BB
2
2021-06-21
CC
3
2021-06-22
DD
DD
I want to join these 2 tables with new columns ( first_opponent_name & second_opponent_name )
if it meets condition as below:
If there is a duplicated match_id but it has different player_id, then display their names in 2 separated columns.
If there is only 1 match_id and it has 1 player_id and has_opponent column is 0, then this should display only first_opponent_name
If there is only 1 match_id and it has 1 player_id and has_opponent column is 1, then first_opponent_name and second_opponent _name should be displayed as the same value.

Not tested but I think this will work, basically it joins the table to itself on the different scenarios and then checks which scenario it's matched.
SELECT
m.match_id
,m.date
,p1.name AS first_opponent_name
,CASE WHEN p2.name IS NOT NULL THEN p2.name
WHEN nop2.match_id IS NOT NULL THEN ''
WHEN samep2.match_id IS NOT NULL THEN p1.name END AS second_opponent_name
FROM
matching AS m
INNER JOIN players AS p1
ON m.player_id = p1.player_id
LEFT JOIN matching AS hasp2
ON m.match_id = hasp2.match_id
AND m.player_id <> hasp2.player_id
LEFT JOIN players AS p2
ON hasp2.player_id = p2.player_id
LEFT JOIN (
SELECT
match_id
FROM
matching
WHERE
has_opponent = 0
GROUP BY
match_id
HAVING COUNT(*) = 1
) AS nop2
ON m.match_id = nop2.match_id
LEFT JOIN (
SELECT
match_id
FROM
matching
WHERE
has_opponent = 1
) AS samep2
ON m.match_id = samep2.match_id
You could arguably leave off either the hasp2 or samep2 join and just assume that if the other two scenarios are not met, depends on how well you trust the data to know if that's a viable option.

WITH cte AS ( SELECT DISTINCT
match_id,
`date`,
MIN(player_id) OVER (PARTITION BY match_id) p1,
MAX(player_id) OVER (PARTITION BY match_id) p2,
has_opponent
FROM matching )
SELECT match_id,
`date`,
p1.name first_opponent_name,
CASE WHEN has_opponent OR (p1 != p2) THEN p2.name END second_opponent_name
FROM cte
JOIN players p1 ON cte.p1 = p1.id
JOIN players p2 ON cte.p2 = p2.id
ORDER BY 1
If CTE and window functions are not supported then
SELECT match_id,
`date`,
p1.name first_opponent_name,
CASE WHEN has_opponent OR (p1 != p2) THEN p2.name END second_opponent_name
FROM ( SELECT match_id,
`date`,
MIN(player_id) p1,
MAX(player_id) p2,
has_opponent
FROM matching
GROUP BY match_id,
`date`,
has_opponent ) cte
JOIN players p1 ON cte.p1 = p1.id
JOIN players p2 ON cte.p2 = p2.id
ORDER BY 1
https://dbfiddle.uk/?rdbms=mysql_8.0&rdbms2=mysql_5.7&fiddle=4b947a2e7d31d50dfc1242b194b204fe
The query assumes that the same match_id strictly matches the same date and match_id.

The only tricky part is dealing with multiple players which is well-suited to using window functions and row_number(); Then it's just a case of aggregating these with a conditional case:
select match_Id, date,
Max(case when cnt=1
then name
else
case when rn=1 then name end
end) first_opponent_name,
Max(case when cnt=1 then
case when has_opponent=1 then name else '' end
else
case when rn=2 then name end
end) second_opponent_name
from (
select m.match_id, m.date, m.has_opponent, p.name,
Row_Number() over(partition by match_id order by player_id)rn,
Count(*) over (partition by match_id) cnt
from matching m
join players p on p.id=m.player_id
)x
group by match_id, date
See working example Fiddle

Related

SQL Query for sorting and getting unique count

I have a table which consists of the following details
Customer
Deal
DealStage
A
D1
Lost
A
D2
Won
A
D3
Contacted
B
D4
Conatcted
B
D5
Lost
C
D6
Lost
D
D7
Lost
I have to develop a query where I should get the unique highest stage for each customer. The Stage priority is Won > Contacted > Lost. For Example, A is having three deals which are Won, Lost, and Contacted. So I should be considering Won. Similarly Contacted for B and Lost for C and D
Is it possible to get an Output like
Customer
Highets Stage
A
Won
B
Contacted
C
Lost
D
Lost
By this, I can generate a pivot table that looks like
Stage
CustomerCount
Won
1
Contacted
1
Lost
2
Thanks in Advance
One option uses aggregation and field():
select customer,
case min(field(deal_stage, 'Won', 'Contacted', 'Lost'))
when 1 then 'Won'
when 2 then 'Contacted'
when 3 then 'Lost'
end as highest_stage
from mytable
group by customer
Actually we could combine this with elt():
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
You can then generate the final result with another level of aggregation:
select highest_stage, count(*)
from (
select customer,
elt(
min(field(deal_stage, 'Won', 'Contacted', 'Lost')),
'Won', 'Contacted', 'Lost'
) as highest_stage
from mytable
group by customer
) t
group by highest_stage
Use windows function as follows:
select * from
(select t.*,
row_number() over (partition by customer
order by case when dealstage = 'Won' then 1
when dealstage = 'Contacted' then 2
when dealstage = 'Lost' then 3
end
) as rn
from your_table t)
where rn = 1;
These are really two different problems. I would, in fact, recommend different approaches to the two. For the first, conditional aggregation:
select customer,
coalesce(max(case when state = 'Won' then state end),
max(case when state = 'Contacted' then state end),
max(case when state = 'Lost' then state end)
) as biggest_state
from t
group by customer;
However, for your final result, I would recommend a correlated subquery:
select t.state, count(*)
from t
where t.state = (select t2.state
from t2
where t2.customer = t.customer
order by field(state, 'Won', 'Contact', 'Lost')
limit 1
)
group by t.state;
Note: This assumes that the original data does not have duplicate rows. If it does, then count(distinct) is one adjustment.

MySql GROUP BY Max Date

I have a table called votes with 4 columns: id, name, choice, date.
****id****name****vote******date***
****1*****sam*******A******01-01-17
****2*****sam*******B******01-05-30
****3*****jon*******A******01-01-19
My ultimate goal is to count up all the votes, but I only want to count 1 vote per person, and specifically each person's most recent vote.
In the example above, the result should be 1 vote for A, and 1 vote for B.
Here is what I currently have:
select name,
sum(case when uniques.choice = A then 1 else 0 end) votesA,
sum(case when uniques.choice = B then 1 else 0 end) votesB
FROM (
SELECT id, name, choice, max(date)
FROM votes
GROUP BY name
) uniques;
However, this doesn't work because the subquery is indeed selecting the max date, but it's not including the correct choice that is associated with that max date.
Don't think "group by" to get the most recent vote. Think of join or some other option. Here is one way:
SELECT v.name,
SUM(v.choice = 'A') as votesA,
SUM(v.choice = 'B') as votesB
FROM votes v
WHERE v.date = (SELECT MAX(v2.date) FROM votes v2 WHERE v2.name = v.name)
GROUP BY v.name;
Here is a SQL Fiddle.
Your answer are close but need to JOIN self
Subquery get Max date by name then JOIN self.
select
sum(case when T.vote = 'A' then 1 else 0 end) votesA,
sum(case when T.vote = 'B' then 1 else 0 end) votesB
FROM (
SELECT name,Max(date) as date
FROM T
GROUP BY name
) AS T1 INNER JOIN T ON T1.date = T.date
SQLFiddle
Try this
SELECT
choice,
COUNT(1)
FROM
votes v
INNER JOIN
(
SELECT
id,
max(date)
FROM
votes
GROUP BY
name
) tmp ON
v.id = tmp.id
GROUP BY
choice;
Something like this (if you really need count only last vote of person)
SELECT
sum(case when vote='A' then cnt else 0 end) voteA,
sum(case when vote='B' then cnt else 0 end) voteB
FROM
(SELECT vote,count(distinct name) cnt
FROM (
SELECT name,vote,date,max(date) over (partition by name) maxd
FROM votes
)
WHERE date=maxd
GROUP BY vote
)
PS. MySQL v 8
select
name,
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
group by name
Or output just one row for the total counts of VoteA and VoteB:
select
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
Based on #d-shish solution, and since introduction (in MySQL 5.7) of ONLY_FULL_GROUP_BY, the GROUP BY statement must be placed in subquery like this :
SELECT v.`name`,
SUM(v.`choice` = 'A') as `votesA`,
SUM(v.`choice` = 'B') as `votesB`
FROM `votes` v
WHERE (
SELECT MAX(v2.`date`)
FROM `votes` v2
WHERE v2.`name` = v.`name`
GROUP BY v.`name` # << after
) = v.`date`
# GROUP BY v.`name` << before
Otherwise, it won't work anymore !

MySQL query to show combinations of output

I have to write a mysql query to find total sales on particular days. I am able to find it, but unable to show combined results.
The table looks like:
Date, Order_id, Product_id, Quantity
'01-JUL-11',O1,P1,5
'01-JUL-11',O2,P2,2
'01-JUL-11',O3,P3,10
'01-JUL-11',O4,P1,10
'02-JUL-11',O5,P3,5
'02-JUL-11',O6,P4,6
'02-JUL-11',O7,P1,2
'02-JUL-11',O8,P5,1
'02-JUL-11',O9,P6,2
'02-JUL-11',O10,P2,4
Desired Output:
Product_id Total Sales on Day '02-JUL-11' Total Sales on Day '02-JUL-11'
P1 15 2
P2 2 4
P3 10 5
P4 0 6
P5 0 1
P6 0 2
What I have tried is:
Select distinct product_id
from orders;
Output:
P1
P2
P3
P4
P5
P6
Select product_id, sum(quantity) from orders
where order_day = '11-07-01'
group by product_id
OUTPUT:
P1 15
P2 2
P3 10
Select product_id, sum(quantity) from orders
where order_day = '11-07-02'
group by product_id
OUTPUT:
P1 2
P2 4
P3 5
P4 6
P5 1
P6 2
This gives me the desired result but I want to somehow combine the columns.
Query that worked for me: (However puts null for 0)
Select X.product_id, X.s, Y.t from
(SELECT A.product_id as product_id, B.s as s FROM
(Select distinct product_id
from orders) A
LEFT JOIN
(Select product_id, sum(quantity) as s from orders
where order_day = '11-07-01'
group by product_id) B
ON A.product_id = B.product_id) X
Left join
(Select product_id, sum(quantity) as t from orders
where order_day = '11-07-02'
group by product_id) Y
on X.product_id = Y.product_id;
If you need only two days summarized in your report, then a simple (non dynamic) pivot query should do the trick:
SELECT product_id,
SUM(CASE WHEN order_day = '11-07-01' THEN quantity ELSE 0 END) AS `Total Sales on Day '01-JUL-11'`,
SUM(CASE WHEN order_day = '11-07-02' THEN quantity ELSE 0 END) AS `Total Sales on Day '02-JUL-11'`
FROM orders
WHERE order_day = '11-07-01' OR order_day = '11-07-02'
GROUP BY product_id
Conditional aggregation is the way to go. But you should learn to use proper date formats. The ISO/ANSI standard is YYYY-MM-DD or YYYYMMDD:
Select product_id,
sum(case when order_day = '2011-07-01' then quantity else 0 end) as q_20110701,
sum(case when order_day = '2011-07-02' then quantity else 0 end) as q_20110702
from orders o
where order_day in ('2011-07-01', '2011-07-02')
group by product_id;
An alternative, if you can live with separate rows for each day, is:
Select product_id, order_day, sum(quantity)
from orders
where order_day in ('2011-07-01', '2011-07-02')
group by product_id, order_day,

Get a count of each value from every column at the same time

I tried to see similar questions but none are helping in solving this the efficient way.
The thing is I have a table with columns like this:
I want to count the occurrences of the values in each column but for all the columns of the table, not only one.
I want to get something like this:
p7 | p7_count | p9 | p9_count
B | 1 | A | 2
A | 1 | E | 1
C | 1
But I'm only able to get this using a single query for each one like:
SELECT p9, count(*) AS p9_Count
FROM respostas
GROUP by p9
ORDER BY p9_Count DESC
But the result I get is:
Is there a way to do this for all the columns instead of having to do it for each one separately and get the result separately?
I think this is the kind of thing you were picturing. It gets kind of messy but you can extend it by adding to the coalesces. To get it to work with a row_number function (for MySQL) I've converted it to use a subquery instead. This is not going to be even remotely efficient when the number of rows gets large because SQL isn't the right tool for this job.
select
p1, p1_count, p2, p2_count, p3, p3_count
from
(
select
p1, p1_count,
(
select count(*) from
(SELECT p1, count(*) AS p1_Count FROM respostas GROUP by p1) as t2
where
t2.p1_Count <= t1.p1_Count
or (t2.p1_Count = t1.p1_Count and t2.p1 <= t1.p1)
) as rownum
from (SELECT p1, count(*) AS p1_Count FROM respostas GROUP by p1) as t1
) as tt1
full outer join
(
select
p2, p2_count,
(
select count(*) from
(SELECT p2, count(*) AS p2_Count FROM respostas GROUP by p2) as t2
where
t2.p2_Count <= t1.p2_Count
or (t2.p2_Count = t1.p2_Count and t2.p2 <= t1.p2)
) as rownum
from (SELECT p2, count(*) AS p2_Count FROM respostas GROUP by p2) as t2
) as tt2
on tt2.rownum = tt1.rownum
full outer join
(
select
p3, p3_count,
(
select count(*) from
(SELECT p3, count(*) AS p3_Count FROM respostas GROUP by p3) as t2
where
t2.p3_Count <= t1.p3_Count
or (t2.p3_Count = t1.p3_Count and t2.p3 <= t1.p3)
) rownum
from (SELECT p3, count(*) AS p3_Count FROM respostas GROUP by p2) as t3
) as tt3
on tt3.rownum = coalesce(tt1.rownum, tt2.rownum)
order by
coalesce(tt1.rownum, tt2.rownum, tt3.rownum)
You would do this with union all. It is a little unclear exactly what you want. Perhaps this is close:
select p, max(p7cnt) as p7cnt, max(p8cnt) as p8cnt, max(p9cnt) as p9cnt
from ((select p7 as p, count(*) as p7cnt, 0 as p8cnt, 0 as p9cnt
from respostas
group by p7
) union all
(select p8, 0 as p7cnt, count(*) as p8cnt, 0 as p9cnt
from respostas
group by p8
) union all
(select p9, 0 as p7cnt, 0 as p8cnt, count(*) as p9cnt
from respostas
group by p9
)
) ppp
group by p;

MYSQL GROUP BY multiple derived tables?

I have this query which does some calculations based on some derived tables that are linked with an INNER JOIN.
At the moment I have a WHERE clause which pulls out one id at a time. But how can I make it list all the ids?
I have tried GROUP BY in various places but can't figure it out.
My query so far is as follows:
SELECT
equipment_id,
service_duration,
available_duration,
(available_duration / service_duration)*100 AS availability
FROM (
SELECT
SUM(service_end_time - service_start_time) AS service_duration
FROM(
SELECT equipment_id,
(CASE
END) AS service_start_time,
(CASE
END) AS service_end_time
FROM t1
WHERE equipment_id = 'EX123'
)AS A
) AS B
JOIN(
SELECT equipment_id,
SUM(available_end_time - available_start_time) AS available_duration
FROM (
SELECT equipment_id,
(CASE
END) AS available_start_time,
(CASE
END) AS available_end_time
FROM t2
WHERE equipment_id = 'EX123'
) AS C
) AS D
ON equipment_id=D.equipment_id
What I want to do is replace the WHERE clause with a GROUP BY to list all the ids, or similar, but getting that to work is beyond my skill level... Any help greatly appreciated :)
Try below:
SELECT
equipment_id, service_duration, available_duration,
(available_duration / service_duration)*100 AS availability
FROM
(
SELECT equipment_id,
SUM(service_end_time - service_start_time) AS service_duration
FROM
(
SELECT equipment_id,
(CASE ... END) AS service_start_time,
(CASE ... END) AS service_end_time
FROM t1
) AS A
GROUP BY equipment_id
) AS B
JOIN
(
SELECT equipment_id,
SUM(available_end_time - available_start_time) AS available_duration
FROM
(
SELECT equipment_id,
(CASE ... END) AS available_start_time,
(CASE ... END) AS available_end_time
FROM t2
) AS C
GROUP BY equipment_id
) AS D
ON equipment_id=D.equipment_id
Try this (replace my field names with your field names):
SELECT
a.emp_id,
service_duration,
available_duration
FROM
(
SELECT
emp_id,
SUM(service_end_time - service_start_time) AS service_duration
FROM
data
GROUP BY
emp_id
) a
JOIN
(
SELECT
emp_id,
SUM(available_end_time - available_start_time) AS available_duration
FROM
data
GROUP BY
emp_id
) b
ON a.emp_id = b.emp_id
GROUP BY
a.emp_id