COnditional JOIN query in MySQL - mysql

I have two mysql tables
user:
|--------------------------------|
| id | name | type | ruser_type |
|--------------------------------|
| 1 | Admin | a | |
| 2 | | r | c |
|--------------------------------|
customer
|-------------------------|
| id | name | user_id |
|-------------------------|
| 1 | Sam | 2 |
|-------------------------|
If user.type is 'a' or 's', then its admin user whose name is in user table.
If user.type is 'r' and ruser_type is 'c', then its regular user which has a relation in customer table where customer.user_id = user.id
I want a query which would run a conditional join.
If user.type is 'a' or 's', then name would be fetched from user table.
If user.type is 'r' and and ruser_type is 'c', then name would be fetched from customer table with the JOIN condition customer.user_id = user.id.
For this, I have written a query like this:-
SELECT users.fname as adminFname, customers.fname as customerFname, users.type FROM users
LEFT JOIN customers ON (customers.user_id = users.id AND
(
(users.type = 'r' AND users.ruser_type = 'c')
OR users.type = 'a'
OR users.type = 's'
)
)
WHERE users.id = 1
Is there any possibility to optimize the query more?
Also, how can I write this query using Laravel eloquent?

FWIW, I find this marginally easier to read...
SELECT u.fname adminFname
, c.fname customerFname
, u.type
FROM users u
LEFT
JOIN customers c
ON c.user_id = u.id
WHERE u.id = 1
AND (
(u.type = 'r' AND u.ruser_type = 'c')
OR (u.type IN('a','s'))
)

I have written two sql query hope this will help you
SELECT CASE CU.type WHEN 'a' OR 's' THEN CU.name END AS name,
CASE WHEN CU.type = 'r' AND CU.ruser_type = 'c' THEN CR.name END AS cust_name, CU.type
FROM
user AS CU
LEFT JOIN customer AS CR ON CR.user_id = CU.id
In this you'll get result like this,
name cust_name type
Sam r
Admin a
and i have wrote another query like this,
SELECT CASE WHEN CU.type = 'a' OR 's' THEN CU.name ELSE CR.name END AS name, CU.type
FROM
user AS CU
LEFT JOIN customer AS CR ON CR.user_id = CU.id
In this you'll get result like this
name type
Sam r
Admin a
DB File Link
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=b02d931b68a4f70d8b4a84144c60a572

This will give you required result for all users:
SELECT u.fname adminFname
, c.fname customerFname
, u.type
FROM users u
LEFT JOIN customers c
ON (u.type = 'r' AND u.ruser_type = 'c' AND c.user_id = u.id)
Add where condition as required.
You can even simplify it further to get common firstName column in output:
SELECT COALESCE(u.fname, c.fname) firstName, u.type
FROM users u
LEFT JOIN customers c
ON (u.type = 'r' AND u.ruser_type = 'c' AND c.user_id = u.id)

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

MySQL select group concat?

