I am using relational division with EAV, but I need to find results in EAV that have some of the categorizations - entity-attribute-value

I have two tables:
CREATE TABLE `EAV` (
`subscriber_id` INT(1) NOT NULL DEFAULT '0',
`attribute_id` CHAR(62) NOT NULL DEFAULT '',
`attribute_value` CHAR(62) NOT NULL DEFAULT '',
PRIMARY KEY (`subscriber_id`,`attribute_id`)
)
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (1,'color','red')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (1,'size','xl')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (1,'garment','shirt')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (2,'color','red')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (2,'size','xl')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (2,'garment','pants')
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (3,'garment','pants')
CREATE TABLE `CRITERIA` (
`attribute_id` CHAR(62) NOT NULL DEFAULT '',
`attribute_value` CHAR(62) NOT NULL DEFAULT ''
)
INSERT INTO CRITERIA (attribute_id, attribute_value) VALUES ('color', 'red')
INSERT INTO CRITERIA (attribute_id, attribute_value) VALUES ('size', 'xl')
To find all subscribers in the EAV that match my criteria, I use relational division:
SELECT DISTINCT(subscriber_id)
FROM EAV
WHERE subscriber_id IN
(SELECT E.subscriber_id FROM EAV AS E
JOIN CRITERIA AS CR ON E.attribute_id = CR.attribute_id AND E.attribute_value = CR.attribute_value
GROUP BY E.subscriber_id
HAVING COUNT(`*`) = (SELECT COUNT(`*`) FROM CRITERIA))
This gives me an unique list of subscribers who have all the criteria. So that means I get back subscriber 1 and 2 since they are looking for the color red and size xl, and that's exactly my criteria.
But what if I want to extend this so that I also get subscriber 3 since this subscriber didn't specifically say what color or size they want (ie. there is no entry for attribute 'color' or 'size' in the EAV table for subscriber 3).
Given my current design, is there a way I can extend my query to include subscribers that have zero or more of the attributes defined, and if they do have the attribute defined, then it must match the criteria?
Or is there a better way to design the table to aid in querying?

It's even easier than your original query.
You basically want to add everyone that matches a criteria, but subtract everyone that has a criteria that doesn't match:
SELECT DISTINCT(E.subscriber_id) FROM EAV AS E
LEFT JOIN CRITERIA AS CR ON (E.attribute_id = CR.attribute_id AND E.attribute_value = E.attribute_value)
WHERE subscriber_id NOT IN (
SELECT subscriber_id FROM EAV AS E
JOIN CRITERIA AS CR ON (E.attribute_id = CR.attribute_id AND E.attribute_value <> CR.attribute_value)
)
To test, I added a few more records:
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (4,'color','blue');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (4,'garment','shirt');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (5,'color','blue');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (5,'size','xl');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (5,'garment','shirt');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (6,'color','red');
INSERT INTO EAV (subscriber_id, attribute_id, attribute_value) VALUES (6,'garment','shirt');
The query returns:
1
2
3
6

SELECT DISTINCT(E.subscriber_id) FROM EAV AS E
LEFT JOIN CRITERIA AS CR ON (E.attribute_id = CR.attribute_id AND E.attribute_value = E.attribute_value)
WHERE subscriber_id NOT IN (
SELECT subscriber_id FROM EAV AS E
JOIN CRITERIA AS CR ON (E.attribute_id = CR.attribute_id AND E.attribute_value <> CR.attribute_value)
)
i think this can be simplified

Related

How to exclude join based on another join in postgresql?

