Flattening two tables and using a group by - mysql

I have two transaction tables like this:
TableA
X R S
71 1 10
71 2 20
71 3 30
72 1 40
72 2 50
72 3 60
TableB
X P Q
71 1 110
71 2 120
71 3 130
73 1 140
73 2 150
73 3 160
I want to flatten the table and execute a query which gives me a result like this:
X S1 S2 S3 Q1 Q2 Q3
71 10 20 30 110 120 130
72 40 50 60 0 0 0
73 0 0 0 140 150 160
where the subscripts in S and Q denote the value of the third column when the second column equals the subscript. For example, S1 denotes the S column value when second column is equal to 1, or Q2 denotes the value of Q when the second column is 2.
I can't figure out what is the best way to about it. Maybe I can use a case statement or may be a subquery. But honestly I have no concrete idea of going about it.

You would want to do this with a join and two aggregations:
select coalesce(a.x, b.x) as x, a.s1, a.s2, a.s3, b.q1, b.q2, b.q3
from (select x,
max(case when r = 1 then s end) as s1,
max(case when r = 2 then s end) as s2,
max(case when r = 3 then s end) as s3
from tableA a
group by x
) a full outer join
(select x,
max(case when p = 1 then q end) as q1,
max(case when p = 2 then q end) as q2,
max(case when p = 3 then q end) as q3
from tableb b
group by x
) b
on a.x = b.x;
EDIT:
If you want to avoid the nested subqueries, you could try:
select coalesce(a.x, b.x) as x,
max(case when r = 1 then s end) as s1,
max(case when r = 2 then s end) as s2,
max(case when r = 3 then s end) as s3
max(case when p = 1 then q end) as q1,
max(case when p = 2 then q end) as q2,
max(case when p = 3 then q end) as q3
from TableA a full outer join
TableB b
on a.x = b.x and a.r = b.p
group by coalesce(a.x, b.x);
It might even be more efficient.

SELECT
ISNULL(A.x, B.x) AS x, SUM(CASE WHEN R = 1 THEN S ELSE 0 END) AS S1,
SUM(CASE WHEN R = 2 THEN S ELSE 0 END) AS S2,
SUM(CASE WHEN R = 3 THEN S ELSE 0 END) AS S3,
SUM(CASE WHEN P = 1 THEN Q ELSE 0 END) AS Q1,
SUM(CASE WHEN P = 2 THEN Q ELSE 0 END) AS Q2,
SUM(CASE WHEN P = 3 THEN Q ELSE 0 END) AS Q3
FROM tableA A FULL JOIN tableB B ON A.X = B.X
GROUP BY ISNULL(A.x, B.x)
T-SQL version

Related

make 2-dimension coordinate using mysql select results

I have question about mysql queries.
I have a table which have data below.
From To Weight
--------------
A B 1
A C 3
B C 2
D E 4
And I want to get sql result like below..
(?) A B C D E
----------------------
A 0 1 3 0 0
B 0 0 2 0 0
C 0 0 0 0 0
D 0 0 0 0 4
E 0 0 0 0 0
And what data is in original table is not determined.
How can I acheive this?
If you know the original columns, you can do:
select c.col1,
sum(case when to = 'A' then weight else 0 end) as a,
sum(case when to = 'B' then weight else 0 end) as b,
sum(case when to = 'C' then weight else 0 end) as c,
sum(case when to = 'D' then weight else 0 end) as d,
sum(case when to = 'E' then weight else 0 end) as d
from (select 'A' as col1 union all select 'B' union all select 'C' union all select 'D' union all select 'E'
) c left join
t
on t.from = c.col1
group by c.col1;
If you don't know the original columns, you could combine the values into a single string:
select col1.col,
group_concat(col2.col, ':', t.weight order by col2.col)
from ((select `from` as col from t
) union -- on purpose to remove duplicates
(select `to` from t
)
) col1 cross join
((select `from` as col from t
) union -- on purpose to remove duplicates
(select `to` from t
)
) col2 left join
t
on col1.col = t.`from` and col2.col = t.`from`
group by col1.col;
If you actually want separate columns and don't know the values, then you would need dynamic SQL.

GROUP BY on table returning incorrect counts in MySQL with LEFT JOIN

