Finding Distinct results in SQL columns - sql

Below is the necessary info.
Table: Parts:
pid, Color
Table: Supplier
sid, sname
Table: Catalog
pid, sid
I am trying to find the pid in parts that have multiple distinct suppliers. I really don't know what command to use to do this.
I know I will have to use INNER JOIN to connect Parts and Supplier but what command ensures that I only get pid that have multiple distinct suppliers?
What about finding parts that have NO suppliers? I know DISTINCT or COUNT could somehow be used but not sure how this would work.

Find Parts with more than 1 supplier :
SELECT
p.Color
,COUNT(DISTINCT s.sname) as nbrSupName
FROM
Parts p
INNER JOIN Catalog c
ON c.pid = p.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color
HAVING
COUNT(DISTINCT s.sname) > 1
Or :
SELECT
p.Color
,s.sname
FROM
(SELECT
p.pid
,COUNT(DISTINCT s.sname) as nbrSupName
FROM
Parts p
INNER JOIN Catalog c
ON c.pid = p.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color) subquery
INNER JOIN Catalog c
ON c.pid = subquery.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color
,s.sname
WHERE
subquery.nbrSupName > 1
Find Parts with NO supplier :
SELECT
p.Color
FROM
Parts p
LEFT JOIN Catalog c
ON c.pid = p.pid
WHERE
c.sid IS NULL
GROUP BY
p.Color
You can also use the 1st query with COUNT(DISTINCT s.sname) = 0

This should work:
select * from parts
where pid in
(select pid
from catalog
group by pid
having count(distinct sid) > 1)
Since you already have a table mapping a pid to one or more sid, you just retrieve the records in that table which have multiple sid values, and use the HAVING clause to implement this filter.
For the pid values with no sid values mapped to them, do a left join like so:
select * from
parts p
left join catalog c on p.pid = c.pid
where c.sid is null
The is null check ensures that only those pid values which do not have a mapped sid in the Catalog table are retrieved.

If you only need the pid you can just work with the Catalog table
SELECT pid
FROM Catalog
GROUP BY pid
HAVING COUNT(sid) > 1
that will work only if a Supplier is identified by the sid and not by the name.
To get the part with no supplier depent on how that information is stored.
If you have the pid in catalog with a NULL sid
SELECT pid
FROM Catalog
WHERE sid IS NULL
If there is no row in the catalog when the part has no supplier
SELECT p.pid
FROM Parts p
LEFT JOIN Catalog c on p.pid = c.pid
WHERE c.pid IS NULL
If your database has the possibility to use a MINUS type command it's possible to do
SELECT pid
FROM Parts
MINUS --EXCEPT in SQLServer 2005+
SELECT pid
FROM Catalog

Related

Joining two indirectly related tables without including columns from tables in between

