How to do I get the name of subjects pre-requisites? - sql

Here's a rough sketch. I have a pre-requisite table and subject table.
I have a rough idea how I can list the subject code. But I am really unsure on how I can get a query that can list out the name and details of the subject and it's pre requisites.
For example, I would like to write a query that will list out the subjects names and its pre requisite names.
So the resultant would come out as (Well I'll do the concatenating texts later):
"Introduction to Computer is a pre-requsite of Operating Systems".
I'm just wondering how do I extract the names of subjects off these two tables?
CREATE TABLE subjects (
subject_code VARCHAR(7) NOT NULL CONSTRAINT subject_pk PRIMARY KEY,
subject_name VARCHAR(50) NOT NULL,
subject_details TEXT NOT NULL
);
CREATE TABLE SubjectPrerequisite
( Primary_Subject_Code VARCHAR(7) NOT NULL,
Prerequisite_Subject_Code VARCHAR(7) NOT NULL,
CONSTRAINT PK_SubjectPrerequisite PRIMARY KEY (Primary_Subject_Code, Prerequisite_Subject_Code),
CONSTRAINT FK_SubjectPrerequisite_Primary_Subject_Code FOREIGN KEY (Primary_Subject_Code) REFERENCES Subject (Subject_Code),
CONSTRAINT FK_SubjectPrerequisite_Prerequisite_Subject_Code FOREIGN KEY (Prerequisite_Subject_Code) REFERENCES Subject (Subject_Code)
)
//EDIT: Here's what I have so far
SELECT subject_name
FROM SubjectPreRequisite t0
INNER JOIN subjects t1
ON t0.subject_code = s1.prerequisite_subject_code

Assuming you want the total list of subject names, do this query:
select subject_name from subjects
Assuming you want the subjects pre requisitites and subject_code has a relation with Primary_Subject_Code, do thus query:
select s.subject_name, r.Prerequisite_Subject_Code
from subjects s
inner join SubjectPrerequisite r on s.subject_code = r.Primary_Subject_Code
And with your concat:
select r.Prerequisite_Subject_Code ' + is a pre-requisite of ' + s.subject_name as 'Pre-Requisites'
from subjects s
inner join SubjectPrerequisite r on s.subject_code = r.Primary_Subject_Code

I assume (perhaps wrongly) you are looking to concatenate the subject names of the prerequisites into a single row. Below is a SQL Server example of how this can be done:
;WITH Prerequisites AS
( SELECT Primary_Subject_Code, Subject_Name
FROM SubjectPrerequisite
INNER JOIN Subjects
ON Subject_Code = Prerequisite_Subject_Code
)
SELECT Subject_Code,
Subject_Name,
Subject_Details,
STUFF( ( SELECT ',' + Subject_Name
FROM Prerequisites
WHERE Primary_Subject_Code = Subject_Code
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 1, '') AS PrerequisiteList
FROM Subjects
I have previously given a full explanation of how the XML PATH Method works here. With a further improvement to my answer pointed out here
See also SQL Server - Possible Pivot Solution?

If you just want to extract the pre-prequsite relationships to 1 level of recursion you could do
SELECT
[original].[subject_code] [OriginalCode]
, [prereq].[subject_code] [Pre-RequisiteCode]
FROM
[subject] [orginal]
LEFT JOIN
[SubjectPrerequisite] [spr]
ON [spr].[Primary_Subject_Code] = [original].[subject_code]
JOIN
[subject] [prereq]
ON [prereq].[subject_code] = [spr].[Prerequisite_Subject_Code]
ORDER BY
[OriginalCode]
, [Pre-RequisiteCode]
If you want to show the recursive chain and concatenate the subjects in some way then a CTE like GarethD's answer is the way to go. However, I suggest doing that with SQL would be wrong for an n-tier application.

Think I solved it:
SELECT t.subject_name + 'is a pre-requisite of' + s.subject_name
FROM subjects s
INNER JOIN pre_requisites r ON s.subject_code = r.subject_code
INNER JOIN subjects t ON t.subject_code = r.pre_requisite_code

Related

Attempting to get a table from SQL Request

I'm working on a project right now and I need to do some request to my DB via SQL *PLUS.
Here is what I'm trying to do.
I want to get a table in which I get Professor first and last name with those conditons (I have to verify the first condition, and then the other):
(First) In a session (let's say 12004), a prof did teach those two courses, INF3180 and INF2110
(Second) In another session, 32003, a prof did teach those two courses, INF1130 and INF1110
Here is the code that created the DB:
CREATE TABLE Professor
(professorCode CHAR(5) NOT NULL,
lastName VARCHAR(10) NOT NULL,
firstName VARCHAR(10) NOT NULL,
CONSTRAINT PrimaryKeyProfessor PRIMARY KEY (professorCode)
)
;
CREATE TABLE Group
(sigle CHAR(7) NOT NULL,
noGroup INTEGER NOT NULL,
sessionCode INTEGER NOT NULL,
maxInscriptions INTEGER NOT NULL,
professorCode CHAR(5) NOT NULL,
CONSTRAINT PrimaryKeyGroup PRIMARY KEY
(sigle,noGroupe,sessionCode),
CONSTRAINT CESigleGroupeRefCours FOREIGN KEY (sigle) REFERENCES Cours,
CONSTRAINT CECodeSessionRefSession FOREIGN KEY (sessionCode) REFERENCES
Session,
CONSTRAINT CEcodeProfRefProfessor FOREIGN KEY(professorCode) REFERENCES
Professor
)
;
And here is my current not working request :
SELECT DISTINCT Professor.firstName, Professor.lastName
FROM Professor, Group
WHERE Group.professorCode = Professor.professorCode
AND Group.sessionCode = 32003
AND (Group.sigle = 'INF1130' AND
Group.sigle = 'INF1110')
OR Group.sessionCode = 12004
AND (Group.sigle = 'INF3180' AND
Group.sigle = 'INF2110')
I know there is a way to combine both results, but I can't seem to find it.
There is only one match possible in that case :
Only one match with 32003 : INF1130, INF1110
None match with 12004 : INF3180, INF2110
The resulting table is supposed to look like this :
--------------------------
First Name Last Name
--------------------------
Denis Tremblay
The proposed solution given by Gordon Linoff looks very good, except it returns me no table since with the following the code, it needs to have the 4 courses and 2 sessionCode to be included. The issue here is that it needs to verify both condition and append the result. Let's say the conditions for the session 12004 results to nothing, then I can consider it as NULL. Then, the second condition, with the session 32003, gives me one match. It should append both results to give me the table presented over.
I want to do one request only for this.
Thanks A LOT!
EDIT : Reformulated
EDIT2 : Gave an example of a known match
EDIT3 : Further explanation why the proposed solution isn't working
Think: group by and having. More importantly, think JOIN, JOIN, JOIN. Never use commas in the from clause.
SELECT p.firstName, p.lastName
FROM Professor p JOIN
Group g
ON g.professorCode = p.professorCode
WHERE (g.sessionCode, g.sigle) IN ( (32003, 'INF1130'), (32003, 'INF1110'),
(12004, 'INF3180'), (12004, 'INF2110')
)
GROUP BY p.firstName, p.lastName
HAVING COUNT(DISTINCT g.sigl) = 4; -- has all four
It seems like you want to list any professor who either taught INF1130 and INF1110 in 32003; or taught INF3180 and INF2110 in 12004. Unfortunately you've presented that as AND (i.e. they have to have taught all four courses - one pair of courses AND the other), not OR (one set of courses OR the other).
As a long-winded way of expanding what I think you want:
SELECT p.firstName, p.lastName
FROM Professor p
WHERE (
EXISTS (
SELECT *
FROM GroupX g
WHERE professorCode = p.professorCode
AND sessionCode = 32003
AND sigle = 'INF1130'
)
AND EXISTS (
SELECT *
FROM GroupX g
WHERE professorCode = p.professorCode
AND sessionCode = 32003
AND sigle = 'INF1110'
)
)
OR (
EXISTS (
SELECT *
FROM GroupX g
WHERE professorCode = p.professorCode
AND sessionCode = 12004
AND sigle = 'INF3180'
)
AND EXISTS (
SELECT *
FROM GroupX g
WHERE professorCode = p.professorCode
AND sessionCode = 12004
AND sigle = 'INF2110'
)
);
Four subqueries isn't going to be terribly efficient. You could do mutiple joins instead.
If you will always be looking for two sigle values per sessionCode then you could modify Gordon's answer to count how many matches each sigle, by adding that to the group-by clause:
SELECT p.firstName, p.lastName
FROM GroupX g
JOIN Professor p
ON p.professorCode = g.professorCode
WHERE (g.sessionCode, g.sigle) IN ( (32003, 'INF1130'), (32003, 'INF1110'),
(12004, 'INF3180'), (12004, 'INF2110')
)
GROUP BY p.firstName, p.lastName, g.sessionCode
HAVING COUNT(*) = 2;
If you did have a professor who taught all four then you would get them listed twice; if that can happen you could add your DISTINCT back in, though that feels a bit wrong. You could also use a subquery and IN to avoid that:
SELECT p.firstName, p.lastName
FROM Professor p
WHERE ProfessorCode IN (
SELECT professorCode
FROM GroupX
WHERE (sessionCode, sigle) IN ( (32003, 'INF1130'), (32003, 'INF1110'),
(12004, 'INF3180'), (12004, 'INF2110')
)
GROUP BY professorCode, sessionCode
HAVING COUNT(*) = 2
)
(I've changed Group to GroupX because that isn't a valid identifier; because it's a keyword. I assume you've changed your real names - maybe from another language?)
use modern join
SELECT Professor.firstName, Professor.lastName
FROM Professor join "Group" g on
g.professorCode = Professor.professorCode
where g.sessionCode in( 32003,12004 )
AND g.sigle in( 'INF1130', 'INF1110','INF3180','INF2110')
group by Professor.firstName, Professor.lastName
having count( distinct sigle )=4

Matching similar entities based on many to many relationship

I have two entities in my database that are connected with a many to many relationship. I was wondering what would be the best way to list which entities have the most similarities based on it?
I tried doing a count(*) with intersect, but the query takes too long to run on every entry in my database (there are about 20k records). When running the query I wrote, CPU usage jumps to 100% and the database has locking issues.
Here is some code showing what I've tried:
My tables look something along these lines:
/* 20k records */
create table Movie(
Id INT PRIMARY KEY,
Title varchar(255)
);
/* 200-300 records */
create table Tags(
Id INT PRIMARY KEY,
Desc varchar(255)
);
/* 200,000-300,000 records */
create table TagMovies(
Movie_Id INT,
Tag_Id INT,
PRIMARY KEY (Movie_Id, Tag_Id),
FOREIGN KEY (Movie_Id) REFERENCES Movie(Id),
FOREIGN KEY (Tag_Id) REFERENCES Tags(Id),
);
(This works, but it is terribly slow)
This is the query that I wrote to try and list them:
Usually I also filter with top 1 & add a where clause to get a specific set of related data.
SELECT
bk.Id,
rh.Id
FROM
Movies bk
CROSS APPLY (
SELECT TOP 15
b.Id,
/* Tags Score */
(
SELECT COUNT(*) FROM (
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = bk.Id
INTERSECT
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = b.Id
) Q1
)
as Amount
FROM
Movies b
WHERE
b.Id <> bk.Id
ORDER BY Amount DESC
) rh
Explanation:
Movies have tags and the user can get try to find movies similar to the one that they selected based on other movies that have similar tags.
Hmm ... just an idea, but maybe I didnt understand ...
This query should return best matched movies by tags for a given movie ID:
SELECT m.id, m.title, GROUP_CONCAT(DISTINCT t.Descr SEPARATOR ', ') as tags, count(*) as matches
FROM stack.Movie m
LEFT JOIN stack.TagMovies tm ON m.Id = tm.Movie_Id
LEFT JOIN stack.Tags t ON tm.Tag_Id = t.Id
WHERE m.id != 1
AND tm.Tag_Id IN (SELECT Tag_Id FROM stack.TagMovies tm WHERE tm.Movie_Id = 1)
GROUP BY m.id
ORDER BY matches DESC
LIMIT 15;
EDIT:
I just realized that it's for M$ SQL ... but maybe something similar can be done...
You should probably decide on a naming convention and stick with it. Are tables singular or plural nouns? I don't want to get into that debate, but pick one or the other.
Without access to your database I don't know how this will perform. It's just off the top of my head. You could also limit this by the M.id value to find the best matches for a single movie, which I think would improve performance by quite a bit.
Also, TOP x should let you get the x closest matches.
SELECT
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title,
COUNT(*) AS matched_tags
FROM
Movie M
INNER JOIN TagsMovie TM1 ON TM1.movie_id = M.movie_id
INNER JOIN TagsMovie TM2 ON
TM2.tag_id = TM1.tag_id AND
TM2.movie_id <> TM1.movie_id
INNER JOIN Movie SM ON SM.movie_id = TM2.movie_id
GROUP BY
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title
ORDER BY
COUNT(*) DESC

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'

Using advance SELECT statement for SQL QUERY

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;

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