How to have multiple tables with multiple joins - sql

I have three tables that I need to join together and get a combination of results. I have tried using left/right joins but they don't give the desired results.
For example:
Table 1 - STAFF
id name
1 John
2 Fred
Table 2 - STAFFMOBILERIGHTS
id staffid mobilerightsid rights
--this table is empty--
Table 3 - MOBILERIGHTS
id rightname
1 Login
2 View
and what I need is this as the result...
id name id staffid mobilerightsid rights id rightname
1 John null null null null 1 login
1 John null null null null 2 View
2 Fred null null null null 1 login
2 Fred null null null null 2 View
I have tried the following :
SELECT *
FROM STAFFMOBILERIGHTS SMR
RIGHT JOIN STAFF STA
ON STA.STAFFID = SMR.STAFFID
RIGHT JOIN MOBILERIGHTS MRI
ON MRI.ID = SMR.MOBILERIGHTSID
But this only returns two rows as follows:
id name id staffid mobilerightsid rights id rightname
null null null null null null 1 login
null null null null null null 2 View
Can what I am trying to achieve be done and if so how?
Thanks

From your comment its now clear you want a cross join (include all rows from staff and mobilerights). Something like this should do it
SELECT
*
FROM Staff, MobileRights
LEFT OUTER JOIN StaffMobileRights ON StaffMobileRights.StaffId = Staff.Id
The FROM clause specifies that we will be including all rows from the Staff table, and all rows from the MobileRights table. The end result will therefore contain (staff * MobileRights) rows.
To bring in rows from StaffMobileRights then we need a join to that table also. We use a LEFT OUTER join to ensure that we always include the left side (rows in the staff table) but we arent too bothered if no rows exist on the right side (StaffMobileRights table). If no row exists for the join then null values are returned.

What you are probably asking is to see null where is no rights. In the rectangular style that results are always returned, this is the only way to represent it with a simple join:
From PaulG's query i changed it a bit to always get everything form the STAFF table.
SELECT
*
FROM STAFF
RIGHT OUTER JOIN StaffMobileRights ON StaffMobileRights.StaffId = Staff.Id
INNER JOIN MobileRights ON MobileRights.Id = StaffMobileRights.MobileRightsId

Related

How to join three tables and set blank fields to null?

I used full join and left join to join the Person, Tasks and Task tables. The result shown on the screen resulted in a number of lines greater than six. Unset fields have the value NULL and that's good.
The expected output can be obtained using such joins, however it is necessary to use clauses that allow the common fields to be joined. How can I do this?
A CROSS JOIN produces all the combinations you want. Then a simple outer join can retrieve the related rows (should they exist).
You don't mention the database you are using so a faily standard query will do. For example (in PostgreSQL):
select
row_number() over(order by p.id, t.id) as id,
p.name,
case when x.st is not null then t.hr end,
x.st
from person p
cross join tasks t
left join task x on x.personid_fk = p.id and x.taskid_fk = t.id
order by p.id, t.id;
Result:
id name case st
--- ----- ------ -----
1 Anna null null
2 Anna null null
3 Luo 13:00 true
4 Luo 14:00 false
5 John null null
6 John null null
See running example at DB Fiddle.

JOIN query, SQL Server is dropping some rows of my first table

