Learning SQL, and was given an assignment to use a single table of students and show the ones that are roommates, selecting by last name. And then the output should be the selected last name ("smith"), and that student's home address, and then the roommate's name and the roommate's home address.
I found this post here to be quite helpful for setting up my recursion:
SQL Recursive query over same table
My problem is figuring out how to get that "static" information, the selected starting person's name and address, into the output.
Data table is titled college (I am ignoring the quotes that should be on the strings...)
StuID
lastName
firstName
homeAdd
dormRoom
1
Johnson
John
237 Maple St
b101
2
Smith
Linda
345 Juniper St
b101
3
Levi
Grant
789 SE 54th St
a304
4
Sousa
Susi
1024 NE 76th Pl
b101
Here is the code:
WITH RECURSIVE roommates AS
(SELECT lastName, firstName, homeAdd, dormRoom
FROM college WHERE lastName = 'Smith'
UNION
SELECT c.lastName, c.firstName, c.homeAdd, c.dormRoom
FROM roommates r
JOIN college c ON c.dormRoom = r.dormRoom
)
SELECT firstName || ' ' || lastName AS Roommate, homeAdd, dormRoom
FROM roommates
The problem is that I need each line to be basically:
Smith 345 Juniper St John Johnson 237 Maple St
Smith 345 Juniper St Susi Sousa 1024 NE 76th Pl
Thoughts?
It doesn't need to use recursive SQL in your case, you should use LEFT JOIN to solve your problem.
SELECT (c1.firstName || ' ' || c1.lastName) AS student,
c1.homeAdd AS studentAddress,
c1.dormRoom,
(c2.firstName || ' ' || c2.lastName) AS roommate,
c2.homeAdd AS roommateAddress
FROM college c1
LEFT JOIN college c2 ON c1.dormRoom = c2.dormRoom AND c1.StuID <> c2.StuID
WHERE c1.lastName LIKE 'Smith%';
Demo
I have two columns which are called surname and name.
For example I have 3 users
Zuckerberg Mark
Gates Bill
Jobs Steve
If my input is only one phrase such as "Ma" or "Mark" or "Gate" etc. my query works well, but if i enter and surname and name I got an empty result
select
u.surname,
u.name,
u.avatar
from users u
where (surname ilike '%Mark Zuckerberg%' or name ilike '%Mark Zuckerberg%')
order by surname, name
How can I rebuild my query for this goal?
Sorry my previous answer had a typo, but this works:
select
u.surname,
u.name,
u.avatar
from users u
where ((u.surname ||' '|| u.name) ilike '%Mark Zuckerberg%' or (u.name ||' '||u.surname) ilike '%Mark Zuckerberg%')
order by surname, name
Here's a sqlfiddle link: http://sqlfiddle.com/#!17/4e6ce/7
Table: FirstNames
NAME
Tom
Joe
Peter
Table: FullNames
FULL_NAME:
Tom Petty.
Joe Satriani.
Peter Griffin.
Sarah Connor.
I would like to run a query:
select *
from FullNames where FULL_NAME like '%' || (select NAME from FirstNames) || '%'
It yields:
ORA-01427: single-row subquery returns more than one row
which seems correct. Is there a way to do that in Oracle?
You could use JOIN:
SELECT *
FROM FullNames f
JOIN FirstNames g
ON f.FULL_NAME LIKE '%' || g.NAME || '%';
You can use exists:
select f.*
from FullNames f
where exists (select 1
from firstnames fn
where f.FULL_NAME like '%' || fn.NAME || '%'
);
names:
id, first, last
879 Scotty Anderson
549 Melvin Anderson
554 Freddy Appleton
321 Grace Appleton
112 Milton Appleton
189 Jackson Black
99 Elizabeth Black
298 Jordan Frey
parents:
id, student_id
549 879
321 554
112 554
99 189
298 189
Expected Output
(without the 'Student:' / 'Parent:')
Student: Anderson, Scotty
Parent: Anderson, Melvin
Student: Appleton, Freddy
Parent: Appleton, Grace
Parent: Appleton, Milton
Student: Black, Jackson
Parent: Black, Elizabeth
Parent: Frey, Jordan
Using the data above, how can I achieve the expected output?
I currently use SQL similar to this to get a list of current students and names.
select b.last, b.first
from term a, names b
where a.id = b.id(+)
order by b.last
Which returns:
Anderson, Scotty
Appleton, Freddy
Black, Jackson
My question is how to take the parents table and add to this query so it has this output:
Anderson, Scotty
Anderson, Melvin
Appleton, Freddy
Appleton, Grace
Appleton, Milton
Black, Jackson
Black, Elizabeth
Frey, Jordan
The idea in a query like this is to break the data down into something that helps you solve the problem, and then put it back together as needed. In this case I'm going to make use of common table expressions, which allows me to treat queries as tables and then recombine them handily.
Looking at the desired results it looks like we want to have the students appear first, followed by their mothers (ladies first :-), and then their fathers. So, OK, let's figure out how to extract the needed data. We can get the students and their associated data pretty simply:
select distinct p.student_id as student_id,
n.first,
n.last,
0 as type
from parents p
inner join names n
on n.id = p.student_id
The type column, with its constant value of zero, is just used to identify that this is a student. (You'll see why in a minute).
Now, getting the mother's is a bit more difficult because we don't have any gender information to use. However, we'll use what we have, which is names. We know that names like Melvin, Milton, and Jordan are "guy" names. (Yes, I know Jordan can be a girls name too. My daughter has a male coach named Jordan, and a female teammate named Jordan. Just go with it - for purposes of argument in this case Jordan is a guys name, 'K? 'K :-). So we'll use that information to help us identify the mom's:
select p.student_id, n.first, n.last, 1 as type
from parents p
inner join names n
on n.id = p.id
where first not in ('Melvin', 'Milton', 'Jordan')
Notice here that we assign the value of 1 to the type column for mothers.
Similarly, we'll find the dads:
select p.student_id, n.first, n.last, 2 as type
from parents p
inner join names n
on n.id = p.id
where first in ('Melvin', 'Milton', 'Jordan')
And here we assign a value of 2 for the type.
OK - given the above we just need to combine the data properly. We don't want to use a JOIN, however, because we want the names to get spit out one after the other from the query - and the way we do THAT in SQL is with the UNION or UNION ALL operator. (Generally, you're going to want to use UNION ALL, because UNION will check the result set to ensure there are no duplicates - which in the case of a large result set takes, oh, more or less FOREVER!). And so, the final query looks like:
with all_students as (select distinct p.student_id as student_id,
n.first,
n.last,
0 as type
from parents p
inner join names n
on n.id = p.student_id),
all_mothers as (select p.student_id, n.first, n.last, 1 as type
from parents p
inner join names n
on n.id = p.id
where first not in ('Melvin', 'Milton', 'Jordan')),
all_fathers as (select p.student_id, n.first, n.last, 2 as type
from parents p
inner join names n
on n.id = p.id
where first in ('Melvin', 'Milton', 'Jordan'))
select last || ', ' || first as name from
(select * from all_students
union all
select * from all_mothers
union all
select * from all_fathers)
order by student_id desc, type;
We just take the student data, followed by the mom data, followed by the dad data, then sort it by the student ID from highest to lowest (I just looked at the desired results to figure out that this should be a descending sort), and then by the type (which results in the student (type=0) being first, following by their mother (type=1) and then their father (type=2)).
SQLFiddle here
Share and enjoy.
generic SQL, mmmmm I'd like there to be A generic SQL :)
First off you want to stop using the antique (+) join syntax that is exclusive to Oracle
select b.last, b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
That is way more generic! (nb: You can abbreviate to just LEFT JOIN)
Now to concatenate (Last Name comma space First Name) there are options some not generic
SQL Server/MySQL and others supporting CONCAT()
select CONCAT(b.last , ', ', b.first)
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
not all versions of Oracle or SQL Server support CONCAT()
Oracle's concat() only takes 2 parameters; grrrrr
ORACLE
select b.last || ', ' || b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
In this form Oracle generally handles data type conversions automatically (I think, please check on date/timestamps maybe others)
TSQL (Sybase, MS SQL Server)
select b.last + ', ' + b.first
from term a
LEFT OUTER JOIN names b ON a.id = b.id
order by b.last
In this form you must explicitly cast/convert data types to n|var|char for concatenation if not already a string type
For your list of concatenated names:
You need in addition to the last name a method to retain the family group together, plus distinguish between student and parent. As you want just one column of names this indicates you need a column of id's that point to the last and first names. So making some assumptions about the table TERM my guess is you list the students from that, then append the parents that relate to that group of students, and finally to output the required list in the required order.
select
case when type = 1 then 'Student' else 'Parent' end as who
, names.last || ', ' || names.first as Name
from (
select
STUDENT_ID as name_id
, STUDENT_ID as family_id
, 1 as TYPE
from term
union all
select
PARENTS.ID as name_id
, PARENTS.STUDENT_ID as family_id
, 2 as TYPE
from PARENTS
inner join term on PARENTS.STUDENT_ID = term.STUDENT_ID
) sq
inner join NAMES ON sq.name_id = NAMES.ID
order by
names.last
, sq.family_id
, sq.type
see: http://sqlfiddle.com/#!4/01804/6
This is too long for a comment.
Your question doesn't make sense. The easy answer to the question is:
select last, first
from names;
But it seems unlikely that is what you want.
Your sample query mentions a table term. That is not mentioned elsewhere in the question. Please clarify the question or delete this one and ask another.
I think I see what you're trying to do. I think you could set up a derived table and then query it. Set up something like: case when student id= id then 1 else 0 as match or whatever. Then query your derived table and group by match.
I would do it like that in SQL:
Select last +', '+ first as fullname from names;
many-to-many name and role table --
create table t (name varchar, role varchar) ;
insert into t (name, role) values ('joe', 'husband'), ('joe', 'father'),
('tom', 'husband'), ('neo', 'bachelor') ;
> select * from t;
name | role
------+----------
joe | husband
joe | father
tom | husband
neo | bachelor
need to convert into mapping of name and the role(s) he does not have --
not_a | name
---------+-----------
husband | neo
father | tom
father | neo
bachelor | joe
bachelor | tom
How to achieve that in true SQL without iterating through each role/name?
To get roles that someone doesn't have is a little complicated. You have to generate all pairs of names and roles and then pick out the ones that don't exist. This uses a left outer join.
The following is standard SQL for doing this:
select r.role as not_a, n.name
from (select distinct name from t) n cross join
(select distinct role from t) r left outer join
t
on t.name = n.name and t.role = r.role
where t.name is null;
As a note: never use varchar() without a length when defining variables and columns. The default values may not do what you expect.
Assuming you only have this table you can use:
SELECT r.role AS not_a, n.Name
FROM (SELECT DISTINCT Name FROM T) AS n
CROSS JOIN (SELECT DISTINCT Role FROM T) AS r
WHERE NOT EXISTS
( SELECT 1
FROM t
WHERE t.Name = n.Name
AND t.Role = r.Role
);
Example on SQL Fiddle
The main query will generate all pairs of names/roles, then the not exists will exlcude all the pairs that already exist.
If you actually have a name and role table, then you can replace the subqueries with the actual tables:
SELECT r.role AS not_a, n.Name
FROM Names AS n
CROSS JOIN Roles AS r
WHERE NOT EXISTS
( SELECT 1
FROM t
WHERE t.Name = n.Name
AND t.Role = r.Role
);
You haven't specified a DBMS, so if you are using MySQL, using LEFT JOIN\IS NULL will perform better than NOT EXISTS
SELECT r.role AS not_a, n.Name
FROM (SELECT DISTINCT Name FROM T) AS n
CROSS JOIN (SELECT DISTINCT Role FROM T) AS r
LEFT JOIN t
ON t.Name = n.Name
AND t.Role = r.Role
WHERE t.Name IS NULL;
I am also assuming it was just a demo, but in your table DDL you have used VARCHAR without a length which is not a good idea at all