Using advance SELECT statement for SQL QUERY - sql

I'm trying to use sql query to show name_id and name attribute for all the people who have only grown tomato (veg_grown) and the result are show ascending order of name attribute.
CREATE TABLE people
(
name_id# CHAR(4) PRIMARY KEY,
name VARCHAR2(20) NOT NULL,
address VARCHAR2(80) NOT NULL,
tel_no CHAR(11) NOT NULL
)
CREATE TABLE area
(
area_id# CHAR(5) PRIMARY KEY,
name_id# REFRENCES people,
area_location_adress VARCHAR2(80) NOT NULL
)
CREATE TABLE area_use
(
area_id# REFERENCES area,
veg_grown VARCHAR (20) NOT NULL
)
but the veg_grown attribute has no relation to the people table but the people and area_use table are linked through area table so I tried using INNER JOIN like this which I confused my-self and didn't even work:
SELECT
name, name_id
FROM
people
INNER JOIN
area USING (name_id)
SELECT area_id
FROM area
INNER JOIN area_use USING (area_id)
WHERE veg_grown = 'tomato'
ORDER BY name ASC;
Surely there must be a way to select name_id and name who has only grown tomato in SQL query
I will take any help or advice :) thanks

SELECT p.name, p.name_id
FROM people p
JOIN area a
ON p.name_id = a.name_id
JOIN area_use au
ON a.area_id = au.area_id
AND au.veg_grown = 'tomato'
LEFT JOIN area_use au2
ON a.area_id = au2.area_id
AND au2.veg_grown <> 'tomato'
WHERE au2.area_id IS NULL;
This will use a LEFT JOIN to find people that only grow tomatoes. To find people that grow tomatoes and possibly anything else too, remove the LEFT JOIN part and everything below it.
An SQLfiddle to test with.
EDIT: If your field names contain # in the actual table, you'll need to quote the identifiers and add the #, I left them out in this sample.

AFAICT you only want entries where all info is available, so there are no left/right joins.
SELECT p.name_id, p.name
FROM people p
JOIN area a
ON p.name_id = a.name_id
JOIN area_use au
ON a.area_id = au.area_id
WHERE au.veg_grown = 'tomato'
ORDER BY p.name ASC

I'm not 100% sure of your data model, but this seems to be what you're trying to do.
SELECT name, people.name_id
FROM people, area, area_use
WHERE area.area_id = area_use.area_id
AND veg_grown = 'tomato'
AND area.name_id = people.name_id
ORDER BY name ASC;

Related

How to work in case in join condition

How to find city when ContactID is provided and condition is if ContactID is coming as 123 then it will look whether it is P or C, If P then it will go to Person table and returns City(USA) as output and If C then it will go to Company table and gives City(AUS) as output.
NB: all tables contain thousands of record and City value comes from run time.
Unless you're dynamically generating the query (i.e. using some language other than SQL to execute it) then you need to join on both tables anyway. If you're joining on both tables then there's no need for a CASE statement:
select *
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
You'll start noting an issue here, for every column from PERSON and COMPANY you're going to have to add some business logic to work out which table you want the information from. This can get very tiresome
select co.contactid
, case when p.id is not null then p.name else c.name end as name
from contacts co
left outer join person p
on co.contactid = p.contactid
and co.person_company = 'P'
left outer join company c
on co.contactid = c.contactid
and co.person_company = 'C'
Your PERSON and COMPANY tables seem to have exactly the same information in them. If this is true in your actual data model then there's no need to split them up. You make the determination as to whether each entity is a person or a company in your CONTACTS table.
Creating additional tables to store data in this manner is only really helpful if you need to store additional data. Even then, I'd still put the data that means the same thing for a person or a companny (i.e. name or address) in a single table.
If there's a 1-2-1 relationship between CONTACTID and PID and CONTACTID and CID, which is what your sample data implies, then you have a number of additional IDs, which have no value.
Lastly, if you're not restricting that only companies can go in the COMPANY table and individuals in the PERSON table. You need the PERSON_COMPANY column to exist in both PERSON and COMPANY, though as a fixed string. It would be more normal to set up this data model as something like the following:
create table contacts (
id integer not null
, contact_type char(1) not null
, name varchar2(4000) not null
, city varchar2(3)
, constraint pk_contacts primary key (id)
, constraints uk_contacts unique (id, contact_type)
);
create table people (
id integer not null
, contact_type char(1) not null
, some_extra_info varchar2(4000)
, constraint pk_people primary key (id)
, constraint fk_people_contacts
foreign key (id, contact_type)
references contacts (id, contact_type)
, constraint chk_people_type check (contact_type = 'P')
);
etc.
you can LEFT JOIN all 3 tables and the using a CASE statement select the one that you need based on the P or C value
SELECT
CASE c.[Person/Company]
WHEN 'P' THEN p.NAME
WHEN 'C' THEN a.Name
END AS Name
FROM Contact c
LEFT JOIN Person p on p.ContactId = c.ContactId
LEFT JOIN Company a on a.ContachId = c.ContactId
Ben's answer is almost right. You might want to check that the first join has no match before doing the second one:
select c.*, coalesce(p.name, c.name) as p.name
from contacts c left outer join
person p
on c.contactid = p.contactid and
c.person_company = 'P' left join
company co
on c.contactid = co.contactid and
c.person_company = 'C' and
p.contactid is null;
This may not be important in your case. But in the event that the second join matches multiple rows and the first matches a single row, you might not want the additional rows in the output.

