SQL : Join of three tables - sql

I am new to SQL and am learning joins of tables now.
I am stuck at joining three tables.
(I have given the rows I have inserted to the tables also for your reference.)
My tables are
--Table1
create table sql_students(
stu_studentid int not null primary key,
stu_name varchar(100) not null,
stu_regnnumber bigint unique not null
)
--Rows inserted to Table1
insert into sql_students (stu_studentid,stu_name,stu_regnnumber) values (1,'John',194300)
insert into sql_students (stu_studentid,stu_name,stu_regnnumber) values (2,'Joy',959595)
insert into sql_students (stu_studentid,stu_name,stu_regnnumber) values (3,'Lucy',474848)
--Table2
create table sql_exam(
exa_examid bigint not null primary key,
exa_name varchar(100) not null,
exa_maxmark decimal(5,2) not null,
exa_minmarkreqdforpass decimal(5,2) not null,
exa_examscheduletime datetime not null
)
--Rows inserted into Table2
insert into sql_exam(exa_examid,exa_name,exa_maxmark,exa_minmarkreqdforpass,exa_examscheduletime) values (1,'Maths',100,40,'2012-10-10 10:00')
insert into sql_exam(exa_examid,exa_name,exa_maxmark,exa_minmarkreqdforpass,exa_examscheduletime) values (2,'English',75,35,'2012-10-11 10:00')
--Table3
create table sql_studentmarks(
stm_studentid int foreign key references sql_students(stu_studentid),
stm_examid bigint foreign key references sql_exam(exa_examid),
stm_mark decimal(5,2)
)
--Rows inserted into Table3
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (1,1,80)
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (2,1,90)
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (3,1,40)
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (1,2,70)
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (2,2,60)
insert into sql_studentmarks(stm_studentid,stm_examid,stm_mark) values (3,2,17)
I need guidelines to get
All students who passed all exams
All students who attended all exams
All students and their mark difference in Maths and English.
Thanks in advance.

For #1
SELECT s.stu_name
FROM sql_studentmarks AS m
JOIN sql_students AS s ON m.stm_studentid = s.student_id
JOIN sql_exam AS x ON COUNT(m.stm_examid) = COUNT (x.stm_examid)
WHERE m.stm_mark >= x.exa_minmarkreqdforpass GROUP BY s.stu_studentid;
For #2 you can base on the exam count, perhaps creating a var in the select statement:
SELECT s.stu_name
FROM sql_studentmarks AS m
JOIN sql_students AS s ON m.stm_studentid = s.stu_studentid
RIGHT OUTER JOIN sql_exam AS x ON COUNT(m.stm_examid) = COUNT (x.stm_examid)
AND m.stm_examid IS NOT NULL
GROUP BY s.stu_student_id;
FOR #3 use the examples from above, simple joins will do:
SELECT s.stu_name, x.exa_name, m.stm_mark
FROM sql_studentmarks AS m
JOIN sql_students AS s ON m.stm_studentid = s.stu_studentid
JOIN sql_exam x ON m.stm_examid = x.exa_examid AND s.stu_studentid = x.exa_studentid;

The code below shows you the first two queries. Your joins are OK.. Try these in SQL Fiddle and then see if you can work the third query out
Query #1: (You were very close, just needed the distinct keyword)
select stm_studentid,stu_name
from sql_exam
join sql_studentmarks on exa_examid=stm_examid
and stm_mark>exa_minmarkreqdforpass
inner join sql_students on stu_studentid=stm_studentid
group by stm_studentid,stu_name
having count(*) = (select count(*) from sql_exam)
Query #2: -
The group by lets you count the number of exams taken.
The having lets you compare the count to number of exams available
select stu_name,count(*) as NumExamsTaken
from sql_studentmarks
join sql_students on stu_studentid=stm_studentid
group by stu_name
having count(*) = (select count(*) from sql_exam)
Query #3:
This should get you started
select stu_studentid,stu_name,
MG.stm_mark as MathGrade,
EG.Stm_mark as EnglishGrade
from sql_students
join sql_exam MATH on MATH.exa_name='MATHS'
join sql_exam ENG on ENG.exa_name='ENGLISH'
join sql_studentmarks MG on MG.stm_studentid=stu_studentid
and MG.stm_examid=MATH.exa_examId
join sql_studentmarks EG on EG.stm_studentid=stu_studentid
and EG.stm_examid=ENG.exa_examId