I am trying to do a select from two tables. I have two tables:
select * from igw41_users;
+-----+------------+----------+-----------------------------+--------------------------------------------------------------+-------+-----------+---------------------+---------------------+------------+--------------------------------------------------------------------------------------------------------------------------------+---------------------+------------+--------+------+--------------+
| id | name | username | email | password | block | sendEmail | registerDate | lastvisitDate | activation | params | lastResetTime | resetCount | otpKey | otep | requireReset |
17 John Doe AAAAAA nothing else is needed from this table.
select * from igw41_user_profiles;
+---------+----------------------+-----------------------+----------+
| user_id | profile_key | profile_value | ordering |
17 profile.account_type "2" 4
17 profile.postal_code "75055" 1
I need to get id (from igw41_users), username igw41_users , postal_code (from igw41_user_profiles) and account_type (from igw41_user_profiles). But the info I need is in profile_values for each igw41_user_profiles.user_id that matches igw41_users.id.
select id, username, GROUP_CONCAT(profile_value SEPARATOR ',')
from igw41_users
left join igw41_user_profiles on igw41_users.id = igw41_user_profiles.user_id and igw41_user_profiles.profile_key = 'profile.account_type'
group by username;
this gives me the account_type, but I can't figure out how to get the postal_code If I do a right join it gives me: ERROR 1066 (42000): Not unique table/alias: 'igw41_user_profiles'
This is what I tried to get both profile values:
select id, username, GROUP_CONCAT(profile_value SEPARATOR ',')
from igw41_users
left join igw41_user_profiles on igw41_users.id = igw41_user_profiles.user_id and igw41_user_profiles.profile_key = 'profile.account_type'
right join igw41_user_profiles on igw41_users.id = igw41_user_profiles.user_id and igw41_user_profiles.profile_key = 'profile.postal_code'
group by username;
Thanks in advance.
take and igw41_user_profiles.profile_key = 'profile.account_type' out of your first query
If you wish to test for both use in
and igw41_user_profiles.profile_key in('profile.account_type','profile.postal_code')
In common way when you need to join same table more then once you need to use aliases for each join:
select id, username, GROUP_CONCAT(profile_value SEPARATOR ',')
from igw41_users
left join igw41_user_profiles p1
on igw41_users.id = p1.user_id and
p1.profile_key = 'profile.account_type'
right join igw41_user_profiles p2
on igw41_users.id = p2.user_id and
p2.profile_key = 'profile.postal_code'
group by username;
In this case the second join is redundant:
select id, username, GROUP_CONCAT(profile_value SEPARATOR ',')
from igw41_users
left join igw41_user_profiles p1 on igw41_users.id = p1.user_id
where
p1.profile_key = 'profile.account_type' OR
p1.profile_key = 'profile.postal_code'
group by username;

How can I optimize my sql code?