Based on the following ERD, I'm trying to write a query that displays reservations made directly by customers and include reservation id, reservation date, customer id, customer first name, tour trip id, tour category name, tour trip date
I've been able to include everything in my query except for the tour category name because the TOUR_SITES table is in the way. Is there a way I can join the category name from the TOUR_PACKAGE table without adding any extra columns?
SELECT r.RESERVATION_ID, r.RESERVATION_DATE,
c.CUSTOMER_ID, c.FIRST_NAME,t.TRIP_ID, o.TRIP_DATE/*, P.CATEGORY_NAME*/
FROM CUSTOMER c
INNER JOIN RESERVATION r
ON
r.CUSTOMER_ID=c.CUSTOMER_ID
INNER JOIN TOURTRIP_RESERVATION t
ON
r.RESERVATION_ID=t.RESERVATION_ID
INNER JOIN TOUR_TRIP O
ON
t.TRIP_ID=o.TRIP_ID
WHERE AGENT_ID IS NULL;
Is there a way I can join the category name from the TOUR_PACKAGE table without adding any extra columns?
Sure; just join all tables you need. You don't have to add additional columns (the ones you don't need) into the select column list, but you do have to use the tour_sites table anyway.
Something like this:
select r.reservation_id, r.reservation_date,
c.customer_id, c.first_name,t.trip_id, o.trip_date,
p.category_name
from customer c inner join reservation r on r.customer_id=c.customer_id
inner join tourtrip_reservation t on r.reservation_id=t.reservation_id
inner join tour_trip o on t.trip_id=o.trip_id
--
join tour_sites s on s.tour_siteid = o.tour_siteid --> add
join tour_package p on p.category_id = s.category_id --> this
where agent_id is null;
You need to first join tour_sites with tour_trip on TOUR_SITEID and then join tour_package with tour_sites on category_id to get the tour category_name. You can join left join on tour_sites in case there are no tour sites assigned for a tour trip like a newly added tour_trip.
SELECT r.RESERVATION_ID, r.RESERVATION_DATE, c.CUSTOMER_ID, c.FIRST_NAME,t.TRIP_ID, o.TRIP_DATE,TP.CATEGORY_NAME
FROM CUSTOMER c
INNER JOIN RESERVATION r ON r.CUSTOMER_ID=c.CUSTOMER_ID
INNER JOIN TOURTRIP_RESERVATION t ON r.RESERVATION_ID=t.RESERVATION_ID
INNER JOIN TOUR_TRIP O ON t.TRIP_ID=o.TRIP_ID
LEFT JOIN TOUR_SITES TS ON TS.TOUR_SITEID = O.TOUR_SITEID
INNER JOIN TOUR_PACKAGE TP ON TP.CATEGORY_ID = TS.CATEGORY_ID
WHERE AGENT_ID IS NULL;

Count occurrences in many to many

In my database i have following tables:
Person (
id,
name,
agentId
)
Agent (
id,
title
)
Agency (
id,
name
)
AgentAgency (
id,
agentId,
agencyId
)
I need query that will get all info about Person -> Agents with extra attribute numberOfAgencies that will show number of agencies of each agent, AND i need to show one more attribute agencyName that will show me name of first or only agency that user have (i need it in case agent have only 1 agency).
I tried something like this but without any success.
SELECT *, COUNT (aa.agentId) as numberOfAgencies
FROM agentAgencies as aa
LEFT JOIN agent as a ON a.id = aa.agentId
LEFT JOIN agency as ag ON aa.agencyId= ag.id
LEFT JOIN person as p ON p.id = ag.personId
GROUP BY ag.id, aa.id, p.id, a.id
For example i expect response like this:
PersonName John, AgencyName Cool Agency, numberOfAgencies 4
SELECT
MAX(p.Name) PersonName,
count(a.id) NoOfAgencies,
MAX(a.name) AgencyName
FROM persons p
LEFT OUTER JOIN agent g ON g.Id=p.agentId
LEFT OUTER JOIN AgentAgency aa ON aa.agentId = g.Id
LEFT OUTER JOIN Agency a on a.id = aa.agencyId
GROUP BY a.Id

Join Two Queries/Select Statement

I don't know how to explain it. But I am trying to join two select statements/queries. I need to include customer and supplier name in the same table.
Table 1 - j:
Job ID, Customer ID
Table 2 - jl:
Job_Line.Job_ID, Supplier_ID
Table 3 - p:
ID, Name
First Select statement - customer name:
Select name
From p
INNER JOIN j ON p.id = j.customer_id
Second Select statement - supplier name:
Select name
From p
INNER JOIN jl ON p.id = jl.supplier_id
Don't know how to join above two selects, so i could have a table like:
id, customer name, supplier name
I am new to SQL and learning online. I understand the basis, but getting stuck at this finding this complex!
This should do the trick
SELECT j.id, pc.name, ps.name
FROM j
INNER JOIN p pc ON j.customer_id = pc.id
INNER JOIN jl ON j.id = jl.job_id
INNER JOIN p ps ON jl.supplier_id = ps.id
Note, pc and ps are table aliases.

Joins with 4 tables returns a row for every entry in the fourth table