Related

PostgreSQL | Need values from right table where is no match in m:n index

I have a three tables issue with PostgreSQL
table_left, table_index, table_right
table_index is m:n ...
I want to get all values from right table matching (m:n) and not-matching (NULL) values based on the values of left table.
SELECT field_left, field_index1, field_index2, field_right
FROM table_left
LEFT JOIN table_index ON left_id = index_left
LEFT JOIN table_right ON index_right = right_id
Using this query I get all values from left to right, but I'm not getting values from table_right were are not based in m:n table_index
If I do something like this ...
SELECT field_left, field_index1, field_index2, field_right
FROM table_left
LEFT JOIN table_index ON left_id = index_left
LEFT JOIN table_right ON index_right = right_id OR right_id NOT IN (1,2,3)
... I will get some strange results ...
field_index1, field_index2 using values from m:n but should be NULL because there is no dependency.
Any suggestions?
EDIT:
Have added some data ... Thx to #jarlh
DROP TABLE IF EXISTS "table_index";
CREATE TABLE "public"."table_index" (
"index_left" integer NOT NULL,
"index_right" integer NOT NULL,
"index_data1" character varying NOT NULL,
"index_data2" character varying NOT NULL,
CONSTRAINT "table_index_index_left_index_right" PRIMARY KEY ("index_left", "index_right"),
CONSTRAINT "table_index_index_left_fkey" FOREIGN KEY (index_left) REFERENCES table_left(left_id) NOT DEFERRABLE,
CONSTRAINT "table_index_index_right_fkey" FOREIGN KEY (index_right) REFERENCES table_right(right_id) NOT DEFERRABLE
) WITH (oids = false);
INSERT INTO "table_index" ("index_left", "index_right", "index_data1", "index_data2") VALUES
(1, 1, 'index-Left-A', 'index-Right-A'),
(1, 2, 'index-Left-A', 'index-Right-B'),
(1, 3, 'index-Left-A', 'index-Right-C'),
(2, 1, 'index-Left-B', 'index-Right-A');
DROP TABLE IF EXISTS "table_left";
DROP SEQUENCE IF EXISTS table_left_left_id_seq;
CREATE SEQUENCE table_left_left_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 1 CACHE 1;
CREATE TABLE "public"."table_left" (
"left_id" integer DEFAULT nextval('table_left_left_id_seq') NOT NULL,
"left_data" character varying NOT NULL,
CONSTRAINT "table_left_left_id" PRIMARY KEY ("left_id")
) WITH (oids = false);
INSERT INTO "table_left" ("left_id", "left_data") VALUES
(1, 'Left-A'),
(2, 'Left-B'),
(3, 'Left-C');
DROP TABLE IF EXISTS "table_right";
DROP SEQUENCE IF EXISTS table_right_right_id_seq;
CREATE SEQUENCE table_right_right_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 START 1 CACHE 1;
CREATE TABLE "public"."table_right" (
"right_id" integer DEFAULT nextval('table_right_right_id_seq') NOT NULL,
"right_data" character varying NOT NULL,
CONSTRAINT "table_right_right_id" PRIMARY KEY ("right_id")
) WITH (oids = false);
INSERT INTO "table_right" ("right_id", "right_data") VALUES
(1, 'Right-A'),
(2, 'Right-B'),
(3, 'Right-C');
Using this query ....
SELECT left_id, left_data, index_left, index_right, index_data1, index_data2, right_id, right_data
FROM table_left
LEFT JOIN table_index ON left_id = index_left
LEFT JOIN table_right ON index_right = right_id
... I get some NULL values as expected...
Using the original database I'm not getting these kind of values. Have seen there is an id col within the index table. Primary isn't set to both id values from left/right like my test. Have changed this in my local db with the same result as my test before. I'm getting these NULL values as expected.
I want to get all values from right table matching (m:n) and not-matching (NULL) values based on the values of left table.
If you want all values from the right table, then left join is the right way to go. However the right table should be the first table in the from clause:
SELECT field_left, field_index1, field_index2, field_right
FROM table_right r LEFT JOIN
table_index i
ON i.index_right = r.right_id LEFT JOIN
table_left l
ON l.left_id = i.index_left;
Notes:
There is a bit of cognitive dissonance (in English) because the roles of left/right are reversed.
I recommend that you use table aliases for your actual tables.
I strongly, strongly recommend that you qualify all column references so it is clear what tables they come from.
You could also use RIGHT JOIN. However, I also recommend using LEFT JOIN for this type of logic. It is easier to follow query logic that says: "Keep all rows in the table you just read" rather than "Keep all rows in some table that you will see much further down in the FROM clause."
EDIT:
Based on your comments, I suspect you want a Cartesian product of all left and right values along with a flag that indicates if it is in the junction table. Something like this:
SELECT field_left, field_index1, field_index2, field_right
FROM table_right r CROSS JOIN
table_left l LEFT JOIN
table_index i
ON i.index_right = r.right_id AND
i.index_left = l.left_id;