Connecting 4 tables

I am only a beginner in SQL, and I have problem that I can not solve.
The problem is the following:
i have four tables
Student: matrnr, name, semester, start_date
Listening: matrnr<Student>, vorlnr<Subject>
Subject: vorlnr, title, sws, teacher<Professor>
Professor: persnr, name, rank, room
I need to list all the students that are listening the Subject of some Professor with samo name.
EDIT:
select s.*
from Student s, Listening h
where s.matrnr=h.matrnr
and h.vorlnr in (select v.vorlnr from Subject v, Professor p
where v.gelesenvon=p.persnr and p.name='Kant');
This is how i solved it but i am not sure is it optimal solution.
Your approach is good. Only, you want to show students, but join students with listings thus getting student-listing combinations.
Moreover you use a join syntax that is out-dated. It was replaced more than twenty years ago with explicit joins (INNER JOIN, CROSS JOIN, etc.)
You can do it with subqueries only:
select *
from Students,
where matrnr in
(
select matrnr
from Listening
where vorlnr in
(
select vorlnr
from Subject
where gelesenvon in
(
select persnr
from Professor
where name='Kant'
)
)
);
Or join the other tables:
select *
from Students
where matrnr in
(
select l.matrnr
from Listening l
inner join Subject s on s.vorlnr = l.vorlnr
inner join Professor p on p.persnr = s.gelesenvon and p.name='Kant'
);
Or with EXISTS:
select *
from Students s
where exists
(
select *
from Listening l
inner join Subject su on su.vorlnr = l.vorlnr
inner join Professor p on p.persnr = su.gelesenvon and p.name='Kant'
where l.matrnr = s.matrnr
);
Some people like to join everthing and then clean up in the end using DISTINCT. This is easy to write, especially as you don't have to think your query through at first. But for the same reason it can get complicated when more tables and more logic are involved (like aggregations) and it can become quite hard to read, too.
select distinct s.*
from Students s
inner join Listening l on l.matrnr = s.matrnr
inner join Subject su on su.vorlnr = l.vorlnr
inner join Professor p on p.persnr = su.gelesenvon and p.name='Kant';
At last it is a matter of taste.
When you have an SQL problem, a good way of presenting the problem is to show us the tables as CREATE TABLE statements. Such statements show details such as the types of the columns and which columns are primary keys. Additionally this allows us to actually build a little database in order to reproduce a faulty behavior or just to test our solutions.
CREATE TABLE Student
(
matrnr NUMBER(9) PRIMARY KEY,
name NVARCHAR2(50),
semester NUMBER(2),
start_date DATE
);
CREATE TABLE Listening
(
matrnr NUMBER(9), -- Student
vorlnr NUMBER(9), -- Subject
CONSTRAINT PK_Listening PRIMARY KEY (matrnr, vorlnr)
);
CREATE TABLE Subject
(
vorlnr NUMBER(9) PRIMARY KEY,
title NVARCHAR2(50),
sws NVARCHAR2(50),
teacher NUMBER(9) -- Professor
);
CREATE TABLE Professor
(
persnr NUMBER(9) PRIMARY KEY,
name NVARCHAR2(50),
rank NUMBER(3),
room NVARCHAR2(50)
);
Using this schema, my solution would look like this:
SELECT *
FROM
Student
WHERE
matrnr IN (
SELECT L.matrnr
FROM
Listening L
INNER JOIN Subject S
ON L.vorlnr = S.vorlnr
INNER JOIN Professor P
ON S.teacher = P.persnr
WHERE P.name = 'Kant'
);
You can find it here: http://sqlfiddle.com/#!4/5179dc/2
Since I didn't insert any records, the only thing it is testing is the syntax and the correct use of table and column names.
Your solution is suboptimal. It does not differentiate between joining of tables and additional conditions specified as where-clause. It can produce several result records per student if they attend several courses of the professor. Therefore my solution puts all the other tables into the sub-select.
select st.name
from student st
join listening l on l.matrnr = st.matrnr
join subject su on su.vorlnr = l.vorlnr
join professor p on su.teacher = p.persnr
where p.name = 'some name'
SELECT *
FROM student
INNER JOIN listening ON student.matrnr = listening.matrnr
INNER JOIN subject ON listening.vorlnr = subject.vorlnr
INNER JOIN professor ON subject.teacher = professor.name
WHERE professor.name = 'some name'

