SQL statement to replace TSQL - sql

Please see the DDL below:
CREATE Table Person (PersonID int)
INSERT INTO Person (1)
INSERT INTO Person (2)
INSERT INTO Person (3)
INSERT INTO Person (4)
INSERT INTO Person (5)
CREATE TABLE PersonCrime (PersonID in,CrimeID int)
INSERT INTO PersonCrime (1,1)
INSERT INTO PersonCrime (2,1)
INSERT INTO PersonCrime (3,2)
INSERT INTO PersonCrime (9,3)
INSERT INTO PersonCrime (4,3)
I want to return all the crimes from PersonCrime where all the persons are in the person table. For example, crime 1 is returned because persons 1 and 2 are in the person table. Crime 2 is also returned because person 3 is in the person table. Crime 3 is not returned because person 9 is not in the person table even though person 4 is.
I have done this using TSQL i.e. looping around all the crimes and checking all persons. However, this takes far to long. I am trying to do it with SQL.

You can do this with GROUP BY and HAVING:
SELECT CrimeID
FROM PersonCrime PC
LEFT JOIN Person P
ON PC.PersonID = P.PersonID
GROUP BY CrimeID
HAVING MIN(CASE WHEN P.PersonID IS NULL THEN 1 ELSE 2 END) <> 1
Demo: SQL Fiddle

