Mix SQL Rows into one - mysql

I am using the following SQL Query:
SELECT package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
,CASE WHEN field_id ='Github' THEN field_value ELSE '' END AS Github
,CASE WHEN field_id ='Repository' THEN field_value ELSE '' END AS Repository
,CASE WHEN field_id ='twitter' THEN field_value ELSE '' END AS Twitter
FROM package_stats, xf_user, xf_user_field_value, xf_user_profile
WHERE xf_user.username = package_stats.developer AND xf_user_profile.user_id = xf_user.user_id AND xf_user_field_value.user_id = xf_user.user_id AND xf_user_field_value.field_value <> ''
Which outputs the following:
developer username email about Github Repository Twitter
John Doe jdoe j#doe.com It's me https://github....
John Doe jdoe j#doe.com It's me
John Doe jdoe j#doe.com It's me https://link....
John Doe jdoe j#doe.com It's me https://twitter.com/...
How can I mix all those rows into a single row?

I believe you can use conditional aggregation:
SELECT ps.developer, u.username, u.email,
up.homepage, up.location, up.about,
MAX(CASE WHEN ufv.field_id = 'Github' THEN ufv.field_value END) AS Github,
MAX(CASE WHEN ufv.field_id = 'Repository' THEN ufv.field_value END) AS Repository,
MAX(CASE WHEN ufv.field_id = 'twitter' THEN ufv.field_value END) AS Twitter
FROM package_stats ps JOIN
xf_user u
ON u.username = ps.developer JOIN
xf_user_profile up
ON up.user_id = u.user_id JOIN
xf_user_field_value ufv
ON ufv.user_id = u.user_id
WHERE ufv.field_value <> ''
GROUP BY ps.developer, u.username, u.email,
up.homepage, up.location, up.about;
Notes:
Table aliases make the query easier to write and to read.
All columns should be qualified with a table alias, to avoid ambiguity.
There is no reason to have an ELSE clause for this purpose. If there are no matches, then the column is NULL (a reasonable value).
If there could be more than one field for a given category, use GROUP_CONCAT().

You could use a (fake) aggregation function and group by
SELECT package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
,min(CASE WHEN field_id ='Github' THEN field_value END) AS Github
,min(CASE WHEN field_id ='Repository' THEN field_value END) AS Repository
,min(CASE WHEN field_id ='twitter' THEN field_value END ) AS Twitter
FROM package_stats
INNER JOIN xf_user ON xf_user.username = package_stats.developer
INNER JOIN xf_user_profile ON xf_user_profile.user_id = xf_user.user_id
INNER JOIN xf_user_field_value v1 ON xf_user_field_value.user_id = xf_user.user_id
WHERE xf_user_field_value.field_value <> ''
GROUP BY package_stats.developer
,xf_user.username
,xf_user.email
,xf_user_profile.homepage
,xf_user_profile.location
,xf_user_profile.about
and don't use implict joins are not clear ..
the implict join is an arcaic sintax

Related

Merge multiple query results into one using case

I have a user-table which contains users information with the fields user_id, fullname, age, username, password and other is tests-table with the fields id, user_id, test_type
When I write the below query
select users.user_id, fullname,
(CASE WHEN test_table.user_id=users.id and test_table.type = 'objectives' THEN 'yes' ELSE 'no' END) AS written_objectives,
(CASE WHEN test_table.user_id=users.id and test_table.type = 'theory' THEN 'yes' ELSE 'no' END) AS written_theory,
from users
LEFT JOIN test_table ON users.id = test_table.user_id
WHERE users.user_id = 1
I get the results like this
user_id|fullname| test_type | written_objectives| written_theory
1 | Ben. | objectivs | yes | no
1 | Ben. | theory | no | yes
But I want the results like this
user_id|fullname| test_type | written_objectives| written_theory
1 | Ben. | objectivs | yes | yes
From the above scenario, the user with id of 1 has submitted both objectives and theory.
You can do conditional aggregation:
select u.user_id, u.fullname,
max(tt.type = 'objectives') written_objectives,
max(tt.type = 'theory' ) written_theory
from users u
left join test_table tt on u.id = tt.user_id
where u.user_id = 1
group by u.user_id
This generates 0/1 values rather than 'yes'/'no' - which I find more expressive. But if you really want thoses strings, then:
select u.user_id, u.fullname,
case when max(tt.type = 'objectives') then 'yes' else 'no' end written_objectives,
case when max(tt.type = 'theory' ) then 'yes' else 'no' end written_theory
from users u
left join test_table tt on u.id = tt.user_id
where u.user_id = 1
group by u.user_id
Notes:
there is no need to repeat the join condition in the conditional expression - the left join handles that already
table aliases make the query easier to write and read

