SQL query with two tables, count and more info - sql-server

I'm just learning this stuff and I'm having trouble with this one. I have two tables, STUDENTS and ADVISORS. The students are assigned advisors within the students table using a foreign key attached to the primary key of the advisors table.
The task here is this: Provide a list of all advisors and the number of active students assigned to each. Filter out any advisors with more than 1 student.
The current script is listed below:
select
Students.AdvisorID, count(Students.AdvisorID) as 'TotalStudents'
from
Students
left outer join
Advisors on Students.AdvisorID = Advisors.AdvisorID
where
Students.IsActive = 1
Group by
Students.AdvisorID
Having
count(Students.AdvisorID) < 2
This will output a proper list showing only the advisorID and total students.
I need to also display the
Advisors.FirstName + ' ' + Advisors.LastName as 'AdvisorName'
Any help would be greatly appreciated.
EDIT
students table
advisors table

I think your original attempt is on the right track, but you need to join again to the Advisors table to pull in the first and last name for each adviser. The reason for this is that after doing the aggregation all that remains is an ID for each adviser and a student count.
SELECT t1.AdvisorID,
t2.TotalStudents,
t1.FirstName + ' ' + t1.LastName AS AdvisorName
FROM Advisors t1
INNER JOIN
(
SELECT a.AdvisorID, COUNT(*) AS TotalStudents
FROM Advisors a
LEFT JOIN Students s
ON a.AdvisorID = s.AdvisorID
GROUP BY a.AdvisorID
HAVING COUNT(*) < 2
) t2
ON t1.AdvisorID = t2.AdvisorID
Other notes:
I chose to LEFT JOIN advisers to students, not the other way around, since you want a statistic for each adviser. Doing the join as you first had it could filter out advisers who do not match to any student. This is not the behavior you want, since an adviser who does not match to any student should have a student count of zero.

Here's a little sample data to work with
USE tempdb
GO
IF OBJECT_ID('tempdb.dbo.Advisors') IS NOT NULL DROP TABLE dbo.Advisors;
IF OBJECT_ID('tempdb.dbo.Students') IS NOT NULL DROP TABLE dbo.Students;
CREATE TABLE dbo.Advisors (AdvisorID int primary key, AdvisorName varchar(100));
CREATE TABLE dbo.Students
(
studentID int identity primary key,
AdvisorID int foreign key references dbo.Advisors(AdvisorID)
);
INSERT dbo.Advisors VALUES (1, 'Mr. White'),(2,'Walter Jr.'),(3,'Mr. Pinkman');
INSERT dbo.Students (AdvisorID)
SELECT TOP (20) abs(checksum(newid())%3)+1 FROM sys.all_columns;
No Left Join needed, I think this will give you what you are looking for.
SELECT a.AdvisorID, total_students = COUNT(*)
FROM dbo.Advisors a
INNER JOIN dbo.Students s ON a.AdvisorID = s.AdvisorID
GROUP BY a.AdvisorID
HAVING COUNT(*) < 2;

Related

I need to find multiple rows with iteration without using loop

