Decrease the number of joins to the same parent table in mysql - mysql

I have an user table containing user info and a signature table which maps to user table with userid, as shown below
users
userid | firstname | lastname
1 | John | P
2 | Pete | C
3 | Tim | D
signs
doneBy | CheckedBy | VerifiedBy
1 | 2 | 3
Is there a better way to do this instead of multiple joins as below,
considering I would have 6 such joins
select
concat(usr1.firstname, ' ', usr1.lastname) as doneby,
concat(usr2.firstname, ' ', usr2.lastname) as checkedby,
concat(usr3.firstname, ' ', usr3.lastname) as verifiedby
from signs sgn
join users usr1 on ( usr1.userid = sgn.doneBy)
join users usr2 on ( usr2.userid = sgn.checkedBy)
join users usr3 on ( usr3.userid = sgn.verifiedBy)

You can do this with conditional aggregation using CASE EXPRESSION like this:
SELECT MAX(CASE WHEN t.doneBy = t.userId THEN t.full_name END) AS doneBy,
MAX(CASE WHEN t.CheckedBy = t.userId THEN t.full_name END) AS doneBy,
MAX(CASE WHEN t.VerifiedBy = t.userId THEN t.full_name END) AS doneBy
FROM (
SELECT u.userid,concat(u.firstname, ' ', u.lastname) as full_name,u.*
FROM signs s
INNER JOIN users u
ON(s.userid IN(s.doneBy,s.CheckedBy,s.VerifiedBy))) t
GROUP BY t.doneBy,t.checkBy,t.VerifiedBy

Related

How to display a query in a certain way?

I'm currently new to queries and below I have a query that I have made
QUERY:
select TITLE_ID, TITLE,NAME, JOB_CATEGORY AS ROLE
FROM MOVIES
NATURAL JOIN NEW_NAMES
WHERE JOB_CATEGORY = 'writer'
OR JOB_CATEGORY = 'director'
ORDER BY TITLE_ID ASC;
Which Displays:
TITLE_ID | TITLE | NAME | ROLE |
753595 | 2F2F | ROB | WRITER |
753595 | 2F2F | YAS | DIRECTOR|
However I would like it to display in this format below:
TITLE_ID | TITLE | WRITER | DIRECTOR|
753595 | 2F2F | ROB | YAS |
You join your NEW_NAMES table in twice. Once for director, once for writer. When you join a table more than once it's necessary that you give the table an Alias, here we use writer and director.
select TITLE_ID, TITLE,writer.NAME as writer_name, director.NAME as director_name
FROM MOVIES
LEFT OUTER JOIN NEW_NAMES as writer
ON MOVIES.TITLE_ID = writer.TITLE_ID
AND writer.JOB_CATEGORY = 'writer'
LEFT OUTER JOIN NEW_NAMES as director
ON MOVIES.TITLE_ID = director.TITLE_ID
AND director.JOB_CATEGORY = 'director'
ORDER BY TITLE_ID ASC;
I've made some assumptions in those ON clauses about which columns in your tables you are joining on. You may need to edit that.
According to error message you got, it is about Oracle, not MySQL.
Anyway, such a (classic?) principle should work: aggregate!
SQL> with
2 -- sample data
3 movies (title_id, title_name) as
4 (select 753595, '2F2F' from dual union all
5 select 123456, '1Z1Z' from dual
6 ),
7 new_names (title_id, job_category, name) as
8 (select 753595, 'writer', 'ROB' from dual union all
9 select 753595, 'director', 'YAS' from dual
10 )
11 -- query you need
12 select m.title_id,
13 m.title_name,
14 max(case when n.job_category = 'writer' then n.name end) as writer,
15 max(case when n.job_category = 'director' then n.name end) as director
16 from movies m left join new_names n on m.title_id = n.title_id
17 group by m.title_id, m.title_name
18 order by m.title_id;
TITLE_ID TITL WRI DIR
---------- ---- --- ---
123456 1Z1Z
753595 2F2F ROB YAS
SQL>

I need to display the row values in different column if they repeated for same person id