I have 4 tables, UnitsCoreDetails, CustomersTable, CustomerSiteTable, CustomerDetailsTable
I need to retrieve the Unit name (From UnitCoreDetails), customerName (From CustomersTable) and CustomerContact (From CustomerDetailsTable)
using the serial number.
I have tried many different joins, inner, outer, left and right and many different combinations but I always end up with a row of data for every entry in the CustomerDetailsTable.
Can anyone help with the syntax of this please or tell me what I am doing wrong?
Select distinct
Serial, Model, Manufacturer, Customer, c.CustomerName, cd.CustomerContact
From
UnitCoreDetails u
Left Join
CustomersTable c
ON
c.CustomerID=u.Customer
INNER JOIN
CustomerSiteTable cs
ON
c.CustomerID = cs.CustomerID
INNER JOIN
CustomerDetailsTable cd
ON
cs.CustomerSiteID=cd.CustomerSiteID
WHERE
u.Serial = 'test'
Try something like this....
SELECT Serial
, Model
, Manufacturer
, Customer
, CustomerName
, CustomerContact
FROM
(
Select Serial
, Model
, Manufacturer
, Customer
, c.CustomerName
, cd.CustomerContact
, ROW_NUMBER() OVER (PARTITION BY Serial ORDER BY Serial) rn
From UnitCoreDetails u
INNER JOIN CustomersTable c ON c.CustomerID=u.Customer
INNER JOIN CustomerSiteTable cs ON c.CustomerID = cs.CustomerID
INNER JOIN CustomerDetailsTable cd ON cs.CustomerSiteID=cd.CustomerSiteID
WHERE u.Serial = 'test'
)Q
WHERE rn = 1

sql triple join: ambigious attribute name on a count

So I want to count a number of books, but the books are stored in 2 different tables with the same attribute name.
I want to get a result that looks like:
name1 [total number of books of 1]
name2 [total number of books of 2]
I tried this triple join;
SELECT DISTINCT name, count(book)
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
LEFT JOIN controls l on l.controller=p.id
GROUP BY name
ORDER BY name DESC
but since book exists as an attribute in writes and in controls, it cant execute the query.
It can only do it if I leave out one of joins so it can identify book.
How can I tell the sql engine to count the number of both book attributes together for each person?
As a result of database design that you interested in, you should issue 2 different sql and then merge them to handle single output.
A)
SELECT DISTINCT w.name as 'Name', count(w.book) as 'Cnt'
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
B)
SELECT DISTINCT l.name as 'Name', count(l.book) as 'Cnt'
FROM controls l
LEFT JOIN person p on p.id = l.controller
LEFT JOIN book b on b.title = l.book
For your purpose, you can get UNION of A and B.
or you can use them as data source on a third SQL
select A.Name, sum(A.Cnt+B.Cnt)
from A, B
where A.Name = B.Name
group by A.Name
order by A.Name
WITH T AS
(
SELECT DISTINCT 'WRITES' FROMTABLE, w.name, w.count(book)
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
GROUP BY name
UNION ALL
SELECT DISTINCT 'CONTROLLS' FROMTABLE, c.name, count(c.book)
FROM controlls c
LEFT JOIN person p on p.id = c.author
LEFT JOIN book b on b.title = c.book
GROUP BY name
)
SELECT * FROM T ORDER BY NAME
Should work.
HTH
This will work on a per distinct author's ID to how many books they've written. The pre-aggregation will return one record per author with how many books by that author. THEN, join to the person table to get the name. The reason I am leaving it by ID and Name of the author is... what if you have two authors "John Smith", but they have respective IDs of 123 and 389. You wouldn't want these rolled-up to the same person (or do you).
select
P.ID,
P.Name,
PreAgg.BooksPerAuthor
from
( select
w.author,
count(*) BooksPerAuthor
from
writes w
group by
w.author ) PreAgg
JOIN Person P
on PreAgg.Author = P.id
order by
P.Name