How to handle a attribute with just two possible values - sql

In my web application i have a company which can be a buyer or a supplier or both.
So my database tables would be like this
Company( id_company, ..., is_buyer, is_supplier, ... )
Or :
Company( id_company, ... )
Type_company( id_type_company, type )
Extra_table(id_company, id_type_company )
Or :
Company( id_company, ... )
Type_company( id_company, id_type_company, type )
I want a explication (Pros and Cons) for every case if it's possible.

You can consider using the common supertype like this
CREATE TABLE companies
(
id int not null primary key,
name varchar(128)
-- other columns
);
CREATE TABLE buyers
(
company_id int not null primary key,
foreign key (company_id) references companies (id)
);
CREATE TABLE suppliers
(
company_id int not null primary key,
foreign key (company_id) references companies (id)
);
Here are some sample queries:
-- Select all buyers
SELECT c.id, c.name
FROM companies c JOIN buyers b
ON c.id = b.company_id;
-- Select all suppliers
SELECT c.id, c.name
FROM companies c JOIN suppliers s
ON c.id = s.company_id;
-- Select companies that are both buers and suppliers
SELECT c.id, c.name
FROM companies c JOIN buyers b
ON c.id = b.company_id JOIN suppliers s
ON c.id = s.company_id;
-- Select companies that are buers BUT NOT suppliers
SELECT c.id, c.name
FROM companies c JOIN buyers b
ON c.id = b.company_id LEFT JOIN suppliers s
ON c.id = s.company_id
WHERE s.company_id IS NULL;
Here is SQLFiddle demo
Recommended reading:
SQL Antipatterns by #BillKarwin

Related

Join only when relation data exists

These are my tables.
CREATE TABLE IF NOT EXISTS categories (
category_id serial primary key,
category text not null,
user_id int not null
);
CREATE TABLE IF NOT EXISTS activities (
activity_id serial primary key,
activity text not null,
user_id int not null
);
CREATE TABLE IF NOT EXISTS categories_to_activities (
category_id int not null REFERENCES categories (category_id) ON UPDATE CASCADE ON DELETE CASCADE,
activity_id int not null REFERENCES activities (activity_id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT category_activity_pkey PRIMARY KEY (category_id, activity_id)
);
This is the query I'm using to get all activities with their categories.
SELECT a.*, ARRAY_AGG ( ROW_TO_JSON (c) ) categories
FROM activities a
JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id
The issue is that if an activity does not have a category assigned, it won't get returned.
I'm having trouble combining JOIN with CASE, which I suppose is what I need. Essentially I want to JOIN only when there is some record in categories_to_activities?
How do I do that?
You could use a left join, and return nulls for activities without categories:
SELECT a.*,
CASE WHEN ca.activity_id IS NOT NULL THEN ARRAY_AGG(ROW_TO_JSON(c))
ELSE ARRAY[]::JSON[]
END as categories
FROM activities a
LEFT JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
LEFT JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id
What I ended up with, thanks to #Murenik
SELECT a.*,
CASE WHEN ca.activity_id IS NOT NULL
THEN ARRAY_AGG ( ROW_TO_JSON (c) )
ELSE ARRAY[]::JSON[]
END as categories
FROM activities a
LEFT JOIN categories_to_activities ca ON a.activity_id = ca.activity_id
LEFT JOIN categories c ON ca.category_id = c.category_id
WHERE a.user_id = ${userId}
GROUP BY a.activity_id, ca.activity_id

What is the most efficient way of selecting data from relational database?

I just started working with databases and
I have this data sample from PostgreSQL tutorial
https://www.postgresqltutorial.com/postgresql-sample-database/
Which diagram looks like this:
I want to find all film categories rented in for example Canada. Is there a way of doing it without using SELECT within SELECT.. statement like this:
SELECT * FROM category WHERE category_id IN (
SELECT category_id FROM film_category WHERE film_id IN (
SELECT film_id FROM film WHERE film_id IN (
SELECT film_id FROM inventory WHERE inventory_id IN (
SELECT inventory_id FROM rental WHERE staff_id IN (
SELECT staff_id FROM staff WHERE store_id IN (
SELECT store_id FROM store WHERE address_id IN (
SELECT address_id FROM address WHERE city_id IN (
SELECT city_id FROM city WHERE country_id IN (
SELECT country_id FROM country WHERE country IN ('Canada')
)
)
)
)
)
)
)
)
)
I'm sure there must be something that i'm missing.
The proper way is to use joins instead of all these nested subqueries:
select distinct c.category_id, c.name
from category c
inner join film_category fc on fc.category_id = c.category_id
inner join inventory i on i.film_id = fc.film_id
inner join rental r on r.inventory_id = i.inventory_id
inner join staff s on s.staff_id = r.staff_id
inner join store sr on sr.store_id = s.store_id
inner join address a on a.address_id = sr.address_id
inner join city ct on ct.city_id = a.city_id
inner join country cr on cr.country_id = ct.country_id
where cr.country = 'Canada'
For your requirement you must join 9 tables (1 less than your code because the table film is not really needed as the column film_id can link the tables film_category and inventory directly).
Notice the aliases for each table which shortens the code and makes it more readable and the ON clauses which are used to link each pair of tables.
Also the keyword DISTINCT is used so you don't get duplicates in the results because all these joins will return many rows for each category.

How can I create a view connecting two tables show which entities in one table are not connected to entities from the other table?

I need to create a view of persons and contracts that they have not yet subscribed to. So far I've come up with a nested select to collect the foreign keys in my Subscription table, but I'm stuck with how to use this information to get contracts a person doesn't have.
SELECT s.PersonId as pId, s.ContractID as cId
FROM dbo.Subscription AS s
FULL OUTER JOIN dbo.Person as p ON s.PersonId = p.Id
FULL OUTER JOIN dbo.Contract as c ON s.ContractID = c.Id
WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription)
Pseudocode of what I want to do:
Get Persons that have Contracts
For each Person, get contract they don't have
Display Persons and each missing contract for Person
Schema (edited to remove business info):
CREATE TABLE [dbo].[Contract]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[ContractNumber] NUMERIC(16) NULL
)
CREATE TABLE [dbo].[Person]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
Name nvarchar(200) NOT NULL
)
CREATE TABLE [dbo].[Subscription]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[PersonID] UNIQUEIDENTIFIER NOT NULL,
[ContractID] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [FK_Subscription_Person] FOREIGN KEY ([PersonID]) REFERENCES [Person]([Id]),
CONSTRAINT [FK_Subscription_Contract] FOREIGN KEY ([ContractID]) REFERENCES [Contract]([Id])
)
Here's a cross join and not exists solution:
SELECT p.Id as pId, c.ID as cId
from dbo.Person as p
cross join dbo.Contract as c
WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription as s1)
and not exists(select 1 from dbo.Subscription as s2 where s2.PersonId = p.Id and s2.ContractID = c.Id)
Get Persons that have Contracts
You already did this correctly with WHERE p.Id IN (SELECT PersonId FROM dbo.Subscription as s1)
For each Person, get contract they don't have
First we take all combinations with cross join, then we filter out the ones you don't want with not exists
For (3.) We just select what we want
use left join
SELECT p.*,s.*,c.*
FROM dbo.Person as p
left OUTER JOIN dbo.Subscription AS s ON s.PersonId = p.Id
left join Contract c on s.ContractID=c.Id

