Query involving join - combine data from 2 of our tables - mysql

We need to combine data from 2 of our tables - active_deals and deal_display_controls. The (relevant) columns in each of them are:
`active_deals` : `deal_id`, `region_code`
`deal_display_controls` : `deal_id`, `display_control_id`
The query needs to fetch all count of all the active deals having a given display_control_id and group them by their region_code (group by region code). Along with this, it also needs to group the active deals by region code (without the display_control_id constraint)
So, if the active deals tables looks like:
deal_id region_code
d1 US
d2 CA
d3 US
And the deal_display_controls looks like:
`deal_id` `display_control_id`
d1 dc1
d2 dc1
d3 dc2
d4 dc1
Then for a given display control id = “dc1”
The query should output:
`region_code` `count_of_active_deals_with_given_display_control_for_region_code `count_of_active_deals_for_region_code`
US 1 2
CA 1 1
I could do this by splitting the above question into 2 parts and writing individual queries for each part:
Part 1: To get the active deals for a given display_control_id and group by region code
Query for the same:
select count(*), region_code from active_deals join deal_display_controls on active_deals.bin_deal_id=deal_display_controls.deal_id where deal_display_controls.display_control_id=0xc04e20724f5f49c9a285cb3c98d777b4 group by active_deals.region_code;
O/p:
`region_code` `count_of_active_deals_with_given_display_control_for_region_code
US 1
CA 1
Part2: To get the active deals and group by region code
Query for the same:
select count(*), region_code from active_deals join deal_display_controls on active_deals.bin_deal_id=deal_display_controls.deal_id group by active_deals.region_code;
O/p:
`region_code` `count_of_active_deals_for_region_code`
US 2
CA 1
I need to way to combine these 2 queries into a single query. Is it possible to do this?

while counting you can add additional condition of control_id
select d.region_code
, count(distinct d.deal_id) count_of_active_deals_for_region_code
, SUM(c.display_control_id = 'dc1') count_of_active_deals_with_given_display_control_for_region_code
from active_deals d
left
join deal_display_controls c
on d.deal_id = c.deal_id
group
by d.region_code
Fiddle: http://sqlfiddle.com/#!9/904670/2

I think you want a join with conditional aggregation:
select d.region_code,
sum(ad.display_control_id = 1),
count(*)
from active_deals ad join
deals d
on ad.deal_id = d.deal_id
group by d.region_code

Related

Using select from another select in MySQL

