I am new to SQL and have some problems with formatting. In column1, I have categories living, food, transportation and in each category, I have subcategories. For example, food.lunch, food, grocery.
Now I want to have a new column called detailed expenses in which I want to display CATEGORY.SUBCATEGORY
for example, food.lunch
How can I achieve this? Any help?
You need to structure your table like this (not an OOP approach)
categories
-----------------------
id name parent
1 food 0
2 lunch 1
...
3 category 0
4 subcategory 3
How about this idea: You can have a table Expenses that contains records to identify category and subcategory with an amount. Then you can total (SUM) the amounts by category or subcategory.
SELECT Category.Name,SubCategory.Name,SUM(Expense.Amount)
FROM Expense ex INNER JOIN Category cat on (ex.CategoryId = cat.Id)
INNER JOIN SubCategory subcat on (ex.SubCategoryId = subcat.Id)
GROUP BY cat.Name, subcat.Name,ex.Amount
CREATE TABLE Category(
Id int NOT NULL PRIMARY KEY,
Name varchar(50) NOT NULL
PRIMARY KEY (Id),
)
CREATE TABLE SubCategory(
Id int NOT NULL PRIMARY KEY,
CategoryId int,
Name varchar(50) NOT NULL
PRIMARY KEY (Id),
FOREIGN KEY (CategoryId) REFERENCES Category(Id))
CREATE TABLE Expense(
ID NOT NULL PRIMARY KEY,
CategoryId int,
SubCategoryId int,
Amount money
PRIMARY KEY (Id),
FOREIGN KEY (CategoryId) REFERENCES Category(Id)
FOREIGN KEY (SubCategoryId) REFERENCES SubCategory(Id))
Related
create table customer
(cust_id integer not null,
cust_name char(20) not null ,
cust_address varchar2(200) ,
emp_id integer not null,
constraint pk_customer primary key(cust_id)
);
create table account
(account_number integer not null,
account_balance number(8,2) not null,
constraint pk_acount primary key(account_number)
);
create table has
(cust_id integer not null,
account_number integer not null,
constraint pk_has
primary key(cust_id, account_number) )
alter table has
add constraint fk_account_has foreign key(account_number)
references account(account_number);
alter table has
add constraint fk_customer_has foreign key(cust_id)
references customer(cust_id);
Q1 Show the names of customers that have accounts
Q2 Show the customer names with the names of the employees they deal with**
Q1 is a simple lookup of the cust_id in junction table has:
select c.cust_name
from customer c
where exists (select 1 from has h where h.cust_id = c.cust_id)
This phrases as: select the customers that have at least one entry in the has table.
When it comes to Q2: your data structures show no sign of employees (all we have is customers and accounts), so this cannot be answered based on the information that you provided. You might want to ask a new question for this, providing sample data for the involved tables, along with desired results and you current attempt at solving the problem.
I'm trying to solve the following problem.
CREATE TABLE leagues (
id SERIAL PRIMARY KEY,
name TEXT,
...
);
CREATE TABLE players (
id SERIAL PRIMARY KEY,
first_name TEXT,
last_name TEXT,
...
);
CREATE TABLE league_members (
league_id INTEGER REFERENCES leagues (id),
player_id INTEGER REFERENCES players (id),
PRIMARY KEY (league_id, player_id)
);
CREATE TABLE games (
id SERIAL PRIMARY KEY,
league_id INTEGER REFERENCES leagues (id),
winner INTEGER REFERENCES players (id),
loser INTEGER REFERENCES players (id)
);
In the games table, is there a constraint I can use that will check to make sure that the winner and loser are both members of the same league?
For example:
players table:
---- id ---- first_name ----
1 "John"
2 "Joe"
3 "Jill"
leagues table:
---- id ---- name ----
1 "League 1"
2 "League 2"
league_members table:
---- id ---- league_id ---- player_id ----
1 1 1
2 1 2
3 2 3
I want to make sure that in the games table the following would cause an error:
INSERT INTO games (league_id, winner, loser) VALUES (1,1,3);
This shouldn't be allowed since player 3 isn't in league 1. Right now I'm just running this query on the league_members table:
SELECT count(*) FROM league_members WHERE league_id = 1 AND (player_id = 1 OR player_id = 3);
and them making sure count is 2, which means both players are members of the league with id 1. If the count is 2 then I allow the insertion into the games table, otherwise I don't. Is there a way to do this but have the database handle the constraint so that I don't have to do the extra query before I try to insert into the games table?
Thanks!
A couple of foreign keys should keep things straight:
CREATE TABLE games (
id SERIAL PRIMARY KEY,
league_id INTEGER REFERENCES leagues (id),
winner INTEGER REFERENCES players (id),
loser INTEGER REFERENCES players (id),
constraint FK_League_Winner FOREIGN KEY (league_id,winner)
REFERENCES league_members (league_id,player_id),
constraint FK_League_Loser FOREIGN KEY (league_id,loser)
REFERENCES league_members (league_id,player_id)
);
Because both of these keys are sharing the league_id column in this table, there cannot be a situation where the members are from different leagues.
I have the following table Widget which is going to have an owner associated with each row.
The owner can be an id from User, Company or Department Tables. I'm guessing how to set this up is to make a link table like so?
id | user | company | department
---------|----------|----------|----------
1 | 4 | NULL | NULL
2 | 6 | 3 | 6
3 | 10 | 3 | 8
and then have the Widget table use that ID as the owner provided logic is in the app that if company is not null then the owner is the company otherwise owner would be user.
a department can't exist if there's no company.
It is not a problem if you want to add three foreign key (FK) columns from the three tables (USER, COMPANY, DEPARTMENT) respectively on the WIDGET table. You can distinguish real owner using JOIN operation described below;
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_USER_ID INTEGER REFERENCES USER(ID),
OWNER_COMPANY_ID INTEGER REFERENCES COMPANY(ID),
OWNER_DEPART_ID INTEGER REFERENCES DEPARTMENT(ID),
);
-- retrieve OWNER_USER (you can JOIN with the USER table)
SELECT OWNER_USER_ID, WIDGET_NAME FROM WIDGET WHERE OWNER_COMPANY_ID IS NULL;
-- retrieve OWNER_COMPANY (plus OWNER_DEPART) (you can JOIN with the COMPANY and DEPARTMENT table)
SELECT OWNER_COMPANY_ID, OWNER_DEPART_ID, WIDGET_NAME FROM WIDGET WHERE OWNER_COMPANY_ID IS NOT NULL;
If you want to add just a single PK column from three tables, it doesn't make sense theoretically, but you can do it under some extra conditions. You said the owner of one widget in WIDGET table is a company if company is not null. But if company is null, then the owner is a user. If user (or corresponding identifier) column in WIDGET table is always not null whether company (or corresponding identifier) column is null or not, then you can just pick up the primary key (PK) column of USER table as a single FK of WIDGET table. Why? User → Company and User → Department dependencies are generated by this condition. It means, if you select a user A, then it is trivial that there is no more two companies related to him or her, and same as between user and department.
-- Schema of USER, COMPANY, DEPARTMENT table
CREATE TABLE USER (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20),
COMPANY_ID INTEGER REFERENCES COMPANY(ID),
DEPART_ID INTEGER REFERENCES DEPARTMENT(ID)
);
CREATE TABLE COMPANY (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20)
);
CREATE TABLE DEPARTMENT (
ID INTEGER PRIMARY KEY,
NAME VARCHAR(20)
);
-- Schema of WIDGET table
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_ID INTEGER REFERENCES USER(ID)
);
-- retrieve OWNER_USER
SELECT U.NAME AS OWNER_USER_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID IS NULL;
-- retrieve OWNER_COMPANY
SELECT C.NAME AS OWNER_COMPANY_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U, COMPANY C
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID = C.ID;
-- retrieve OWNER_DEPARTMENT
SELECT D.NAME AS OWNER_DEPART_NAME, W.WIDGET_NAME
FROM WIDGET W, USER U, DEPARTMENT D
WHERE U.ID = W.OWNER_ID AND U.COMPANY_ID IS NOT NULL AND U.DEPART_ID IS NOT NULL AND U.DEPART_ID = D.ID;
But if user column in WIDGET table can be null even though company column is not null, then you build up another OWNER table to keep your owner information (USER, COMPANY, DEPARTMENT). Of course, each record of WIDGET must be unique so composite unique index may be needed. (See http://www.postgresql.org/docs/current/static/indexes-unique.html)
-- Schema of OWNER table
CREATE TABLE OWNER (
ID INTEGER PRIMARY KEY.
OWNER_USER_ID INTEGER REFERENCES USER(ID),
OWNER_COMPANY_ID INTEGER REFERENCES COMPANY(ID),
OWNER_DEPARTMENT_ID INTEGER REFERENCES DEPARTMENT(ID)
);
-- unique index on OWNER
CREATE UNIQUE INDEX OWNER_UIDX ON OWNER( OWNER_USER_ID, OWNER_COMPANY_ID, OWNER_DEPARTMENT_ID );
-- Schema of WIDGET table
CREATE TABLE WIDGET (
WIDGET_NAME VARCHAR(20),
OWNER_ID INTEGER REFERENCES OWNER(ID)
);
I'm using PostgreSQL. I have this following schema:
CREATE TABLE users (
id int PRIMARY KEY
);
CREATE TABLE trips (
id int PRIMARY KEY,
driver_id int,
FOREIGN KEY driver_id REFERENCES users(id)
);
CREATE TABLE trip_passengers (
id int PRIMARY KEY,
user_id int,
trip_id int,
FOREIGN KEY trip_id REFERENCES trips(id)
FOREIGN KEY user_id REFERENCES users(id)
);
CREATE TABLE feedbacks (
id int PRIMARY KEY,
trip_id int,
rater_id int,
ratee_id int,
review TEXT,
FOREIGN KEY trip_id REFERENCE trips(id),
FOREIGN KEY rater_id REFERENCES users(id),
FOREIGN KEY ratee_id REFERENCES users(id),
UNIQUE (trip_id, rater_id, ratee_id)
);
Here are the following rules:
Trip can contain multiple passengers (through trip_passengers table).
User can only make a review if they made a trip together.
Drivers can make a review to a passenger.
Passenger can also make a review to a driver.
Passenger can make a review to a passenger.
Cases:
Table: users
| id | username |
|-----+------------+
| 1 | driver |
| 2 | passenger1 |
| 3 | passenger2 |
Table: trips
| id | driver_id |
|-----+-----------+
| 1 | 1 |
Table: trip_passengers
| id | trip_id | user_id |
|-----+---------+---------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
Let say I want to get all trips from driver that with passenger1 that not yet reviewed. That would return trip#1.
If driver make a review to passenger1, and run again the query, it will return nothing.
Same case for passengers, if I want to get all trips of passenger1 with passenger2 that has not yet reviewed, I will get trip#1, since they both ride the same trip.
How can I return all trips from a particular user to another user that has not yet reviewed?
Here is my last attempt but some cases were not met.
SELECT trips.* FROM trips
INNER JOIN trip_passengers AS t1 ON t1.trip_id = trips.id
INNER JOIN trip_passengers AS t2 ON t1.passenger_id = :rater_id AND
t2.passenger_id = :ratee_id AND t1.trip_id = t2.trip_id
LEFT JOIN feedbacks ON feedbacks.trip_id = trips.id AND
feedbacks.rater_id = t1.passenger_id
WHERE feedbacks.review IS NULL
With this design, your requirements are not enforced. Any user can make a feedback for any other user, regardless of whether the reviewer or the reviewed participated in the trip!
Here is an suggestion:
Users
CREATE TABLE users
( user_id int PRIMARY KEY
) ;
Trips
CREATE TABLE trips
( trip_id int PRIMARY KEY
) ;
Trip_participants (passengers and drivers)
CREATE TABLE trip_participants
( trip_id int,
user_id int,
PRIMARY KEY (trip_id, user_id),
FOREIGN KEY trip_id REFERENCES trips (trip_id),
FOREIGN KEY user_id REFERENCES users (user_id)
) ;
Trip_drivers
CREATE TABLE trip_drivers
( trip_id int,
driver_id int,
PRIMARY KEY (trip_id, driver_id),
UNIQUE KEY (trip_id) -- optional, to enforce that a trip
-- has only one driver.
FOREIGN KEY (trip_id, driver_id)
REFERENCES trip_participants (trip_id, user_id)
) ;
Feedbacks
CREATE TABLE feedbacks
( feedback_id int PRIMARY KEY,
trip_id int,
rater_id int,
ratee_id int,
review TEXT,
UNIQUE KEY (trip_id, rater_id, ratee_id) ,
FOREIGN KEY (trip_id, rater_id)
REFERENCES trips(trip_id, user_id),
FOREIGN KEY (trip_id, ratee_id)
REFERENCES trips(trip_id, user_id),
CHECK (rater_id <> ratee_id)
) ;
and the missing trip_passengers would be a View:
Trip_passengers
CREATE VIEW trip_passengers AS
SELECT trip_id, user_id
FROM trip_participants
WHERE (trip_id, user_id) NOT IN
( SELECT trip_id, driver_id
FROM trip_drivers
)
;
Finally, the query to return all trips that have not been yet reviewed:
SELECT t.*
FROM trips AS t
WHERE t.trip_id NOT IN
( SELECT trip_id
FROM feedbacks
) ;
If any number of reviews are accepted as reviewed trip then this should do it.
SELECT * from trips
where t.id not in
(SELECT distinct(t.id)
FROM trips t, feedbacks f
WHERE t.id = f.trip_id)
Using subquery selecting all reviewed trips and then selecting trips not in reviewed trips.
try this:
SELECT a.*
FROM trips a
INNER JOIN feedbacks f
on a.ID = b.trip_id
LEFT JOIN trips c
on a.ID = c.ID
WHERE c.ID IS NULL
I have a database with four tables as follows:
Addressbook
--------------------
id
more fields
Contact
---------------------
id
addressbook id
more fields
Group
---------------------
id
addressbook id
more fields
Group to Contact
---------------------
Composite key
Group id
Contact id
My relationships are one to many for addressbook > contact, one to many for addressbook > group and many to many between contact and groups.
So in summary, I have an addressbook. Contacts and groups can be stored within it and they cannot be stored in more than one addressbook. Furthermore as many contacts that are needed can be added to as many groups as are needed.
My question now poses as follows. I wish to add the constraint that a contact can only be a member of a group if both of them have the same addressbook id.
As I am not a database person this is boggling my brain. Does this mean I have designed my table structure wrong? Or does this mean that I have to add a check somewhere before inserting into the group to contact table? This seems wrong to me because I would want it to be impossible for SQL queries to link contacts to groups if they do not have the same id.
You should be able to accomplish this by adding a addressbook_id column to your Group to Contact bridge table, then using a compound foreign key to both the Contacts and Groups tables.
In PostgreSQL (but easily adaptable to any DB, or at least any DB that supports compound FKs):
CREATE TABLE group_to_contact (
contact_id INT,
group_id INT,
addressbook_id INT,
CONSTRAINT contact_fk FOREIGN KEY (contact_id,addressbook_id)
REFERENCES contacts(id,addressbook_id),
CONSTRAINT groups_fk FOREIGN KEY (group_id,addressbook_id)
REFERENCES groups(id,addressbook_id)
)
By using the same addressbook_id column in both constraints, you are of course enforcing that they are the same in both referenced tables.
OK - the Many to Many is governed by the GroupToContact table.
So the constraints are between Group and GroupToContact and between Contact and GroupToContact (GTC)
Namely
[Group].groupId = GTC.GroupId AND [Group].AddressBookid = GTC.AddressBookId
And
Contact.ContactId = GTC.ContactID AND Contact.AddressBookId = GTC.AddressBookId
So you will need to add AddressBookId to GroupToContact table
One further note - you should not define any relationship between Contact and Group directly - instead you just define the OneToMany relationships each has with the GroupToContact table.
As BonyT suggestion:
Addressbook
---------------
*id*
...more fields
PRIMARY KEY (id)
Contact
-----------
*id*
addressbook_id
...more fields
PRIMARY KEY (id)
FOREIGN KEY (addressbook_id)
REFERENCES Addressbook(id)
Group
---------
*id*
addressbook_id
...more fields
PRIMARY KEY (id)
FOREIGN KEY (addressbook_id)
REFERENCES Addressbook(id)
Group to Contact
--------------------
*group_id*
*contact_id*
addressbook_id
PRIMARY KEY (group_id, contact_id)
FOREIGN KEY (addressbook_id, contact_id)
REFERENCES Contact(addressbook, id)
FOREIGN KEY (addressbook_id, group_id)
REFERENCES Group(addressbook, id)
As A CHECK Constraint can't include sub-queries.
You could create a trigger that checks that the group and contact have the same addressbookid
and generate an error if they do not.
Although a database trigger defined to enforce an integrity rule does not check the data already in the table, I would recommended that you use a trigger only when the integrity rule cannot be enforced by an integrity constraint.
CREATE TRIGGER tr_Group_to_Contact_InsertOrUpdate on Group_to_Contact
FOR INSERT, UPDATE AS
IF (SELECT Count(*) FROM inserted i
INNER JOIN Group g ON i.groupid= g.groupid AND a.addressbookid=i.addressbookid
INNER JOIN Address a ON a.addressbookid=I.addressbookid AND a.addressd=i.addressid) = 0
BEGIN
RAISERROR('Address Book Mismatch', 16, 1)
rollback tran
END
Note:(This is from memory so probably not syntactically correct)
In your E-R (Entity-Relationship) model, the entities Group and Contact are (or should be) "dependent entities", which is to say that the existence of a Group or Contact is predicated upon that of 1 or more other entities, in this case AddressBook, that contributes to the identity of the dependent entity. The primary key of a dependent entity is composite and includes foreign keys to the entity(ies) upon which it is dependent.
The primary key of both Contact and Group include the primary key of the AddressBook to which they belong. Once you do that, everything falls into place:
create table Address
(
id int not null ,
... ,
primary key (id) ,
)
create table Contact
(
address_book_id int not null ,
id int not null ,
... ,
primary key ( address_book_id , id ) ,
foreign key ( address_book_id ) references AddressBook ( id ) ,
)
create table Group
(
address_book_id int not null ,
id int not null ,
... ,
primary key ( address_book_id , id ) ,
foreign key ( address_book_id ) references AddressBook( id ) ,
)
create table GroupContact
(
address_book_id int not null ,
contact_id int not null ,
group_id int not null ,
primary key ( address_book_id , contact_id , group_id ) ,
foreign key ( address_book_id , contact_id ) references Contact ( address_book_id , id ) ,
foreign key ( address_book_id , group_id ) references Group ( address_book_id , id ) ,
)
Cheers.