I have two tables customer_details and address_details. I want to display customer details with their corresponding address, so I was using a LEFT JOIN, but when I'm executing this query, SQL Server drops rows where street_no of customer_details table doesn't match with the street_no in address_detials table and displays only rows where `street_no' of customer_detials = street_no of address_details table. I need to display a complete customer_details table and in case if street_no doesn't matches it should display empty string or anything. Am I doing anything wrong in my SQL join?
Table customer_details:
case_id customer_name mob_no street_no
-------------------------------------------------
1 John 242342343 4324234234234
1 Rohan 343233333 43332
1 Ankit 234234233 2342332423433
1 Suresh 234234324 2342342342342
1 Ranjeet 343424323 32233
1 Ramu 234234333 2342342342343
Table address_details:
s_no streen_no address city case_id
------------------------------------------------------
1 4324234234234 Roni road Delhi 1
2 2342332423433 Natan street Lucknow 1
3 2342342342342 Koliko road Herdoi 1
SQL JOIN query:
select
a.*, b.address
from
customer_details a
left join
address_details b on a.street_no = b.street_no
where
b.case_id = 1
Now that it became clear that you used b.case_id=1, I will explain why it filters:
The LEFT JOIN itself returns some rows that contain all NULL values for table b in the result set, which is what you want and expect.
But by using WHERE b.case_id=1, the rows containing NULL values for table b are filtered out because none of them matches the condition (all those rows have b.case_id=NULL so they don't match).
It might work to instead use WHERE a.case_id=1, but we don't know if a.case_id and b.case_id are always the same value for matching rows (they might not be; and if they are always the same, then we just identified a potential redundancy).
There are two ways to fix this for sure.
(1) Move b.case_id = 1 into the left join condition:
left join address_details b on a.street_no = b.street_no and b.case_id = 1
(2) Keep b.case_id = 1 in the WHERE but also allow for NULLED-out b values:
left join address_details b on a.street_no = b.street_no
where b.case_id = 1
or b.street_no IS NULL
Personally I'd go for (1) because that is the most clear way to express that you want to filter b on two conditions, without affecting the rows of a that are being returned.
I do think that Wilhelm Poggenpohl answer is kind of right. You just need to change the last join condition a.case_id=1 to b.case_id=1
select a.* , b.address
from customer_details a
left join address_details b on a.street_no=b.street_no
and b.case_id=1
This query will show every row from customer_details and the corresponding adress if there is a match of street_no and the adress meets the condition case_id=1.
This is because of the where clause. Try this:
select a.* , b.address
from customer_details a
left join address_details b on a.street_no=b.street_no
and a.case_id=1

How can I modify a LEFT OUTER JOIN to add a filter for the RIGHT side table

I have three tables in my database.
AdminTest - Holds a list of tests that are available for users
AdminTestQuestion - Holds a list of questions
UserTest - Holds a list of tests that users have purchased. There's a UserId column in this table and rows in the table always have a value for this. When doing a select I need to be able to filter out the rows in this table by UserId
The data looks like this:
The database stores the results of three tests. test1, test2 and test3
The person with userId = 1 purchased test2
The person with userId = 2 purchased test3.
I am using the following SQL:
SELECT
AdminTest.AdminTestId,
AdminTest.Title,
COUNT(AdminTestQuestion.AdminTestQuestionId) Questions,
AdminTest.Price,
UserTest.PurchaseDate
FROM AdminTest
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
JOIN AdminTestQuestion
ON AdminTest.AdminTestId = AdminTestQuestion.AdminTestId
GROUP BY
AdminTest.AdminTestId,
AdminTest.Title,
UserTest.UserId
Which gives me a report like this:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0 1/1/2011
3 Test3 10 10 2/2/2012
Can someone suggest how I could modify this so the SQL takes a parameter of UserId so it could correctly show the tests that have been purchased by a particular user:
This is what I would like to see when I provide a value of 1 for the UserId parameter:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0 1/1/2011
3 Test3 10 10
This is what I would like to see when I provide a value of 2 for the UserId parameter:
AdminTestId Title Questions Price PurchaseDate
1 Test1 10 0
2 Test2 20 0
3 Test3 10 10 2/2/2012
What I have tried so far is adding WHERE clauses with the UserId to the AdminUser part of the select. But this does not seem to work. I hope someone can point me in the right direction.
For reference here's the DDL of the UserTest table that I want to filter out with UserId somehow:
CREATE TABLE [dbo].[UserTest] (
[UserTestId] INT IDENTITY (1, 1) NOT NULL,
[AdminTestId] INT NOT NULL,
[UserId] INT NOT NULL,
[PurchaseDate] DATETIME NOT NULL,
CONSTRAINT [PK_UserTest] PRIMARY KEY CLUSTERED ([UserTestId] ASC)
);
To expand on what #RichardHansell said...
You can filter a JOIN by adding things to the 'ON' clause of the script. The ON clause does not have to be only links between the two tables, you can add other filters in as well. Like so...
SELECT AdminTest.AdminTestId,
AdminTest.Title,
COUNT(AdminTestQuestion.AdminTestQuestionId) Questions,
AdminTest.Price,
UserTest.PurchaseDate
FROM AdminTest
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
AND UserTest.UserId = #FilteredUserId
JOIN AdminTestQuestion
ON AdminTest.AdminTestId = AdminTestQuestion.AdminTestId
GROUP BY AdminTest.AdminTestId, AdminTest.Title, UserTest.UserId
If you put the parameter test in the ON clause of the Left Outer Join, you should get the results you're after:
...
LEFT OUTER JOIN UserTest
ON AdminTest.AdminTestId = UserTest.AdminTestId
AND UserTest.UserId = #UserId
...

MySQL COUNT with GROUP BY and zero entries

I have 2 tables:
Table 1. options_ethnicity with the following entries:
ethnicity_id ethnicity_name
1 White
2 Hispanic
3 African/American
Table 2. inquiries with the following entries:
inquiry_id ethnicity_id
1 1
2 1
3 1
4 2
5 2
I want to generate a table that shows the number of inquires by ethnicity. My query so far looks like this:
SELECT options_ethnicity.ethnicity_name, COUNT('inquiries.ethnicity_id') AS count
FROM (inquiries
LEFT JOIN options_ethnicity ON
options_ethnicity.ethnicity_id = inquiries.ethnicity_id)
GROUP BY options_ethnicity.ethnicity_id
The query gives the correct answer but there is no column for African/American which has 0 results.
White 3
Hispanic 2
If I replace the LEFT JOIN with a RIGHT JOIN, I get all 3 ethnicity names, but the count for African/American is wrong.
White 3
Hispanic 2
African/American 1
Any help would be appreciated.
Here's an update to this post with what seems to be a working query:
SELECT
options_ethnicity.ethnicity_name,
COALESCE(COUNT(inquiries.ethnicity_id), 0) AS count
FROM options_ethnicity LEFT JOIN inquiries ON inquiries.ethnicity_id = options_ethnicity.ethnicity_id
GROUP BY options_ethnicity.ethnicity_id
UNION ALL
SELECT
'NULL Placeholder' AS ethnicity_name,
COUNT(inquiries.inquiry_id) AS count
FROM inquiries
WHERE inquiries.ethnicity_id IS NULL
Because you're using a LEFT JOIN, references to the table defined in the LEFT JOIN can be null. Which means you need to convert this NULL value to zero (in this case):
SELECT oe.ethnicity_name,
COALESCE(COUNT(i.ethnicity_id), 0) AS count
FROM OPTIONS_ETHNICITY oe
LEFT JOIN INQUIRIES i ON i.ethnicity_id = oe.ethnicity_id
GROUP BY oe.ethnicity_id
This example uses COALESCE, an ANSI standard means of handling NULL values. It will return the first non-null value, but if none can be found it will return null. IFNULL is a valid alternative on MySQL, but it is not portable to other databases while COALESCE is.
In the real database table, there are some entries in the inquires table where the ethnicity_id is NULL, i.e. the ethnicity was not recorded. Any idea on how to get these null values to be counted so that they can be shown?
I think I understand the issue you're facing:
SELECT oe.ethnicity_name,
COALESCE(COUNT(i.ethnicity_id), 0) AS count
FROM (SELECT t.ethnicity_name,
t.ethnicity_id
FROM OPTIONS_ETHNICITY t
UNION ALL
SELECT 'NULL placeholder' AS ethnicity_name,
NULL AS ethnicity_id) oe
LEFT JOIN INQUIRIES i ON i.ethnicity_id = oe.ethnicity_id
GROUP BY oe.ethnicity_id
This will pickup all the NULL ethncity_id instances, but it will attribute the counting to the "NULL placeholder" group. IE:
ethnicity_name | COUNT
------------------------
White | 3
Hispanic | 2
NULL placeholder | ?
You counted a string instead of the right column
SELECT options_ethnicity.ethnicity_name, COUNT(inquiries.ethnicity_id) AS count
FROM inquiries
RIGHT JOIN options_ethnicity ON options_ethnicity.ethnicity_id = inquiries.ethnicity_id
GROUP BY options_ethnicity.ethnicity_id
Why don't you "reverse" your query?
SELECT
options_ethnicity.ethnicity_name,
COUNT(inquiries.ethnicity_id) AS count
FROM
options_ethnicity
Left Join inquiries On options_ethnicity.ethnicity_id = inquiries.ethnicity_id
GROUP BY
options_ethnicity.ethnicity_id
You still might need a Coalesce call, but to me, this query makes more sense for what you're trying to accomplish.

Joining two tables - multiple columns to single column

Table 1
-------
LANG_VALUE
LANG_DESC
Table 2
-------
EmpId
LANG1
LANG2
LANG3
LANG4
LANG5
LANG6
It looks like as below:
Table 1
-------
LANGVALUE LANGDESC
-------------------
SPAN SPANISH
GERM GERMAN
Table 2
-------
EmpId LANG1 LANG2 LANG3 LANG4 LANG5 LANG6
-----------------------------------------
1 SPAN NULL NULL NULL NULL NULL
2 GERM SPAN NULL NULL NULL NULL
3 GERM NULL NULL NULL NULL NULL
Expected Result:
EmpId LANG1 LANG2 LANG3 LANG4 LANG5 LANG6
-----------------------------------------
1 SPANISH NULL ...
2 GERMAN SPANISH NULL...
3 GERMAN NULL...
How to do this in sql query?
SELECT EmpId,
K1.LANGDESC AS LANG1,
K2.LANGDESC AS LANG2,
K3.LANGDESC AS LANG3,
K4.LANGDESC AS LANG4,
K5.LANGDESC AS LANG5,
K6.LANGDESC AS LANG6
FROM Table2 T
LEFT JOIN Table1 K1 ON K1.LANGVALUE = T.LANG1
LEFT JOIN Table2 K2 ON K2.LANGVALUE = T.LANG2
LEFT JOIN Table3 K3 ON K3.LANGVALUE = T.LANG3
LEFT JOIN Table4 K4 ON K4.LANGVALUE = T.LANG4
LEFT JOIN Table5 K5 ON K5.LANGVALUE = T.LANG5
LEFT JOIN Table6 K6 ON K6.LANGVALUE = T.LANG6
WHERE...
Note: this unsolicited in the question, but it may be a good idea to consider a schema where the Employee to Language is stored in a separate table, in a one-to-many fashion. This will allow having employees with more than 6 languages (hope you have many of these...) as well as not carrying so many nulls for employees that ain't so polyglot, and more importantly it would allow querying say all employees with a particular language without having to worry if that is to be found in the LANG2, or LANG3 etc...
Note, the Employee-Langage table could also have an attribute indicating the "order" (or the level of fluency), so that you can also search for employees who's first listed language is x.
I think you'd have to do this with subqueries based on the format of your table 2. I have the distinct feeling you aren't properly normalized for table-2, which is why you have this problem in the first place.
select EmpId,(Select LANGDESC from [Table 1] t1 where t2.Lang1=LANGVALUE) as LANG1,(and so on) where from [Table 2] t2