How to select members of a special kind of relationship in sql - sql

I have 3 table as follow :
s(s# int,sname nchar(10))
p(p# int,pname nchar(10))
sp(s# int,p# int)
table "s" is table of suppliers and "s#" is primary key of it.also table "p" is table of products and "p#" is primary key on it."s#" and "p#" are foreign key in table "sp".
now my question is "How can I select name of suppliers from table "s" which producing all of products in table "p"...

SELECT p.*, s.sname FROM s, sp, p WHERE s.s# = sp.s# AND sp.p# = p.p#;
This statement will output all products with all their suppliers.
Now we group my suppliers, and count how many products they provide:
SELECT s.sname, count(*) FROM s, sp, p WHERE s.s# = sp.s# AND sp.p# = p.p# GROUP BY s.s#;
Now we know exacly, how many products each supplier provides. And we also know, how many products are in the productstable:
SELECT count(*) FROM p;
If you compare these values, you get your desired result:
SELECT amounts.name FROM
( SELECT s.sname AS name, count(*) AS offers
FROM s, sp, p
WHERE s.s# = sp.s# AND sp.p# = p.p#
GROUP BY s.s# ) amounts, -- this is a temp. tablename
( SELECT count(*) AS avaiable FROM p ) countTbl
WHERE amounts.offers = countTbl.avaiable;
Notice, that I didn't test the query. But you should get an idea on how to solve this problem.
It might also be possible to write this query more efficient, but this one can be understood easily.

There are two ways to do this, the first I thought of was to invert the logic.
Rather than attempting to find every entry of P let's just look for any that don't exist, then exclude those entries from S:
SELECT *
FROM S
WHERE
NOT EXISTS (
SELECT *
FROM P
LEFT JOIN SP
ON P.P# = SP.P#
AND SP.S# = S.S#
WHERE
SP.P# IS NULL
)

Related

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.

Matching similar entities based on many to many relationship

I have two entities in my database that are connected with a many to many relationship. I was wondering what would be the best way to list which entities have the most similarities based on it?
I tried doing a count(*) with intersect, but the query takes too long to run on every entry in my database (there are about 20k records). When running the query I wrote, CPU usage jumps to 100% and the database has locking issues.
Here is some code showing what I've tried:
My tables look something along these lines:
/* 20k records */
create table Movie(
Id INT PRIMARY KEY,
Title varchar(255)
);
/* 200-300 records */
create table Tags(
Id INT PRIMARY KEY,
Desc varchar(255)
);
/* 200,000-300,000 records */
create table TagMovies(
Movie_Id INT,
Tag_Id INT,
PRIMARY KEY (Movie_Id, Tag_Id),
FOREIGN KEY (Movie_Id) REFERENCES Movie(Id),
FOREIGN KEY (Tag_Id) REFERENCES Tags(Id),
);
(This works, but it is terribly slow)
This is the query that I wrote to try and list them:
Usually I also filter with top 1 & add a where clause to get a specific set of related data.
SELECT
bk.Id,
rh.Id
FROM
Movies bk
CROSS APPLY (
SELECT TOP 15
b.Id,
/* Tags Score */
(
SELECT COUNT(*) FROM (
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = bk.Id
INTERSECT
SELECT x.Tag_Id FROM TagMovies x WHERE x.Movie_Id = b.Id
) Q1
)
as Amount
FROM
Movies b
WHERE
b.Id <> bk.Id
ORDER BY Amount DESC
) rh
Explanation:
Movies have tags and the user can get try to find movies similar to the one that they selected based on other movies that have similar tags.
Hmm ... just an idea, but maybe I didnt understand ...
This query should return best matched movies by tags for a given movie ID:
SELECT m.id, m.title, GROUP_CONCAT(DISTINCT t.Descr SEPARATOR ', ') as tags, count(*) as matches
FROM stack.Movie m
LEFT JOIN stack.TagMovies tm ON m.Id = tm.Movie_Id
LEFT JOIN stack.Tags t ON tm.Tag_Id = t.Id
WHERE m.id != 1
AND tm.Tag_Id IN (SELECT Tag_Id FROM stack.TagMovies tm WHERE tm.Movie_Id = 1)
GROUP BY m.id
ORDER BY matches DESC
LIMIT 15;
EDIT:
I just realized that it's for M$ SQL ... but maybe something similar can be done...
You should probably decide on a naming convention and stick with it. Are tables singular or plural nouns? I don't want to get into that debate, but pick one or the other.
Without access to your database I don't know how this will perform. It's just off the top of my head. You could also limit this by the M.id value to find the best matches for a single movie, which I think would improve performance by quite a bit.
Also, TOP x should let you get the x closest matches.
SELECT
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title,
COUNT(*) AS matched_tags
FROM
Movie M
INNER JOIN TagsMovie TM1 ON TM1.movie_id = M.movie_id
INNER JOIN TagsMovie TM2 ON
TM2.tag_id = TM1.tag_id AND
TM2.movie_id <> TM1.movie_id
INNER JOIN Movie SM ON SM.movie_id = TM2.movie_id
GROUP BY
M.id,
M.title,
SM.id AS similar_movie_id,
SM.title AS similar_movie_title
ORDER BY
COUNT(*) DESC

Multiple unique ways of executing a simple query (ORACLE)?

I have to come up with 5 different ways (unique execution plans) to process the following query.
Find the items that are delivered by all suppliers.
My database holds the following tables:
QSPL – it holds a list of supplier names
SPLNO (number)
SPLNAME (varchar)
QDEL– it holds delivery items, suppliers, and departments
DELNO (number)
DELQTY (number)
ITEMNAME (varchar)
DEPTNAME (varchar)
SPLNO (number)
QITEM – it holds list of items
ITEMNAME (varchar)
ITEMTYPE (varchar)
ITEMCOLOR (varchar)
I was able to successfully come up with the following four unique queries.
1.
select itemname --, etc.
from qitem
where itemname not in
(select itemname
from qitem, qspl
where (char(splno)+itemname) not in
(select char(splno)+itemname
from qdel));
2.
select itemname --,etc.
from qitem
where not exists
(select *
from qspl
where not exists
(select *
from qdel
where qdel.itemname = qitem.itemname
and Qdel.splno = qspl.splno));
3.
select a.itemname --, etc
from qitem a join qdel b on a.itemname = b.itemname
group by a.itemname
having count (distinct splno) = (select count(*) from qspl);
4.
select itemname
from qdel
group by itemname
having count (distinct splno) = (select count(*) from qspl);
I have no idea what to do for a 5th unique query.
Does anyone have a clue?
I tried to put this question in the best possible context with significant detail, feedback is greatly appreciated.
Thanks
Maybe some SQL 86 syntax:
select a.itemname --, etc
from qitem a, qdel b
where a.itemname = b.itemname
group by a.itemname
having count (distinct splno) = (select count(*) from qspl);
Or an outer join
select a.itemname --, etc
from qspl s, qdel b
WHERE s.splno (+)= b.splno
group by s.splno
having count (distinct b.splno) = (select count(*) from qspl);
This is another unique way (which I'm sure it's horribly inefficient):
select distinct splname
from (
select qi.itemname,
qs.splname,
count(distinct qi.itemname) over () as total_items,
count(distinct qd.itemname) over (partition by qd.splno) as items_per_supp
from qitem qi
left join qdel qd on qi.itemname = qd.itemname
left join qspl qs on qs.splno = qd.splno
) t
where total_items = items_per_supp
Or a variant of your #3 which will probably use a different execution plan:
with supplier_items as (
select splno, count(*) item_count
from qdel
group by splno
)
select splname
from qspl qs
join supplier_items si on qs.splno = si.splno
where si.item_count = (select count(*) from qitem);
Since this is homework, I will be obtuse: Check out the Oracle MINUS operator.

SubQuery returning Multiple rows

I have got Two Tables
Product (Id, Name, CCode)
Category (CCode, CatName) - No Primary Key
Insert Into ProductNew (DW_Prod_Id, ProdId, ProdName, CC, CName)
Select Dw_Prod_Id.Nextval, Id, Name, CCode,
(Select CatName
From Category cc, Product p
Where cc.CCode IN p.CatCode
Group By CatName )
From Product;
SQL Error: ORA-01427: single-row subquery returns more than one row
01427. 00000 - "single-row subquery returns more than one row"
I am getting the above error Because my SubQuery returns more than one row.
I would like to Match the CatCode of each row from Product table to the Category Table so that I can obtain the CatName and then Insert rows into my New Table :)
if product can have only one category :
INSERT INTO ProdcutNew (DW_Prod_Id, ProdId, ProdName, CC, CName)
(SELECT Dw_Prod_Id.Nextval, p.Id, p.Name, cc.CCode, cc.CName
FROM Product p
INNER JOIN Category cc on p.CatCode = cc.CCode)
And you can correct your table name
ProdcutNew
to ProductNew ;)
EDIT :
But if, as #Gordon Linoff pointed, you have duplicates CCode, this won't work.
If you don't want a primary key on Category table, add at least a unique constraint (you'll have to clean your datas first)
ALTER TABLE Category ADD CONSTRAINT Unique_code UNIQUE(CCode);
EDIT 2 :
But the proper way would be :
Add an Id in Category as PK, and use it as Category_ID FK in Product (if CCode can change)
With the unique constraint on CCode.
You appear to have dulicates in your category table; otherwise a simple join would suffice:
select p.*, c.ccode
from Category c join
Product p
on c.ccode = p.catcode
To choose one category arbitrarily, do something like:
select p.*, c.ccode
from (select c.*
from (select c.*, row_number() over (partition by c.ccode order by c.ccode) as seqnum
from Category c
) c
where seqnum = 1
) c join
Product p
on c.ccode = p.catcode

SQL query for finding row with same column values that was created most recently

If I have three columns in my MySQL table people, say id, name, created where name is a string and created is a timestamp.. what's the appropriate query for a scenario where I have 10 rows and each row has a record with a name. The names could have a unique id, but a similar name none the less. So you can have three Bob's, two Mary's, one Jack and 4 Phil's.
There is also a hobbies table with the columns id, hobby, person_id.
Basically I want a query that will do the following:
Return all of the people with zero hobbies, but only check by the latest distinct person created, if that makes sense. Meaning if there is a Bob person that was created yesterday, and one created today.. I only want to know if the Bob created today has zero hobbies. The one from yesterday is no longer relevant.
select pp.id
from people pp, (select name, max(created) from people group by name) p
where pp.name = p.name
and pp.created = p.created
and id not in ( select person_id from hobbies )
SELECT latest_person.* FROM (
SELECT p1.* FROM people p1
WHERE NOT EXISTS (
SELECT * FROM people p2
WHERE p1.name = p2.name AND p1.created < p2.created
)
) AS latest_person
LEFT OUTER JOIN hobbies h ON h.person_id = latest_person.id
WHERE h.id IS NULL;
Try This:
Select *
From people p
Where timeStamp =
(Select Max(timestamp)
From people
Where name = p.Name
And not exists
(Select * From hobbies
Where person_id = p.id))