MariaDB/Mysql SELECT on CASE Statement inside CAST causes an error

I'm working with a frustrating set of data that contains null, 0, 1, and 2 values for the 'active' column in my users table.
Why does this work:
SELECT u.id AS LegacyContactKey,
u.first_name AS FirstName,
u.last_name AS LastName,
c.company_name AS CompanyName,
u.email AS EmailAddress,
(CASE WHEN u.active = 1 THEN 1 WHEN u.active = 2 THEN 1 ELSE 0 END) as IsMember
FROM tb_users AS u
INNER JOIN tb_company AS c ON u.company_id = c.company_id
But not this?
SELECT u.id AS LegacyContactKey,
u.first_name AS FirstName,
u.last_name AS LastName,
c.company_name AS CompanyName,
u.email AS EmailAddress,
CAST((CASE WHEN u.active = 1 THEN 1 WHEN u.active = 2 THEN 1 ELSE 0 END) AS BOOLEAN) as IsMember
FROM tb_users AS u
INNER JOIN tb_company AS c ON u.company_id = c.company_id
I want to cast one of my query results as a boolean so it returns true or false but when I add CAST() I get an error (near BOOLEAN)
Running MariaDB 10.3
I don't think cast() supports boolean. But the simplest method is really:
(u.active IN (1, 2)) as IsMember
You can just use a boolean expression and assign a column alias to it.
EDIT:
If NULL is an issue, just include it in the expression:
(u.active IN (1, 2) AND u.active IS NOT NULL) as IsMember

shows mysql records twice because of inner joining

In below query (Mentors) are 13 which shows me 26, while (SchoolSupervisor) are 5 which shows me 10 which is wrong. it is because of the Evidence which having 2 evidance, because of 2 evidence the Mentors & SchoolSupervisor values shows me double.
please help me out.
Query:
select t.c_id,t.province,t.district,t.cohort,t.duration,t.venue,t.v_date,t.review_level, t.activity,
SUM(CASE WHEN pr.p_association = "Mentor" THEN 1 ELSE 0 END) as Mentor,
SUM(CASE WHEN pr.p_association = "School Supervisor" THEN 1 ELSE 0 END) as SchoolSupervisor,
(CASE WHEN count(file_id) > 0 THEN "Yes" ELSE "No" END) as evidence
FROM review_m t , review_attndnce ra
LEFT JOIN participant_registration AS pr ON pr.p_id = ra.p_id
LEFT JOIN review_files AS rf ON rf.training_id = ra.c_id
WHERE 1=1 AND t.c_id = ra.c_id
group by t.c_id, ra.c_id order by t.c_id desc
enter image description here
You may perform the aggregations in a separate subquery, and then join to it:
SELECT
t.c_id,
t.province,
t.district,
t.cohort,
t.duration,
t.venue,
t.v_date,
t.review_level,
t.activity,
pr.Mentor,
pr.SchoolSupervisor,
rf.evidence
FROM review_m t
INNER JOIN review_attndnce ra
ON t.c_id = ra.c_id
LEFT JOIN
(
SELECT
p_id,
COUNT(CASE WHEN p_association = 'Mentor' THEN 1 END) AS Mentor,
COUNT(CASE WHEN p_association = 'School Supervisor' THEN 1 END) AS SchoolSupervisor,
FROM participant_registration
GROUP BY p_id
) pr
ON pr.p_id = ra.p_id
LEFT JOIN
(
SELECT
training_id,
CASE WHEN COUNT(file_id) > 0 THEN 'Yes' ELSE 'No' END AS evidence
FROM review_files
GROUP BY training_id
) rf
ON rf.training_id = ra.c_id
ORDER BY
t.c_id DESC;
Note that this also fixes another problem your query had, which was that you were selecting many columns which did not appear in the GROUP BY clause. Under this refactor, there is nothing wrong with your current select, because the aggregation take place in a separate subquery.
try adding this to the WHERE part of your query
AND pr.p_id IS NOT NULL AND rf.training_id IS NOT NULL
You can add a group by pr.p_id to remove the duplicate records there. Since, the group by on pr is not present as of now, there might be multiple records of same p_id for same ra
group by t.c_id, ra.c_id, pr.p_id order by t.c_id desc