Show correct result with SQL Joins

Firstly, I am newbie to SQL (T-SQL), I would appreciate guidance with this.
I have 3 tables with values created as below.
CREATE Table StudentProject
(ID int identity (1,1) PRIMARY KEY,
ProjectName Varchar (30),
DueDate Date)
CREATE Table StudentName
(ID int identity (1,1) PRIMARY KEY,
StudentName Varchar (30))
CREATE Table StudentWork
(ID int identity (1,1) PRIMARY KEY,
ProjectID int,
StudentID int)
Insert Into StudentProject values
('Omega','1/2/2005'),('KingOmega','1/3/2000'),('Beast','1/6/2007'),
('DeltaMovie','3/7/2008')
Insert into StudentName values
('Roger'),('John'),('James'),('Juliet'),('William')
Insert into StudentWork values
(1,1),(1,2),(2,2),(2,3),(3,3),(3,4),(1,3)
The goal is to produce the below outcome but seems that i cant or i'm sure i'm doing something wrong.
SQL_Outcom
Please help.
SELECT StudentProject.ProjectName, StudentName.StudentName
FROM StudentWork
INNER JOIN StudentProject ON StudentProject.ID = StudentWork.ProjectID
INNER JOIN StudentName ON StudentName.ID = StudentWork.StudentID
You have 3 tables, try to recognize the "master table", then join them to other tables, after joining them you will have access to their columns.
Hello World :)
UPDATE:
In order to confirm Roger always with id 1 and all the other students in StudentName table, you need to use SET IDENTITY_INSERT which guarantee you the ordering of rows.
Instead of :
Insert into StudentName values
('Roger'),('John'),('James'),('Juliet'),('William')
Do this:
SET IDENTITY_INSERT StudentName ON
Insert into StudentName values
(1,'Roger'),(2,'John'),(3,'James'),(4,'Juliet'),(5,'William')
SET IDENTITY_INSERT StudentName OFF
You need to use inner join and in order to match the records related to each others:
Select p.ProjectName, s.StudentName from StudentName s
Inner join Studentwork sw on sw.studentid = s.Id
inner join StudentProject p on p.ID= sw.ProjectId
Order by P.ProjectName desc
You need inner join
Try this :
select s.StudentName,p.ProjectName from StudentName s
inner join StudentProject sp on sp.id= sw.ProjectId
inner join StudentWork sw on sw.studentid = s.id;