I have following tables
contacts
contact_id | contact_slug | contact_first_name | contact_email | contact_date_added | company_id | contact_is_active | contact_subscribed | contact_last_name | contact_company | contact_twitter
contact_campaigns
contact_campaign_id | contact_id | contact_campaign_created | company_id | contact_campaign_sent
bundle_feedback
bundle_feedback_id | bundle_id, contact_id | company_id | bundle_feedback_rating | bundle_feedback_favorite_track_id | bundle_feedback_supporting | campaign_id
bundles
bundle_id | bundle_name | bundle_created | company_id | bundle_is_active
tracks
track_id | company_id | track_title
I wrote this query, but it works slowly, how can I optimize this query to make it faster ?
SELECT SQL_CALC_FOUND_ROWS c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks/sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.bundle_feedback_supporting, 0) AS feedbackSupporting
FROM contacts AS c
LEFT JOIN
(SELECT c.contact_id,
count(cc.contact_campaign_id) AS sendCampaignsCount
FROM contacts AS c
LEFT JOIN contact_campaigns AS cc ON cc.contact_id = c.contact_id
WHERE c.company_id = '876'
AND c.contact_is_active = '1'
AND cc.contact_campaign_sent = '1'
GROUP BY c.contact_id) AS icc ON icc.contact_id = c.contact_id
LEFT JOIN
(SELECT bf.contact_id,
count(*) AS countfeedbacks,
bf.bundle_feedback_supporting
FROM bundle_feedback bf
JOIN bundles b
JOIN contacts c
LEFT JOIN tracks t ON bf.bundle_feedback_favorite_track_id = t.track_id
WHERE bf.bundle_id = b.bundle_id
AND bf.contact_id = c.contact_id
AND bf.company_id='876'
GROUP BY bf.contact_id) AS ibf ON ibf.contact_id = c.contact_id
WHERE c.company_id = '876'
AND contact_is_active = '1'
ORDER BY percentFeedback DESC LIMIT 0, 25;
I have done 2 improvements
1) Removed the contacts which is getting joined unnecessarily twice and put the condition at the final where condition.
2) Removed as per SQL_CALC_FOUND_ROWS
Which is fastest? SELECT SQL_CALC_FOUND_ROWS FROM `table`, or SELECT COUNT(*)
SELECT c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks/sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.bundle_feedback_supporting, 0) AS feedbackSupporting
FROM contacts AS c
LEFT JOIN
(SELECT cc.contact_id,
count(cc.contact_campaign_id) AS sendCampaignsCount
FROM contact_campaigns
WHERE cc.contact_campaign_sent = '1'
GROUP BY cc.contact_id) AS icc ON icc.contact_id = c.contact_id
LEFT JOIN
(SELECT bf.contact_id,
count(*) AS countfeedbacks,
bf.bundle_feedback_supporting
FROM bundle_feedback bf
JOIN bundles b
LEFT JOIN tracks t ON bf.bundle_feedback_favorite_track_id = t.track_id
WHERE bf.bundle_id = b.bundle_id
GROUP BY bf.contact_id) AS ibf ON ibf.contact_id = c.contact_id
WHERE c.company_id = '876' and c.contact_is_active = '1'
First, you are not identifying any indexes you have to optimize the query. That said, I would ensure you have at least the following composite / covering indexes.
table index
contacts ( company_id, contact_is_active )
contact_campaigns ( contact_id, contact_campaign_sent )
bundle_feedback ( contact_id, bundle_feedback_supporting )
Next, as noted in other answer, unless you really need how many rows qualified, remove the "SQL_CALC_FOUND_ROWS".
In your first left-join (icc), you do a left-join on contact_campaigns (cc), but then throw into your WHERE clause an "AND cc.contact_campaign_sent = '1'" which turns that into an INNER JOIN. At the outer query level, these would result in no matching record and thus NULL for your percentage calculations.
In your second left-join (ibf), you are doing a join to the tracks table, but not utilizing anything from it. Also, you are joining to the bundles table but not using anything from there either -- unless you are getting multiple rows in the bundles and tracks tables which would result in a Cartesian result and possibly overstate your "CountFeedbacks" value. You also do not need the contacts table as you are not doing anything else with it, and the feedback table has the contact ID basis your are querying for. Since that is only grouped by the contact_id, your "bf.bundle_feedback_supporting" is otherwise wasted. If you want counts of feedback, just count from that table per contact ID and remove the rest. (also, the joins should have the "ON" clauses instead of within the WHERE clause for consistency)
Also, for your supporting feedback, the data type and value are unclear, so I implied as a Yes or No and have a SUM() based on how many are supporting. So, a given contact may have 100 records but only 37 are supporting. This gives you 1 record for the contact having BOTH values 100 and 37 respectively and not lost in a group by based on the first entry found for the contact.
I would try to summarize your query to below:
SELECT
c.contact_id,
c.contact_first_name,
c.contact_last_name,
c.contact_email,
c.contact_date_added,
c.contact_company,
c.contact_twitter,
concat(c.contact_first_name," ", c.contact_last_name) AS fullname,
c.contact_subscribed,
ifnull(icc.sendCampaignsCount, 0) AS sendCampaignsCount,
ifnull(round((ibf.countfeedbacks / icc.sendCampaignsCount * 100),2), 0) AS percentFeedback,
ifnull(ibf.SupportCount, 0) AS feedbackSupporting
FROM
contacts AS c
LEFT JOIN
( SELECT
c.contact_id,
count(*) AS sendCampaignsCount
FROM
contacts AS c
JOIN contact_campaigns AS cc
ON c.contact_id = cc.contact_id
AND cc.contact_campaign_sent = '1'
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
GROUP BY
c.contact_id) AS icc
ON c.contact_id = icc.contact_id
LEFT JOIN
( SELECT
bf.contact_id,
count(*) AS countfeedbacks,
SUM( case when bf.bundle_feedback_supporting = 'Y'
then 1 else 0 end ) as SupportCount
FROM
contacts AS c
JOIN bundle_feedback bf
ON c.contact_id = bf.contact_id
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
GROUP BY
bf.contact_id) AS ibf
ON c.contact_id = ibf.contact_id
WHERE
c.company_id = '876'
AND c.contact_is_active = '1'
ORDER BY
percentFeedback DESC LIMIT 0, 25;

getting count on same column using inner join

