Using Two Tables to give one output - sql

Table: Person
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+-------------+---------+
PersonId is the primary key column for this table.
Table: Address
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+-------------+---------+
AddressId is the primary key column for this table.
Write a SQL query for a report that provides the following information for each person in the Person table, regardless if there is an address for each of those people:
FirstName, LastName, City, State
So this is a pretty straight forward question and my answer is
select p.FirstName,p.LastName, a.City, a.State
from Person p , Address a
where p.PersonId = a.PersonId
And I'm not getting anything in the output and yes I filled up the tables with some simple lines of data.

Sample data would help, but - the way you described it, it looks as if you need outer join. Something like this:
select p.FirstName, p.LastName, a.City, a.State
from Person p left join Address a on a.personid = p.personid

MySQL query
SELECT Person.FirstName, Person.LastName, Address.City, Address.State
FROM Person
LEFT JOIN Address
ON Person.PersonId=Address.PersonId;

Related

Why the output of a SELECT can be another SELECT?

I am rather confused about the following SQL query:
SELECT (SELECT S.name FROM student AS S
WHERE S.sid = E.sid) AS sname
FROM enrolled as E
WHERE cid='15-455';
SELECT should be followed by an output, but why here there is another SELECT? How to understand the step-by-step meaning of this query?
The following is the query that has the same result of the above query, but its meaning is rather explicit: the output of the second SELECT is passed into the IN() function.
SELECT name FROM student
WHERE sid IN (
SELECT sid FROM enrolled
WHERE cid = '15-445'
);
Here are the original tables of this question:
mysql> select * from student;
+-------+--------+------------+------+---------+
| sid | name | login | age | gpa |
+-------+--------+------------+------+---------+
| 53666 | Kanye | kayne#cs | 39 | 4.00000 |
| 53688 | Bieber | jbieber#cs | 22 | 3.90000 |
| 53655 | Tupac | shakur#cs | 26 | 3.50000 |
+-------+--------+------------+------+---------+
mysql> select * from enrolled;
+-------+--------+-------+
| sid | cid | grade |
+-------+--------+-------+
| 53666 | 15-445 | C |
| 53688 | 15-721 | A |
| 53688 | 15-826 | B |
| 53655 | 15-445 | B |
| 53666 | 15-721 | C |
+-------+--------+-------+
mysql> select * from course;
+--------+------------------------------+
| cid | name |
+--------+------------------------------+
| 15-445 | Database Systems |
| 15-721 | Advanced Database Systems |
| 15-826 | Data Mining |
| 15-823 | Advanced Topics in Databases |
+--------+------------------------------+
In real life I'd say both queries are just two creepy ways to avoid joins.
But in this particular case they were included in the slides you've found in order to show in how many place nested loops can be used.
They all do the same thing as the following
SELECT name
FROM student s
JOIN enrolled e
ON s.sid = e.sid
WHERE cid = '15-445';
As for your question about step-by-step meaning of the first query. It is the following
This will loop through every record from "enrolled" table that has cid = '15-455'.
FROM enrolled as E
WHERE cid='15-455';
For every record from step 1 it will perform the following query
SELECT S.name
FROM student AS S
WHERE S.sid = E.sid;
This construct:
SELECT (SELECT S.name FROM student S WHERE S.sid = E.sid) AS sname
-------^
is called a scalar subquery. This is a special type of subquery that has two important properties:
It returns one column.
It returns at most one row.
In this case, the scalar subquery is also a correlated subquery meaning that it references columns in the outer query, via the where clause.
A scalar subquery can be using almost anywhere that a scalar (i.e. constant value) can be used in a query. They can be handy. They are not exactly equivalent to a join, because:
An inner join can filter values. A scalar subquery returns NULL if there are no rows returned.
A join can multiply the number of rows. A scalar subquery returns an error if it returns more than one row.
If you want to get informations like :
Name of student | CID | Grade |
You can do something like :
select t.name, e.cid, e.grade
from enrolled e
inner join student t on (e.sid = t.sid)
Or without join (for optimization) :
select (name from student t where t.sid = e.sid) as name, e.cid, e.grade
from enrolled e
so results are the same but in the second one you're avoiding joins.

SQL Self Join not "joining" in PopSQL