Sybase SQL join RESULT SET

I have a problem with a result set of two tables.
I create two tables.
create table person
(ID int not null PRIMARY KEY,
NAME varchar (50)
)
create table address
( ID int NOT NULL PRIMARY KEY,
PERSON_ID int not null,
ADDRESS VARCHAR (20) null,
ZIP int)
insert into person (ID, NAME) values ( 1,'Hans')
insert into person values (2,'Peter')
insert into address values (1,1, 'Andernach',56626)
insert into address values (2,2,'Koblenz',56000)
insert into address values (3,3,'Neuwied',56100)
I would like to get a result set of these tables.
If I use this query with p.ID = a.PERSON_ID
I get right result.
BUT if I use query with p.ID != a.PERSON_ID
I get a lot of results
select a.ADDRESS,
a.ZIP,
a.PERSON_ID,
p.NAME
from address a,
person p
where a.PERSON_ID != p.ID
My question: Why do I get a lot of results with !=?
Thanks
:-)
Because lots of rows in the first table don't match lots of rows in the second. In fact, almost all rows in the second will match each in the first. Presumably you want:
select a.ADDRESS,
a.ZIP,
a.PERSON_ID,
p.NAME
from person p left join
address a
on a.PERSON_ID = p.ID
where p.id is null;
This gets rows in person that don't have an address. (Putting the tables in the other order or using right join would get addresses with no person.)

SQL Loop/Crawler

I am trying to figure out some ways to accomplish this script. I import an excel sheet and then I need to populate 5 different tables based on this excel sheet. However for this example I just need help with the initial loop then I think I can work through the rest.
select distinct Department from IPACS_New_MasterList
where Department is not null
This provides me a list of 7 different departments.
Dep1, Dep2, Dep3, Dep4, Dep5, Dep6, Dep7
For each of these departments I need to perform some code.
Step #1:
Insert the department into table_one
I then need to keep the SCOPE_IDENTITY() for the rest of the code.
Step #2
perform the second loop (inserting all functions in that department into table2.
I'm not sure how to really do a foreach row in this select statement loop, or if I need to do something completely different. I've looked at several answers but can't seem to find exactly what I'm looking for.
Sample Data:
Source Table
Dep1, func1, process1, procedure1
dep1, func1, process1, procedure2
dep1, func1, process2, procedure3
dep1, func1, process2, procedure4
dep1, func1, process2, procedure5
dep1, func2, process3, procedure6
dep2, func3, process4, procedure7
My Tables:
My first table is a list of every department from the above query. With a key on the departmentID. Each department can have many functions.
My second table is a list of all functions with a key on functionID and a foreign key on departmentID. Each function must have 1 department and can have many processes
My third table is a list of all processes with a key on processID and a foreign key on functionID. Each process must have 1 function and can have many procedures.
There are two approaches you can use without a loop.
1) If you have candidate keys in your source (department name) just join your source table back to the table you inserted
e.g.
INSERT INTO Department
(Name)
SELECT DISTINCT Dep1
FROM SOURCE;
INSERT INTO Functions
(
Name,
DepartmentID)
SELECT DISTINCT
s.Func1,
d.DepartmentID
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name;
INSERT INTO
processes
(
name,
FunctionID,
[Procedure]
)
SELECT
s.process1,
f.FunctionID,
s.procedure1
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN Functions f
on d.DepartmentID = f.departmentID
and s.func1 = f.name;
SQL Fiddle
2) If you don't have candidate keys in your source then you can use the output clause
For example here if a department weren't guaranteed to be unique this would correctly find only the newly add
DECLARE #Department TABLE
(
DepartmentID INT
)
DECLARE #Functions TABLE
(
FunctionID INT
)
INSERT INTO Department
(Name)
OUTPUT INSERTED.DepartmentID INTO #Department
SELECT DISTINCT Dep1
FROM SOURCE
INSERT INTO Functions
(
Name,
DepartmentID)
OUTPUT INSERTED.FunctionID INTO #FunctionID
SELECT DISTINCT
s.Func1,
d.DepartmentID
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN #Department d2
ON d.departmentID = d2.departmentID;
INSERT INTO
processes
(
name,
FunctionID,
[Procedure]
)
SELECT
s.process1,
f.FunctionID,
s.procedure1
FROM
source s
INNER JOIN Department d
on s.dep1 = d.name
INNER JOIN Functions f
on d.DepartmentID = f.departmentID
and s.func1 = f.name
INNER JOIN #Functions f2
ON f.Functions = f2.Functions
SELECT * FROM Department;
SELECT * FROm Functions;
SELECT * FROM processes;
SQL Fiddle
If I am understanding what you are trying to do... yes you can use a loop. Its not really talked about and I bet I am going to get some feedback from other SQL developers that its not a best practice. But if you really need to do a loop
DECLARE #rowcount as int
DECLARE #numberOfRows as int
SET #rowcount = 0
SET #numberOfRows = SELECT COUNT(*) from tablename --put in anything to get the number of times to loop.
WHILE #numberOfRows <= #rowcount
BEGIN
--Put whatever process you need to repeat here
SET #rowcount = #rowcount + 1
END
Assuming you have tables set up with an IDENTITY field set for the Primary Key, you can populate each successive table's foreign key by joining to the previous table and the source table, something like:
INSERT INTO Table1
SELECT DISTINCT Department
FROM SourceTable
GO
INSERT INTO Table2
SELECT DISTINCT b.Deptartment_ID, a.Function
FROM SourceTable a
JOIN Table1 b
ON a.Department = b.Department
GO
INSERT INTO Table3
SELECT DISTINCT b.Function_ID, a.Process
FROM SourceTable a
JOIN Table2 b
ON a.Function = b.Function
GO
INSERT INTO Table4
SELECT DISTINCT b.Process_ID, a.Procedure
FROM SourceTable a
JOIN Table3 b
ON a.Process = b.Process
GO