I have a table like this, in which I need to set the male and female counts for the primary key id:
summaryTable:
id femaleCount maleCount
-----------------------------
s1 ? ?
s2 ? ?
... and so on
There is a detail table as below, that has the users corresponding to each id of summaryTable:
id parentId userId
--------------------------
1 s1 u1
2 s1 u2
3 s2 u2
4 s2 u2
...and so on
The third is the user table like this:
userTable:
userId gender
-------------
u1 M
u2 F
u3 F
..and so on
I have to update the summary table with the counts of male and female. So as per the above, for id=s1, femaleCount should be set to 1 , maleCOunt=1
For id=s2, femaleCOunt should get set to 2 and maleCount=0
Is this possible to do using an UPDATE query in MySQL?
I tried the following, but this returns the sum of occurences of a user i.e. if u1 occurs 2 times for p1(say), then it will return count as 2 and not 1:
SELECT
d.parentId,
SUM(gender = 'F') AS 'F#',
sum(gender = 'M') as 'M#'
FROM detailsTable as d
JOIN userTable as c on c.userId = d.userId
GROUP BY d.parentId;
Also tried as below, but it gave an error:
select d.parentId,
count(case when c.gender='M' then 1 end) as male_cnt,
count(case when c.gender='F' then 1 end) as female_cnt,
from detailsTable d, userTable c where d.userId=c.userId group by d.parentId, d.userId ;
Further, my problem doesnt just end at the select, I need to get the values and then update these in the summary table too.
I might be rusty on the syntax for MySql but I believe this does what you need. The CASE/SUM is effectively a pivot to get the counts, then you can update the table as normal.
UPDATE summaryTable AS st
INNER JOIN ( SELECT parentId
,SUM(CASE WHEN gender = 'f' THEN 1
ELSE 0
END) femaleCount
,SUM(CASE WHEN gender = 'm' THEN 1
ELSE 0
END) maleCount
FROM userTable d
INNER JOIN (SELECT DISTINCT parentId, userId FROM detail) ut ON d.userId = ut.userId
GROUP BY parentId
) AS c ON c.parentId = st.parentId
SET femaleCount = c.femaleCount
,maleCount = c.maleCount

How can I select the users which are belonging to group A?

How can I select the users which are belonging to group A?
My tables are below.
my user table.
ID | name |sex
1 | bob |1
2 | kayo |2
3 | ken |1
my fos_group table
ID | name
1 | student
2 | teacher
my fos_user_user_group
user_id | group_id
1 | 1
2 | 2
3 | 1
Bob and Ken are belonging to group_1(student)
Kayo is belonging to group_2(teacher)
ex) I can select 'Bob' from user table like this
$query = $em->createQuery(
"SELECT p.name,p.sex
FROM UserBundle:User p WHERE
p.id = '1' );
But I would like to select the users which belongs to student group(Bob and Ken)
How should I change the sentence in createQuery?
I just guess I need to join the tables though...
additional....
I have tried like this accroding to Fabio's answer
$query = $em->createQuery(
"SELECT p,p.id,p.username,p.userKey
FROM UserBundle:User p
INNER JOIN fos_user_user_group b
ON a.ID = b.user_id
INNER JOIN fos_group c
ON b.group_id = c.ID
WHERE c.group_id = '1'");
$this->data["teachers"] = $query->getResult();
but it says
[Semantical Error] line 0, col 94 near 'fos_user_user_group': Error: Class 'fos_user_user_group' is not defined.
I guess it means I dont have entity for 'fos_user_user_group'.
I have only entity class for Group and User,other tables were created automatically.
In meanwhile,I used like this in other place in $formmapper.
->add('teacher',
null,
array(
'query_builder' =>
function (\Doctrine\ORM\EntityRepository $rep) {
return $rep->
createQueryBuilder('s')
->join('s.groups', 'g')
->where('g.name = :group')->setParameter('group','TeacherGroup');
})
)
it works well,
how can I change this sentence for createQuery()?
I think you can use a INNER JOIN query
SELECT p.name,p.sex
FROM User p
INNER JOIN fos_user_user_group b
ON a.ID = b.user_id
INNER JOIN fos_group c
ON b.group_id = c.ID
WHERE c.group_id = '1'