SQL help on query - sql

I want to create a query for the cheapest package for a holiday to Spain, given package ID. I'm just stuck to how to go about it when executing my query. I need help on what to include in the values for the 'package' table and I also need help on how to present the query.
Here is the table:
USE [zachtravelagency]
CREATE TABLE package (
[packageID] INTEGER NOT NULL IDENTITY (1,1) PRIMARY KEY,
[hotelID] INTEGER FOREIGN KEY REFERENCES hotels NOT NULL,
[excursionID] INTEGER FOREIGN KEY REFERENCES excursions NOT NULL,
[transportID] INTEGER FOREIGN KEY REFERENCES transport NOT NULL,
[flightID] INTEGER FOREIGN KEY REFERENCES flight NOT NULL,
);
Here are the columns, followed by some NULL values as I'm not sure what to put in.
Insert Into package (packageID, hotelID, excursionID, transportID, flightID)
Values (1, '', '', '', '')
Here is an example of entering data into my 'hotel' table (this is an example of one row)
Insert Into hotels (hotelID, hotelName, numRooms, location, totalCost, rating)
Values (1, 'Supreme Oyster Resort & Spa', '255', 'Spain', '250', '4')
I'm new to SQL so thank you for your patience.

First, for your insert statement for 'package', you don't specify packageId since it's an identity column. Instead it should look something like this
Insert Into package (hotelID, excursionID, transportID, flightID)
Values (1, 54, 43, 23)
Then to run a SELECT Query to find the cheapest package to Spain you will have to join your hotel, excursion, transport, and flight table on package, and sum the totalCost from each of the tables.
Example:
SELECT p.*, (h.totalCost + e.totalCost + t.totalCost, f.totalCost) as 'Total Package Cost' FROM Package p
INNER JOIN hotel h ON h.hotelId = p.hotelId
INNER JOIN excursion e ON e.excursionId = p.excursionId
INNER JOIN transport t ON t.transportId = p.transportId
INNER JOIN flight f ON f.flightId = p.flightId
WHERE h.location = 'Spain'
ORDER BY (h.totalCost + e.totalCost + t.totalCost, f.totalCost) ASC
Your cheapest packages will be listed first. If you only want the cheapest then you can use SELECT TOP 1
This query also assumes that each of the tables had a totalCost column.

Apparently you need to create a total of five tables. Because of the foreign keys you'll have to insert data in the packages table last. Let's assume all that is completed and you now want to query.
If you're given the packageID then you already have the answer. I'm not sure what you mean by that. If you want the minimum cost of a package that has a hotel in Spain then do this:
select min(h.totalCost)
from package as p inner join hotels as h on h.hotelID = p.hotelID
where h.location = 'Spain'
If you want packages that include a hotel in Spain of the lowest cost, try this. It could match more than one:
select * from package where hotelID in (
select hotelID from hotels where totalCost = (
select min(h.totalCost)
from package as p inner join hotels as h on h.hotelID = p.hotelID
where where p.packageID = ? and h.location = 'Spain'
)
)

It's really hard to help you out with what data you should enter in Package table. It can be anything. As long as, data is of the same type as of the type you have provided for each column. Since, all the columns in Package table are integers, you can add any number. Don't put them in '' though. It makes them string. E.g. I'll write following to insert data into Package table:
Insert Into package (packageID, hotelID, excursionID, transportID, flightID)
Values (1, 777, 7777, 4444) -- Doesn't matter what value you put, unless you have other Hotel, Excursion, Transport and Flight table which contains Id as primary key, then you need to use that.
Similarly, you can insert more records into both tables. After that, use the query provided by user below shawnt00 and it should return you some result.

Related

SQL insert multiple rows depending on number of rows returned from subquery