Let's say I have 2 tables.
Users Table
and Have one more table which defines hierarchy of user.
hierarchy Table
So as you can see:
C is a supervisor of D
B is a supervisor of C
A is a supervisor of B
So when I pass User D, then it should return all the supervisor like A,B,C
same when I pass User C, then it should return all the supervisor like A,B
What I tried.
Create table Users
(
Id int primary key identity (1,1),
Name varchar(1),
)
Insert into Users values ('A')
Insert into Users values ('B')
Insert into Users values ('C')
Insert into Users values ('D')
Create table Hierarchy
(
Id int primary key identity (1,1),
EmployeeId int FOREIGN KEY REFERENCES Users(Id),
SupervisorId int FOREIGN KEY REFERENCES Users(Id)
)
Insert into Hierarchy values (4,3)
Insert into Hierarchy values (3,2)
Insert into Hierarchy values (2,1)
select * from Users
select * from Hierarchy
with HierarchyData as
(
select mbh.* from Hierarchy mbh where mbh.EmployeeId = 4
union all
select mbh.* from Hierarchy mbh
join Hierarchy on mbh.SupervisorId = Hierarchy.EmployeeId
where mbh.EmployeeId <> 4
)
select e.Name as EmpName, s.Name as SupervisorName from HierarchyData h
join Users e on h.EmployeeId = e.Id
join Users s on h.SupervisorId = s.Id
But I am getting only one level data.
Any kind of help would be appreciated.
#Vishal as per my understanding written query for you can you please check it's working or not?
here I used LEFT JOIN you can go with INNER JOIN
If you go with LEFT JOIN as per your example A not have any supervisor so the record can be empty.
If you go with INNER JOIN as per your example you got only the B, C, D record.
Please check the below test query.
DECLARE #User TABLE
(
UserID INT,
UserName NVARCHAR(50)
)
DECLARE #EmployeeTable TABLE
(
ID INT,
EmployeeID INT,
supervisorID INT
)
INSERT INTO #User VALUES(1,'A'),
(2,'B'),
(3,'C'),
(4,'D')
INSERT INTO #EmployeeTable VALUES
(1,4,3),
(2,3,2),
(3,2,1)
SELECT [U].[UserName] [EmployeeName],
[ET].[EmployeeID],
[ET].[SupervisorID],[ST].[SupervisorName]
FROM #User [U]
LEFT JOIN #EmployeeTable [ET]
ON [U].[UserID] = [ET].[EmployeeID]
LEFT JOIN
(
SELECT [U].UserName [SupervisorName] ,[ST].* FROM #User [U]
INNER JOIN #EmployeeTable [ST]
ON [ST].[supervisorID] = [U].[UserID]
) [ST]
ON [ST].[supervisorID] = [ET].[supervisorID]
Left join query result
Inner join query result
let me know if I can help more :).

postgresql: Insert two values in table b if both values are not in table a

I'm doing an assignment where I am to make an sql-database of a tournament result. Players can be added by their name, and when the database has at least two or more players who has not already been assigned to a match, two players should be matched against each other.
For instance, if the tables currently are empty I add Joe as a player. I then also add James and since the table then has two players, who also are not in the matches-table, a new row in the matches-table is created with their p_id set to left_player_P_id and right_player_P_id.
I thought it would be a good idea to create a function and a trigger so that every time a row is added to the player-table, the sql-code would run and create the row in the matches as needed. I am open to other ways of doing this.
I've tried multiple different approaches including SQL - Insert if the number of rows is greater than and Using IF ELSE statement based on Count to execute different Insert statements but I am now at a loss.
Problematic code:
This approach returns a syntax error.
IF ((select count(*) from players_not_in_any_matches) >= 2)
begin
insert into matches values (
(select p_id from players_not_in_any_matches limit 1),
(select p_id from players_not_in_any_matches limit 1 offset 1)
)
end;
Alternative approach (still problematic code):
This approach seems more promising (but less readable). However, it inserts even if there are no rows returned inside the where not exists.
insert into matches (left_player_p_id, right_player_p_id)
select
(select p_id from players_not_in_any_matches limit 1),
(select p_id from players_not_in_any_matches limit 1 offset 1)
where not exists (
select * from players_not_in_any_matches offset 2
);
Tables
CREATE TABLE players (
p_id serial PRIMARY KEY,
full_name text
);
CREATE TABLE matches(
left_player_P_id integer REFERENCES players,
right_player_P_id integer REFERENCES players,
winner integer REFERENCES players
);
Views
-- view for getting all players not currently assigned to a match
create view players_not_in_any_matches as
select * from players
where p_id not in (
select left_player_p_id from matches
) and
p_id not in (
select right_player_p_id from matches
);
Try:
insert into matches (left_player_p_id, right_player_p_id)
select p1.p_id, p2.p_id
from players p1
join players p2
on p1.p_id <> p2.p_id
and not exists(
select 1 from matches m
where p1.p_id in (m.left_player_p_id, m.right_player_p_id)
)
and not exists(
select 1 from matches m
where p2.p_id in (m.left_player_p_id, m.right_player_p_id)
)
limit 1
Anti joins (not-exists operators) in the above query could be further simplified a bit using LEFT JOINs:
insert into matches (left_player_p_id, right_player_p_id)
select p1.p_id, p2.p_id
from players p1
join players p2
left join matches m1
on p1.p_id in (m1.left_player_p_id, m1.right_player_p_id)
left join matches m2
on p2.p_id in (m2.left_player_p_id, m2.right_player_p_id)
where m1.left_player is null
and m2.left_player is null
limit 1
but in my opinion the former query is more readable, while the latter one looks tricky.