This is just simplified example - actual schema is much more complicated.
Each car is offered in "base" color (offer.model_id = null) or as models in different colors. I need to exclude cars with existing models NOT in BLUE or WHITE but keep cars in base color without extra models.
Schema (PostgreSQL v13)
CREATE TABLE color (
id int4 NOT NULL,
name varchar NOT NULL,
CONSTRAINT color_pkey PRIMARY KEY (id)
);
INSERT INTO color (id, name) VALUES(1, 'WHITE');
INSERT INTO color (id, name) VALUES(2, 'BLUE');
INSERT INTO color (id, name) VALUES(3, 'RED');
INSERT INTO color (id, name) VALUES(4, 'BLACK');
CREATE TABLE car (
id int4 NOT NULL,
name varchar NOT NULL,
CONSTRAINT car_pkey PRIMARY KEY (id)
);
INSERT INTO car (id, name) VALUES(1, 'Ford');
INSERT INTO car (id, name) VALUES(2, 'Skoda');
INSERT INTO car (id, name) VALUES(3, 'Toyota');
CREATE TABLE model (
id int4 NOT NULL,
car_id int4 NOT NULL,
name varchar NOT NULL,
CONSTRAINT model_pkey PRIMARY KEY (id)
);
INSERT INTO model (id, car_id, name) VALUES(1, 1, 'Escort');
INSERT INTO model (id, car_id, name) VALUES(2, 1, 'Puma');
INSERT INTO model (id, car_id, name) VALUES(3, 2, 'Octavia');
INSERT INTO model (id, car_id, name) VALUES(4, 3, 'Yaris');
CREATE TABLE offer (
id int4 NOT NULL,
car_id int4 NOT NULL,
model_id int4,
color_id int4 NOT NULL,
CONSTRAINT offer_pkey PRIMARY KEY (id)
);
ALTER TABLE offer ADD CONSTRAINT "offer_car_id" FOREIGN KEY ("car_id") REFERENCES car(id);
ALTER TABLE offer ADD CONSTRAINT "offer_model_id" FOREIGN KEY ("model_id") REFERENCES model(id);
ALTER TABLE offer ADD CONSTRAINT "offer_color_id" FOREIGN KEY ("color_id") REFERENCES color(id);
CREATE UNIQUE INDEX "offer_car_color" ON offer USING btree (car_id) WHERE (model_id IS NULL);
CREATE UNIQUE INDEX "offer_model_color" ON offer USING btree (car_id, model_id) WHERE (model_id IS NOT NULL);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(1, 1, null, 1);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(2, 2, null, 2);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(3, 3, null, 4);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(4, 2, 3, 3);
INSERT INTO offer (id, car_id, model_id, color_id) VALUES(5, 3, 4, 2);
Query #1
FULL OFFER - CARS AND MODELS TOGETHER
select car.id, car.name as car_name, model.name as model_name, color.name as offered_color
from offer
left outer join model on model.id = offer.model_id
inner join color on color.id = offer.color_id
inner join car on car.id = offer.car_id
order by car.name, model.name NULLS first;
id
car_name
model_name
offered_color
1
Ford
WHITE
2
Skoda
BLUE
2
Skoda
Octavia
RED
3
Toyota
BLACK
3
Toyota
Yaris
BLUE
Query #2
LIST OF CARS IN WHITE OR BLUE (base color or model's color)
select car.id, car.name as car_name, model.name as model_name, color.name as offered_color
from car
inner join offer on offer.car_id = car.id
inner join color on color.id = offer.color_id
left outer join model on model.id = offer.model_id
where color.name in ('WHITE', 'BLUE')
order by car.name;
id
car_name
model_name
offered_color
1
Ford
WHITE
2
Skoda
BLUE
3
Toyota
Yaris
BLUE
Query #3
REQUIRED QUERY: EXCLUDE CARS WITH EXISTING MODELS BUT NOT IN WHITE OR BLUE
select ...?
id
car_name
model_name
offered_color
1
Ford
WHITE
3
Toyota
Yaris
BLUE
Skoda is excluded even it is offered in base BLUE but it has models and non is in BLUE or WHITE.
View on DB Fiddle
I think I've found the solution. The question is if it's the best one...?
https://www.db-fiddle.com/f/9Fh6SPxATLuHQCxG7iWktk/6
select
car.id,
car.name as car_name,
model.name as model_name,
color.name as color_name
from
car
inner join offer on
car.id = offer.car_id
inner join color on
color.id = offer.color_id
left join model on
model.id = offer.model_id
where
color.name in ('WHITE', 'BLUE')
and
((not exists (
select
1
from
offer o
where
o.car_id = car.id
and o.model_id is not null))
or (
exists (
select
1
from
offer o
inner join color c on
c.id = o.color_id
where
o.car_id = car.id
and o.model_id is not null
and c.name in ('WHITE', 'BLUE'))
))
order by
car.name;
I can't follow the logic in your description, but I might be able to offer some help on how to write the SQL so you can figure it out.
I suggest adopting the style of using CTEs so you can make it more readable and built it with building blocks.
Once you have all the different conditions you want as CTEs, you can use EXISTS() to filter based on whether they exist in each of those various conditions.
Example: (this does not produce the correct result, though)
WITH FULL_OFFER AS (
select
car.id,
car.name as car_name,
model.name as model_name,
color.name as offered_color
from offer
left outer join model on model.id = offer.model_id
inner join color on color.id = offer.color_id
inner join car on car.id = offer.car_id
),
BASE_MODELS AS (
SELECT *
FROM FULL_OFFER
WHERE model_name IS NULL
),
BASE_WHITE_OR_BLUE AS (
SELECT *
FROM BASE_MODELS
WHERE COALESCE(offered_color,'NONE') in('WHITE', 'BLUE')
),
NO_MODELS AS (
SELECT *
FROM FULL_OFFER
WHERE NOT EXISTS (
SELECT 1
FROM BASE_MODELS
WHERE FULL_OFFER.id=BASE_MODELS.id
AND FULL_OFFER.offered_color = BASE_MODELS.offered_color)
),
ANY_WHITE_BLUE AS (
SELECT *
FROM FULL_OFFER
WHERE COALESCE(offered_color,'NONE') in('WHITE', 'BLUE')
)
SELECT *
FROM FULL_OFFER
WHERE
(EXISTS (SELECT 1
FROM BASE_WHITE_OR_BLUE
WHERE FULL_OFFER.id = BASE_WHITE_OR_BLUE.id)
AND EXISTS (SELECT 1
FROM NO_MODELS
WHERE FULL_OFFER.id = NO_MODELS.id)
)
OR EXISTS (SELECT 1
FROM ANY_WHITE_BLUE
WHERE FULL_OFFER.id = ANY_WHITE_BLUE.id)
This doesn't produce what you want, but only because I can't translate the conditions from your description.
Hopefully those 2 tips will help you out, if someone else doesn't come along and help you with the correct SQL.

Using where clause with not-equal condition after join

I am trying to use WHERE with not-equal condition after joining two tables but it does not work.
Example: I have a table with data on famous people and a separate table with their works. Some works can have several authors. So I want a table listing authors with their co-authors:
CREATE TABLE famous_people (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
profession TEXT,
birth_year INTEGER);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Landau", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Lifshitz", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Fisher", "statistician", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Ginzburg", "physicist", 1916);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("A. Strugatsky", "writer", 1925);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("B. Strugatsky", "writer", 1933);
CREATE TABLE works (id INTEGER PRIMARY KEY AUTOINCREMENT,
person_id INTEGER,
work TEXT);
INSERT INTO works (person_id, work)
VALUES (1, "Theoretical Physics");
INSERT INTO works (person_id, work)
VALUES (2, "Theoretical Physics");
INSERT INTO works (person_id, work)
VALUES (1, "Theory of Superconductivity");
INSERT INTO works (person_id, work)
VALUES (4, "Theory of Superconductivity");
INSERT INTO works (person_id, work)
VALUES (3, "Fisher test");
INSERT INTO works (person_id, work)
VALUES (5, "Roadside Picnic");
INSERT INTO works (person_id, work)
VALUES (6, "Roadside Picnic");
INSERT INTO works (person_id, work)
VALUES (5, "Hard to Be a God");
INSERT INTO works (person_id, work)
VALUES (6, "Hard to Be a God");
/* Co-authors */
SELECT a.name AS author, b.name AS coauthor FROM works
JOIN famous_people a
ON works.person_id = a.id
JOIN famous_people b
ON works.person_id = b.id;
It is Ok, except each author also has themselves as their own co-author, so I am trying to filter it out by adding WHERE author <> coauthor as the last line. But what I get is a table with two columns: work and name. Same weird result with WHERE a.name <> b.name
Funny enough, WHERE author = coauthor works fine but this is not what I want.
Expected result: a table with 2 columns:
author co-author
Landau Lipshitz
A. Strugatsky B. Strugatsky
Fisher NULL
Find all works that have two authors (using inner join on same work but different authors) and find all works that have one author (using not exists). Then combine the results:
SELECT w1.work, p1.name AS author, p2.name AS coauthor
FROM works AS w1
JOIN works AS w2 ON w1.work = w2.work AND w1.person_id < w2.person_id
JOIN famous_people AS p1 ON w1.person_id = p1.id
JOIN famous_people AS p2 ON w2.person_id = p2.id
UNION ALL
SELECT w1.work, p1.name, null
FROM works AS w1
JOIN famous_people AS p1 ON w1.person_id = p1.id
WHERE NOT EXISTS (
SELECT 1
FROM works AS w2
WHERE w2.work = w1.work AND w2.person_id <> w1.person_id
)
Demo on DB<>Fiddle
Your query cannot work. Keep in mind that a join works on rows. So there is one works row with one person ID that you look at at a time in your where clause. Then you join the person to the works row and then you join the person to the works row. That is the same person twice of course, because one works row only refers to one person.
This shows another, minor, problem. You call this table works. I would consider "Theoretical Physics" a work. You do so too; you named the column work. But then, why is the same work twice in the works table? This must not be. A works table shall store works, i.e. one work per row. What you have is a work_author table actually, and a work is uniquely identified by its title. This kind of makes sense; a title may uniquely identify a work - as long as no other author happens to name their work "Theoretical Physics", too :-( And as long as there are no typos in the table either.
This would be a better model:
person (person_id, name, birth_year, ...)
work (work_id, title, year, ...)
work_author (work_id, person_id)
If you have a typo in a title in this model, there is one row where you correct it and the data stays intact.
Now you want to get the authors of a work. This is easily done with aggregation:
select w.*, group_concat(p.name) as authors
from work_author wa
join person p on p.person_id = wa.person_id
join work w on w.work_id = wa.work_id
group by w.work_id
order by w.work_id;
You forgot to tell us your DBMS. As you are using double quotes where it must be single quotes according to the SQL standard, and your DBMS doesn't complain, this may be MySQL. (You should still always use single quotes for string literals.) For MySQL the string aggregation function is GROUP_CONCAT, so guessing MySQL, I used that in my query. Other DBMS use STRING_AGG, LISTAGG or something else.
If you just want to show up to two authors per work, you can take the minimum and maximum name (and compare the two in order not to show the same author twice):
select
w.*,
min(p.name) as author1,
case when min(p.name) <> max(p.name) then max(p.name) end as author2
from ...
UPDATE
In the comments you say that for every author you want to know all authors who worked with them. For this you need to join authors to authors based on their works. Still assuming MySQL:
select p1.name, group_concat(distinct p2.name) as others
from work_author wa1
join work_author wa2 on wa2.work_id = wa1.work_id
and wa2.person_id <> wa1.person_id
join person p1 on p1.person_id = wa1.person_id
join person p2 on p2.person_id = wa2.person_id
group by p1.name
order by p1.name;
Or not aggregated:
select distinct p1.name as person1, p2.name as person2
from work_author wa1
join work_author wa2 on wa2.work_id = wa1.work_id
and wa2.person_id <> wa1.person_id
join person p1 on p1.person_id = wa1.person_id
join person p2 on p2.person_id = wa2.person_id
order by p1.name, p2.name;
I changed the model as proposed by Thorsten Kettner and solved the task of matching authors with their co-authors as follows:
CREATE TABLE famous_people (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
profession TEXT,
birth_year INTEGER);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Landau", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Lifshitz", "physicist", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Fisher", "statistician", 1908);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("Ginzburg", "physicist", 1916);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("A. Strugatsky", "writer", 1925);
INSERT INTO famous_people (name, profession, birth_year)
VALUES ("B. Strugatsky", "writer", 1933);
CREATE TABLE works (id INTEGER PRIMARY KEY AUTOINCREMENT,
work TEXT,
subject TEXT);
INSERT INTO works (work, subject)
VALUES ("Theoretical Physics", "physics");
INSERT INTO works (work, subject)
VALUES ("Theory of Superconductivity", "physics");
INSERT INTO works (work, subject)
VALUES ("Fisher test", "statistics");
INSERT INTO works (work, subject)
VALUES ("Roadside Picnic", "scifi");
INSERT INTO works (work, subject)
VALUES ("Hard to Be a God", "scifi");
CREATE TABLE author_works (id INTEGER PRIMARY KEY AUTOINCREMENT,
work_id INTEGER,
author_id INTEGER);
INSERT INTO author_works (work_id, author_id) VALUES (1, 1);
INSERT INTO author_works (work_id, author_id) VALUES (1, 2);
INSERT INTO author_works (work_id, author_id) VALUES (2, 1);
INSERT INTO author_works (work_id, author_id) VALUES (2, 4);
INSERT INTO author_works (work_id, author_id) VALUES (3, 3);
INSERT INTO author_works (work_id, author_id) VALUES (4, 5);
INSERT INTO author_works (work_id, author_id) VALUES (4, 6);
INSERT INTO author_works (work_id, author_id) VALUES (5, 5);
INSERT INTO author_works (work_id, author_id) VALUES (5, 6);
/* List of authors and their works */
SELECT famous_people.name, works.work FROM author_works
JOIN famous_people
ON author_works.author_id = famous_people.id
JOIN works
ON works.id = author_works.work_id;
/* Authors and co-authors ids*/
SELECT DISTINCT a.name, b.name
FROM author_works aw1
JOIN author_works aw2
ON aw1.work_id = aw2.work_id
JOIN famous_people a
ON aw1.author_id = a.id
JOIN famous_people b
ON aw2.author_id = b.id
WHERE aw1.author_id <> aw2.author_id;

How to select from a master table but replace certain rows using a secondary, linked table?

I have two tables with a foreign key relationship on an ID. I'll refer to them as master and secondary to make things easier and also not worry about the FK for now. Here is cut down, easy to reproduce example using table variables to represent the problem:
DECLARE #Master TABLE (
[MasterID] Uniqueidentifier NOT NULL
,[Description] NVARCHAR(50)
)
DECLARE #Secondary TABLE (
[SecondaryID] Uniqueidentifier NOT NULL
,[MasterID] Uniqueidentifier NOT NULL
,[OtherInfo] NVARCHAR(50)
)
INSERT INTO #Master ([MasterID], [Description])
VALUES ('0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3', 'Test')
,('2696ECD2-FFDB-4E26-83D0-F146ED419C9C', 'Test 2')
,('F21568F0-59C5-4950-B936-AA73DA6009B5', 'Test 3')
INSERT INTO #Secondary (SecondaryID, MasterID, Otherinfo)
VALUES ('514673A6-8B5C-429B-905F-15BD8B55CB5D','0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3','Other info')
SELECT [MasterID], [Description], NULL AS [OtherInfo] FROM #Master
UNION
SELECT S.[MasterID], M.[Description], [OtherInfo] FROM #Secondary S
JOIN #Master M ON M.MasterID = S.MasterID
With the results.....
0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3 Test NULL
0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3 Test Other info
F21568F0-59C5-4950-B936-AA73DA6009B5 Test 3 NULL
2696ECD2-FFDB-4E26-83D0-F146ED419C9C Test 2 NULL
.... I would like to only return records from #Secondary if there is a duplicate MasterID, so this is my expected output:
0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3 Test Other info
F21568F0-59C5-4950-B936-AA73DA6009B5 Test 3 NULL
2696ECD2-FFDB-4E26-83D0-F146ED419C9C Test 2 NULL
I tried inserting my union query into a temporary table, then using a CTE with the partition function. This kind of works but unfortunately returns the row from the #Master table rather than the #Secondary table (regardless of the order I select). See below.
DECLARE #Results TABLE (MasterID UNIQUEIDENTIFIER,[Description] NVARCHAR(50),OtherInfo NVARCHAR(50))
INSERT INTO #Results
SELECT [MasterID], [Description], NULL AS [OtherInfo] FROM #Master
UNION
SELECT S.[MasterID], M.[Description], [OtherInfo] FROM #Secondary S
JOIN #Master M ON M.MasterID = S.MasterID
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY [MasterID] ORDER BY [Description] DESC) FROM #Results
)
SELECT * FROM CTE WHERE RN =1
Results:
0C1F1A0C-1DB5-4FA2-BC70-26AA9B10D5C3 Test NULL 1
F21568F0-59C5-4950-B936-AA73DA6009B5 Test 3 NULL 1
2696ECD2-FFDB-4E26-83D0-F146ED419C9C Test 2 NULL 1
Note that I am not just trying to select the rows which have a value for OtherInfo, this is just to help differentiate the two tables in the result set.
Just to reiterate, what I need to only return the rows present in #Secondary, when there is a duplicate MasterID. If #Secondary has a row for a particular MasterID, I don't need the row from #Master. I hope this makes sense.
What is the best way to do this? I am happy to redesign my database structure. I'm effectively trying to have a master list of items but sometimes take one of those and assign extra info to it + tie it to another ID. In this instance, that record replaces the master list.
You are way overcomplicating this. All you need is a left join.
SELECT M.[MasterID], M.[Description], S.[OtherInfo] FROM #Master M
LEFT JOIN #Secondary S ON M.MasterID = S.MasterID
Union seems to be the wrong approach... I would suggest a left join:
SELECT m.[MasterID], m.[Description], s.[OtherInfo]
FROM #Master m
LEFT JOIN #Secondary s ON s.MasterID = m.MasterID