How do i select datas from two different tables when count() = 2 with join?

I have a db with 3 tables, as following
CREATE TABLE Person
(id_pers number (3),
name varchar(20) NOT NULL,
phone number (15),
PRIMARY KEY(id_pers)
);
CREATE table Book(
id_book number(3),
title varchar(30),
about varchar (200),
nr_of_books number (3),
type varchar (11),
PRIMARY KEY(id_book)
);
CREATE table Author(
id_book number(3),
id_aut number(3),
FOREIGN KEY (id_book) REFERENCES Book(id_book),
FOREIGN KEY (id_aut) REFERENCES Person(id_pers)
);
I want to display the title of the book that has exactly 2 authors and the name of the authors which wrote the book. Example "Book1 - Author1, Author2"
All i managed to do is to take the book id and the number of authors but i want them more precisely.
The code that I wrote is this:
SELECT au.id_book, count(au.id_aut)
FROM author au join book bk ON au.id_book = bk.id_book
JOIN person p ON p.id_pers = au.id_aut
GROUP BY au.id_book
HAVING COUNT(au.id_aut) = 2;
everything I thought about had as result either "no group by expression" either some error because of the syntax.
Should I make a nested query? if so, what should i type in "where" to be equal to this?
I'm so confused. Any help would be appreciated.
This will include your title in the result, and it won't give errors about missing group by. I am not sure this is all you want, though...
SELECT au.id_book, bk.title, count(au.id_aut)
FROM author au
JOIN book bk
ON au.id_book = bk.id_book
JOIN person p
ON p.id_pers = au.id_aut
GROUP BY au.id_book, bk.title
HAVING COUNT(au.id_aut) = 2;
In order to include the authors names, I resorted to something a bit more elaborate:
DECLARE #result varchar(500)
DECLARE #numAut int
SET #result = ''
SET #numAut = 2
SELECT #result = #result + [Name] + ', '
FROM person WHERE id_pers in
(SELECT id_aut FROM author WHERE id_book in
( SELECT id_book FROM author
GROUP BY id_book HAVING COUNT(*) = #numAut)
);
SELECT bk.title, #result
FROM author au JOIN book bk ON au.id_book = bk.id_book
GROUP BY au.id_book, bk.title
HAVING COUNT(*) = #numAut;
First, we enumerate the names of the authors that appear in the "books with N authors"-list. This result Is then included in th (now a bit simpler) actual query, where I basically only select the titles of the books in that same list.
This works, but i am wondering if there is not a more elegant way...
SELECT au.id_book, count(au.id_aut)as count
FROM author au join book bk ON au.id_book = bk.id_book
JOIN person p ON p.id_pers = au.id_aut
GROUP BY au.id_book
HAVING (count=2)
Given your data structure, I don't think the joins are necessary.
Although your query looks fine, the following simpler version may also work:
SELECT au.id_book, count(au.id_aut)
FROM author au
GROUP BY au.id_book
HAVING COUNT(au.id_aut) = 2;
In order to display the book title and the authors you're going to need a GROUP_CONCAT:
SELECT au.id_book, bk.title, count(au.id_aut), GROUP_CONCAT(p.name) authors
FROM author au
JOIN book bk ON au.id_book = bk.id_book
JOIN person p ON p.id_pers = au.id_aut
GROUP BY au.id_book, bk.title
HAVING COUNT(au.id_aut) = 2

Most efficient SQL for this example

Table A: Person: id, name
Table B: Toys: id, person_id, toy_name
I have a search screen that includes a dropdown of fixed toy names.
A search is found if a subset of the total set of toys for a person is matched.
Example, a person name=bob has toys: doll, car, house, hat
A search is done for person name=bob and toys=doll, hat.
I want to return bob and ALL of his toys, not just what toys were searched for(doll, hat).
Bob is found because a subset of his toys are a match.
I don't know what the most efficient/least db calls way to accomplish this.
I can do a search for bob and get all of his toys, then parse through the result set to see if the searched for toys find a match, but that seems wrong, that the db call could return rows for which no match is found (and that seems wrong?).
okay,
select
p.id,
p.name,
t.id as toyid,
t.toy_name
from
person p
join
toys t
on p.id = t.person_id
where
p.id in (
select person_id from toys where toy_name = 'doll'
intersect
select person_id from toys where toy_name = 'hat');
Fiddle Here
If you normalise your schema a little further,
create table Person
(
Id int,
Name varchar(100)
);
create table Toy
(
Id int,
Name varchar(100)
);
create table PersonToy
(
Id int,
PersonId int,
ToyId int
);
It should make the complexity of the problem clearer. It will also save some space. A statement of the form,
select
p.Name PersonName,
t.Name ToyName
from
Person p
join
PersonToy pt
on pt.PersonId = p.Id
join
Toy t
on t.Id = pt.ToyId
where
p.Id in
(
select PersonId from PersonToy where ToyId = 1
intersect
select PersonId from PersonToy where ToyId = 4
);
will work efficiently.
Updated Fiddle
Here's one way to do it using a subquery and checking for the existence of Hat and Doll in the HAVING clause:
select p.id, p.name,
t.id as toyid, t.name as toyname
from person p
inner join toys t on p.id = t.person_id
inner join (
select person_id
from toys
group by person_id
having sum(name = 'hat') > 0 and
sum(name = 'doll') > 0
) t2 on p.id = t2.person_id
SQL Fiddle Demo

What's the best way to get related data from their ID's in a single query?

I have a table where each row has a few fields that have ID's that relate to some other data from some other tables.
Let's say it's called people, and each person has the ID of a city, state and country.
So there will be three more tables, cities, states and countries where each has an ID and a name.
When I'm selecting a person, what's the easiest way to get the names of the city, state and country in a single query?
Note: I know this is possible with joins, however as there are more related tables, the nested joins makes the query hard to read, and I'm wondering if there is a cleaner way. It should also be possible for the person to have those fields empty.
Assuming the following tables:
create table People
(
ID int not null primary key auto_increment
,FullName varchar(255) not null
,StateID int
,CountryID int
,CityID int
)
;
create table States
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Countries
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
create table Cities
(
ID int not null primary key auto_increment
,Name varchar(255) not null
)
;
With the Following Data:
insert into Cities(Name) values ('City 1'),('City 2'),('City 3');
insert into States(Name) values ('State 1'),('State 2'),('State 3');
insert into Countries(Name) values ('Country 1'),('Country 2'),('Country 3');
insert into People(FullName,CityID,StateID,CountryID) values ('Has Nothing' ,null,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has City' , 1,null,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has State' ,null, 2,null);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Country' ,null,null, 3);
insert into People(FullName,CityID,StateID,CountryID) values ('Has Everything', 3, 2, 1);
Then this query should give you what you are after.
select
P.ID
,P.FullName
,Ci.Name as CityName
,St.Name as StateName
,Co.Name as CountryName
from People P
left Join Cities Ci on Ci.ID = P.CityID
left Join States St on St.ID = P.StateID
left Join Countries Co on Co.ID = P.CountryID
JOINS are the only way to really do this.
You might be able to change your schema, but the problem will be the same regardless.
(A City is always in a State, which is always in a Country - so the Person could just have a reference to the city_id rather than all three. You still need to join the 3 tables though).
There is no cleaner way than joins. If the fields are allowed to be empty, use outer joins
SELECT c.*, s.name AS state_name
FROM customer c
LEFT OUTER JOIN state s ON s.id = c.state
WHERE c.id = 10
According to the description of the schema that you have given you will have to use JOINS in a single query.
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON p.state_id = s.id
LEFT OUTER JOIN country co
ON p.country_id = co.id;
The LEFT OUTER JOIN will allow you to fetch details of person even if some IDs are blank or empty.
Another way is to redesign your lookup tables. A city is always in a state and a state in a country. Hence your city table will have columns : Id, Name and state_id. Your state table will be : Id, Name and country_id. And country table will remain the same : Id and Name.
The person table will now have only 1 id : city_id
Now your query will be :
SELECT
p.first_name
, p.last_name
, c.name as city
, s.name as state
, co.name as country
FROM people p
LEFT OUTER JOIN city c
ON p.city_id = c.id
LEFT OUTER JOIN state s
ON c.state_id = s.id
LEFT OUTER JOIN country co
ON s.country_id = co.id;
Notice the difference in the last two OUTER JOINS
If the tables involved are reference tables (i.e. they hold lookup data that isn't going to change during the life time of a session), depending on the nature of your application, you could pre-load the reference data during you application start up. Then your query doesn't need to do the joins, instead it returns the id values, and in your application you do a decode of the ids when you need to display the data.
The easiest solution is to use the names as the primary keys in city, state, and country. Then your person table can reference them by the name instead of the pseudokey "id". That way, you don't need to do joins, since your person table already has the needed values.
It does take more space to store a string instead of a 4-byte pseudokey. But you may find the tradeoff worthwhile, if you are threatened by joins as much as you seem to be (which, by the way, is like a PHP programmer being reluctant to use foreach -- joins are fundamental to SQL in the same way).
Also there are many city names that appear in more than one state. So your city table should reference the state table and use these two columns as the primary key.
CREATE TABLE cities (
city_name VARCHAR(30),
state CHAR(2),
PRIMARY KEY (city_name, state),
FOREIGN KEY (state) REFERENCES states(state)
);
CREATE TABLE persons (
person_id SERIAL PRIMARY KEY,
...other columns...
city_name VARCHAR(30),
state CHAR(2),
country_name VARCHAR(30),
FOREIGN KEY (city_name, state) REFERENCES cities(city_name, state),
FOREIGN KEY (country_name) REFERENCES countries(country_name)
);
This just an example of the technique. Of course it's more complex than this, because you may have city names in more than one country, you may have countries with no states, and so on. The point is SQL doesn't force you to use integer pseudokeys, so use CHAR and VARCHAR keys where appropriate.
A disadvantage of standard SQL is the the return data needs to be in tabular format.
However some database vendors have added features that makes it possible to select data in non-tabular format. I don't know whether MySQL knows such features.
Create a view that does the Person, City, State, and Country joins for you. Then just reference the View in all other joins.
Something like:
CREATE VIEW FullPerson AS
SELECT Person.*, City.Name, State.Name, Country.Name
FROM
Person LEFT OUTER JOIN City ON Person.CityId = City.Id
LEFT OUTER JOIN State ON Person.StateId = State.Id
LEFT OUTER JOIN Country ON Person.CountryId = Country.Id
Then in other queries, you can
SELECT FullPerson.*, Other.Value
FROM FullPerson LEFT OUTER JOIN Other ON FullPerson.OtherId = Other.Id
All great answers but the questioner specified they didn't want to use joins. As one respondent demonstrated, assuming your Cities, States, and Countries tables have an Id and a Description field you might be able to do something like this:
SELECT
p.Name, c.Description, s.Description, ct.Description
FROM
People p, Cities c, States s, Countries ct
WHERE
p.Id = value AND
c.Id = value AND
s.Id = value AND
ct.Id = value;
Joins are the answer. With practise they will become more readable to you.
There may be special cases where creating a function would help you, for example you could do the following (in Oracle, I don't know any mysql):
You could create a function to return a formatted address given the city state and country codes, then your query becomes
SELECT first_name, last_name, formated_address(city_id, state_id, country_id)
FROM people
WHERE some_where_clause;
where formated_address does individual lookups on the city state and country tables and puts separators between the decoded values, or returns "no address" if they are all empty, etc