SELECT from multiple queries

I have this tables:
tblDiving(
diving_number int primary key
diving_club int
date_of_diving date)
tblDivingClub(
number int primary key not null check (number>0),
name char(30),
country char(30))
tblWorks_for(
diver_number int
club_number int
end_working_date date)
tblCountry(
name char(30) not null primary key)
I need to write a query to return a name of a country and the number of "Super club" in it.
a Super club is a club which have more than 25 working divers (tblWorks_for.end_working_date is null) or had more than 100 diving's in it(tblDiving) in the last year.
after I get the country and number of super club, I need to show only the country's that contains more than 2 super club.
I wrote this 2 queries:
select tblDivingClub.name,count(distinct tblWorks_for.diver_number) as number_of_guids
from tblWorks_for
inner join tblDivingClub on tblDivingClub.number = tblWorks_for.club_number,tblDiving
where tblWorks_for.end_working_date is null
group by tblDivingClub.name
select tblDivingClub.name, count(distinct tblDiving.diving_number) as number_of_divings
from tblDivingClub
inner join tblDiving on tblDivingClub.number = tblDiving.diving_club
WHERE tblDiving.date_of_diving <= DATEADD(year,-1, GETDATE())
group by tblDivingClub.name
But I don't know how do I continue.
Every query works separately, but how do I combine them and select from them?
It's university assignment and I'm not allowed to use views or temporary tables.
It's my first program so I'm not really sure what I'm doing:)
WITH CTE AS (
select tblDivingClub.name,count(distinct tblWorks_for.diver_number) as diving_number
from tblWorks_for
inner join tblDivingClub on tblDivingClub.number = tblWorks_for.club_number,tblDiving
where tblWorks_for.end_working_date is null
group by tblDivingClub.name
UNION ALL
select tblDivingClub.name, count(distinct tblDiving.diving_number) as diving_number
from tblDivingClub
inner join tblDiving on tblDivingClub.number = tblDiving.diving_club
WHERE tblDiving.date_of_diving <= DATEADD(year,-1, GETDATE())
group by tblDivingClub.name
)
SELECT * FROM CTE
You can combine the queries using a UNION ALL as long as there are the same number of columns in each query. You can then roll them into a Common Table Expression (CTE) and do a select from that.

insert results of join in SQL server 2005

I have two tables with more than 800 rows in each.Table names are 'education' and 'sanitation'.The column name 'ID' is common in both the tables.Now i want to join these both tables as full outer join and i want to save the results of this table as a new table.I can join it very easily,But how to save those data's as a new table.Please help Me.
select * into bc from education e join sanitation s on e.id=s.id
I have around 30 columns in each table.So i can not explicitly create table schema for a new table.
I want all the columns from both tables.I have 20 tables with each 800 rows.From this 20 tables i want to make one master table taking 'ID' as primary key in all.
Sample Code:
Table one:
create table dummy1(
id int , fname varchar(50)
)
insert into dummy1 (id,fname) values (1,'aaa')
insert into dummy1 (id,fname) values (2,'bbb')
insert into dummy1 (id,fname) values (3,'ccc')
insert into dummy1 (id,fname) values (3,'ccc')
Table Two
create table dummy2(
id int , lname varchar(50)
)
insert into dummy2 (id,lname) values (1,'abc')
insert into dummy2 (id,lname) values (2,'pqr')
insert into dummy2 (id,lname) values (3,'mno')
Now Create new table 3
create table dummy3(
id int , fname varchar(50),lname varchar(50)
)
Insert Query for table 3 look like
insert into dummy3 (id,fname,lname)
(select a.id,a.fname,b.lname from dummy1 a inner join dummy2 b on a.id=b.id)
Table 3 will contain table1, table2 data
Follow below:
SELECT t1.Column1, t2.Columnx
INTO DestinationTable
FROm education t1
INNER JOIN sanitation t2 ON t1.Id = t2.Id
EDIT:
SELECT * will not work for you because you have a column ID which exists in both the tables. So the above solution will work you.
EDIT:
1- You can temporarily rename the Id column in one table, then try
2- SELECT *
INTO DestinationTable
FROm education t1
INNER JOIN sanitation t2 ON t1.Id = t2.Id
3- Revert the column name back to Id.