SQL INNER JOIN entity

I want to execute this query :
-- The most expensive item sold ever
SELECT
c.itemID, c.itemName
FROM
item AS c
JOIN
(SELECT
b.itemID as 'itemid', MAX(b.item_initialPrice) AS 'MaxPrice'
FROM
buyeritem AS a
INNER JOIN
item AS b ON a.item_ID = b.itemID) AS d ON c.itemID = d.itemid
GROUP BY
c.itemID, c.itemName;
My item table looks like this:
create table item
(
itemID int IDENTITY(1000, 1) NOT NULL,
itemName varchar(15) NOT NULL,
Item_desc varchar(255),
Item_initialPrice MONEY,
ItemQty int,
ownerID int NOT NULL,
condition varchar(20) NOT NULL,
PRIMARY KEY (itemID),
FOREIGN KEY (ownerID) REFERENCES seller (sellerID)
);
The problem is that column item.itemID is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. I tried to add a group by clause at the end
group by c.itemID, c.itemName
but I still get the same error? I don't really know where the problem comes from.
I also have this query
-- The most active seller(the one who has offered the most number of items)
SELECT
a.ownerID, b.sellerName
FROM
item AS a
INNER JOIN
seller AS b ON a.ownerID = b.sellerID
GROUP BY
a.ownerID, b.sellerName
ORDER BY
COUNT(a.itemID) DESC;
I want to add itemQty along with the ownerID and sellerName from item table stated above, what would be the best way to achieve that?
Just write distinct instead of Group By as Group By will not work with out an aggregated function like sum,max etc. in select statement which is missing in your query.An example of this is second query which I have written
SELECT distinct c.itemID, c.itemName
FROM item AS c
JOIN (
SELECT b.itemID as itemid, MAX(b.item_initialPrice) AS MaxPrice FROM buyeritem AS a
INNER JOIN item AS b ON a.item_ID = b.itemID
GROUP BY b.itemID) as d
ON c.itemID = d.itemid ;
For second query
Select a.* from
(
SELECT a.ownerID, b.sellerName, count(distinct a.ITEM_ID) as item_qty
FROM item AS a
INNER JOIN seller AS b ON a.ownerID = b.sellerID
GROUP BY a.ownerID,b.sellerName
) a
order by item_qty DESC

SQL: How can I retrieve total amount from joined tables?

My tables
CREATE TABLE Customers (
id SERIAL PRIMARY KEY,
firstname VARCHAR(50),
lastname VARCHAR(50)
);
CREATE TABLE Payments (
id SERIAL PRIMARY KEY,
amount INT,
customer_id INT,
CONSTRAINT fk_CustomerPayment FOREIGN KEY (customer_id) REFERENCES Customers (id)
);
I want to get total payment amount for all customers. Here is my try:
SELECT SUM(p.amount)
FROM Customers c
JOIN Payments p
ON c.id = p.customer_id
GROUP BY p
select sum(p.amount) as total
from
customers c
inner join
payments p on c.id = p.customer_id
If there can be null values in payments.customer_id the join condition will exclude them.
Or cheaper without the join:
select sum(amount) as total
from payments
where customer_id is not null
remove group by from query..
SELECT SUM(p.amount)
FROM Customers c
JOIN Payments p
ON c.id = p.customer_id