I'm currently learning SQL and came across an obstacle.
I have been looking around the web and found no case where the table is being "replicated" as is my case.
The case is based on a classical example where we want to have a result where we get the employee name and the next column with the supervisor name.
Desired result:
|Employee Name | Supervisor Name|
---------------------------------
|Dave | NULL |
|Rick | Dave |
|Andy | Rick |
The current table goes like this (employee):
|emp_id | first_name | last_name| super_id |
--------------------------------------------
|100 | Dave | Star | NULL |
|101 | Rick | Port | 100 |
|101 | Andy | Huds | 101 |
I'm trying to do this with a SELF JOIN in POPSQL (MySQL based) but first I will show how the employee table was created.
CREATE TABLE employee (
emp_id INT PRIMARY KEY,
first_name VARCHAR(40),
super_id INT,
);
ALTER TABLE employee
ADD FOREIGN KEY (super_id)
REFERENCES employee(emp_id)
ON DELETE SET NULL;
This is the code I'm using to make the query:
SELECT m.first_name, s.first_name
FROM employee AS m
INNER JOIN employee AS s
ON m.super_id = s.emp_id;
And below the result I'm getting.
Notice how the table has two problems.
First, each row seems to be repeated (David appears in two rows).
Second, the emp_id is not matched to sup_id (as per the ON m.super_id = s.emp_id).
|emp_id | first_name |
----------------------
|David | David |
|David | David |
|Rick | Rick |
|Rick | Rick |
Could somebody help me?
Thanks
if you need to get all records (including Dave), you need to use OUTER JOIN
SELECT m.first_name AS EmployeeName,
s.first_name AS SupervisorName
FROM employee AS m
LEFT JOIN employee AS s
ON m.super_id = s.emp_id;
SELECT m.first_name, s.first_name
FROM employee AS m
left JOIN employee AS s
ON m.super_id = s.emp_id;

Self Referencing SQL query when condition is met