How can I get this query to return 0 instead of null?

I have this query:
SELECT (SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)) AS TenantBalance, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
But there's a problem with it; there are other TenantID's that don't have transactions and I want to get those too.
For example, the transaction table has 3 rows for bob, 2 row for john and none for jane. I want it to return the sum for bob and john AND return 0 for jane. (or possibly null if there's no other way)
How can I do this?
Tables are like this:
Tenants
ID
Other Data
Transactions
ID
TenantID (fk to Tenants)
Other Data
(You didn't state your sql engine, so I'm going to link to the MySQL documentation).
This is pretty much exactly what the COALESCE() function is meant for. You can feed it a list, and it'll return the first non-null value in the list. You would use this in your query as follows:
SELECT COALESCE((SUM(tr.AmountPaid) - SUM(tr.AmountCharged)), 0) AS TenantBalance, te.ID
FROM tblTenant AS te
LEFT JOIN tblTransaction AS tr ON (tr.TenantID = te.ID)
GROUP BY te.ID;
That way, if the SUM() result would be NULL, it's replaced with zero.
Edited: I rewrote the query using a LEFT JOIN as well as the COALESCE(), I think this is the key of what you were missing originally. If you only select from the Transactions table, there is no way to get information about things not in the table. However, by using a left join from the Tenants table, you should get a row for every existing tenant.
Below is a full walkthrough of the problem. The function isnull has also been included to ensure that a balance of zero (rather than null) is returned for Tenants with no transactions.
create table tblTenant
(
ID int identity(1,1) primary key not null,
Name varchar(100)
);
create table tblTransaction
(
ID int identity(1,1) primary key not null,
tblTenantID int,
AmountPaid money,
AmountCharged money
);
insert into tblTenant(Name)
select 'bob' union all select 'Jane' union all select 'john';
insert into tblTransaction(tblTenantID,AmountPaid, AmountCharged)
select 1,5.00,10.00
union all
select 1,10.00,10.00
union all
select 1,10.00,10.00
union all
select 2,10.00,15.00
union all
select 2,15.00,15.00
select * from tblTenant
select * from tblTransaction
SELECT
tenant.ID,
tenant.Name,
isnull(SUM(Trans.AmountPaid) - SUM(Trans.AmountCharged),0) AS Balance
FROM tblTenant tenant
LEFT JOIN tblTransaction Trans ON
tenant.ID = Trans.tblTenantID
GROUP BY tenant.ID, tenant.Name;
drop table tblTenant;
drop table tblTransaction;
Select Tenants.ID, ISNULL((SUM(tblTransaction.AmountPaid) - SUM(tblTransaction.AmountCharged)), 0) AS TenantBalance
From Tenants
Left Outer Join Transactions Tenants.ID = Transactions.TenantID
Group By Tenents.ID
I didn't syntax check it but it is close enough.
SELECT (SUM(ISNULL(tblTransaction.AmountPaid, 0))
- SUM(ISNULL(tblTransaction.AmountCharged, 0))) AS TenantBalance
, tblTransaction.TenantID
FROM tblTransaction
GROUP BY tblTransaction.TenantID
I only added this because if you're intention is to take into account for one of the parts being null you'll need to do the ISNULL separately
Actually, I found an answer:
SELECT tenant.ID, ISNULL(SUM(trans.AmountPaid) - SUM(trans.AmountCharged),0) AS Balance FROM tblTenant tenant
LEFT JOIN tblTransaction trans
ON tenant.ID = trans.TenantID
GROUP BY tenant.ID

Resources