Imagine dance teams (of varying size) can choose one or more outfits and each outfit can have one or more items. All members of a team must have all same outfits and all the items of each outfit, i.e., incomplete outfits are bad -- we want to find those bad outfits.
The tables below define two teams: APPLE and BANANA. Team APPLE has 3 members, team BANANA two members. The APPLEs have chosen a single outfit which has a single item, the YELLOW PANTSUIT outfit, naturally a crowd favorite. Team BANANA have two outfits: RED, and BLUE; but, BAILEY is missing the BANDANNAS item for the BLUE outfit. Awkward.
Let's get BAILEY out of trouble. Create a query to find missing items in the outfits chosen by the teams.
Thanks to #Brits for the SQL below:
Teams and team-members:
CREATE TABLE team (
id int unique,
name text
);
INSERT INTO team (id, name)
VALUES (1, 'APPLE'),
(2, 'BANANA');
CREATE TABLE team_member (
id int unique,
name text,
team_id int references team (id)
);
INSERT INTO team_member (id, name, team_id)
VALUES (1, 'ADAM', 2),
(2, 'BAILEY', 2),
(3, 'CATE', 1),
(4, 'DAVE', 1),
(5, 'ERIN', 1);
Outfits and outfit items:
CREATE TABLE outfit (
id int unique,
name text
);
INSERT INTO outfit (id, name)
VALUES (1, 'RED'),
(2, 'YELLOW'),
(3, 'BLUE');
CREATE TABLE outfit_item (
id int unique,
name text,
outfit_id int references outfit (id)
);
INSERT INTO outfit_item (id, name, outfit_id)
VALUES (1, 'SHORTS', 1),
(2, 'SHIRT', 1),
(3, 'PANTSUIT', 2),
(4, 'BANDANNA', 3),
(5, 'HAT', 3);
Team member outfit items:
CREATE TABLE member_item (
member_id int references team_member (id),
item_id int references outfit_item (id)
);
INSERT INTO member_item (member_id, item_id)
VALUES (1, 1),
(1, 2),
(1, 4),
(1, 5),
(2, 1),
(2, 2),
(2, 5),
(3, 3),
(4, 3),
(5, 3);
Team APPLE's members all have the YELLOW PANTSUIT outfit so the APPLEs are ready to rumble.
Team BANANA chose the RED and BLUE outfits; sadly, I failed to give team BANANA the YELLOW PANTSUIT outfit - they would have killed it, but whatever. Team BANANA's ADAM and BAILEY have the items for the RED outfit, but BAILEY does not have the BANDANNA for the BLUE outfit; let's not let a bandanna get in the way to team BANANA so we need a query to return just:
BANANA BAILEY BLUE BANDANNAS
To find the number of items in an outfit:
SELECT
o.name
, count(*) AS "number of items"
FROM
outfit_item i
, outfit o
WHERE
i.outfit_id = o.id
GROUP BY
i.outfit_id
, o.name
name | number of items
--------+-----------------
RED | 2
BLUE | 2
YELLOW | 1
Similarly, the number of members in a team:
SELECT
t.name
, count(*) AS "number of members"
FROM
team_member m
, team t
WHERE
m.team_id = t.id
GROUP BY
m.team_id
, t.name
name | number of members
--------+-------------------
BANANA | 2
APPLE | 3
That's all well and good, but how do we combine this information so we can bail BAILEY out of this BANANA blunder?
Taking on board the comment "If any member has an outfit item then all team members must have all items in that outfit" I believe the following will do what you are looking for (I'm using a CTE to work out what outfits are associated with each team).
with teamoutfit as (
-- If any mmber of a team has any part of an outfit then that team is linked to that outfit
select distinct te.id as team_id, oft.id as outfit_id from
team te
inner join team_member tm on tm.team_id = te.id
inner join member_item mi on mi.member_id = tm.id
inner join outfit_item oi on oi.id = mi.item_id
inner join outfit oft on oft.id = oi.outfit_id
)
select te.name as team, tm.name as member, of.name as outfit, oi.name as item from
team te
inner join team_member tm on tm.team_id = te.id
inner join teamoutfit tof on tof.team_id = tm.team_id
inner join outfit of on of.id = tof.outfit_id
inner join outfit_item oi on tof.outfit_id = oi.outfit_id
left join member_item mi on oi.id = mi.item_id and tm.id = mi.member_id
where
mi.member_id is null
Output:
| team | member | outfit | item |
|--------|--------|--------|----------|
| BANANA | BAILEY | BLUE | BANDANNA |
I setup a SQL Fiddle you can use to play with this (and perhaps clarify your question if I misunderstood what you are trying to do).
Confirmed to work on postgres, albeit not pretty and am 85% sure this can be simplified. Also, I was a little confused on how outfit and outfit_item tables are related. In the way you laid out the tables, bandanas and hats are are always blue, pantsuits are always yellow and shorts/shirts are always red when you join outfit to outfit_item (outfit.id = outfit_item.outfit_id). The test data and query has been written with that understanding.
select distinct
tm.name as member_name,
t.name as team_name,
o.name as outfit_color,
oi.name as outfit_item
from team_member tm
join team t
on t.id = tm.team_id
join member_items mi
on mi.member_id = tm.id
join(
with outfit_item_count as
(SELECT outfit_id, count(*) AS items
FROM outfit_items
GROUP BY 1)
select mi.member_id, oi.outfit_id, oic.items as items_needed, count(mi.item_id) as member_item_count
from member_items mi
join outfit_items oi on oi.id = mi.item_id
join outfit_item_count oic on oic.outfit_id = oi.outfit_id
group by 1,2,3)z
on z.member_id = tm.id
join outfit_items oi
on oi.outfit_id = z.outfit_id
join outfit o
on o.id = oi.outfit_id
where z.items_needed > z.member_item_count
and oi.id not in (select item_id from member_items)
Related
I have the following tables:
CREATE TABLE books
(
codBook INTEGER PRIMARY KEY,
title CHAR(20) NOT NULL
);
INSERT INTO books
VALUES (1, 'Book 1'), (2, 'Book 2'), (3, 'Book 3');
CREATE TABLE people
(
name CHAR(10) PRIMARY KEY,
address VARCHAR(50),
CP NUMERIC(5)
);
INSERT INTO people
VALUES ('Carl', 'C/X nº 1', '12345'), ('Louis', 'C/X nº 2', '12345'),
('Joseph', 'C/Y nº 3', '12346'), ('Anna', 'C/Z nº 4', '12347');
CREATE TABLE lends
(
codBook INTEGER REFERENCES books,
member CHAR(10) REFERENCES people,
date DATE,
PRIMARY KEY (codBook, member, date)
);
INSERT INTO lends
VALUES (1, 'Joseph', CURRENT_DATE - 10),
(1, 'Carl', CURRENT_DATE - 9),
(1, 'Louis', CURRENT_DATE - 8),
(2, 'Joseph', CURRENT_DATE - 10);
I am trying to get all the rows with the title, address and CP where they were borrowed only if they were borrowed in CP=12345 and the rows that are not from CP 12345 to appear but without the address and the CP. As book 1 has CP 12345 and 12346, I only want it to appear with CP 12345.
My expected solution is:
"Book 1";"C/X nº 1";12345
"Book 1";"C/X nº 2";12345
"Book 2";null;null
"Book 3";null;null
I tried joining all the tables using 2 left joins:
SELECT title, address, CP
FROM books
LEFT JOIN lends USING (codBook)
LEFT JOIN people ON (name = member)
WHERE CP = 12345;
But I only get the rows with CP=12345 and if I remove WHERE CP=12345 I obtain all the rows, even the book 1 with CP 12346. I am looking for a way to solve this.
If you join LENDS and PEOPLE first as INNER JOIN and add the CP number to the ON clause you get your result
SELECT title , address, CP
FROM books
LEFT JOIN (lends
INNER JOIN people ON (name = member AND CP = 12345)) USING (codBook)
title
address
cp
Book 1
C/X nº 2
12345
Book 1
C/X nº 1
12345
Book 2
null
null
Book 3
null
null
SELECT 4
fiddle
I hope this query will solve your problem:
select books.title, sub.address, sub.CP
from books
left join (
SELECT address, CP, codbook
FROM books
LEFT JOIN lends USING (codBook)
JOIN people ON (name = member and CP = 12345)
) as sub on books.codbook = sub.codbook
I have 3 tables (People, ProjectGroupAssoc and Projects), and I'm looking to show all people who do not have a specific project_Id. As in, I would like to have the query return the person's name but NULL in the project_id, and project_name columns.
EDITED
Let's say Tim should be assigned a 'Database' project; however, he currently doesn't have a database assignment in the ProjectGroupAssoc table. How would one query Tim, and any other "people" who don't have 'Database' projects, without returning multiple row for each person (i.e. returning just those people with NULL for database projects, and not the other projects they have been assigned?
This is to ensure certain "people" have been assigned their respective projects for tracking and auditing. Here's my sample database/tables/relationships.
create table People (
person_Id int,
name varchar(255)
);
INSERT INTO People (person_Id, name)
VALUES (0, 'John'), (1, 'Paul'), (2, 'Tim');
create table ProjectGroupAssoc (
person_Id int,
groupId int
);
INSERT INTO ProjectGroupAssoc (person_Id, groupId)
VALUES (0, 255), (1, 1700), (2, 35), (0, 17), (0, 333), (1, 255)
CREATE TABLE Projects (
proj_Id int,
p_name varchar(255)
);
INSERT INTO Projects(proj_Id, p_name)
VALUES (255, 'Database'), (1700, 'Development'), (333, 'Training'),
(35, 'security'), (17, 'analytics')
select p.person_Id, p.name, pga.groupId, pro.p_name
from People p
left join ProjectGroupAssoc pga on p.person_Id = pga.person_Id
left join Projects pro on pga.groupId = pro.proj_Id
where pga.groupId = 255;
I guess you are looking for something like this.
select p.Id, p.name, pga.groupId, pro.p_name
from People p
left join ProjectGroupAssoc pga on p.Id = pga.Id AND pga.groupId = 255
left join Projects pro on pga.groupId = pro.Id
If you want show people who do not have a project id
SELECT p.person_Id
FROM People p
WHERE NOT EXISTS (SELECT 1 FROM ProjectGroupAssoc pga WHERE p.person_Id = pga.person_Id AND pga.groupId = 255)
Hello I have these two tables:
Team:
team_id
team_name
division
and
Match:
match_id
match_date
...
Team_team_id
Team_team_id1
Example of the Team data:
(1, Anaheim, P)
(2, Arizona, P)
(3, Boston, S)
(4, Buffalo, M)
(5, Detroit, M)
and Match:
(1, date, 1, 2)
(2, date, 2, 3)
(3, date, 5, 3)
and result should be only Anaheim(played against Arizona in same division)
I want to list only team's names, which played matches only against teams in the same division. How could I do that in SQL and relational algebra? Division is only character value. Thank you for any help...
SELECT t1.team_name AS team_name1,
t2.team_name AS team_name2
FROM team t1
INNER JOIN match m
ON ( t1.team_id = m.team_team_id )
INNER JOIN team t2
ON ( t2.team_id = m.team_team_id1 )
WHERE t1.division = t2.division
I have a table "Cars" and a table "Person". A Person drives many Cars and a Car can be driven by many People so I have another table "Person_Car" which has both id's per row.
Car(id, name)
Person(id, name)
Person_Car(car_id, person_id)
How can I get a list of all people with the cars it drives (car names concatenated), something like this:
("John", "Car 1, Car 2, Car 3")
("Kate", "Car 2, Car 4, Car 5")
Example is here: http://sqlfiddle.com/#!15/ba949/1
Test data:
Create table Car(id int, name text);
Create table Person(id int, name text);
Create table Person_Car(car_id int, person_id int);
INSERT INTO Car VALUES (1, 'Car 1'),
(2, 'Car 2'),
(3, 'Car 3'),
(4, 'Car 4'),
(5, 'Car 5');
INSERT INTO Person VALUES(1, 'John'), (2, 'Kate');
INSERT INTO Person_Car VALUES (1,1), (2,1), (3,1), (2,2), (4,2), (5,2);
Your desired code:
SELECT p.name, array_to_string(array_agg(c.name), ',') FROM Person p
INNER JOIN Person_Car pc ON p.id=pc.person_id
INNER JOIN Car c ON c.id=pc.car_id
GROUP by p.name
Output:
John Car 1,Car 2,Car 3
Kate Car 2,Car 4,Car 5
Just in case you want to avoid the GROUP BY
Option 1
WITH forienKeyTable AS
(
SELECT pc.person_id, c.name
FROM Car c
JOIN Person_Car pc ON pc.car_id = c.id
)
SELECT p.name
, array_to_string
(ARRAY(
SELECT fkt.name
FROM forienKeyTable fkt
WHERE fkt.person_id = p.id
)::text[], ','::text, 'empty'::text)
FROM Person p;
Option 2
SELECT p.name
, array_to_string
(ARRAY(
SELECT c.name
FROM Car c
JOIN Person_Car pc ON pc.car_id = c.id
WHERE pc.person_id = p.id
)::text[], ','::text, 'empty'::text)
FROM Person p;
I did this snippet to demonstrate: http://sqlfiddle.com/#!6/ed243/2
Schema:
create table professional(
id int identity(1,3) primary key,
name varchar(20)
)
insert into professional values('professional A')
insert into professional values('professional B')
insert into professional values('professional C')
create table territory(
id int identity(2,3) primary key,
name varchar(20)
)
insert into territory values('territory A')
insert into territory values('territory B')
insert into territory values('territory C')
create table panel(
id int identity(3,3) primary key,
idProfessional int not null,
idTerritory int not null,
)
insert into panel values(1, 2)
insert into panel values(4, 5)
insert into panel values(7, 8)
insert into panel values(1, 5)
insert into panel values(7, 8)
insert into panel values(7, 2)
And the query I've got so far:
select
p.id, p.name, count(*) as Territories
from
(select distinct idProfessional, idTerritory from panel) panel
inner join
professional p
on p.id = panel.idProfessional
group by
p.id,
p.name
having count(*) > 1
order by p.id
The above query shows as result in how many territories each professional works filtering with distinct and by showing only professionals that work in more than one territory with having:
-------------------------------------------------------
| id | name | Territories |
-------------------------------------------------------
| 1 | professional A | 2 |
| 7 | professional C | 2 |
-------------------------------------------------------
Ok, but.. is it possible to show in Territories each idTerritory joined like "2, 5" instead of count(*) ?
Thanks in advance.
When it's necessary, I usually use the FOR XML function to do this kind of concatenation of multiple rows. I think this query does what you are looking for:
select
p.id, p.name, STUFF(
(select ', ' + CAST(t.id AS VARCHAR(10))
from panel panel2
inner join territory t
ON t.id = panel2.idTerritory
where panel2.idProfessional = p.id
order by t.name
for xml path(''), root('XMLVal'), type
).value('/XMLVal[1]','varchar(max)')
, 1, 2, '') as Territories
from panel
inner join
professional p
on p.id = panel.idProfessional
group by
p.id,
p.name
having count(*) > 1
order by p.id
I used this blog in creating my answer: http://blogs.lobsterpot.com.au/2010/04/15/handling-special-characters-with-for-xml-path/