Right now i have the output like this but i need the output in different manner
Peron_id Diagnosis1 doagnosis2 diagnosis3 diagnosis4
1 fever
2 fever
2 backpain
2 UTI
I need the output like this
Peron_id Diagnosis1 doagnosis2 diagnosis3 diagnosis4
1 fever
2 fever backpain UTI
Please help me on this.
My framed the complete query now and its showing some error(operand should contain 1 column(s).I paste the query below.please help me
SELECT
visits.id as VisitId,
visits.person_id as MemberId,
dept.name as Specialty,
visits.date as VisitDate,
visits.followup_to_visit_id as FollowToVisitID,
visits.chief_complaint as ChiefComplaint1,
visits.chief_complaint_2 as ChiefComplaint2,
visits.chief_complaint_3 as ChiefComplaint3,
visits.valid_state as ValidStatus,
visits.clinic_type as ClinicType,
visits.illness_duration as IllnessDuration,
vitals.temperature_f as Temp,
vitals.pulse as Pulse,
vitals.respiratory_rate as RespRate,
vitals.bp_systolic as BPSystolic,
vitals.bp_diastolic as BPDiastolic,
vitals.height_cm as HeightCms,
vitals.weight_kg as WeightKgs,
vitals.waist_cm as WaistCms,
vitals.hip_cm as HipCms,
referral.referred_to_provider as ReferredOut,
visits.provider_id as DoctorId,
prov.full_name as DoctorName,
prov.registration_number as DoctorRegNum,
prov.qualification as DoctorQual,
provloc.name as VisitUphc,
tk.name as VisitUlb,
districts.name as VisitDistrict,
"TN" as VisitState,
provloc.cachment_code as VisitCatchment,
visits.entry_date as VisitEntryDate,
(visits.end_time - visits.begin_time) as VisitEntryTimeTaken,
visits.created_date as VisitCreateDt,
visits.last_modified_date as VisitLastModDt,
(Select visit_id,
max(case when i=1 then diagnosis end)as provisional_diagnosis_1,
max(case when i=2 then diagnosis end)as provisional_diagnosis_2,
max(case when i=3 then diagnosis end)as provisional_diagnosis_3,
max(case when i=4 then diagnosis end)as provisional_diagnosis_4
from
(
select visit_id, diagnosis as diagnosis,
#i :=IF(#iter=visit_id,#i+1,1)as i,
#iter :=visit_id
from visit_diagnosis_entries v1
join (select #iter :=0) v2
) vv)
FROM
visits
LEFT JOIN visit_vitals vitals ON vitals.visit_id = visits.id
LEFT JOIN visit_referral_entries referral ON referral.visit_id = visits.id
LEFT JOIN visit_diagnosis_entries diag ON diag.visit_id = visits.id
INNER JOIN providers prov ON prov.id = visits.provider_id
INNER JOIN provider_locations provloc ON provloc.id = visits.provider_location_id
INNER JOIN talukas tk ON tk.id = provloc.taluka_id
INNER JOIN districts ON districts.id = provloc.district_id
INNER JOIN departments dept ON dept.id = visits.department_id limit 5;
Previously i explained with one table and now i need all the columns and additionally with these columns.
If you are using MySQL 8+, then we can try a pivot query with the help of ROW_NUMBER:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Peron_id ORDER BY Diagnosis) rn
FROM yourTable
)
SELECT
Peron_id,
MAX(CASE WHEN rn = 1 THEN Diagnosis END) AS Diagnosis1,
MAX(CASE WHEN rn = 2 THEN Diagnosis END) AS Diagnosis2,
MAX(CASE WHEN rn = 3 THEN Diagnosis END) AS Diagnosis3,
MAX(CASE WHEN rn = 4 THEN Diagnosis END) AS Diagnosis4
FROM cte
GROUP BY
Peron_id;
If separated columns are not mandatory instead of separated columns in mysql you could try using group_concat to get the result in the same row
select person_id, group_concat(Diagnosis1) as diagnosis
from my_table
group by person_id
You can use conditional aggregation with iteration within the subquery as:
select Person_ID,
max(case when i = 1 then Diagnosis end) as Diagnosis1,
max(case when i = 2 then Diagnosis end) as Diagnosis2,
max(case when i = 3 then Diagnosis end) as Diagnosis3
from
(
select t.Person_ID, t.Diagnosis1 as Diagnosis,
#i := IF(#iter = Person_ID, #i + 1, 1) as i,
#iter := Person_ID
from tab t
join (select #iter := null,#i:=0) t2
) tt
group by Person_ID;
+-----------+------------+-------------+------------+
| Person_ID | Diagnosis1 | Diagnosis2 | Diagnosis3 |
+-----------+------------+-------------+------------+
| 1 | fever | | |
| 2 | fever | backpain | UTI |
+-----------+------------+-------------+------------+
This method even works for former versions.
Demo
Edit ( thanks to #GordonLinoff ): Indeed,above method gives all three diagnosis columns,there's a uncertainity about the order of presence among them. So,I think you need one more column such as Diag_order.In this case,you'd better to use this below instead of the above one:
select Person_ID,
max(case when Diag_order = 1 then Diagnosis1 end) as Diagnosis1,
max(case when Diag_order = 2 then Diagnosis1 end) as Diagnosis2,
max(case when Diag_order = 3 then Diagnosis1 end) as Diagnosis3
from tab t
group by Person_ID;
+-----------+------------+-------------+------------+
| Person_ID | Diagnosis1 | Diagnosis2 | Diagnosis3 |
+-----------+------------+-------------+------------+
| 1 | fever | | |
| 2 | fever | backpain | UTI |
+-----------+------------+-------------+------------+

Query: I have 4 rows, need to add the results from 3 rows into one, and leave the last row untouched

I have a kind of tricky question for this query. First the code:
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
What Am I doing?
For example, I am counting police reports of robbery, made from different kind of users. In my example, "admin" users reported 6 incidents of code "2" (robbery) and so on, as is showed in 'where' clause (incident must be robbery, also code 2).
this brings the following result:
+-----------------------+----------+
| user_type_description | Quantity |
+-----------------------+----------+
| Admin | 6 |
| Moderator | 8 |
| Fully_registered_user | 8 |
| anonymous_user | 9 |
+-----------------------+----------+
Basically Admin,Moderator and Fully_registered_user are appropriately registered users. I need to add them in a result where it shows like:
+--------------+------------+
| Proper_users | Anonymous |
+--------------+------------+
| 22 | 9 |
+--------------+------------+
I am not good with sql. Any help is appreciated. Thanks.
You can try to use condition aggregate function base on your current result set.
SUM with CASE WHEN expression.
SELECT SUM(CASE WHEN user_type_description IN ('Admin','Moderator','Fully_registered_user') THEN Quantity END) Proper_users,
SUM(CASE WHEN user_type_description = 'anonymous_user' THEN Quantity END) Anonymous
FROM (
SELECT user_type.user_type_description,COUNT(incident.user_id) as Quantity
FROM incident
INNER JOIN user ON incident.user_id=user.user_id
INNER JOIN user_type ON user.user_type=user_type.user_type
WHERE incident.code=2
GROUP BY user.user_type
) t1
You just need conditional aggregation:
SELECT SUM( ut.user_type_description IN ('Admin', 'Moderator', 'Fully_registered_user') ) as Proper_users,
SUM( ut.user_type_description IN ('anonymous_user') as anonymous
FROM incident i INNER JOIN
user u
ON i.user_id = u.user_id INNER JOIN
user_type ut
ON u.user_type = ut.user_type
WHERE i.code = 2;
Notes:
Table aliases make the query easier to write and to read.
This uses a MySQL shortcut for adding values -- just just adding the booelean expressions.
I would solve it with a CTE, but it would be better to have this association in a table.
WITH
user_type_categories
AS
(
SELECT 'Admin' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Moderator' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'Fully_registered_user' AS [user_type_description] , 'Proper_users' AS [user_type_category]
UNION SELECT 'anonymous_user' AS [user_type_description] , 'Anonymous' AS [user_type_category]
)
SELECT
CASE WHEN utc.[user_type_category] = 'Proper_users' THEN
SUM(incident.user_id)
END AS [Proper_Users_Quantity]
, CASE WHEN utc.[user_type_category] = 'Anonymous' THEN
SUM(incident.user_id)
END AS [Anonymous_Quantity]
FROM
[incident]
INNER JOIN [user] ON [incident].[user_id] = [user].[user_id]
INNER JOIN [user_type] ON [user].[user_type] = [user_type].[user_type]
LEFT JOIN user_type_categories AS utc ON utc.[user_type_description] = [user_type].[user_type_description]
WHERE
[incident].[code] = 2

Multi-Join Query, Pivot? Troubles

Thank you in advance for anyone who helps with this. I know i've seen this done before without too much pain but cant seem to find the solution
My database looks something like:
`tbl_user:
----------
id ( pkey )
email
fName
lName
tbl_userSparseType:
-------------------
id ( pkey )
varName
displayName
tbl_userSparse:
---------------
id ( pkey )
value ( Value of Sparse Type )
user_id ( => tbl_user.id )
userSparseType_id ( => tbl_userSparseType.id )
with sample data:
tbl_user:
(id, email, fName, lName)
1 Bob#example.com Billy Bob
2 Betty#example.com Betty Sue
3 Beam#example.com Jim Beam
tbl_userSparseType:
(id, varName, displayName)
1 fullName Full Name
2 dayPhone Day Phone
3 nightPhone Night Phone
4 cellPhone Cell Phone
5 homeAddr Home Address
tbl_userSparse:
(id, value, user_id, userSparseType_id)
1 Billy John Bob 1 1
2 James B. Beam 3 1
3 123-234-3456 1 2
4 234-345-4567 1 4
5 24 Best st. 2 5
6 456-567-6789 3 3
I tried doing two left joins, but this gave me a tbl_user row for each sparse entry like:
(id, email, fName, lName, displayName, value)
1,"Bob#example.com","Billy","Bob","Full Name","Billy John Bob"
1,"Bob#example.com","Billy","Bob","Day Phone","123-234-3456"
1,"Bob#example.com","Billy","Bob","Cell Phone","234-345-4567"
And despite a few 45 or so minute sessions of looking, I cant find a way to get something more like the following without explicitly naming the columns, I need a dynamic way to only pull all display names that apply to the subset of tbl_user rows being queried:
WHERE tbl_user.id IN (1,2)
id | email | fName | lName | Full Name, | Day Phone | Cell Phone |
Home Address
-------------------------------------------------------------------------------------------------------
1 | Bob#example.com | Billy | Bob | Billy John Bob | 123-234-3456 | 234-345-4567 |
2 | Betty#example.com | Betty | Sue | | | | 24 Best St.
Thanks again in advance, I'm hoping this can be done without too much fuss. :\
Unfortunately MySQL does not have a PIVOT function which is basically what you are trying to do. So you will need to use an aggregate function with a CASE statement. If you know the number of columns, then you can hard-code the values:
select u.id,
u.email,
u.fname,
u.lname,
max(case when t.displayname = 'Full Name' then us.value end) FullName,
max(case when t.displayname = 'Day Phone' then us.value end) DayPhone,
max(case when t.displayname = 'Cell Phone' then us.value end) CellPhone,
max(case when t.displayname = 'Home Address' then us.value end) HOmeAddress
from tbl_user u
left join tbl_userSparse us
on u.id = us.user_id
left join tbl_userSparseType t
on us.userSparseType_id = t.id
where u.id in (1, 2)
group by u.id, u.email, u.fname,u.lname;
See SQL Fiddle With Demo
Now if you want to perform this dynamically, meaning you do not know ahead of time the columns to transpose, then you should review the following article:
Dynamic pivot tables (transform rows to columns)
Your code would look like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when t.displayname = ''',
t.displayname,
''' then us.value end) AS ',
replace(t.displayname, ' ', '')
)
) INTO #sql
FROM tbl_userSparse us
left join tbl_userSparseType t
on us.userSparseType_id = t.id;
SET #sql = CONCAT('SELECT u.id, u.email, u.fname, u.lname, ', #sql, '
from tbl_user u
left join tbl_userSparse us
on u.id = us.user_id
left join tbl_userSparseType t
on us.userSparseType_id = t.id
where u.id in (1, 2)
group by u.id, u.email, u.fname, u.lname');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo

MySql - Sql query or procedure to generate report by transposing rows into columns

This is the first time - I'm trying to do something like this - so please bear with me.
This is on MySql.
I am trying to generate a report to see which students have completed which topics and on which dates.
This is the current query that I run
select u.email,t.topic_name,tu.date_created as 'date completed'
from topic_user tu
join topic t ON tu.topic_id = t.topic_id
join user u ON tu.user_id = u.user_id
which will return results like
email | topic_name | date completed
abc#gmail.com | ABC | 03/01/2012
abc#gmail.com | DEF | 03/02/2012
abc#gmail.com | ABC | 03/08/2012
abc#gmail.com | GHI | 03/08/2012
def#gmail.com | ABC | 03/02/2012
def#gmail.com | XYZ | 03/10/2012
The way I want to generate the report is have the topic names as column headers and the date they completed it as values
email | ABC | DEF | GHI | JKL | XYZ
abc#gmail.com | 03/08/2012 | 03/02/2012 | 03/08/2012 | null | null
def#gmail.com | 03/02/2012 | null | null | null | 03/10/2012
Few things to note are:
1) All the topic names would come from the topic table - even if they have not been completed by the students - the value should appear as null
2) Incase of student abc#gmail.com - he has studied the topic ABC twice - but the report should get the latest date.
I guess I have to write a stored procedure to accomplish this. Like maybe first pull all the topic names from the topic table and then create a temp view and populate it.
I would appreciate any help you can provide.
Thanks much
I've not tested this, and my experience with MySQL is limited but I hope the below is what you are after. It dynamically creates the SELECT statement using the GROUP_CONCAT function, then executes it (This is the bit I am not certain of the way to do it in MySQL).
SET #SQL = (
SELECT CONCAT('SELECT Email,', GROUP_CONCAT(SelectText), ' FROM Topic_User tu INNER JOIN Users u ON u.User_ID = tu.User_ID GROUP BY Email')
FROM ( SELECT CONCAT(' MAX(CASE WHEN Topic_ID = ', Topic_ID, ' THEN tu.Date_Created END) AS `', Topic_Name, '`') AS SelectText
FROM Topic
) AS d);
PREPARE stmt FROM #SQL;
EXECUTE stmt
Of course if your topics are not changing very regularly you could just use:
SELECT Email,
MAX(CASE WHEN Topic_ID = 1 THEN tu.Date_Created END) AS ABC,
MAX(CASE WHEN Topic_ID = 2 THEN tu.Date_Created END) AS DEF,
MAX(CASE WHEN Topic_ID = 3 THEN tu.Date_Created END) AS GHI,
MAX(CASE WHEN Topic_ID = 4 THEN tu.Date_Created END) AS JKL,
MAX(CASE WHEN Topic_ID = 5 THEN tu.Date_Created END) AS XYZ
FROM Topic_User tu
INNER JOIN users u
ON u.User_ID = tu.User_ID
GROUP BY Email
and alter the query each time a new topic is added (This is the query produced by the process above).
You could do this with a dynamically generated crosstab query. The query you want to end up with is something like this -
SELECT
u.email,
MAX(IF(t.topic_name = 'ABC', tu.date_created, NULL)) AS 'ABC',
MAX(IF(t.topic_name = 'DEF', tu.date_created, NULL)) AS 'DEF',
etc
FROM topic_user tu
INNER JOIN topic t
ON tu.topic_id = t.topic_id
INNER JOIN user u
ON tu.user_id = u.user_id
GROUP BY u.user_id;
So, in your server side language of choice you would dynamically build your field list based on first querying for a list of topics.
You could slightly reduce the overhead of this query by using the topic_ids instead of the topic_names so that you can drop the join to the topics table -
SELECT
u.email,
MAX(IF(tu.topic_id = 1, tu.date_created, NULL)) AS 'ABC',
MAX(IF(tu.topic_id = 2, tu.date_created, NULL)) AS 'DEF',
etc
FROM topic_user tu
INNER JOIN user u
ON tu.user_id = u.user_id
GROUP BY u.user_id;