I'm trying to create a SQL query to return the column values from a table that meet certain criteria.
Currently I have used the CONCAT function to join the first and last names into a single column in the query result for employees that have the role of 'Programmer'.
SELECT
person.id, CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM
person, role
WHERE
person.role_id = role.id AND role.name = 'Programmer'
This successfully runs and returns all programmers from the tables. Notice in my table structure I have an actingas_id column. This is the key to another person.id for people who are working on behalf of another people whilst they're on leave from work.
Thus, we arrive at my Question: How do I modify the SQL query such that when a person is acting that the query retrieves the first and last name of this person as well as the person who's 'shoes are being filled'?
My table structure is as follows:
person:
id | firstname | lastname | role_id | actingas_id |
role:
id | name |
+----+-----------+----------+---------+-------------+
| id | firstname | lastname | role_id | actingas_id |
+----+-----------+----------+---------+-------------+
| 1 | John | Smith | 1 | 0 |
| 2 | Kevin | Tull | 2 | 1 |
| 3 | Michael | Woods | 1 | 0 |
+----+-----------+----------+---------+-------------+
Here Kevin is Acting for for John, and Michael is also a Programmer, so the result of my query should be:
+----+-------------------------+
| id | NAME |
+----+-------------------------+
| 1 | John Smith - Kevin Tull |
| 3 | Michael Woods |
| x | Other Programmers.. |
+----+-------------------------+
This untested query should give you the result you whant:
SELECT person1.id, CASE WHEN person1.actingas_id =0 then CONCAT(person1.firstname,' ', person1.lastname) else CONCAT(person1.firstname,' ', person1.lastname,' - ', person2.firstname,' ', person2.lastname) AS FULLNAME
FROM person person1 left join person person2 on person1.actingas_id=person2.id
join role on person1.role_id=role.id
WHERE role.name = 'Programmer'
Use UNION ALL to add those additional records:
SELECT person.id,
CONCAT(person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer'
AND NOT EXISTS(SELECT 1 FROM person p WHERE p.actingas_id = person.id)
UNION ALL
SELECT a.id,
CONCAT(a.firstname,' ', a.lastname, ' - ', person.firstname,' ', person.lastname) AS FULLNAME
FROM person INNER JOIN person a
ON a.acting_as = person.id
INNER JOIN role
ON person.role_id = role.id
WHERE role.name = 'Programmer' AND a.actingas_id <> 0
Also, avoid using old style comma-separated JOINs. Use INNER JOINs

Conditional join based on lookup

Apologies if a similar problem is posted earlier, I couldn't find the same.
Problem: I need to join two tables based a conditional look up in the second table.
Tables: Below are the two tables which have a subset of the total fields.
+-------------------------------------------------------+
| Persons |
+----------+------------+---------------+---------------+
| PersonID | PersonName | HomeAddressID | WorkAddressID |
+----------+------------+---------------+---------------+
| P1 | Doe, John | HA1 | WA1 |
+----------+------------+---------------+---------------+
| P2 | Doe, Jane | HA2 | WA2 |
+----------+------------+---------------+---------------+
| P3 | Doe, Jane | | WA3 |
+----------+------------+---------------+---------------+
+-----------------------------------+
| Addresses |
+-----------+--------+------+-------+
| AddressID | Street | City | State |
+-----------+--------+------+-------+
| HA1 | 123 | A | B |
+-----------+--------+------+-------+
| WA1 | 456 | C | D |
+-----------+--------+------+-------+
| HA2 | 111 | | |
+-----------+--------+------+-------+
| WA2 | 101 | G | H |
+-----------+--------+------+-------+
| WA3 | 333 | I | J |
+-----------+--------+------+-------+
Current Scenario: The SELECT query in a view fetches PersonName from first table and work address fields from second table. (Join is on WorkAddressID)
Expected Result: The SELECT query should fetch PersonName field from first table and address fields from second table conditions being:
If state for home address is available then display Street, City and State for home address.
If state for home address is NULL/blank then display Street, City and State for work address.
Notes:
Many rows in Persons table do not have HomeAddressID but all do have WorkAddressID.
Many rows in Addresses table do not have City and State information for Home addresses.
While this may look like a design flaw, I'm not in a position to re-engineer the database as there are hundreds of objects and sub-objects depending on the original view.
There are 3 million+ rows in the Persons table so performance needs to be acceptable.
The current query has joins to at least 5 other views.
Please advise as to how I can address this problem.
Many thanks,
-V
Here's a MySQL solution:
SELECT PersonName,
IF(h.State = '' OR h.State IS NULL, w.Street, h.Street) AS Street,
IF(h.State = '' OR h.State IS NULL, w.City, h.City) AS City,
IF(h.State = '' OR h.State IS NULL, w.State, h.State) AS State
FROM Persons AS p
JOIN Addresses AS w ON w.AddressID = p.WorkAddressID
LEFT JOIN Addresses as h ON h.AddressID = p.HomeAddressID
A self join would handle this:
select
p.personname,
case when ha.state is null then wa.street else ha.street end as street,
case when ha.state is null then wa.city else ha.city end as city,
case when ha.state is null then wa.state else ha.state end as state
from
Persons p
inner join addresses wa on p.workaddressid = wa.addressid
left join addresses ha on p.homeaddressid = ha.addressid
This syntax would be for MSSQL
Edit: changed the home to a left join because of the criterion Many rows in Persons table do not have HomeAddressID

Issue with JOIN command

Hi New comer to Stack overflow so if I do not present this correctly I am sorry.
I have used Google, W3schools and read the FQA on SQL.
I am running SQL using the SQL command line in WAMP2.0. I am currently doing a project where the aim is to create a min University DB. With students, grades, programmes, modules ect
One of the tasks is to to list all the students, there modules and there correspoding grades. To do this I am trying to use a JOIN command to select all the names from the Students table, with all there corresponding modules + grades from the records table.
+------------+-------+------------+-----------------+
| Student_id | Name | DOB | Address |
+------------+-------+------------+-----------------+
| 4665236 | Paddy | 1985-09-18 | 123 Fake Street |
| 5665236 | Paul | 1984-06-12 | Good manlane |
| 6665236 | John | 1984-03-09 | Docotor town |
| 7665236 | Aidan | 1983-07-09 | Banker worlds |
| 8665236 | Joe | 1983-07-09 | 24 hitherwood |
+------------+-------+------------+-----------------+
+------------+--------+------+-------+
| Student_id | Mod_id | GPA | Grade |
+------------+--------+------+-------+
| 4655236 | 2222 | 3.84 | A- |
| 5655236 | 11111 | 3.44 | B+ |
| 6655236 | 33333 | 3.24 | B |
| 7655236 | 44444 | 2.45 | C- |
| 8655236 | 44444 | 2.45 | C- |
+------------+--------+------+-------+
The PRIMARY KEY in the students table is Student_id INT 11
The PRIMARY KEY for records is (Student_id,Mod_id)
Individual SELECT FROM , statements work fine on both tables.
Issue occurs when I use
SELECT students.Name, records.Grade
FROM students
INNER JOIN records
ON students.Student_id=Student_id
ORDER BY students.Name
I get the following error
ERROR 1052 (23000): Column 'Student_id' in on clause is ambiguous
Thanks for amazingly fast response I tried
SELECT students.Name, records.Grade
FROM students
INNER JOIN records
ON students.Student_id=records.Student_id
ORDER BY students.Name;
And Got ---- Empty set (0.00 sec) ?
You have to qualify that column Student_Id with an alias, something like records.studentId so that it will be un ambiguous in the ON clause, or:
SELECT s.Name, r.Grade
FROM students AS s
INNER JOIN records AS r ON s.Student_id= r.Student_id
ORDER BY s.Name
You need to supply the table name for column Student_id to avoid ambiguity because it both exist on the two tables.
SELECT students.Name, records.Grade
FROM students
INNER JOIN records
ON students.Student_id = records.Student_id -- << THIS
ORDER BY students.Name
The reason you're column name is being flagged as ambiguous is because you've got two different tables that each have the Student_id field. You can join a table to itself, so even though you've got an identifier on the first instance of the field you need one on both.
Try the following code:
SELECT students.Name, records.Grade
FROM students
INNER JOIN records
ON students.Student_id=records.Student_id
ORDER BY students.Name
You can also alias the tables if that helps your code look cleaner by using the following:
SELECT s.Name, r.Grade
FROM students s
INNER JOIN records r
ON s.Student_id=r.Student_id
ORDER BY s.Name
However, this only works if the Student IDs match in both tables. in the example data you've presented there are no matching records. 4665236 != 4655236