How to join tables , query sql

I have the following recipe database tables and their data .
how i can find the total number of recipes , number of category for each Ingredient ? I used many joining methods but i couldn't do the query i want.
I need as out put
Ingredient id , how much recipe we can find this ingredient in, how much categories we can find this ingredients in.
This is my attempt
The problem with my attempt is if i had ingredient who is in one recipe and in two categories
It will show in results that this ingredient is in 2 recipe , 2 categories
SELECT
I.idIng,COUNT(CI.idcat)AS "CAT FOR ING" , COUNT(RI.idRecipe )AS "RECETTE
FOR
ING"
FROM
INGREDIENT I
LEFT JOIN
Ingredient_Recipe RI ON I.idIng = ri.idIng
RIGHT JOIN
Ingredient_Catigory CI ON I.idIng = CI.idIng
GROUP BY
I.idIng
ORDER BY
I.idIng;
Below is some test data:
Here i created my category its will have many to many relation with Ingredient.
-- creating table cat category
CREATE TABLE category (
idCat INT NOT NULL PRIMARY KEY,
nomCat INT NOT NULL
);
Here i created my Recipe table its will have many to many relation with Ingredient.
-- creating table cat Recipe
CREATE TABLE Recipe(
idRecipe INT NOT NULL PRIMARY KEY,
nameRecipe VARCHAR2(30) NOT NULL
);
This is my Ingredient table that will be link with both Recipe ,category .
-- creating table Ingredient
CREATE TABLE Ingredient(
idIng INT NOT NULL PRIMARY KEY ,
nameIng VARCHAR2(30) NOT NULL
);
This is the intermediate table between Ingredient ,category because the relation is many to many.
-- creating table Ingredient_category
CREATE TABLE Ingredient_category (
idIng INT NOT NULL,
idCat INT NOT NULL,
CONSTRAINT idIng_FK FOREIGN KEY (idIng) REFERENCES Ingredient(idIng),
CONSTRAINT idCat_FK FOREIGN KEY (idCat) REFERENCES category(idCat)
);
This is the intermediate table between Ingredient ,Recipe because the relation is many to many.
-- creating table Ingredient_Recipe
CREATE TABLE Ingredient_Recipe(
idIng INT NOT NULL,
idRecipe INT NOT NULL,
CONSTRAINT idIngRecipe_FK FOREIGN KEY (idIng) REFERENCES
Ingredient(idIng),
CONSTRAINT idRecipe_FK FOREIGN KEY (idRecipe) REFERENCES Recipe(idRecipe)
);
Here we insert the data for testing.
-- insert data into Recipe
INSERT INTO Recipe VALUES(1,'SOUP');
INSERT INTO Recipe VALUES(2,'FRIED');
INSERT INTO Recipe VALUES(3,'BURGER');
-- insert data into category
INSERT INTO category VALUES(1,'VEGES');
INSERT INTO category VALUES(2,'DAIRY');
INSERT INTO category VALUES(3,'MEAT');
INSERT INTO category VALUES(4,'ANIMAL PRODUCT');
-- insert data into Ingredient
INSERT INTO Ingredient VALUES (1,'Eggs');
INSERT INTO Ingredient VALUES (2,'milk');
INSERT INTO Ingredient VALUES (3,'Beef');
INSERT INTO Ingredient VALUES (4,'chess');
-- insert data into Ingredient_Catigory
INSERT INTO Ingredient_Catigory VALUES(1,4);
INSERT INTO Ingredient_Catigory VALUES(2,2);
INSERT INTO Ingredient_Catigory VALUES(2,4);
INSERT INTO Ingredient_Catigory VALUES(3,3);
INSERT INTO Ingredient_Catigory VALUES(3,4);
INSERT INTO Ingredient_Catigory VALUES(4,2);
INSERT INTO Ingredient_Catigory VALUES(4,4);
-- insert data into Ingredient_Recip
INSERT INTO Ingredient_Recipe VALUES (1,2);
INSERT INTO Ingredient_Recipe VALUES (1,3);
INSERT INTO Ingredient_Recipe VALUES (2,1);
INSERT INTO Ingredient_Recipe VALUES (3,3);
INSERT INTO Ingredient_Recipe VALUES (3,2);
I think you're looking for COUNT(DISTINCT value), you're almost there. Try something like this;
SELECT
I.idIng,
COUNT(DISTINCT CI.idCat)AS [Categories],
COUNT(DISTINCT RI.idRecipe)AS [Recipes]
FROM #INGREDIENT I
LEFT JOIN #Ingredient_Recipe RI ON I.idIng = ri.idIng
LEFT JOIN #Ingredient_Category CI ON I.idIng = CI.idIng
GROUP BY
I.idIng
ORDER BY
I.idIng
The results look like this;
idIng Categories Recipes
1 1 2
2 2 1
3 2 2
4 2 0
Please note, I think a bit of the spelling was incorrect in the sample data but I've corrected it on my test system (and I've used #TempTables). I've changed your RIGHT JOIN to a LEFT JOIN (as a note, I've never seen a need to use RIGHT JOIN in production code, try to avoid them).
Edit: I've just noticed that this is now an Oracle question, the query above has only been tested on SQL Server although a cursory glance at the documentation shows that the syntax should be the same for Oracle too.
select * from Ingredient_Recipe a join Ingredient_category b on a.idIng=b.idIng join Ingredient c on a.idIng=c.idIng join Recipe d on a.idRecipe=d.idRecipe join category e on b.idCat=e.idCat
try this out