I have 3 SQL tables Companies, Materials and Suppliers as follows.
Tables
I need to insert values into Suppliers from a list which contains Company Name and Material Name as headers. However, I have multiple companies with the same name in the database and i need to add a new value into suppliers for each one of those companies.
For e.g. my list containes values ['Wickes','Bricks'] . I have this sql below to add a new entry into the suppliers table but since i have multple companies called 'Wickes' I'll get an error as the subquery will return more than 1 value.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT Id FROM Companies WHERE Name = 'Wickes'),(SELECT Id FROM Materials WHERE Name = 'Bricks'))
Whats the best solution to get the Id of all the companies there are called 'Wickes' and then add vales into the suppliers table with that Id and the relevant material Id of 'Bricks'.
You can use INSERT () SELECT.. rather than INSERT () VALUES(), e.g
INSERT INTO Suppliers (Id, CompanyId, MaterialId)
SELECT NEWID(), c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
This will ensure that if you have multiple companies/materials with the same name, all permutations are inserted. Example on db<>fiddle
Although based on your image Suppliers.Id is an integer, so I think NEWID() is not doing what you think it is here, you probably just want:
INSERT INTO Suppliers (CompanyId, MaterialId)
SELECT c.Id, m.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
And let IDENTITY take care of the Id column in Suppliers.
As a further aside, I've also just noted that MaterialId is VARCHAR in your Suppliers table, that looks like an error if it is supposed to reference the integer Id column in Materials.
INSERT INTO Suppliers(Id,CompanyId,MaterialId) VALUES (NEWID(), (SELECT distict Id FROM Companies WHERE Name = 'Wickes'),(SELECT distict Id FROM Materials WHERE Name = 'Bricks'));
If I understand rightly Companies are the suppliers and the Suppliers table is the one that says where you can buy each material from.
Why do you have duplicates? Do you have an account for different branches of Wickes for example? If they are really duplicates and you don't care which one you use a function like MIN() will do the job of ensuring that only one value is returned. If you have duplicates it would be a good idea to find a way of disactivating all except one. This will make is simpler for you everytime you want to deal with the supplier: minimum orders, chasing overdue orders, payments etc.
Also Companies.ID and Materials.ID should be foreign keys of the Suppliers table. It is also a good idea for the ID column to be auto-incrementing, which makes it easier to add new products as you do not need to specify the ID column.
If you cannot or do not want to modify the id column to auto-incrementing IDENTITY you can continue to use NEWID().
create table Companies(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Materials(
id INT PRIMARY KEY NOT NULL IDENTITY,
name VARCHAR(25));
create table Suppliers(
id INT PRIMARY KEY NOT NULL IDENTITY,
CompanyId INT FOREIGN KEY REFERENCES Companies(id),
MaterialId INT FOREIGN KEY REFERENCES Materials(id)
);
INSERT INTO Companies (name) VALUES ('Wickes');
INSERT INTO Materials (name) VALUES ('Bricks');
INSERT INTO Suppliers ( CompanyId, MaterialId)
SELECT c.Id, M.Id
FROM Companies AS c
CROSS JOIN Materials AS m
WHERE c.Name = 'Wickes'
AND m.Name = 'Bricks';
SELECT * FROM Companies;
SELECT * FROM Materials;
SELECT * FROM Suppliers;
GO
id | name
-: | :-----
1 | Wickes
id | name
-: | :-----
1 | Bricks
id | CompanyId | MaterialId
-: | --------: | ---------:
1 | 1 | 1
db<>fiddle here
INSERT INTO SUPPLIERS
(ID, COMPANYID, MATERIALID)
VALUES (NEWID(),
(SELECT DISTINCT ID FROM COMPANIES WHERE NAME = 'Wickes'), (SELECT DISTINCT ID FROM MATERIALS WHERE NAME = 'Bricks'))

Aggregate SQLite query across multiple tables using JSON1

I can't get my head around the following problem. The other day I learned how to use the JSON1 family of functions, but this time it seems to be more of an SQL issue.
This is my database setup:
CREATE TABLE persons(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)
CREATE TABLE interests(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE)
CREATE TABLE persons_interests(person INTEGER, interest INTEGER, FOREIGN KEY(person) REFERENCES persons(id), FOREIGN KEY(interest) REFERENCES interests(id))
INSERT INTO persons(name) VALUES('John')
INSERT INTO persons(name) VALUES('Jane')
INSERT INTO interests(name) VALUES('Cooking')
INSERT INTO interests(name) VALUES('Gardening')
INSERT INTO interests(name) VALUES('Relaxing')
INSERT INTO persons_interests VALUES(1, 1)
INSERT INTO persons_interests VALUES(1, 2)
INSERT INTO persons_interests VALUES(2, 3)
Based on this data I'd like to get the following output, which is all interests of all persons aggregated into a single JSON array:
[{name: John, interests:[{name: Cooking},{name: Gardening}]}, {name: Jane, interests:[{name: Relaxing}]}]
Now the following is what I tried to do. Needless to say, this doesn't give me what I want:
SELECT p.name, json_object('interests', json_group_array(json_object('name', i.name))) interests
FROM persons p, interests i
JOIN persons_interests pi ON pi.person = p.id AND pi.interest = i.id
The undesired output is:
John|{"interests":[{"name":"Cooking"},{"name":"Gardening"},{"name":"Relaxing"}]}
Any help is highly appreciated!
For using json_group_array you must group line , in your case by person , except you want only one row with all your results .
Example 1)
This first version , will give you 1 json object by person , so the result will be N rows for N persons :
SELECT json_object( 'name ',
p.name,
'interests',
json_group_array(json_object('name', i.name))) jsobjects
FROM persons p, interests i
JOIN persons_interests pi ON pi.person = p.id AND pi.interest = i.id
group by p.id ;
Example 2)
This second version , will give return 1 big json array that contains all persons , but you fetch only one row .
SELECT json_group_array(jsobjects)
FROM (
SELECT json_object( 'name ',
p.name,
'interests',
json_group_array(json_object('name', i.name))) jsobjects
FROM persons p, interests i
JOIN persons_interests pi ON pi.person = p.id AND pi.interest = i.id
group by p.id
) jo ;