MySQL sum of column value from derived table

This is my query:
SELECT usr.id,
count(DISTINCT sol.id) as 'Asked',
count(DISTINCT ans.id) as 'Answered',
sum(DISTINCT CASE ans.accepted WHEN 1 THEN 1 ELSE 0 end) as 'Accepted'
FROM tbl_users usr
LEFT JOIN tbl_solutions sol on sol.authorID = usr.id
LEFT JOIN tbl_solution_answers ans on ans.authorID = usr.id
group by usr.id, sol.authorID
My above query with the sum(DISTINCT CASE ans.accepted WHEN 1 THEN 1 ELSE 0 end) only ever returns 1 though I know that's not the case. I've tried adding a group clause on the ans.authorID but it has no effect.
How can I get the sum of all rows from the tbl_solution_answers ans table where the authorID is that of tbl_users.id and Accepted is 1.
SELECT usr.id,
count(DISTINCT sol.id) as 'Asked',
count(DISTINCT ans.id) as 'Answered',
count(DISTINCT case ans.accepted when 1 then ans.id end) as 'Accepted'
FROM tbl_users usr
LEFT JOIN tbl_solutions sol on sol.authorID = usr.id
LEFT JOIN tbl_solution_answers ans on ans.authorID = usr.id
group by usr.id, sol.authorID, ans.authorID
After so many permutations count(DISTINCT case ans.accepted when 1 then ans.id end) as 'Accepted' seems to work. Now if an authorID in tbl_solution_answers has 8 rows they'll all be returned as Answered and if say 3 of them are Accepted then 3 is returned as Accepted.

Mysql COUNT result rows for a related table

