SQL having trouble getting desired query - sql

Say we have the following relation:
Author(name,title,language)
What I'm trying to accomplish is to make query that lists all the authors and how many books in a particular language they made, and if they made none in that language then display zero.
What I've tried so far is this
select name, count("language") as SomeLanguage
from Author
where "language"='English'
group by name;
Which gives the count of how many books in the specified language they made, but I don't know how to include the other authors that did not write a book in that language.
Example Output:
Name | SomeLanguage
----------------
Bill | 4
Rob | 1
Bob | 0

You want conditional aggregation:
select name,
sum(case when language = 'English' then 1 else 0 end) as SomeLanguage
from Author
group by name;

Related

SQL selecting where A equals both B and C

name | course
Jay | LAWS0001
Mark | LAWS0002
Sam | LAWS0002
Alice | LAWS0001
Ryan | LAWS0001
Ryan | LAWS0002
Hey guys, I've got this database and I want to only select the names that take both 'LAWS0001' and 'LAWS0002'. So from this example, it should select 'Ryan' because he's the only person to take both courses.
I tried IN operator:
SELECT name
FROM student
WHERE course IN ('LAWS0001', 'LAWS0002')
but this takes everyone because everyone is taking either of the courses.
Is there an operator for my problem?
You can use your existing query, using a GROUP BY clause to COUNT the number of distinct courses each student is taking in the set ('LAWS0001', 'LAWS0002') and only selecting those students where the count is 2:
SELECT name
FROM student
WHERE course IN ('LAWS0001', 'LAWS0002')
GROUP BY name
HAVING COUNT(DISTINCT course) = 2
Demo on SQLFiddle

Counting prefixes in a joined table in SQL