With a table variable for clarity:
DECLARE #IgnoreCrime TABLE (CrimeID int);
INSERT INTO #IgnoreCrime SELECT CrimeID FROM PersonCrime WHERE PersonID NOT IN (SELECT PersonID FROM Person)
SELECT DISTINCT(CrimeID) FROM PersonCrime WHERE CrimeID NOT IN (SELECT CrimeID FROM #IgnoreCrime)
without:
SELECT DISTINCT(CrimeID)
FROM #PersonCrime
WHERE CrimeID NOT IN
(SELECT CrimeID
FROM #PersonCrime
WHERE PersonID NOT IN (SELECT PersonID FROM #Person))

Defined another way - "give me all CrimeIDs where there is not a PersoinID that is not in the Person table":
SELECT DISTINCT CrimeID
FROM PersonCrime pc
WHERE NOT EXISTS
(
SELECT 1 FROM PersonCrime
WHERE CrimeID = pc.CrimeID
AND PersonID NOT IN (SELECT PersonID FROM PERSON)
)

Use Exists Operator to find the personID present in Person try this.
SELECT DISTINCT CrimeID
FROM PersonCrime PC
WHERE EXISTS (SELECT 1
FROM Person P
WHERE p.PersonID = pc.PersonID)
GROUP BY CrimeID
HAVING Count(CrimeID) = (SELECT Count(1)
FROM PersonCrime PC1
WHERE PC.CrimeID = PC1.CrimeID)
SQLFIDDLE DEMO

Related

Cannot perform an aggregate function on an expression containing an aggregate or a subquery. SQL Server

We got some records in a table Employee. And different Students are linked with a particular ClassId. In the screenshot, StudentId 5,6 belongs to ClassId 1, while StudentId 5,6,7 belongs to another ClassId 2.
I want to fetch the ClassId from above table that only contains StudentId 5 and 6. So in above example it should return ClassId 1. The tricky part is that the value 5 and 6 is coming from another table.
So when I run below query it throws an error
SELECT ClassId
FROM employee
GROUP BY ClassId
HAVING COUNT(CASE WHEN StudentId NOT IN (select id from test) THEN 1 END) = 0;
Error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
If I remove the sub query part then it works fine
SELECT ClassId
FROM employee
GROUP BY ClassId
HAVING COUNT(CASE WHEN StudentId NOT IN (5,6) THEN 1 END) = 0;
Below are the create table scripts for both of above tables
CREATE TABLE Student (Id INT)
INSERT INTO Student VALUES (5),(6)
CREATE TABLE Employee
(
Id INT IDENTITY,
ClassId INT,
StudentId INT
)
INSERT INTO employee
VALUES (1, 5), (1, 6), (2, 5), (2, 6), (2, 7)
Can someone suggest a workaround to run the first query without error?
Use group by and having like this:
SELECT e.ClassId
FROM employee e LEFT JOIN
test t
ON t.StudentId = e.id
GROUP BY e.ClassId
HAVING COUNT(*) = COUNT(t.StudentId) AND -- all rows in `e` match a row in `t`
COUNT(*) = (SELECT COUNT(*) FROM test) -- all match
This is not allowed, but you can do left join. then use coalesce() to check those which are not in test table.
select t1.ClassId
from employee t1
left join test t2 on t2.id = t1.StudentId
group by t1.ClassId
having count(case when coalesce(t2.id, 0) = 0 then 1 end)= 0
You can use apply to simplify the HAVING clause.
declare #Student table (ID int) ;
insert into #Student values (5),(6) ;
declare #employee table (ID int identity, ClassId int, StudentId int) ;
insert into #employee values (1,5),(1,6), (2,5),(2,6), (2,7) ;
SELECT e.ClassId AS ClassId , COUNT(e.StudentId)
FROM #employee AS e
CROSS APPLY (
SELECT ID
FROM #Student AS s
WHERE s.ID = e.StudentId
) AS student
GROUP BY e.ClassId
HAVING COUNT(e.StudentId) = (SELECT COUNT('8') FROM #Student) ;

Inner join an inner join with another inner join

I'm wondering if it is possible to inner join an inner join with another inner join.
I have a database of 3 tables:
people
athletes
coaches
Every athlete or coach must exist in the people table, but there are some people who are neither coaches nor athletes.
What I am trying to do is find a list of people who are active (meaning play or coach) in at least 3 different sports. The definition of active is they are either coaches, athletes or both a coach and an athlete for that sport.
The person table would consist of (id, name, height)
the athlete table would be (id, sport)
the coaching table would be (id, sport)
I have created 3 inner joins which tell me who is both a coach and and an athlete, who is just a coach and who is just an athlete.
This is done via inner joins.
For example,
1) who is both a coach and an athlete
select
person.id,
person.name,
coach.sport as 'Coaches and plays this sport'
from coach
inner join athlete
on coach.id = athlete.id
and coach.sport = athlete.sport
inner join person
on athlete.id = person.id
That brings up a list of everyone who both coaches and plays the same sport.
2) To find out who only coaches sports, I have used inner joins as below:
select
person.id,
person.name,
coach.sport as 'Coaches this sport'
from coach
inner join person
on coach.id = person.id
3) Then to find out who only plays sports, I've got the same as 2) but just tweaked the words
select
person.id,
person.name,
athlete.sport as 'Plays this sport'
from athlete
inner join person
on athlete.id = person.id
The end result is now I've got:
1) persons who both play and coach the same sport
2) persons who coach a sport
3) persons who play a sport
What I would like to know is how to find a list of people who play or coach at least 3 different sports? I can't figure it out because if someone plays and coaches a sport like hockey in table 1, then I don't want to count them in table 2 and 3.
I tried using these 3 inner joins to make a massive join table so that I could pick the distinct values but it is not working.
Is there an easier way to go about this without making sub-sub-queries?
What I would like to know is how to find a list of people who play /
coach at least 3 different sports? I can't figure it out because if
someone plays and coaches a sport like hockey in table 1, then I don't
want to count them in table 2 and 3.
you can do something like this
select p.id,min(p.name) name
from
person p inner join
(
select id,sport from athlete
union
select id,sport from coach
)
ca
on ca.id=p.id
group by p.id
having count(ca.sport)>2
CREATE TABLE #person (Id INT, Name VARCHAR(50));
CREATE TABLE #athlete (Id INT, Sport VARCHAR(50));
CREATE TABLE #coach (Id INT, Sport VARCHAR(50));
INSERT INTO #person (Id, Name) VALUES(1, 'Bob');
INSERT INTO #person (Id, Name) VALUES(2, 'Carol');
INSERT INTO #person (Id, Name) VALUES(2, 'Sam');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Golf');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Football');
INSERT INTO #coach (Id, Sport) VALUES(1, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #coach (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Swimming');
-- so Bob has 3 sports, Carol has only 2 (she both coaches and plays Tennis)
SELECT p.Id, p.Name
FROM
(
SELECT Id, Sport
FROM #athlete
UNION -- this has an implicit "distinct"
SELECT Id, Sport
FROM #coach
) a
INNER JOIN #person p ON a.Id = p.Id
GROUP BY p.Id, p.Name
HAVING COUNT(*) >= 3
-- returns 1, Bob
I have created a SQL with some test data - should work in your case:
Connecting the two results in the subselect with UNION:
UNION will return just non-duplicate values. So every sport will be just counted once.
Finally just grouping the resultset by person.Person_id and person.name.
Due to the HAVING clause, just persons with 3 or more sports will be returned-
CREATE TABLE person
(
Person_id int
,name varchar(50)
,height int
)
CREATE TABLE coach
(
id int
,sport varchar(50)
)
CREATE TABLE athlete
(
id int
,sport varchar(50)
)
INSERT INTO person VALUES
(1,'John', 130),
(2,'Jack', 150),
(3,'William', 170),
(4,'Averel', 190),
(5,'Lucky Luke', 180),
(6,'Jolly Jumper', 250),
(7,'Rantanplan ', 90)
INSERT INTO coach VALUES
(1,'Football'),
(1,'Hockey'),
(1,'Skiing'),
(2,'Tennis'),
(2,'Curling'),
(4,'Tennis'),
(5,'Volleyball')
INSERT INTO athlete VALUES
(1,'Football'),
(1,'Hockey'),
(2,'Tennis'),
(2,'Volleyball'),
(2,'Hockey'),
(4,'Tennis'),
(5,'Volleyball'),
(3,'Tennis'),
(6,'Volleyball'),
(6,'Tennis'),
(6,'Hockey'),
(6,'Football'),
(6,'Cricket')
SELECT person.Person_id
,person.name
FROM person
INNER JOIN (
SELECT id
,sport
FROM athlete
UNION
SELECT id
,sport
FROM coach
) sports
ON sports.id = person.Person_id
GROUP BY person.Person_id
,person.name
HAVING COUNT(*) >= 3
ORDER BY Person_id
The coaches & athletes, ie people who are coaches or athletes, are relevant to your answer. That is union (rows in one or another), not (inner) join rows in one and another). (Although outer join involves a union, so there is a complicated way to use it here.) But there's no point in getting that by unioning only-coaches, only-athletes & coach-athletes.
Idiomatic is to group & count the union of Athletes & Coaches.
select id
from (select * from Athletes union select * from Coaches) as u
group by id
having COUNT(*) >= 3
Alternatively, you want ids of people who coach or play a 1st sport and coach or play a 2nd sport and coach or play a 3rd sport where the sports are all different.
with u as (select * from Athletes union select * from Coaches)
select u1.id
from u u1
join u u2 on u1.id = u2.id
join u u3 on u2.id = u3.id
where u1.sport <> u2.sport and u2.sport <> u3.sport and u1.sport <> u3.sport
If you wanted names you would join that with People.
Is there any rule of thumb to construct SQL query from a human-readable description?](https://stackoverflow.com/a/33952141/3404097)

inner join and partial matches

I'm not sure if the title's correct, so here goes.
The following script returns the correct result: currently Roy Brown teaches Math 101. Roy Brown's TeacherID is 1225, but then they later added the prefix 001- for other purposes.
What I would like to include in the result is some identification that Roy Brown did have another course at one point in time, since Math 101 Old has 1225. It doesn't even have to show how many other courses he had; just something that will let me know that there is more than one row in #coursesCsv. But the result should remain to 2 rows.
What I don't want is to display an extra row for Roy Brown, which is why I'm not doing the commented inner join (ie. right(t.teacherid,4) = right(c.teacherid,4)).
There are no relationships between these two tables since the data in #coursesCSV comes from a csv file.
IF OBJECT_ID('tempdb..#teacher') IS NOT NULL DROP TABLE #teacher
IF OBJECT_ID('tempdb..#coursesCsv') IS NOT NULL DROP TABLE #coursesCsv
create table #teacher
(
TeacherID varchar(10),
FullName varchar(30)
)
insert into #teacher select '001-1225', 'Roy Brown'
insert into #teacher select '001-1230', 'Woody Boyd'
create table #coursesCsv
(
CourseName varchar(30),
TeacherID varchar(10)
)
insert into #coursesCsv select 'Math 101', '001-1225'
insert into #coursesCsv select 'Math 101 Old', '002-1225'
insert into #coursesCsv select 'History 101', '001-1230'
select t.teacherid, c.coursename from
#teacher t inner join #coursesCsv c
on t.teacherid = c.teacherid
--on right(t.teacherid,4) = right(c.teacherid,4)
Do a "Group Count" of courses for the same right(teacherid,4) within a Derived Table (or a Common Table Expression = WITH) and join to it:
select t.teacherid, c.coursename, c.coursecnt
from teacher t
inner join
(
select
teacherid,
coursename,
count(*)
over (partition by right(teacherid,4)) as coursecnt
from coursesCsv
) as c
on t.teacherid = c.teacherid

SQL Simple SELECT Query

create table Person(
SSN INT,
Name VARCHAR(20),
primary key(SSN)
);
create table Car(
PlateNr INT,
Model VARCHAR(20),
primary key(PlateNr)
);
create table CarOwner(
SSN INT,
PlateNr INT,
primary key(SSN, PlateNR)
foreign key(SSN) references Person (SSN),
foreign key(PlateNr) references Car (PlateNr)
);
Insert into Person(SSN, Name) VALUES ('123456789','Max');
Insert into Person(SSN, Name) VALUES ('123456787','John');
Insert into Person(SSN, Name) VALUES ('123456788','Tom');
Insert into Car(PlateNr, Model) VALUES ('123ABC','Volvo');
Insert into Car(PlateNr, Model) VALUES ('321CBA','Toyota');
Insert into Car(PlateNr, Model) VALUES ('333AAA','Honda');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456789','123ABC');
Insert into CarOwner(SSN, PlateNr) VALUES ('123456787','333AAA');
The problem I'm having is the SELECTE query I wanna make. I wan't to be able to SELECT everything from the Person and wan't the include the PlateNr of the car he's the owner of, an example:
PERSON
---------------------------------
SSN NAME Car
123456789 Max 123ABC
123456787 John 3338AAA
123456788 Tom
----------------------------------
So, I want to be able to show everything from the Person table and display the content of CarOwner aswell if the person is in fact a CarOwner. What I have so far is: "SELECT * from Person, CarOwner WHERE Person.SSN = CarOwner.SSN;". But this obviously results in only showing the person(s) that are CarOwners.
Hope I explained me well enough, Thanks.
Try this:
SELECT p.*, c.*
FROM Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr
Show SQLFiddle
P.S. I've changed the type of your primary key PlateNr (in varchar and not in int)
select ssn, name, car
from Person p
LEFT OUTER JOIN CarOwner co
ON p.SSN = co.SSN
LEFT OUTER JOIN Car c
ON co.PlateNr = c.PlateNr

SQL Multiple Duplicate Row Detection

I'm trying to determine a correct way to isolate rows within a table that have the same values in 2 columns.
There are two tables, one (Name) with the person's names and IDs, and the other one (Nation) with people's IDs and their nations. I join the two tables with inner join, and now the new table columns consist of an ID, first name, last name, and nation. If I want to find pairs of people who have the same last name and are from the same nation, why isn't
select ID, FName, LName, Nation
from (Name inner join Nation on Name.ID = Nation.ID)
group by Name, Nation
having count(Name) > 1 and count(Nation) > 1
working?
I'm aiming for the result to be a table with columns:
ID -------First--------------- Last ---------Nation
where the last names and nations will be identical pairs while first names will be different.
I feel like the group by part isnt appropriate, but is there even an alternate way? Thanks for any help.
If you are using MS SQL Server:
select
*
from
(
select
Name.*,
Nation.Nation,
cnt = count(*) over(partition by LName, Nation)
from Name
join Nation on Nation.ID = Name.ID
) t
where cnt > 1
Try this:
SELECT * FROM (
SELECT Name.ID, Name.FName, Name.LName, Nation.Nation
FROM Name
INNER JOIN Nation ON (Name.ID = Nation.ID)
) a
INNER JOIN (
SELECT Name.ID, Name.FName, Name.LName, Nation.Nation
FROM Name
INNER JOIN Nation ON (Name.ID = Nation.ID)
) b ON (a.LName = b.LName AND a.Nation = b.Nation)
WHERE a.ID < b.ID
As Simon Righarts hinted, something's not right with the design.
Scenario 1)
If a name can have multiple nations, you would have 3 tables implementing an n:m relationship.
CREATE TABLE name (name_id int, name text, ...);
CREATE TABLE nation (nation_id int, nation text, ...);
CREATE TABLE nationality (name_id int references name(name_id)
,nation_id int references nation(nation_id)
... );
Query for the scenario:
SELECT a.name_id, a.fname, a.lname, n.nation
FROM name a
JOIN nationality na USING (name_id)
JOIN nation n USING (nation_id)
JOIN (
SELECT a.lname, na.nation_id
FROM name a
JOIN nationality na USING (name_id)
GROUP BY 1,2
HAVING count(*) > 1) x USING (lname, nation_id)
Scenario 2)
If a name can only have one nation, there would be a column nation_id in the table name:
CREATE TABLE name (name_id int
,name text
,nation_id int references nation(nation_id), ...);
CREATE TABLE nation (nation_id int, nation text, ...);
Query for this scenario:
SELECT a.name_id, a.fname, a.lname, n.nation
FROM name a
JOIN nation n USING (nation_id)
JOIN (
SELECT a.lname, a.nation_id
FROM name a
GROUP BY 1,2
HAVING count(*) > 1) x USING (lname, nation_id);
All multiple occurrences are included here, not just "pairs" - assuming you meant that.
Your actual description doesn't fit either scenario.