Select rows that have a specific set of items associated with them through a junction table

Suppose we have the following schema:
CREATE TABLE customers(
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE items(
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE customers_items(
customerid INTEGER,
itemid INTEGER,
FOREIGN KEY(customerid) REFERENCES customers(id),
FOREIGN KEY(itemid) REFERENCES items(id)
);
Now we insert some example data:
INSERT INTO customers(name) VALUES ('John');
INSERT INTO customers(name) VALUES ('Jane');
INSERT INTO items(name) VALUES ('duck');
INSERT INTO items(name) VALUES ('cake');
Let's assume that John and Jane have id's of 1 and 2 and duck and cake also have id's of 1 and 2.
Let's give a duck to John and both a duck and a cake to Jane.
INSERT INTO customers_items(customerid, itemid) VALUES (1, 1);
INSERT INTO customers_items(customerid, itemid) VALUES (2, 1);
INSERT INTO customers_items(customerid, itemid) VALUES (2, 2);
Now, what I want to do is to run two types of queries:
Select names of customers who have BOTH a duck and a cake (should return 'Jane' only).
Select names of customers that have a duck and DON'T have a cake (should return 'John' only).
For the two type of queries listed, you could use the EXISTS clause. Below is an example query using the exists clause:
SELECT cust.name
from customers AS cust
WHERE EXISTS (
SELECT 1
FROM items
INNER JOIN customers_items ON items.id = customers_items.itemid
INNER JOIN customers on customers_items.customerid = cust.id
WHERE items.name = 'duck')
AND NOT EXISTS (
SELECT 1
FROM items
INNER JOIN customers_items ON items.id = customers_items.itemid
INNER JOIN customers on customers_items.customerid = cust.id
WHERE items.name = 'cake')
Here is a working example: http://sqlfiddle.com/#!6/3d362/2