How to resolve this fan trap in sql design - sql

I am having this 3 table, which is available Trip , country , and location. the relation in my ERD design is
Location | M-1 | Country | 1-M | Available Trip
This is my attempt for the resolution
create table country
(
countryID int not null IDENTITY(1,1) PRIMARY KEY,
countryName varchar(50),
passportRegulation text,
currency varchar(20),
)
create table location
(
locationID int not null IDENTITY(1,1) PRIMARY KEY,
locationName varchar(100),
countryID int references country(countryID)on delete cascade
)
create table availableTrip
(
availableTripID int not null IDENTITY(1,1) PRIMARY KEY,
countryID int references country(countryID),
locationID int references location(locationID)
)
Is it possible to be correct if i having two foreign key to added into available trip , however i feel this is redundant because based on location i can know the country. I am kind of lost my direction for this design

Assuming that countryId needs to match the country in locationId, then you only want locationId in availableTrip.
You would look up the appropriate country using joins:
select avt.*, l.countryid
from availabletrip avt join
location l
on avt.locationid = l.locationid;
If the countryId could be different from the locationId, then you would want two columns.

Related

Is there a way to relationship between two table which table one has 3 unique keys and table two has 2 unique keys in SQL Server?

I want to create relation between two tables, here's my table structure
Table1:
ID INT PrimaryKey AUTO_INCREMENT,
CountryCode INT UNIQUE,
Division VARCHAR(4) UNIQUE,
SubDivision VARCHAR(10) UNIQUE
Table2:
ID INT PrimaryKey AUTO_INCREMENT,
CountryCode INT UNIQUE,
Division VARCHAR(4) UNIQUE,
ReportNo VARCHAR(10) UNIQUE
Table1:
ID |CountryCode |Division |SubDivision
-------+------------------+--------------+-----------
1 |IDN |A |A-1
2 |IDN |B |B-1
3 |IDN |B |B-2
Table2
ID |CountryCode |Division |ReportNo
-------+------------------+--------------+-----------
1 |IDN |A |Re001
2 |IDN |A |Re002
3 |IDN |B |Re003
I want to create a relationship between those two tables which table2 (CountryCode, Division) refers to table1 (CountryCode, Division).
So when I want to delete in table1 with CountryCode = IDN and Division = A, SQL will prompt error when table2 contains CountryCode = IDN and Division = A.
I had tried to create a relationship between those two tables, but SQL Server always throws this error:
The column in table 'table1' do not match an existing primary key or unique constraint
Can anyone guide me how can I create a relationship between those two tables?
Thanks in advance
You can't do it this way. SQL Server requires a unique constraint (or primary key constraint) on the target of a foreign key - and you have duplicates in the source table.
For this to work, you would need to have a separate table that references all possible (CountryCode, Division) combinations. Actually, your whole schema should be normalized into something like:
-- "top" table that stores the countries
create table countries (
country_id int primary key
name varchar(100)
);
-- the table that stores the divisions
create table divisions (
division_id int primary key,
country_id int references countries(country_id),
name varchar(100)
);
-- the table that stores the subdivisions
-- this corresponds to "table1" in your question
create table subdivisions (
subdivision_id int primary key,
division_id int references divisions(division_id),
name varchar(100)
);
-- the table that stores the reports
-- this corresponds to "table2" in your question
create table reports (
report_id int primary key,
division_id references divisions(division_id),
name varchar(100)
);
You can make the primary keys automatic by using identity columns (which is the SQL Server equivalent for MySQL's AUTO_INCREMENT).
As an example, here is how you would generate the current output that you are showing for the subdivisions:
select
sd.id,
c.name country,
d.name division,
sd.name subdivision
from subdivisions sd
inner join divisions d on d.division_id = sd.division_id
inner join countries c on c.country_id = d.country_id
As GMB has answered you cannot do it in this way because of the duplicates. GMB's answer is the best way to solving your problem. If for some reason you cannot follow his advice then maybe my answer would help.
You could use a composite primary key on the columns CountryCode, Division, SubDivision. Then add subdivision to Table2. And then reference this primary key in the foreignkey restraint. (notice that my example throws an error on purpose to show that the value cannot be deleted)
DROP TABLE IF EXISTS Table2;
DROP TABLE IF EXISTS Table1;
CREATE TABLE Table1
(ID INT IDENTITY(1,1)
, CountryCode CHAR(3)
, Division VARCHAR(4)
, SubDivision VARCHAR(10)
, CONSTRAINT PK_Table1 PRIMARY KEY(CountryCode, Division, SubDivision)
)
INSERT INTO Table1(CountryCode, Division, SubDivision)
VALUES ('IDN', 'A', 'A-1')
, ('IDN', 'B', 'B-1')
, ('IDN', 'B', 'B-2');
CREATE TABLE Table2
(ID INT IDENTITY(1,1) PRIMARY KEY
, CountryCode CHAR(3)
, Division VARCHAR(4)
, SubDivision VARCHAR(10)
, ReportNo VARCHAR(10)
, CONSTRAINT FK_CountryDivision FOREIGN KEY(CountryCode, Division, SubDivision) REFERENCES Table1(CountryCode, Division, SubDivision)
);
INSERT INTO Table2(CountryCode, Division, SubDivision, ReportNo)
VALUES ('IDN', 'A', 'A-1', 'Re001')
, ('IDN', 'B', 'B-1', 'Re002')
, ('IDN', 'B', 'B-2', 'Re003');
DELETE FROM Table1
WHERE Division = 'A';
Of course this change could add a whole set of new problems for instance when a report is for the whole division what should the subdivision value then be.
ps. i had to change up the example tables a bit because the values did not match, ie string values into an int column.
SQL fiddle
thank you for the all greats answer.
But in my design I can't doing this, because I has been wrong at the first time.
And the greats answer was from Mr. #10676716 #GMB.
-- "top" table that stores the countries
create table countries (
country_id int primary key
name varchar(100)
);
-- the table that stores the divisions
create table divisions (
division_id int primary key,
country_id int references countries(country_id),
name varchar(100)
);
-- the table that stores the subdivisions
-- this corresponds to "table1" in your question
create table subdivisions (
subdivision_id int primary key,
division_id int references divisions(division_id),
name varchar(100)
);
-- the table that stores the reports
-- this corresponds to "table2" in your question
create table reports (
report_id int primary key,
division_id references divisions(division_id),
name varchar(100)
);
Thanks again Mr. GMB.

List which of the most competing members from each category got the highest score

I have 4 tables:
User
create table user (
id int not null identity(1,1) primary key,
name varchar(30),
soyad varchar(50),
city varchar(30),
e_mail varchar(100),
pass varchar(10)
);
Category
create table category(
id int not null primary key,
name varchar(50)
);
Competition
create table competition(
id int primary key,
date date,
category_id int foreign key references category(id)
);
Competition_User
create table competition_user(
id int primary key,
competition_id int foreign key references competition(id),
joker_id int foreign key references joker(joker_id),
user_id int foreign key references user(id),
point int
);
I want write SQL code for this:
Write the SQL query, which will list which of the most competing members from each category got the highest score, in which category it competed, and how many questions are correct and how many questions are wrong.
I write this SQL query but failure:
SELECT category.id, user.id, count(competition_user.competition.id) AS competitioncount
FROM user, competition_user, competition, category
WHERE competition.id = competition_user.competition_id AND compettion.category_id = category.id AND user.id = competition.user_id
GROUP BY category.id, user.id
ORDER BY competitioncount desc

RANK() Function on DATE IN SQL

I am using PopSQL for this.
Please consider the following: I am trying to rank each country with PayDate. For each PayDate, there is more than one country linked. My wish is to group each country with specific PayDate or its count and rank them accordingly.
CREATE TABLE Country
(
CountryID INT PRIMARY KEY,
CountryName VARCHAR(40)
);
CREATE TABLE State
(
StateID INT PRIMARY KEY,
StateName VARCHAR(40),
CountryID INT,
FOREIGN KEY(CountryID) REFERENCES Country(CountryID)
);
CREATE TABLE City
(
CityID INT PRIMARY KEY,
CityName VARCHAR(40),
StateID INT,
FOREIGN KEY(StateID) REFERENCES State(StateID)
);
CREATE TABLE Rooms
(
RoomID INT PRIMARY KEY,
RoomTypeID INT,
RoomBandID INT,
RoomFacilityID INT,
CityID INT,
Floor INT,
AddionalNotes VARCHAR(255),
FOREIGN KEY(RoomTypeID) REFERENCES RoomType(RoomTypeID),
FOREIGN KEY(RoomBandID) REFERENCES RoomBand(RoomBandID),
FOREIGN KEY(RoomFacilityID) REFERENCES RoomFacility(RoomFacilityID),
FOREIGN KEY(CityID) REFERENCES City(CityID)
);
CREATE TABLE DT_Date
(
DateID INT NOT NULL,
FullDate Datetime NOT NULL,
DateMonth INT NOT NULL,
Quarter INT NOT NULL,
DateYear year NOT NULL,
PRIMARY KEY(DateID)
);
CREATE TABLE Customer
(
CustomerID INT PRIMARY KEY,
CustomerForename VARCHAR(20),
CustomerSurname VARCHAR(20),
CustomerDOB DATetime,
CustomerHomePhone INT,
CustomerMobilePhone INT,
CustomerWorkPhone INT,
CustomerEmail VARCHAR(40),
CityID INT,
FOREIGN KEY (CityID) REFERENCES City(CityID)
);
CREATE TABLE Payments
(
PaymentID INT PRIMARY KEY,
PaymentComment VARCHAR(255),
PaymentsMethodID INT,
PayDate Datetime,
RoomID INT,
DateID INT,
CustomerID INT,
Price INT,
PaymentAmount INT,
FOREIGN KEY(RoomID) REFERENCES Rooms(RoomID),
FOREIGN KEY(PaymentsMethodID) REFERENCES PaymentsMethod(PaymentsMethodID),
FOREIGN KEY(DateID) REFERENCES DT_Date(DateID),
FOREIGN KEY(CustomerID) REFERENCES Customer(CustomerID)
);
Please also have a look at my last attempt:
select
rank() over (PARTITION BY CountryName order by count(PayDate)),
CountryName, PayDate, count(Paydate)
from
City, Payments, Rooms, Customer, State, Country, DT_Date
where
Payments.PayDate >= "2010-00-00 00:00:00"
and Payments.CustomerID = Customer.CustomerID
and State.CountryID = Country.CountryID
and City.StateID = State.StateID
and Customer.CityID = City.CityID
and Payments.DateID = DT_Date.DateID
group by
CountryName, PayDate;
The query worked. However, results are not correct because there are two PayDates for UK and one PayDate for USA. Following is the result shown:
Rank CountryName PayDate count(Paydate)
1 UK 2015-12-31 00:00:00 4
1 UK 2014-06-10 00:00:00 4
1 USA 2011-11-25 00:00:00 4
Expected output:
Rank CountryName
1 UK
2 USA
Don't group by PayDate, only by CountryName. This means you cannot include the PayDate in the results, but you don't want that anyway.
Also remove CountryName from your partitioning. Your rank is just ordered by the PayDate count.
And to have the largest count ranked first, use a descending order.
select
rank() over (order by count(PayDate) desc ) as "rank",
CountryName
from City , Payments,Rooms,Customer,State,Country,DT_Date
WHERE Payments.PayDate >= '2010-00-00 00:00:00'
ANd Payments.CustomerID = Customer.CustomerID
And State.CountryID = Country.CountryID
AND City.StateID = State.StateID
And Customer.CityID = City.CityID
and Payments.DateID = DT_Date.DateID
GROUP by CountryName
Here's a simplified demo.
A few side notes.
Though it might work in your SQL implementation, 2010-00-00 00:00:00 is a dodgy datetime. Use 2010-01-01 00:00:00 or better your database's equivalent of year(Payments.PayDate) >= 2010
Don't group by CountryName, group by Country.CountryId. Grouping by name invites accidentally grouping two things together, and unique country names are not enforced in the schema (they should be).
Finally, explicit joins do make it easier to understand and maintain a query.

How do i fill my table with data from 3 different tables?

So I am working on a football world cup database. These are my important tables:
CREATE TABLE Countries(
Cid SERIAL PRIMARY KEY,
Name VARCHAR(256) NOT NULL UNIQUE
);
CREATE TABLE Stadiums(
Sid SERIAL PRIMARY KEY,
Name VARCHAR(256) NOT NULL UNIQUE,
Cid INT REFERENCES Countries NOT NULL
);
CREATE TABLE Groups(
Gid SERIAL PRIMARY KEY,
Name VARCHAR(64) NOT NULL,
TYear SMALLINT REFERENCES Tournaments NOT NULL
);
CREATE TABLE Teams(
Tid SERIAL PRIMARY KEY,
Cid INT REFERENCES Countries NOT NULL,
Gid INT REFERENCES Groups NOT NULL
);
CREATE TABLE Matches(
Mid INT PRIMARY KEY,
HomeTid INT REFERENCES Teams NOT NULL,
VisitTid INT REFERENCES Teams NOT NULL,
HomeScore SMALLINT NOT NULL,
VisitScore SMALLINT NOT NULL,
MatchDate DATE NOT NULL,
MatchType VARCHAR(64) NOT NULL,
Sid INT REFERENCES Stadiums NOT NULL
);
CREATE TABLE tempmatches(
year INTEGER,
host_country VARCHAR(255),
match_id INTEGER,
type VARCHAR(255),
date DATE,
location1 VARCHAR(255),
team1 VARCHAR(255),
team2 VARCHAR(255),
score1 INTEGER,
score2 INTEGER
);
so my current problem is that I need to populate the columns HomeTid and VisitId of the Matches table with the tid's from the team's table that corresponds with the country of that team from the countries table but I'm not sure how to do that. I tried a few queries but none of them seemed to work. Has anyone an idea on how to solve this?
Using JOIN keyword you can combine 2 tables data.
Here, in this case, you have to use 3-way join.
Eg:
3-way JOIN can be used in this way:
SELECT * FROM table1 JOIN table2 ON table1.col1=table2.col2 JOIN table3 ON table3.col3=table.col1;
Here the 2 tables will get joined based on the columns mentioned after the "ON" keyword.
Here is a reference link in which JOIN is explained clearly
https://www.w3schools.com/sql/sql_join.asp
Use JOIN operation. In SQL it helps to combine several tables(queries) in one

SQL Query that displays the number of athletes that have competed in at least ten different places(locations)

I'm building a query based on the fact that they have competed in ten or more places. Note that it does not matter how many sports or competitions they competed in, just how many places they have competed in.
CREATE TABLE Gender (
gender CHAR(1),
description VARCHAR(10),
PRIMARY KEY (gender));
CREATE TABLE People (
ID INT,
name VARCHAR(50),
gender CHAR(1),
height FLOAT,
PRIMARY KEY (ID),
FOREIGN KEY (gender) REFERENCES Gender (gender));
CREATE TABLE Sports (
ID INT,
name VARCHAR(50),
record FLOAT,
PRIMARY KEY (ID),
UNIQUE (name));
CREATE TABLE Competitions (
ID INT,
place VARCHAR(50),
held DATE,
PRIMARY KEY (ID));
CREATE TABLE Results (
peopleID INT NOT NULL,
competitionID INT NOT NULL,
sportID INT NOT NULL,
result FLOAT,
PRIMARY KEY (peopleID, competitionID, sportID),
FOREIGN KEY (peopleID) REFERENCES People (ID),
FOREIGN KEY (competitionID) REFERENCES Competitions (ID),
FOREIGN KEY (sportID) REFERENCES Sports (ID));
If anyone can help me with this it would be much appreciated!
As commented by Brad, you can use a simple aggregated query that JOINs table People with Results, with a HAVING BY clause to filter on the number of competitions each person participated to. It seems like you don’t need to bring in any other table to achieve your goal.
SELECT
p.ID,
p.Name
FROM
People p
Results r ON r.peopleID = p.ID
GROUP BY
p.ID,
p.Name
HAVING COUNT(*) >= 10