SQL - select selective row multiple times

I need to produce mailing labels for my company and I thought I would do a query for that:
I have 2 tables - tblAddress , tblContact.
In tblContact I have "addressNum" which is a foreign key of address and "labelsNum" column that represents the number of times the address should appear in the labels sheet.
I need to create an inner join of tblcontact and tbladdress by addressNum,
but if labelsNum exists more than once it should be displayed as many times as labelsNum is.
I suggest using a recursive query to do the correct number of iterations for each row.
Here is the code (+ link to SQL fiddle):
;WITH recurs AS (
SELECT *, 1 AS LEVEL
FROM tblContact
UNION ALL
SELECT t1.*, LEVEL + 1
FROM tblContact t1
INNER JOIN
recurs t2
ON t1.addressnum = t2.addressnum
AND t2.labelsnum > t2.LEVEL
)
SELECT *
FROM recurs
ORDER BY addressnum
Wouldn't the script return multiple lines for different contacts anyway?
CREATE TABLE tblAddress (
AddressID int IDENTITY
, [Address] nvarchar(35)
);
CREATE TABLE tblContact (
ContactID int IDENTITY
, Contact nvarchar(35)
, AddressNum int
, labelsNum int
);
INSERT INTO tblAddress VALUES ('foo1');
INSERT INTO tblAddress VALUES ('foo2');
INSERT INTO tblContact VALUES ('bar1', 1, 1);
INSERT INTO tblContact VALUES ('bar2', 2, 2);
INSERT INTO tblContact VALUES ('bar3', 2, 2);
SELECT * FROM tblAddress a JOIN tblContact c ON a.AddressID = c.AddressNum
This yields 3 rows on my end. The labelsNum column seems redundant to me. If you add a third contact for address foo2, you would have to update all labelsNum columns for all records referencing foo2 in order to keep things consistent.
The amount of labels is already determined by the amount of different contacts.
Or am I missing something?