SQL multiple natural inner joins - sql

Why does this correctly return the Order ID of an order, the Customer ID of the person who made the order, and the Last Name of the employee in charge of the transaction
SELECT "OrderID", "CustomerID", "LastName"
FROM orders O
NATURAL INNER JOIN customers JOIN employees ON O."EmployeeID" = employees."EmployeeID";
while
SELECT "OrderID", "CustomerID", "LastName"
FROM orders O
NATURAL INNER JOIN customers NATURAL INNER JOIN employees;
returns 0 rows?
I am sure that they have common columns.
Table orders
OrderId
EmployeeID
CustomerID
...
Table employees
EmployeeID
...
Table customers
CustomerID
...

Without seeing your full, unedited schema it's hard to be sure, but I'd say there are more common columns than you intended.
E.g. as #ClockworkMuse sugested:
CREATE TABLE orders (
OrderId integer primary key,
EmployeeID integer not null,
CustomerID integer not null,
created_at timestamp not null default current_timestamp,
...
);
CREATE TABLE employees (
EmployeeID integer primary key,
created_at timestamp not null default current_timestamp,
...
);
then orders NATURAL JOIN employees will be equivalent to orders INNER JOIN employees USING (EmployeeID, created_at). Which surely isn't what you intended.
You should use INNER JOIN ... USING (colname) or INNER JOIN ... ON (condition).
NATURAL JOIN is a poorly thought out feature that should really be avoided except on quick and dirty ad-hoc queries, if even then. Even if it works now, if you later add an unrelated column to a table it might change the meaning of existing queries. That's ... well, avoid natural joins.

Related

How to successfully use JOIN queries?

These are my schemas:
CREATE TABLE CUSTOMER
(
customerID numeric,
name text,
email varchar(320),
cell varchar,
address varchar,
flag text NULL,
PRIMARY KEY(customerID)
);
CREATE TABLE REFERRALS
(
customerID numeric NOT NULL,
name text NOT NULL,
PRIMARY KEY(customerID, name)
);
CREATE TABLE RENTAL
(
customerID numeric NOT NULL,
model numeric NOT NULL,
borrowDate timestamp NOT NULL,
dueDate date NOT NULL,
charge money NOT NULL,
returnDate timestamp NULL,
addFees money NULL,
notes text NULL,
PRIMARY KEY(customerID, model, borrowDate)
);
CREATE TABLE SCOOTER
(
model bigserial NOT NULL,
manufacturer text NOT NULL,
country text NOT NULL,
range numeric NOT NULL,
weight numeric NOT NULL,
topspeed numeric NOT NULL,
condition text NOT NULL,
availability text NOT NULL,
PRIMARY KEY(model)
);
For the first query, I want to show the model and manufacturer columns from SCOOTER, the name column from CUSTOMER, and the dueDate column from RENTAL, but only for in rows where SCOOTER.model = RENTAL.model and where RENTAL.returnDate is NULL. And finally, in descending order by dueDate.
This is the query I wrote:
SELECT
s.model, s.manufacturer, c.name, r.duedate
FROM
SCOOTER AS s, CUSTOMER AS c
INNER JOIN
RENTAL AS r ON r.model = s.model AND r.returnDate IS NULL
ORDER BY
r.duedate DESC;
I get this error however:
HINT: There is an entry for table "s", but it cannot be referenced from this part of the query.
STATEMENT: SELECT s.model, s.manufacturer, c.name, r.duedate FROM SCOOTER AS s, CUSTOMER AS c
INNER JOIN RENTAL AS r ON r.model = s.model AND r.returnDate IS NULL ORDER BY r.duedate desc;
ERROR: invalid reference to FROM-clause entry for table "s"
LINE 2: INNER JOIN RENTAL AS r ON r.model = s.model AND r.returnDate...
^
HINT: There is an entry for table "s", but it cannot be referenced from this part of the query.
Well, I think you should study a little bit better SQL. You only connect table RENTAL and SCOOTER, but you left out the connection with CUSTOMER.
Your code should probably look more like
SELECT SCOOTER.model, SCOOTER.manufacturer, CUSTOMER.name, RENTAL.duedate
FROM SCOOTER
INNER JOIN RENTAL ON RENTAL.model = SCOOTER.model
INNER JOIN CUSTOMER ON RENTAL.customerID = CUSTOMER.customerID
WHERE RENTAL.returnDate IS NULL ORDER BY RENTAL.duedate desc;
Hope it helps!
Cheers
You're mixing join styles there, something to be avoided. Joins look like this:
SELECT * FROM
a
INNER JOIN b ON a.column = b.column
INNER JOIN c ON a.column = c.column ...
Every row from a is connected to every row from b, where the ON clause is true. Then every row from a-b is connected to C again where the ON clause is true. This causes the data to grow sideways as more data from more tables is joined on. Tables can even be joined to themselves.
It's hard (and off topic for SO) to go into depth about every aspect of JOINs so some background reading will probably be essential