I have the following query where I will have finally a 205 patient IDs to work with:
select
patient_id
FROM
visit
WHERE
month(visit.date_of_visit)=3
AND
year(visit.date_of_visit)=2018
AND
visit.visit_status='Active'
GROUP BY patient_id
I want to get all the 205 IDs and run them into other query to see how many diseases we have as cardio-vascular and then as respiratory disease.
My database structure is as the following:
What I want is to get for each patient id, what they are diagnosed at ONLY their first visit to the hospital (so here we will work with min(visit.date_of_visit))
The desired result for `diagnosis_name LIKE '%Cardio%':
E.g>:
Patients: 150 (Or something)
And the query is changed to get the respiratory info.
I tried the following for the Cardio diseases where I use select from select:
SELECT count(*)
FROM
(
select
min(visit.date_of_visit), visit_id, patient_id, count(*) as patientId
FROM
visit
WHERE
month(visit.date_of_visit)=3
AND
year(visit.date_of_visit)=2018
AND
visit.visit_status='Active'
GROUP BY patient_id
) as vid
LEFT JOIN
consultation ON consultation.visit_id=vid.visit_id
LEFT JOIN
diagnosis ON diagnosis.diagnosis_id=consultation.diagnosis_id
WHERE diagnosis.diagnosis_name LIKE '%Cardio%'
The result was: 5 which is a wrong number.
This can be done easily with PHP and MYSQL together but this will exhaust the server by repeating the same query for 205 times and increment a counter. So the desired result should be only done with MySQL.
Data example:
Visit Table
visit_id= 1; date_of_visit=2018-03-03; visit_reason=Active; ...;
patient_id=1234;
visit_id= 2; date_of_visit=2018-03-04; visit_reason=Active; ...;
patient_id=1239;
visit_id= 3; date_of_visit=2018-03-07; visit_reason=Active; ...;
patient_id=1234;
Consultation Table
consultation_id=1; ...; diagnosis_id=12; visit_id=1;...;
consultation_id=2; ...; diagnosis_id=12; visit_id=2;...;
Diagnosis Table
diagnosis_id=12; diagnosis_name: hypertension (cardio disease);
diagnosis_id=13; diagnosis_name: renal disease
By running the query to see patients who came to hospital and that they were diagnosed as having cardio disease in their initial first visit, the result should be in the example as 2 as you can see from the example where patient_id=1234 had 2 visits but I need to know what he had in his first one.
You can use window functions in MySQL 8+. But in older versions you need to calculate the value some other way.
The question for you is what you are counting:
SELECT COUNT(*) as num_diagnoses, COUNT(DISTINCT patient_id) as num_patients
FROM visit v JOIN
(SELECT patient_id,
MIN(v.date_of_visit) as min_dov
FROM visit v
WHERE v.date_of_visit >= '2018-03-01' AND
v.date_of_visit < '2018-04-01' AND
v.visit_status = 'Active'
) vf
ON v.patient_id = vf.patient_id AND v.date_of_visit = vf.min_dov JOIN
consultation c
ON c.visit_id = v.visit_id JOIN
diagnosis d
ON d.diagnosis_id = c.diagnosis_id
WHERE d.diagnosis_name LIKE '%Cardio%';
When working with dates, it is best to compare column values directly to dates, rather than dissecting them.
BRO, it works fine. Test it now on the live scenario.
SELECT count(*)
FROM
(
select
min(visit.date_of_visit) first_date, patient_id, count(*) as patientId
FROM
visit
WHERE
month(visit.date_of_visit)=3
AND
year(visit.date_of_visit)=2018
AND
visit.visit_status='Active'
GROUP BY patient_id
) as vid
INNER JOIN visit b ON
B.patient_id = vid.patient_id AND
B.date_of_visit = vid.first_date and
month(B.date_of_visit)=3 AND
year(B.date_of_visit)=2018 AND
B.visit_reason='Active'
INNER JOIN consultation ON
consultation.visit_id = B.visit_id
INNER JOIN diagnosis ON
diagnosis.diagnosis_id = consultation.diagnosis_id AND
diagnosis.diagnosis_name LIKE '%Cardio%'

Improving query speed: simple SELECT from SELECT in huge table

I have a table contains 3 columns : age , name , nickname
I would like to get only the names (+age) where the name+age does not exist at all in nickname+age.
For example : if table : DETAILS contains 2 rows :
age: 5 , name: suzi, nickname: suzi
age:2 , name : gil, nickname: g
query will return : age:2 , name : gil
SELECT d1.AGE, d1.NAME
FROM DETAILS d1
WHERE d1.NAME NOT IN (SELECT d2.NICKNAME FROM DETAILS d2 WHERE d2.AGE = d1.AGE)
This query runs only on small data.
Any idea how to improve it?
The critical point in SQL query performance is using index. So you have to have the index in the querying/joining columns and you need to use it (via join).
E.g. query:
SELECT DISTINCT D1.AGE, D1.NAME
FROM DETAILS D1 LEFT JOIN DETAILS D2 ON D1.AGE = D2.AGE
WHERE D1.NAME <> D2.NICKNAME
Note that you have to create indexes on columns AGE, NAME, AND NICKNAME beforehand to fully benefit from this query.
Use Left Join/Left Outer Join instead of WHERE ... NOT IN ...
The orders of execution of SQL
FROM
ON
OUTER
WHERE
GROUP BY
CUBE | ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
You can easily get the result by this:
SELECT d1.AGE,
CASE WHEN d1.NAME IS NULL
THEN d1.NIKCNAME ELSE d1.NAME END as [NewName]
FROM DETAILS d1
INNER JOIN DETAILS d2
ON d1.AGE = d2.AGE
WHERE d2.NAME <> d1.NICKNAME

find elements with same charateristics

Supposing I have a table where a material has asignments of different characteristics. A material can have one or more charateristics. Then I would like to find to a certain material similar materials, that means at least 2 characteristics should match. In this example I should find material C when I compare with A and D should find B. Is there any solution in SQL?
material | character
----------------------
A | 2
A | 5
B | 1
B | 3
B | 4
C | 2
C | 5
D | 3
D | 1
This is an Entity-Attribute-Value table, and it notoriously painful to search. (In this case, the value is implied as being TRUE for has this attribute.)
It involves comparing everything against everything, grouping the results, and checking if the groups match. Virtually no use of indexes or intelligence of any kind.
SELECT
material_a.material AS material_a,
material_b.material AS material_b
FROM
material AS material_a
LEFT JOIN
material AS material_b
ON material_a.character = material_b.character
AND material_a.material <> material_b.material
GROUP BY
material_a.material,
material_b.material
HAVING
0 = MAX(CASE WHEN material_b.character IS NULL THEN 1 ELSE 0 END)
This gives every material_b that has all of the characteristics that material_a has.
- The HAVING clause will check that every 0 of material a's characteristics are missing from material b.
Changing to an INNER JOIN and changing the HAVING CLAUSE will get the share at least two materials.
SELECT
material_a.material AS material_a,
material_b.material AS material_b
FROM
material AS material_a
INNER JOIN
material AS material_b
ON material_a.character = material_b.character
AND material_a.material <> material_b.material
GROUP BY
material_a.material,
material_b.material
HAVING
COUNT(*) >= 2
Either way, you still are joining the whole table against the whole table, then filtering out the failures. With 100 materials, that's 9,900 material-material comparison. Imagine when you have 1000 materials and have 999,000 comparisons. Or 1million materials...
You could use something like the following grouped table to determine all items with more than 2 similar characteristics
SELECT
material = t1.material
, similarMaterial = t2.material
FROM
tableName t1
INNER JOIN tableName t2 ON t1.character = t2.character AND NOT(t1.material = t2.material)
GROUP BY material
HAVING
COUNT(*) >= 2
Yes, you can find all paired of similar materials with SQL similar to this:
SELECT c1.material, c2.material, COUNT(*) as characterCount
FROM charateristics c1
CROSS JOIN charateristics c2
WHERE c1.material > c2.material AND c1.character = c2.character
GROUP BY c1.material, c2.material
HAVING characterCount >= 2;
This would give you the results based on a material input:
SELECT b.material
FROM table1 a
INNER JOIN table1 b
ON a.character = b.character AND a.material <> b.material
WHERE a.material = 'A' -- Your input
GROUP BY b.material
HAVING COUNT(*) > 1;
sqlfiddle demo
Or do this to give you the pairs:
SELECT a.material as LEFT_MATERIAL ,b.material AS RIGHT_MATERIAL
FROM table1 a
INNER JOIN table1 b ON a.character = b.character AND a.material <> b.material
GROUP BY a.material,b.material
HAVING COUNT(*) > 1;
sqlfiddle demo

How to get multi columns via subquery?

I want to get members and their photos. Every member has 2 photos. (I am not talking about profile image)
There are 2 tables named as Members and MemberPhotos.
Here is my query which doesn't work(expectedly):
SELECT
M.Name as MemberName,
M.LastName as MemberLastName,
(
SELECT
TOP 1
MP.PhotoName
FROM
MemberPhotos MP
WHERE
MP.MemberID = M.ID
AND
MP.IsFirst = 1
) as MemberFirstPhoto,
(
SELECT
TOP 1
MP.PhotoName
FROM
MemberPhotos MP
WHERE
MP.MemberID = M.ID
AND
MP.IsFirst = 0
) as MemberSecondPhoto,
FROM
Members M
Maybe somebody going to say that I should use inner join instead, I don't want to use inner join, if I use it I get data multiple like:
Name Surname PhotoName
Bill Gates bill.png
Bill Gates bill2.png
Steve Jobs steve.jpg
Steve Jobs steve2.jpg
What do you recommend me about query?
Thanks.
EDIT:
Here is the output I want to get:
Name Surname FirstPhoto SecondPhoto
Bill Gates bill.png bill2.png
Steve Jobs steve.jpg steve2.png
The only issue with your example query is that you have an extra comma after
as MemberSecondPhoto
If you remove this it works fine.
SQL Fiddle with demo.
However, while that query is working now, because you know that each member only has two photos, you can use a much simpler query:
SELECT
M.Name as MemberName,
M.LastName as MemberLastName,
MPF.PhotoName as MemberFirstPhoto,
MPS.PhotoName as MemberSecondPhoto
FROM Members M
LEFT JOIN MemberPhotos MPF ON M.ID = MPF.MemberID AND MPF.IsFirst = 1
LEFT JOIN MemberPhotos MPS ON M.ID = MPS.MemberID AND MPS.IsFirst = 0
SQL Fiddle with demo.

Mysql scenario - Get all tasks even if there is no entry?

I have three tables
Tasks with columns Taskid, Taskname
TaskAllocations with columns Taskid, EmpNum
TaskEntries with columns TaskId, EmpNum, WorkedDate, Hoursspent
Now I want to get all the task entries along a particular week. Here my problem is even if there is no Taskentry for a particular task I should get atleast a row with that TaskId, and Taskname with Hoursspent as Null in the query's resultset. I have been trying to get this with the below query.
SELECT A.TaskId,
B.TaskName,
SUM( C.HoursSpent ) as TotalHours ,
C.WorkedDate, C.Comments
FROM TaskAllocations A
LEFT OUTER JOIN TaskEntries C
ON A.TaskId = C.TaskId
AND A.EmpNum = C.EmpNum
INNER JOIN Tasks B
ON A.TaskId = B.TaskId
WHERE A.EmpNum =123456
AND C.WorkedDate
IN ('2010-01-17','2010-01-18','2010-01-19',
'2010-01-20','2010-01-21','2010-01-22','2010-01-23' )
GROUP BY A.TaskId, C.WorkedDate
ORDER BY A.TaskId,C.WorkedDate ASC ';
What I am getting for this SQL piece is if and only if there is an entry for a particular task id, then only i am getting a row for that. but what I want is to get atleast a row for each and every task that is available to a EmpNum. Even if I get one row for each TaskId and WorkedDate combination no issues. Please help me with this. Actual intention of this is to build a HTML two dimensional table with each task entry against date and task as shown below.
---------------------------------------------------------
TaskId TaskName Sun Mon Tue Wed Thu Fri Sat
---------------------------------------------------------
18 name1 2 3 4:30 3:30
19 name2
20 name3 4 2:30
22 name4 2:30
23 name5
24 name6 1:30 6
---------------------------------------------------------
So that this can be updated by the user for each year week. First I thought of group_concat but because of performance I am using normal group by query.
Note: for a particular taskid and workeddate there will be only one entry of hoursspent.
I have almost built the frontend. Please help me to get all task ids as above even if there is no entry. Do I need to use subquery.
don't user an inner join, use a left or right join, depending which values from which tables you want.
so:
SELECT *
FROM tasks t
LEFT JOIN taskentries te
ON t.id = te.id
which is the same statement as:
SELECT *
FROM tasksentries te
RIGHT JOIN tasks t
ON te.id = t.id
will get you all tasks, even if there is no taskentry
an inner join will only select rows when there are rows in both tables, left join selects all rows from the left (first) table and matching from the other row (if there is no such row, null will be the value of all columns). right join will do the oposite: select all rows from right (second) table and matching from left.
a LEFT JOIN b is the same as b RIGHT JOIN a
After rigorous testing of different options I came up with the below solution which will give the required results for me.
SELECT Final.TaskId,
Final.TaskName,
Tmp.HoursSpent AS TotalHours,
Tmp.WorkedDate
FROM (
SELECT A.TaskId, B.TaskName, A.EmpNum
FROM TaskAllocations A
INNER JOIN
Tasks B
ON ( A.TaskId = B.TaskId )
WHERE a.empnum = "333"
)Final
LEFT OUTER JOIN (
SELECT New.TaskId, New.EmpNum, New.WorkedDate, New.HoursSpent
FROM TaskEntries New
WHERE New.WorkedDate
IN
('2010-01-17','2010-01-18','2010-01-19',
'2010-01-20','2010-01-21','2010-01-22','2010-01-23' )
OR New.WorkedDate IS NULL
AND New.EmpNum = "333"
)Tmp
ON Tmp.TaskId = Final.TaskId
AND Tmp.EmpNum = Final.EmpNum
ORDER BY Final.TaskId, Tmp.WorkedDate ASC ;
The first query of mine in the question was not working as I was putting a condition on right table's column while doing Left Outer Join. Thanks to all for the support.