I am trying to return multiple counts and averages from multiple tables sorting by gender and am getting incorrect data. I understand that the following is incorrect, but I am unsure of how to fix it. (Edit: Problem with group by gender. See below.)
Here is the query:
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) ) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) ) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM ((mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id)
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id)
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
Here is the table which would be correct if the totals in the fields were correct:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 2 0.5000 2 0.5000 105 22.5000
M 98 2 0.5000 2 0.5000 98 18.8780
EDIT: Here is an example of what I am hoping to achieve. I am certain that the above values are being repeated. The below values are just examples of what I am expecting:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 8 0.0761 4 0.0380 2 22.5000
M 98 2 0.0204 1 0.0102 1 18.8000
Edit:
After playing around a bit, I thought that I had discovered that the joining the cons table was what is giving me problematic returns, but the problem is with GROUP BY when using gender. To illustrate, this query (which is grouped by mailing name instead of gender) works beautifully.
select m.mailing_name AS 'mailing',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE
when mrc.mailing_recipient_click_type_id = 2 THEN 1
END)
AS 'Open Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1
ELSE 0
END) AS 'Avg Open',
SUM(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
END)
AS 'Click Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
ELSE 0
END) AS 'Avg Click',
COUNT(ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM
mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc
ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON mr.cons_id = c.cons_id
WHERE m.mailing_id = 1
GROUP BY m.mailing_name;
The statement is identical with the exception of the first and last lines.
Try this:
I'm not sure what you mean by Avg Open and Avg Click.
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
I also think that mrc.mailing_recipient_click_type_id = 2 means open and mrc.mailing_recipient_click_type_id = 1 mean click seems strange to me. I would expect this data to be exclusive and stored in two different fields.

Mysql split row sum by count

I have the following tables
http://sqlfiddle.com/#!2/d0a3d
sp | product | exp
1 A | 50
1 B 50
1 A 100
1 B 100
2 B 200
2 C 200
3 A 50
3 B 50
Technical I want to divide exp to total number of products associated with t_id
The final result should be
sp | A | B | C
1 | 150 | 150 | 0
2 | 0 | 200 | 200
3 | 50 | 50 | 0
Can be done like this (done to match your sql fiddle columns):-
SELECT b.sp_id,
SUM(IF(a.product = 'A', b.exp, 0)) AS A,
SUM(IF(a.product = 'B', b.exp, 0)) AS B,
SUM(IF(a.product = 'C', b.exp, 0)) AS C
FROM topic_product a
INNER JOIN exp_speaker_topic b
ON a.t_id = b.t_id
GROUP BY b.sp_id
But a mess waiting to happen when extra values get added.
EDIT - amended to give what I think you are saying you want.
SELECT sp_id,
SUM(IF(product = 'A', avg_exp, 0)) AS A,
SUM(IF(product = 'B', avg_exp, 0)) AS B,
SUM(IF(product = 'C', avg_exp, 0)) AS C
FROM
(
SELECT sp_id, a.product, exp / Sub1.product_count AS avg_exp
FROM topic_product a
INNER JOIN exp_speaker_topic b
ON a.t_id = b.t_id
INNER JOIN
(
SELECT t_id, COUNT(*) AS product_count
FROM topic_product
GROUP BY t_id
) Sub1
ON a.t_id = Sub1.t_id
) Sub2
GROUP BY sp_id
SQL fiddle:-
http://sqlfiddle.com/#!2/d0a3d/33
OK, slow day. Just do the following, and handle missing results and display logic in the presentation layer/application-level code (e.g. a simple php loop acting upon an ordered array)...
SELECT p.product
, s.sp_id
, SUM(s.exp/x.cnt) total
FROM topic_product p
JOIN exp_speaker_topic s
ON s.t_id = p.t_id
JOIN
( SELECT t_id
, COUNT(0) cnt
FROM topic_product
GROUP
BY t_id
) x
ON x.t_id = p.t_id
GROUP
BY sp_id,product;
Here is the SQL Fiddle demonstrating the below queries.
You could also use CASE statements like the following:
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id;
If you want to divide by the number of record use the following:
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'A' THEN 1 ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'B' THEN 1 ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) /
SUM(CASE WHEN t.product = 'C' THEN 1 ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id;
If you want to get rid of the Nulls you could use the following:
SELECT m.sp_id,
CASE WHEN ISNULL(m.A) = 0 THEN m.A ELSE 0 END AS A,
CASE WHEN ISNULL(m.B) = 0 THEN m.B ELSE 0 END AS B,
CASE WHEN ISNULL(m.C) = 0 THEN m.C ELSE 0 END AS C
FROM
(
SELECT e.sp_id,
SUM(CASE WHEN t.product = 'A' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'A' THEN 1 ELSE 0 END) AS A,
SUM(CASE WHEN t.product = 'B' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'B' THEN 1 ELSE 0 END) AS B,
SUM(CASE WHEN t.product = 'C' THEN e.exp ELSE 0 END) / SUM(CASE WHEN t.product = 'C' THEN 1 ELSE 0 END) AS C
FROM topic_product t INNER JOIN exp_speaker_topic e ON t.t_id = e.t_id
GROUP BY e.sp_id
) AS m;

Getting Count based on values of multiple rows of the same column

I am relatively new to database.
Say that if I have table like below:
PHeader AHeader IHeader
p1 a1 0
p1 a1 2
p1 a1 3
p1 a2 0
p1 a3 0
p1 a4 0
p1 a4 2
p1 a4 3
p2 a5 0
The expected output is :
PHeader BCount TCount
p1 2 2
p2 0 1
BCount: is for the given PHeader and AHeader Value, if the IHeader values has all the values of (0,2 &3) then BCount value of PHeader is increased by 1 , Since for the p1 it has a1 & a4 with all the vaues of 0,2 & 3 as IHeader Values the BCount for p1 is given as 2
TCount: is for given given PHeader and AHeader value , if IHeader value has only 0 but not 2 or 3, then TCount for the given PHeader is increased by 1. Hence the TCount for p1
is given as 2.
Could you please give me tips in writing the query?
You can approach this using two aggregations. The inner one aggregates at the pheader, aheader level. This counts the values that you are interested in.
The outer one applies the logic that you've described to these summaries:
select pheader,
sum(ih_0 > 0 and ih_2 > 0 and ih_3 > 0) as bcount,
sum(ih_0 > 0 and (ih_2 = 0 or ih_3 = 0)) as tcount
from (select pheader, aheader,
sum(iheader = 0) as ih_0,
sum(iheader = 2) as ih_2,
sum(iheader = 3) as ih_3
from table t
group by pheader, aheader
) pa
group by pheader;
EDIT:
The question is tagged MySQL. The following will work in both databases:
select pheader,
sum(case when ih_0 > 0 and ih_2 > 0 and ih_3 > 0 then 1 else 0 end) as bcount,
sum(case when ih_0 > 0 and (ih_2 = 0 or ih_3 = 0) then 1 else 0 end) as tcount
from (select pheader, aheader,
sum(case when iheader = 0 then 1 else 0 end) as ih_0,
sum(case when iheader = 2 then 1 else 0 end) as ih_2,
sum(case when iheader = 3 then 1 else 0 end) as ih_3
from table t
group by pheader, aheader
) pa
group by pheader;
Please check beloq sql query
SELECT
PHeader ,
COUNT(*) as BCount , --total
SUM(CASE WHEN PHeader = 'p1' THEN 1 ELSE 0 END) as TCount
FROM
yourtablename
group by PHeader
Hope this will give you some idea.

how to select and group mysql data based on the following table

how can I achieve the desired result in mysql if my table looks like this.
result|year
1 |2011
2 |2011
1 |2011
0 |2011
1 |2012
2 |2012
1 = Won, 2 = lost, 0 = draw
Every year can have multiple values like this. Not sure how I can get the desired result like below.
year won lost draw totalPlayed
2011 2 1 1 3
2012 1 1 0 2
I have tried the following query but does not get the desired result
select year,
league_types.league_name,
sum(if(result = 1,1,0)) as won,
sum(if(result = 0,1,0)) as draw,
sum(if(result = 4,1,0)) as noResult,
sum(if(result = 2,1,0)) as lost,
sum(if(result = 3,1,0)) as tied,
sum(if(result > 0 and result < 4,1,0)) as played
from match_score_card
inner join fixtures on match_score_card.match_id = fixtures.match_id
inner join league_types on fixtures.league_id = league_types.league_id
where
team_id = 1 group by year order by year desc
Here is the SQL Fiddle that demonstrates the following query:
SELECT m.year,
SUM(CASE WHEN m.result = 1 THEN 1 ELSE 0 END) AS 'Won',
SUM(CASE WHEN m.result = 2 THEN 1 ELSE 0 END) AS 'Lost',
SUM(CASE WHEN m.result = 0 THEN 1 ELSE 0 END) AS 'Draw',
COUNT(*) AS 'TotalPlayed'
FROM MyTable AS m
GROUP BY m.year
I'm not familiar with that IF function in mySQL, but this standard SQL should work:
select year
, league_types.league_name
, sum(CASE WHEN result = 1 THEN 1 ELSE 0 END) as won
, sum(CASE WHEN result = 2 THEN 1 ELSE 0 END) as lost
, sum(CASE WHEN result = 3 THEN 1 ELSE 0 END) as draw
, sum(CASE WHEN result = 4 THEN 1 ELSE 0 END) as noResult
, sum(CASE WHEN result = 1
or result = 2 THEN 1 ELSE 0 END) as played
from match_score_card
inner join fixtures
on match_score_card.match_id = fixtures.match_id
inner join league_types
on fixtures.league_id = league_types.league_id
where team_id = 1
group by year, league_types.league_name
order by year desc, league_types.league_name
I'm guessing that you only want to count wins and losses as "played".