SQL - Selecting data from two tables and removing duplicates

So I have two tables and I'm trying to display some data from both and remove the duplicates. Sorry, I'm new to SQL and databases. Here's my code
Table 1
CREATE TABLE customer
(
customer_id VARCHAR2(5),
customer_name VARCHAR2(50) NOT NULL,
customer_address VARCHAR2(150) NOT NULL,
customer_phone VARCHAR2(11) NOT NULL,
PRIMARY KEY (customer_id)
);
Table 2
CREATE TABLE shop
(
shop_id VARCHAR2(7),
shop_address VARCHAR2(150) NOT NULL,
customer_id VARCHAR2(7),
PRIMARY KEY (shop_id),
FOREIGN KEY (customer_id) REFERENCES customer (customer_id)
);
I want to display everything from the SHOP table, and customer_id, customer_name from the CUSTOMER TABLE.
I've tried this so far, but it's displaying everything from both tables and I get two duplicate customer_id columns:
SELECT *
FROM shop
JOIN customer ON shop.customer_id = customer.customer_id
ORDER BY customer_name;
Anyone able to help?
Thanks
Due to both tables has column customer_id, so you can show everything on shop table and only column customer_name from customer table
SELECT s.*, c.customer_name
FROM shop s
JOIN customer c ON s.customer_id = c.customer_id
ORDER BY c.customer_name;
select distinct c.customer_id, c.customer_name, s.*
from customer c
inner join shop s on c.customer_id = s.customer_id
To remove duplicates, you need to use distinct keyword
https://www.w3schools.com/sql/sql_distinct.asp
You need to manually list the columns you want. Using * will pull in every column from every table. SQL does not have any way of saying "select all columns except these...".
I hope you're only using * casually - it's a very bad idea to use SELECT * inside program code that then expects certain columns to exist in a particular order or with a certain name.
To save typing, you could use * for one of the tables and manually name the rest:
SELECT
customer.*,
shop.shop_id,
shop.shop_address
FROM
...

How to compose select request using many-to-many relationship in PostgreSQL?

I have this tables:
CREATE TABLE orders (
order_id serial PRIMARY KEY
, number_of_things int
);
CREATE TABLE things (
thing_id serial PRIMARY KEY
, cost int
);
CREATE TABLE orders_to_things (
order_id int REFERENCES orders (order_id)
, thing_id int REFERENCES things (thing_id)
);
How to compose a request for select all orders where cost of things more than some number?
I tried to use:
SELECT orders.order_id
FROM orders
INNER JOIN orders_to_things ON (orders_to_things.order_id = orders.order_id)
JOIN things ON (orders_to_things.thing_id=things.thing_id)
WHERE (select SUM(things.cost) FROM things) > *some number*
but didn't get the correct result.
Try this:
SELECT O.order_id, sum(T.cost)
FROM orders O
INNER JOIN orders_to_things ON orders_to_things.order_id = orders.order_id
JOIN things T ON orders_to_things.thing_id=things.thing_id
GROUP BY O.order_id
HAVING T.cost > 'number....'
If you want all order ids, you don't need the orders table. The simplest way to write the query is:
SELECT ott.order_id, sum(t.cost)
FROM orders_to_things ott JOIN
things t
ON ott.thing_id = t.thing_id
GROUP BY ott.order_id
HAVING sum(t.cost) > <number>;

Writing a query to combine results from multiple tables with all possible combinations

I have this database schema:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name char(50) NOT NULL UNIQUE
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name char(50) NOT NULL,
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
uid INTEGER REFERENCES users (id) NOT NULL,
pid INTEGER REFERENCES products (id) NOT NULL,
quantity INTEGER NOT NULL,
price FLOAT NOT NULL CHECK (price >= 0)
);
I am trying to write a query that will give me all combinations of users and products, as well as the total amount spent by the user on that product. Specifically, if I have 5 products and 5 users, there should be 25 rows in the table. Right now I have a query that almost gets the job done, however, if the user has never purchased that product then there is no row printed at all.
Here's what I've written so far:
SELECT u.name as username, p.name as productname, SUM(o.quantity * o.price) as totalPrice
FROM users u, orders o, products p
WHERE u.id = o.uid
AND p.id = o.pid
GROUP BY u.name, p.name
ORDER BY u.name, p.name
I figure that this requires some sort of join, but my SQL knowledge is limited and I am not sure what would be the best way to go about doing this. I think if somebody can help me figure this out then I will have a much better understanding.
You can do this using cross join and left join:
select u.name as username, p.name as productname,
sum(o.quantity * o.price) as totalPrice
from users u cross join
products p left join
orders o
on o.uid = u.id and o.pid = p.id
group by u.name, p.name;
The cross join generates all the rows. The left join brings in the matching rows. A simple rule when using SQL is: Never use commas in the FROM clause. Always use explicit JOIN syntax.

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