I am new to sql joins. can anyone explain me with a simple example how left/right/outer joins work with respect to joining 3 tables?
say i have the following tables
Area
Area ID PersonID
1 11
2 12
3 13
Disease
DiseaseID Disease Name
4 ABC
5 DEF
Attack
AttackID Disease ID AreaID
111 4 1
222 4 2
222 5 1
I want to know the count of people who were attacked and who were not attacked by disease.
I hope it will full fill your requirement
SELECT R.AREA_ID,
COUNT(A.DISEASEID) ATTACKED_PEOPLE
FROM AREA R
LEFT JOIN ATTACK A
ON A.AREAID=R.AREA_ID
LEFT JOIN DISEASE D
ON D.SISEASE_ID=A.DISEASEID
GROUP BY R.AREA_ID;
Here we have used joining of three tables using LEFT JOIN concept. So the left side table (AREA) retrieves all the records and the right side table (ATTACK) retrieves only matched records with left side table. As well as second LEFT JOIN condition is for retrieving the count of diseases from ATTACK table. So that we can get count of people who were attacked and who were not using the above query.
Only tables AREA and ATTACK contains informations you need here (you don't want disease name),
so at first attach informations about attacks to table containing person ID:
select personid, count(diseaseid) cnt
from area left join attack using (areaid)
group by personid
PersonID Cnt
11 2
13 0
12 1
Now you can count ill and healthy persons:
select
count(case when cnt > 0 then 1 end) Affected,
count(case when cnt = 0 then 1 end) NotAffected
from (
select personid, count(diseaseid) cnt
from area left join attack using (areaid)
group by personid)
Affected NotAffected
2 1
SQLFiddle demo
There are several ways to get last output, try to find others. And example how to join all three tables:
select personid, count(diseaseid) cnt,
listagg(DiseaseName, ', ') within group (order by DiseaseName) Diseases
from area
left join attack using (areaid)
left join disease using (diseaseid)
group by personid
PERSONID CNT DISEASES
-------- ---- ----------
11 2 ABC, DEF
12 1 ABC
13 0
If you want to find only affected persons use INNER JOIN instead of LEFT.
Related
I want to join columns from multiple tables to one column, in my case column 'battery_value' and 'technical_value' into column 'value'. I want to fetch data for only given category_ids, but because of UNION, I get data from other tables as well.
I have 4 tables:
Table: car
car_id model_name
1 e6
Table: battery
battery_category_id car_id battery_value
1 1 125 kW
Table: technical_data
technical_category_id car_id technical_value
1 1 5
3 1 2008
Table: categories
category_id category_name category_type
1 engine power battery
1 seats technical
3 release year technical
From searching, people are suggesting that I use union to join these columns. My query now looks like this:
SELECT CARS.car_id
category_id,
CATEGORIES.category_name,
value,
FROM CARS
left join (SELECT BATTERY.battery_category_id AS category_id,
BATTERY.car_id AS car_id,
BATTERY.value AS value
FROM BATTERY
WHERE `BATTERY`.`battery_category_id` IN (1)
UNION
SELECT TECHNICAL_DATA.technical_category_id AS category_id,
TECHNICAL_DATA.car_id AS car_id,
TECHNICAL_DATA.value AS value
FROM TECHNICAL_DATA
WHERE `TECHNICAL_DATA`.`technical_category_id` IN (3))
tt
ON CARS.car_id = tt.car_id
left join CATEGORIES
ON category_id = CATEGORIES.id
So the result I want is this, because I only want to get the data where category_id 1 is in battery table:
car_id category_id category_name technical_value
1 1 engine power 125 kW
1 3 release year 2008
but with the query above I get this, category_id 1 from technical table is included which is not something I want:
car_id category_id category_name value
1 1 engine power 125 kW
1 1 seats 125 kW
1 3 release year 2008
How can get exclude the 'seats' row?
For the results you want, I don't see why the cars table is needed. Then, you seem to need an additional key for the join to categories based on which table it is referring to.
So, I suggest:
SELECT tt.*, c.category_name
FROM ((SELECT b.battery_category_id AS category_id,
b.car_id AS car_id, b.value AS value,
'battery' as which
FROM BATTERY b
WHERE b.battery_category_id IN (1)
) UNION ALL
(SELECT td.technical_category_id AS category_id,
td.car_id AS car_id, td.value AS value,
'technical' as which
FROM TECHNICAL_DATA td
WHERE td.technical_category_id IN (3)
)
) tt LEFT JOIN
CATEGORIES c
ON c.id = tt.category_id AND
c.category_type = tt.which;
That said, you seem to have a problem with your data model, if the join to categories requires "hidden" data such as the type. However, that is outside the scope of the question.
I have 3 Tables
campaign1 (TABLE)
id campaign_details
1 'some detail'
campaign2 (TABLE)
id campaign_details
1 'some other detail'
campaign_list (TABLE)
id campaign_table_name
1 'campaign1'
2 'campaign2'
Campaign list table contains the table name of the two tables described above. I want to Select from the Campaign List table and get the record count using the table name i get from this select
For eg.
using select i get campaign1(Table name). Then i run select query on campaign1 to count number of records.
What i'm doing right now is .
-Select from campign_list
-loop through all campaign_table_names and run select query individually
Is there a way to do this using a single query
something like this
select campaign_name,(SELECT COUNT(*) FROM c.campaign_name) as campcount from campaign_list c
SQLFiddle : http://sqlfiddle.com/#!9/b766d/2
It's not possible inside a single query to build it dynamically but it's possible to cheat. Especially if there are only two linked tables.
I've listed two options
left outer join both tables
select campaign_name,
coalesce(c1.campaign_details, c2.campaign_details)
from campaign_list c
left join campaign1 c1 using (id)
left join campaign2 c2 using (id);
union all two different selects
select campaign_name,
campaign_details
from campaign_list c
join campaign1 c1 using (id)
union all
select campaign_name,
campaign_details
from campaign_list c
join campaign2 c2 using (id);
sqlfiddle
Combine your campaign tables to 1 table and add an column named 'type' (int).
campaign_items tables:
item_id item_details item_type
1 'some detail' 1
2 'some detail' 1
3 'some other detail' 2
4 'some other detail' 2
campaign_lists table
campaign_id campaign_name
1 'campaign1'
2 'campaign2'
Then you can use the following select statement:
SELECT campaign_name, (SELECT COUNT(*) FROM campaign_items WHERE item_type = campaign_id) as campaign_count
FROM campaign_lists
Oops, writing took me so long that you got this answered by Colin Raaijmakers already. Well, I'll post my answer anyway in spite of being more or less the same answer. Maybe my elaboration helps you see the problem.
Your problem stems from a bad database design. A database is made to order data and its relations. A CD database holds albums, songs, artists, etc. A business database may hold items, warehouses, sales and so on. Your database holds table names. [... time for thinking :-) ]
(When writing a DBMS you would want to store table names, column names, constraints etc., but I guess I am right supposing that you are not writing a new DBMS.)
So create tables that deal with your actual data. E.g.:
campain_type (id_campain_type, description, ...)
campain (id_campain, id_campain_type, campain_date, ...)
campain_type
id_campain_type description
1 Type A
2 Type B
3 Type C
campain
id_campain id_campain_type date
33 1 2015-06-03
85 2 2015-10-23
97 2 2015-12-01
query
select
ct.description,
(select count(*) from campain c where c.id_campain_type = ct.id_campain_type) as cnt
from campain_type ct;
result
description cnt
Type A 1
Type B 2
Type C 0
Table semesters:
semesterID startDate
1 2013-01-01
2 2013-03-01
3 2013-06-01
Table classes:
classID class_title semesterID
1 Math 1
2 Science 1
3 Math 2
4 Science 2
5 Math 3
6 Science 3
Table persons:
personID firstName lastName
1 John Jones
2 Steve Smith
Table class_person:
classID personID
1 1
2 1
5 1
6 1
3 2
4 2
5 2
6 2
I need to get a list of all the people, with the first semester in which they took a class (semester with the oldest startDate).
firstName, lastName, semesterID, startDate
John Jones 1 2013-01-01
Steve Smith 2 2013-03-01
I've spent hours trying to figure this out. Here's the closest I've gotten (although it is not close at all!):
SELECT p.firstName, p.lastName, MIN(s.startDate) AS min_startDate
FROM semesters s
INNER JOIN classes c ON s.semesterID = c.semesterID
INNER JOIN class_person cp ON cp.classID = c.classID
INNER JOIN persons p ON p.personID = cp.personID
GROUP BY cs.personID
ORDER BY min_startDate, p.lastName, p.firstName
Any help would be massively appreciated. Thank you.
You could end up using a monster like the following (fiddle):
select persons.firstName, persons.lastName,
semesters.semesterID, semesters.startDate
from persons, semesters,
(select p.personID,
(select semesters.semesterID
from semesters, classes, class_person
where semesters.semesterID = classes.semesterID
and classes.classID = class_person.classID
and class_person.personID = p.personID
order by semesters.startDate
limit 1) as semesterID
from (select distinct personID from class_person) as p
) as ps
where persons.personID = ps.personID
and semesters.semesterID = ps.semesterID
The subquery p identifies all persons. For each, ps will contain a single row. Its personID is simply copied, its semesterID is computed by a subquery, which sorts semesters by date but returns the ID. The outermost query then re-adds the date.
If you don't really need the semesterID, you could avoid one layer. If your semesters are in order, i.e. their IDs have the same order as their startDates, then you could simply use a single query, much like your own, and return min(semesterID) and min(startDate).
On the whole, this question reminds me a lot of my own question, Select one value from a group based on order from other columns. Answers suggested there will likely apply here as well. In particular, there are approaches using user variables which I still don't feel comfortable about, but which will make this whole mess a lot easier and seem to work well enough. So adapting this answer, you get a query like this (fiddle):
SELECT p.firstName, p.lastName, s2.semesterID, s2.startDate
FROM persons p
INNER JOIN (
SELECT #rowNum:=IF(#personID=cp.personID,#rowNum+1,1) rowNum,
#personId:=cp.personID personID,
s.semesterID, s.startDate
FROM (SELECT #personID:=NULL,#rowNum:=0) dummy
INNER JOIN semesters s
INNER JOIN classes c ON s.semesterID = c.semesterID
INNER JOIN class_person cp ON cp.classID = c.classID
ORDER BY cp.personID, s.startDate
) s2 ON p.personID = s2.personID
WHERE s2.rowNum = 1
I'll leave adapting the other answers as an excercise.
I am trying to retrieve the CategoryID and CategoryName by seeing the CategoryBusinessMapping and Review Rating table. I am trying to retrieve the data of following Category table:
Category ParentCategoryID CategoryName
1 null Education
2 1 School
3 null Health
4 3 Doctors
5 1 Colleges
I have the Business table which has BusinessID and BusinessName and BusinessDescription like this:
BusinessID BusinessName BusinessDescription
YP00001 XYZ ABCD
YP00002 ABC XYZA
I have the CategoryBusinessMapping table like this:
MappingID CategoryID BusinessID
1 1 YP00001
2 2 YP00001
3 5 YP00001
4 3 YP00002
5 4 YP00002
I have this mapping table to map the different Category with the Business. I also have the Rating table like this:
RatingID BusinessID
1 YP00001
2 YP00001
3 YP00001
4 YP00002
5 YP00002
Here in this table I am assuming that a record having same BusinessID is fall under most popular Business. Meaning, here in above the Business ABCD having ID = YP00001 has four records in Rating table. Therefore it falls under most popular Business. Similarly YP00002 falls next to YP00001. By seeing the most popular Business in descending order I want to retrieve CategoryName and CategoryID. I have tried this to retrieve from the Rating table only:
select Distinct ReviewRating.BusinessID
,Count(*)as Rating
from YP.utblYPReviewRatingDtls as ReviewRating
group by ReviewRating.BusinessID
order by Rating desc
I have tried this:
SELECT distinct c.CategoryName, b.BusinessID
FROM Category c
INNER JOIN categoryBusinessMapping cbm
ON (c.CategoryID=cbm.CategoryID)
INNER JOIN Business b
ON (cbm.BusinessID=b.BusinessID)
LEFT JOIN Rating r
ON (cbm.BusinessID=r.BusinessID)
where c.ParentCategoryID is null
but I get the result which is redundant. I also remove the BusinessID from the query and I get the result but the result is incorrect. How can I remove redundancy and also get the proper output?
Use join and take the count of BusinessID from rating table and order your results
SELECT c.*, COUNT(r.BusinessID) AS bcount FROM Category c
INNER JOIN CategoryBusinessMapping cbm ON (c.Category=cbm.CategoryID)
INNER JOIN Business b ON (cbm.BusinessID=b.BusinessID)
LEFT JOIN Rating r ON (cbm.BusinessID=r.BusinessID)
GROUP BY r.BusinessID
ORDER BY bcount DESC
Let's say I have a table that lists various toys and the types of batteries they take:
toy_id battery_id battery_qty
1 3 1
2 2 4
2 3 1
3 1 1
I want to construct a query that will tell me which toys take both battery types 2 and 3 (but might take others as well). In the example above, that's toy_id 2.
How do I go about writing a query like that?
Here's what I have so far, but it seems messy:
SELECT t1.toy_id,
t1.battery_id b1, t2.battery_id b2,
t1.battery_qty qty1, t2.battery_qty qty2
FROM toys t1
LEFT JOIN toys t2
ON t1.toy_id = t2.toy_id
WHERE t1.battery_id = 2
AND t2.battery_id = 3
HAVING q1 > 0
AND q2 > 0
ORDER BY toy_id ASC;
The results look correct, but I'm curious if I missed something.
I think join is not really need here. You only need to count the number of instances of the record and which is equal to the number of conditions you have searched.
SELECT toy_ID
FROM toys
WHERE battery_ID IN (2,3)
GROUP BY toy_ID
HAVING COUNT(*) = 2
SQLFiddle Demo
but if unique constraint was not define on battery_id for each toy_id, DISTINCT is need on the HAVING clause.
SELECT toy_ID
FROM toys
WHERE battery_ID IN (2,3)
GROUP BY toy_ID
HAVING COUNT(DISTINCT battery_ID) = 2
SQLFiddle Demo
I think you can use full join without qty (assuming battery_qty is always > 0 in your table) as:
SELECT distinct t1.toy_id
FROM toys t1
JOIN toys t2
ON t1.toy_id = t2.toy_id
WHERE t1.battery_id = 2
AND t2.battery_id = 3
ORDER BY toy_id ASC;