I have
users
------------------------
id | name | other_stuff.....
.
engagement
------------------------
user_id | type_code |
type_code is a varchar, but either A, B, C or NULL
[ EDIT for clarity: Users can have many engagements of each type code. SO I want to count how many they have of each. ]
I want to return ALL user rows, but with a count of A, B and C type engagements. E.g.
users_result
------------------------
user_id | user_name | other_stuff..... | count_A | count_B | count_C |
I've done quite a bit of searching, but found the following issues with other solutions:
The "other_stuff..." is actually grouped / concatenated results from a dozen other joins, so it's a bit of a monster already. So I need to be able to just add the additional fields to the pre-existing "SELECT ...... FROM users..." query.
The three additional required bits of data all come from the same engagement table, each with their own condition. I havent found anything to allow me to use the three conditions on the same related table.
Thanks
[edit]
I tried to simplify the question so people didn't have to look through loads of unnecessary stuff, but seems I might not have given enough info. Here is 'most' of the original query. I've taken out a lot of the selected fields as there are loads, but I've left most of the joins in so you can see basically what is actually going on.
SELECT
user.id,
user.first_name,
user.second_name,
GROUP_CONCAT(DISTINCT illness.id ORDER BY illness.id SEPARATOR ',' ) AS reason_for_treatment,
IF(ww_id=1000003, 1,'') as user_refused_program,
Group_CONCAT(DISTINCT physical_activity.name SEPARATOR ', ') AS programme_options,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM `user`
LEFT JOIN session AS session_induction ON (user.id = session_induction.user_id AND session_induction.session_type_id = 3)
LEFT JOIN stats AS stats_induction ON session_induction.id = stats_induction.session_id
LEFT JOIN session AS session_interim ON (user.id = session_interim.user_id AND session_interim.session_type_id = 4)
LEFT JOIN stats AS stats_interim ON session_interim.id = stats_interim.session_id
LEFT JOIN session AS session_final ON (user.id = session_final.user_id AND session_final.session_type_id = 5)
LEFT JOIN stats AS stats_final ON session_final.id = stats_final.session_id
LEFT JOIN user_has_illness ON user.ID = user_has_illness.user_id
LEFT JOIN illness ON user_has_illness.illness_id = illness.id
LEFT JOIN user_has_physical_activity ON user.ID = user_has_physical_activity.user_id
LEFT JOIN physical_activity ON user_has_physical_activity.physical_activity_id = physical_activity.id
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
WHERE (user.INDUCTION_DATE>='2010-06-09' AND user.INDUCTION_DATE<='2011-06-09' AND user.archive!='1' )
GROUP BY user.id, engagement_item.user_id
It's worth mentioning that it works fine - returns all users with all details required. Except for the count_A B and C cols.
[edit added slightly more simplified query below]
Stripped out the unrelated joins and selects.
SELECT
user.id,
user.first_name,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM `user`
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
GROUP BY user.id, engagement_item.user_id
SELECT e.user_id, u.name,
COUNT(CASE type_code WHEN 'A' THEN 1 ELSE NULL END) as count_A,
COUNT(CASE type_code WHEN 'B' THEN 1 ELSE NULL END) as count_B,
COUNT(CASE type_code WHEN 'C' THEN 1 ELSE NULL END) as count_C
FROM engagement e join users u on (e.user_id = u.id)
GROUP BY e.user_id, u.name
I would use COUNT instead of SUM just because that is what it is made for, counting things when not NULL.
SELECT
user.id,
user.first_name,
user.second_name,
GROUP_CONCAT(DISTINCT illness.id ORDER BY illness.id SEPARATOR ',' ) AS reason_for_treatment,
IF(ww_id=1000003, 1,'') as user_refused_program,
Group_CONCAT(DISTINCT physical_activity.name SEPARATOR ', ') AS programme_options,
ei.count_A, ei.count_B, ei.count_C
FROM `user`
LEFT JOIN ( SELECT user_id
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_A
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm12%' THEN 1 ELSE NULL END) as count_B
, COUNT(CASE WHEN engagement_item.type_code LIKE 'wm6%' THEN 1 ELSE NULL END) as count_C
FROM engagement_item
GROUP BY userid ) ei
LEFT JOIN session AS session_induction ON (user.id = session_induction.user_id AND session_induction.session_type_id = 3)
LEFT JOIN stats AS stats_induction ON session_induction.id = stats_induction.session_id
LEFT JOIN session AS session_interim ON (user.id = session_interim.user_id AND session_interim.session_type_id = 4)
LEFT JOIN stats AS stats_interim ON session_interim.id = stats_interim.session_id
LEFT JOIN session AS session_final ON (user.id = session_final.user_id AND session_final.session_type_id = 5)
LEFT JOIN stats AS stats_final ON session_final.id = stats_final.session_id
LEFT JOIN user_has_illness ON user.ID = user_has_illness.user_id
LEFT JOIN illness ON user_has_illness.illness_id = illness.id
LEFT JOIN user_has_physical_activity ON user.ID = user_has_physical_activity.user_id
LEFT JOIN physical_activity ON user_has_physical_activity.physical_activity_id = physical_activity.id
LEFT JOIN engagement_item ON user.ID = engagement_item.user_ID
WHERE (user.INDUCTION_DATE>='2010-06-09' AND user.INDUCTION_DATE<='2011-06-09' AND user.archive!='1' )
GROUP BY user.id, engagement_item.user_id, ei.count_A, ei.count_B, ei.count_C
Something like this perhaps?
select e.user_id, u.name,
sum(case e.type_code when 'A' then 1 else 0 end) as count_A,
sum(case e.type_code when 'B' then 1 else 0 end) as count_B,
sum(case e.type_code when 'C' then 1 else 0 end) as count_C
from engagement e join users u on (e.user_id = u.id)
group by e.user_id, u.name
The interesting part is the use of CASE inside the SUM to split the counting into three chunks.