How would I select from two different tables and get the following in Oracle

I need to do the following:
From each branch, find the manager names and the #customers they are managing and the total deposit balance of the customers they manage
My database looks like this
drop table branch;
CREATE TABLE branch (
BNO NUMBER(1,0),
MANAGER_NAME VARCHAR(6),
Salary NUMBER(6,0),
MGRSTARTDATE TIMESTAMP (2)
);
INSERT INTO branch VALUES
(1,'BOB',100000,'19-JUN-2001');
INSERT INTO branch VALUES
(2,'CHRIS',150000,'01-Jan-2005');
INSERT INTO branch VALUES
(3,'ANGELA',90000,'22-May-1998');
INSERT INTO branch VALUES
(4,'KIM',90000,'29-May-1996');
drop table account;
CREATE TABLE account (
ACC NUMBER(3,0),
CNAME VARCHAR(4),
BNO NUMBER(1,0),
BALANCE NUMBER(4,0)
);
INSERT INTO account VALUES
(101,'LISA',1,100);
INSERT INTO account VALUES
(102,'LISA',2,500);
INSERT INTO account VALUES
(103,'TOM',1,400);
INSERT INTO account VALUES
(104,'JOHN',3,1200);
INSERT INTO account VALUES
(105,'TOM',3,900);
All I have so far and don't know what to do next is
SELECT MANAGER_NAME
FROM branch;
I think I need to do some type of join but don't know how.
Try this
select br.manager_name, acc.cname from account acc
left join branch br
ON acc.bno = br.bno
And the below will sum balance that manager manage
select br.manager_name, acc.cname, sum(acc.balance) from account acc
left join branch br
ON acc.bno = br.bno
group by br.bno, br.manager_name
You'll want to use a OUTER JOIN (this is the same as a LEFT/RIGHT join). An outer join takes all values from the one table and adds data from the second table matching on a key. The key you'd use to match is the "BNO" value -- hoangnh's example covers this
For reference, the other type of join is an INNER JOIN which wouldn't work since that would only return values with key matches in both tables (in your example, doing an inner join would exclude BNO=4 from the final result. Doing an outer join would have BNO=4 included with nulls for the customer values)