I am trying to count how many makes of car a person owns. Car makes are only defined by a prefix in my Links table.
Table 1 (Person)
UniqueID Name
PER0001 Adrian
PER0002 Michael
Per0003 James
Table 2 (Links)
UniqueID LinkEnd1_ID LinkEnd2_ID
LIN0001 PER0001 FER02332
LIN0002 PER0001 FER02112
LIN0003 PER0001 POR12122
LIN0004 PER0002 FER12321
LIN0005 PER0003 MAS12382
LIN0006 PER0003 FER22982
LIN0006 PER0003 MAS12232
Output (option 1)
Name Car_Make Count
Adrian FER 2
Adrian POR 1
Michael FER 1
James MAS 2
James FER 1
Output (option 2 - preferred)
Name FER POR MAS
Adrian 1 2
Michael 1
James 1 2
The reason I am using a link table to count the number of car makes is because every car make has a different table I would need to join in.
I've tried
select count left(LinkEnd2_ID,3) which doesnt work, i've also tried group by which I cant seem to crack.
I guess what I want to be able to do is
select
count(left(LinkEnd2_ID,3)='FER'
,count(left(LinkEnd2_ID,3)='POR'
,count(left(LinkEnd2_ID,3)='MAS'
but thats a query in a select and I decipher how to code that properly.
Heres where I am starting from (or the base I keep going back to start afresh)-
SELECT
Person.Unique_ID
,Person.Name
,left(Link.LinkEnd2_ID,3) as Car_Make
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
Any help you can offer would be appreciated.
Nearly there, you just need to add a group by, and change all the columns to aggregate functions.
Your option 1:
SELECT
max(Person.Name) as Person_Name
,left(Link.LinkEnd2_ID,3) as Car_Make
,count(*) as No_of_Car
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID
For your option 2, you need to wrap your aggregate functions around case statements
you have to hardcode the 3 different car make, so if you have unknown number of them, it wouldn't work.
SELECT
max(Person.Name) as Person_Name
,sum(case when left(Link.LinkEnd2_ID,3) ='FER' then 1 else 0 end) as FER
,sum(case when left(Link.LinkEnd2_ID,3) ='POR' then 1 else 0 end) as POR
,sum(case when left(Link.LinkEnd2_ID,3) ='MAS' then 1 else 0 end) as MAS
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID

How to do an exact match followed by ORDER BY in PostgreSQL

I'm trying to write a query that puts some results (in my case a single result) at the top, and then sorts the rest. I have yet to find a PostgreSQL solution.
Say I have a table called airports like so.
id | code | display_name
----+------+----------------------------
1 | SDF | International
2 | INT | International Airport
3 | TES | Test
4 | APP | Airport Place International
In short, I have a query in a controller method that gets called asynchronously when a user text searches for an airport either by code or display_name. However, when a user types in an input that matches a code exactly (airport code is unique), I want that result to appear first, and all airports that also have int in their display_name to be displayed afterwards in ascending order. If there is no exact match, it should return any wildcard matches sorted by display_name ascending. So if a user types in INT, The row (2, INT, International Airport) should be returned first followed by the others:
Results:
1. INT | International Airport
2. APP | Airport Place International
3. SDF | International
Here's the kind of query I was tinkering with that is slightly simplified to make sense outside the context of my application but same concept nonetheless.
SELECT * FROM airports
WHERE display_name LIKE 'somesearchtext%'
ORDER BY (CASE WHEN a.code = 'somesearchtext` THEN a.code ELSE a.display_name END)
Right now the results if I type INT I'm getting
Results:
1. APP | Airport Place International
2. INT | International Airport
3. SDF | International
My ORDER BY must be incorrect but I can't seem to get it
Any help would be greatly appreciated :)
If you want an exact match on code to return first, then I think this does the trick:
SELECT a.*
FROM airports a
WHERE a.display_name LIKE 'somesearchtext%'
ORDER BY (CASE WHEN a.code = 'somesearchtext' THEN 1 ELSE 2 END),
a.display_name
You could also write this as:
ORDER BY (a.code = 'somesearchtext') DESC, a.display_name
This isn't standard SQL, but it is quite readable.
I think you can achieve your goal by using a UNION.
First get an exact match and then add that result to rest of the data as you which.
e.g.. (you will need to work in this a bit)
SELECT * FROM airports
WHERE code == 'somesearchtext'
ORDER BY display_name
UNION
SELECT * FROM airports
WHERE code != 'somesearchtext' AND display_name LIKE 'somesearchtext%'
ORDER BY display_name

Group by a field not in select

I want to find how many modules a lecturer taught in a specific year and want to select name of the lecturer and the number of modules for that lecturer.
Problem is that because I am selecting Name, and I have to group it by name to make it work. But what if there are two lecturers with same name? Then sql will make them one and that would be wrong output.
So what I really want to do is select name but group by id, which sql is not allowing me to do. Is there a way around it?
Below are the tables:
Lecturer(lecturerID, lecturerName)
Teaches(lecturerID, moduleID, year)
This is my query so far:
SELECT l.lecturerName, COUNT(moduleID) AS NumOfModules
FROM Lecturer l , Teaches t
WHERE l.lecturerID = t.lecturerID
AND year = 2011
GROUP BY l.lecturerName --I want lectureID here, but it doesn't run if I do that
SELECT a.lecturerName, b.NumOfModules
FROM Lecturer a,(
SELECT l.lecturerID, COUNT(moduleID) AS NumOfModules
FROM Lecturer l , Teaches t
WHERE l.lecturerID = t.lecturerID
AND year = 2011
GROUP BY l.lecturerID) b
WHERE a.lecturerID = b.lecturerID
You should probably just group by lecturerID and include it in the select column list. Otherwise, you're going to end up with two rows containing the same name with no way to distinguish between them.
You raise the problem of "wrong output" when grouping just by name but "undecipherable output" is just as big a problem. In other words, your desired output (grouping by ID but giving name):
lecturerName Module
------------ ------
Bob Smith 1
Bob Smith 2
is no better than your erroneous output (grouping by, and giving, name):
lecturerName Module
------------ ------
Bob Smith 3
since, while you now know that one of the lecturers taught two modules and the other taught one, you have no idea which is which.
The better output (grouping by ID and displaying both ID and name) would be:
lecturerId lecturerName Module
---------- ------------ ------
314159 Bob Smith 1
271828 Bob Smith 2
And, yes, I'm aware this doesn't answer your specific request but sometimes the right answer to "How do I do XYZZY?" is "Don't do XYZZY, it's a bad idea for these reasons ...".
Things like writing operating systems in COBOL, accounting packages in assembler, or anything in Pascal come to mind instantly :-)
You could subquery your count statement.
SELECT lecturername,
(SELECT Count(*)
FROM teaches t
WHERE t.lecturerid = l.lecturerid
AND t.year = 2011) AS NumOfModules
FROM lecturer l
Note there are other ways of doing this. If you also wanted to elimiate the rows with no modules you can then try.
SELECT *
FROM (SELECT lecturername,
(SELECT Count(*)
FROM teaches t
WHERE t.lecturerid = l.lecturerid
AND t.year = 2011) AS NumOfModules
FROM lecturer l) AS temp
WHERE temp.numofmodules > 0

SQL Count unique rows where a column contains two different values

I have taken a good look around and not been able to find any questions that match mine. Maybe I am not using the right language when searching or whatever, but here goes.
I have an SQL table called Classes that looks something like this
Student_Name | Class
--------------------
Edgar | Chemistry
Allan | Chemistry
Burt | Chemistry
Edgar | Math
Sue | Math
Hamilton | Math
Edgar | English
Sue | English
Edgar | German
Ben | German
I want to count how many students are taking both Math and German.
Assuming the following in this example:
- Student names are unique
- One student can have many classes
Logically I would use a select statement to get a result set of students who are taking Math. Then I would go through each Student_Name from the result set and check them against the table to see how many are taking German.
In this case I would expect a return of 1 as only Edgar is taking both Math and German.
Here are some of the queries I have tried so far to no avail :-(
This one was after doing some research on DISTINCT:
SELECT COUNT(DISTINCT Student_Name) FROM Classes WHERE Class = 'Math' AND Class = 'German';
And this one was after finding out more about GROUP BY:
SELECT COUNT(*) FROM (
SELECT DISTINCT Student_Name FROM Classes
WHERE Class IN ( 'Math', 'German' )
GROUP BY Student_Name
);
Neither of these came out quite right any help would be highly appreciated.
SELECT COUNT(*) totalStudent
FROM
(
SELECT student_name
FROM Classes
WHERE class IN ('Math','German')
GROUP BY student_name
HAVING COUNT(*) = 2
) subAlias
SQLFiddle Demo
OUTPUT
╔══════════════╗
║ TOTALSTUDENT ║
╠══════════════╣
║ 1 ║
╚══════════════╝
Could also do the following:
select count(distinct a.Student_name)
from Classes a inner join Classes b on
a.Class = 'German' and
b.Class = 'Math' and
a.Student_Name = b.Student_name;
This solves the problem where the table contains duplicate rows (as pointed out by a commenter to another answer)