SQL joins with multiple records into one with a default

My 'people' table has one row per person, and that person has a division (not unique) and a company (not unique).
I need to join people to p_features, c_features, d_features on:
people.person=p_features.num_value
people.division=d_features.num_value
people.company=c_features.num_value
... in a way that if there is a record match in p_features/d_features/c_features only, it would be returned, but if it was in 2 or 3 of the tables, the most specific record would be returned.
From my test data below, for example, query for person=1 would return
'FALSE'
person 3 returns maybe, person 4 returns true, and person 9 returns default
The biggest issue is that there are 100 features and I have queries that need to return all of them in one row. My previous attempt was a function which queried on feature,num_value in each table and did a foreach, but 100 features * 4 tables meant 400 reads and it brought the database to a halt it was so slow when I loaded up a few million rows of data.
create table p_features (
num_value int8,
feature varchar(20),
feature_value varchar(128)
);
create table c_features (
num_value int8,
feature varchar(20),
feature_value varchar(128)
);
create table d_features (
num_value int8,
feature varchar(20),
feature_value varchar(128)
);
create table default_features (
feature varchar(20),
feature_value varchar(128)
);
create table people (
person int8 not null,
division int8 not null,
company int8 not null
);
insert into people values (4,5,6);
insert into people values (3,5,6);
insert into people values (1,2,6);
insert into p_features values (4,'WEARING PANTS','TRUE');
insert into c_features values (6,'WEARING PANTS','FALSE');
insert into d_features values (5,'WEARING PANTS','MAYBE');
insert into default_features values('WEARING PANTS','DEFAULT');
You need to transpose the features into rows with a ranking. Here I used a common-table expression. If your database product does not support them, you can use temporary tables to achieve the same effect.
;With RankedFeatures As
(
Select 1 As FeatureRank, P.person, PF.feature, PF.feature_value
From people As P
Join p_features As PF
On PF.num_value = P.person
Union All
Select 2, P.person, PF.feature, PF.feature_value
From people As P
Join d_features As PF
On PF.num_value = P.division
Union All
Select 3, P.person, PF.feature, PF.feature_value
From people As P
Join c_features As PF
On PF.num_value = P.company
Union All
Select 4, P.person, DF.feature, DF.feature_value
From people As P
Cross Join default_features As DF
)
, HighestRankedFeature As
(
Select Min(FeatureRank) As FeatureRank, person
From RankedFeatures
Group By person
)
Select RF.person, RF.FeatureRank, RF.feature, RF.feature_value
From people As P
Join HighestRankedFeature As HRF
On HRF.person = P.person
Join RankedFeatures As RF
On RF.FeatureRank = HRF.FeatureRank
And RF.person = P.person
Order By P.person
I don't know if I had understood very well your question, but to use JOIN, you need your table loaded already and then use the SELECT statement with INNER JOIN, LEFT JOIN or whatever you need to show.
If you post some more information, maybe turn it easy to understand.
There are some aspects of your schema I'm not understanding, like how to relate to the default_features table if there's no match in any of the specific tables. The only possible join condition is on feature, but if there's no match in the other 3 tables, there's no value to join on. So, in my example, I've hard-coded the DEFAULT since I can't think of how else to get it.
Hopefully this can get you started and if you can clarify the model a bit more, the solution can be refined.
select p.person, coalesce(pf.feature_value, df.feature_value, cf.feature_value, 'DEFAULT')
from people p
left join p_features pf
on p.person = pf.num_value
left join d_features df
on p.division = df.num_value
left join c_features cf
on p.company = cf.num_value

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