Find recipe by ingredient name, and sort if possiple - sql

I have trouble writing queries for my database. I've found a number of similar examples here, and have been trying to get it around my head for hours, but none of the examples helped me figure out how to get the result i want. I'm building a recipe app but I'm really new to everything that concerns databases.
I would like to pick 1 or more ingredients and get matching recipe titles, and if possible, sorted by best result (recipe which contains most ingredients).
I've tried to make an inner join where I try to get recipe names together with matching ingredients.
And I've tried to do as this person does in this question because we have the same design:
Recipe Database, search by ingredient
But even when i try to do the join as he/she does, just to test (and not as i would like it) it i get:
ERROR: missing FROM-clause entry for table
Here are my tables:
Recipe_Ingredient
+-----------+---------------+
| recipe_id | ingredient_id |
+-----------+---------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 1 | 5 |
| 1 | 6 |
| 1 | 7 |
| 2 | 1 |
| 2 | 8 |
| 2 | 9 |
| 2 | 10 |
| 2 | 11 |
+-----------+---------------+
Recipe
+-----------+-----------------------+--------------+
| id | name | instructions |
+-----------+-----------------------+--------------+
| 1 | Guacamole | sample text |
| 2 | Grilled avocado toast | sample text |
+-----------+-----------------------+--------------+
Ingredient
+------------------+
| id | name |
+----+-------------+
| 1 | avocado |
| 2 | tomato |
| 3 | chili |
| 4 | garlic |
| 5 | lemon |
| 6 | salt |
| 7 | black pepper|
| 8 | pesto |
| 9 | spinach |
| 10 | hard cheese |
| 11 | bread |
+------------------+
sql
create table Recipe (
id SERIAL PRIMARY KEY,
name CHAR(50),
prep_time CHAR(10),
instructions VARCHAR(2000));
create table Ingredient (
id SERIAL PRIMARY KEY,
name CHAR(50));
create table Recipe_Ingredient (
recipe_id INT NOT NULL,
ingredient_id INT NOT NULL,
FOREIGN KEY(recipe_id) REFERENCES Recipe(id),
FOREIGN KEY(ingredient_id) REFERENCES Ingredient(id));

Are you looking query like this:
with cte as (
select recipe_id, count(*) as cnt from Recipe_Ingredient
group by recipe_id
) select r.id as Recipeid, r.name, c.cnt from Recipe r join cte c
on r.id = c.recipe_id
order by c.cnt desc

Related

How do I get data on a hierarchical structure?

In my PostgreSQL database I have a table called answers. This table stores information about how users respond to certain questions. Also, I have organizations table which stores information about the hierarchical relationship between organizations.
PostgreSQL version: PostgreSQL 11.4 (on Debian)
The answers table has such a structure:
| employee | tree_organization_id | question_id | question_text | option_id | option_text |
|----------|----------------------|-------------|-------------------------------|-----------|-------------|
| Alex | \1 | 1 | What is your favourite color? | 1 | Red |
| Mark | \1\2\3 | 1 | What is your favourite color? | 3 | Brown |
| Lily | \1\2\4 | 1 | What is your favourite color? | 2 | Yellow |
| Grace | \1\2\4 | 1 | What is your favourite color? | 1 | Red |
| Evie | \5 | 1 | What is your favourite color? | 1 | Red |
| Bob | \5\6 | 1 | What is your favourite color? | 2 | Yellow |
| Mark | \5\7 | 1 | What is your favourite color? | 3 | Brown |
The organizations table has such:
| organization_id | organization_name | parent_organization_id | tree_organization_id | organization_rang |
|-----------------|-------------------|------------------------|----------------------|-------------------|
| 1 | Alphabet | | \1 | 1 |
| 2 | Google | 1 | \1\2 | 2 |
| 3 | Calico | 1 | \1\3 | 2 |
| 4 | Youtube | 2 | \1\2\4 | 3 |
| 5 | Nest | 2 | \1\2\5 | 3 |
| 6 | Facebook | | \6 | 1 |
| 7 | Whatsapp | 5 | \6\7 | 2 |
| 8 | Instagram | 5 | \6\8 | 2 |
Let's say as input I have specific organization_id value. For example, it can be 4 (Youtube). I need to show the number of people who answered the question in this organization and its parents.
In other words, I'm trying to get a similar result:
| organization_id | organization_name | tree_organization_id | total |
|-----------------|-------------------|----------------------|-----------------|
| 1 | Alphabet | \1 | 3 | <- Alex, Lily, Grace
| 2 | Google | \1\2 | 2 | <- Lily, Grace
| 4 | Youtube | \1\2\4 | 2 | <- Lily, Grace
I tried such code but it incorrectly calculates parent's organizations.
select
organizations.organization_id,
organizations.organization_name,
organizations.tree_organization_id,
calculation.total
from
organizations
join lateral (
select
count(*) as total
from
answers
where
tree_organization_id like concat('%', '\', organizations.organization_id, '%')
and
question_id = 1
) calculation on 1 = 1
where
organization_id in (4);
Also, I used such a code. I found the parents of the organization, but how do I calculate the values in the total column correctly?
with recursive organizations_hierarchy as (
select
organizations.organization_id,
organizations.organization_name,
organizations.parent_organization_id,
organizations.tree_organization_id,
organizations.organization_rang
from
organizations
where
organizations.organization_id in (4)
union all
select
a.organization_id,
a.organization_name,
a.parent_organization_id,
a.tree_organization_id,
a.organization_rang
from
organizations a
inner join
organizations_hierarchy b
on
a.organization_id = b.parent_organization_id
)
select
organizations_hierarchy.organization_id,
organizations_hierarchy.organization_name,
organizations_hierarchy.tree_organization_id
from
organizations_hierarchy
order by
organizations_hierarchy.organization_rang;
Finally, I solve my problem. Below is the working code that I use:
select
x.organization_id,
x.organization_name,
x.tree_organization_id,
calculation.total
from (
with recursive organizations_hierarchy as (
select
organizations.organization_id,
organizations.organization_name,
organizations.organization_rang,
organizations.parent_organization_id,
organizations.tree_organization_id
from
organizations
where
organizations.orgnization_id in(4)
union
select
a.organization_id,
a.organization_name,
a.organization_rang,
a.parent_organization_id,
a.tree_organization_id
from
organizations a
inner join
organizations_hierarchy b
on
a.organization_id = b.parent_organization_id
)
select
a.*,
(
select
array_agg(b.tree_organization_id order by b.organization_rang)
from
organizations_hierarchy b
where
B.tree_organization_id like CONCAT('%', '\', a.organization_id, '%')
) as hierarchy
from
organizations_hierarchy a
order by
a.organization_rang,
a.organization_id
) x
join lateral (
select
count(*) as total
from
answers
where
tree_organization_id = any(x.hierarchy)
and
question_id = 1
) calculation on 1 = 1;

Selecting path (multiple records) where an ID number (fk) in a table references another ID number (pk) in same table

I have a table with ID numbers that reference old ID numbers (the foreign keys reference primary keys in the same table). The problem I am having is there is an unknown number of records that need to be cycled through to get back to the original RecordID. For example, the data might look like:
CategoryID | CategoryName | ParentCategory
-----------+--------------+---------------
1 | Books | NULL
2 | Music | NULL
3 | Hardcover | 1
4 | Softcover | 1
5 | Electronic | 1
6 | CD | 2
7 | MP3 | 2
8 | Rock | 6
9 | Hard Rock | 6
10 | Classic Rock | 6
11 | Fiction | 3
12 | Fiction | 4
13 | Non-Fiction | 3
14 | Reference | 13
15 | Biography | 13
So, my result set needs to be something like:
Column A | Column B | Column C
----------+--------------+-------------
1 | Books | [NULL]
2 | Music | [NULL]
3 | Hardcover | [Books]
4 | Softcover | [Books]
5 | Electronic | [Books]
6 | CD | [Music]
7 | MP3 | [Music]
8 | Rock | [CD, Music]
9 | Hard Rock | [Rock, CD, Music]
10 | Classic Rock | [Rock, CD, Music]
11 | Fiction | [Softcover, Books]
12 | Fiction | [Hardcover, Books]
13 | Non-Fiction | [Hardcover, Books]
14 | Reference | [Non-Fiction, Hardcover, Books]
15 | Biography | [Non-Fiction, Hardcover, Books]
...You get the idea. I could have an infinite number of categories to loop through and need to show a path back to the first record with a null value.
I know I need some sort of case and loop on this, if exists, then select this, if exists then select next and probably have it mapped to an XML path but I am really struggling with the syntax and logic to make this happen. Thanks!
When working with an adjacency list list like this, you will often need to use recursive common table expressions like this:
;with cte as (
select
t.*
, Path = convert(varchar(128),CategoryName)
, ParentPath = convert(varchar(128),null)
from t
where t.ParentCategory is null
union all
select
c.*
, [Path] = convert(varchar(128),p.Path + ', ' + c.CategoryName)
, ParentPath = p.Path
from t as c
inner join cte p
on c.ParentCategory = p.CategoryID
)
select
CategoryId
, CategoryName
, Path=quotename(ParentPath)
from cte
order by CategoryId
test setup: http://rextester.com/SSWK51498
returns:
+------------+--------------+---------------------------------+
| CategoryId | CategoryName | Path |
+------------+--------------+---------------------------------+
| 1 | Books | NULL |
| 2 | Music | NULL |
| 3 | Hardcover | [Books] |
| 4 | Softcover | [Books] |
| 5 | Electronic | [Books] |
| 6 | CD | [Music] |
| 7 | MP3 | [Music] |
| 8 | Rock | [Music, CD] |
| 9 | Hard Rock | [Music, CD] |
| 10 | Classic Rock | [Music, CD] |
| 11 | Fiction | [Books, Hardcover] |
| 12 | Fiction | [Books, Softcover] |
| 13 | Non-Fiction | [Books, Hardcover] |
| 14 | Reference | [Books, Hardcover, Non-Fiction] |
| 15 | Biography | [Books, Hardcover, Non-Fiction] |
+------------+--------------+---------------------------------+
References:
Sample chapter on Traversing Hierarchies - Sql Server Bible 2008
Louis Davidson - Presentations - How to Optimize a Hierarchy In SQL Server - Presentations & Demo Code

SQL compare multiple rows or partitions to find matches

The database I'm working on is DB2 and I have a problem similar to the following scenario:
Table Structure
-------------------------------
| Teacher Seating Arrangement |
-------------------------------
| PK | seat_argmt_id |
| | teacher_id |
-------------------------------
-----------------------------
| Seating Arrangement |
-----------------------------
|PK FK | seat_argmt_id |
|PK | Row_num |
|PK | seat_num |
|PK | child_name |
-----------------------------
Table Data
------------------------------
| Teacher Seating Arrangement|
------------------------------
| seat_argmt_id | teacher_id |
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
------------------------------
---------------------------------------------------
| Seating Arrangement |
---------------------------------------------------
| seat_argmt_id | row_num | seat_num | child_name |
| 1 | 1 | 1 | Abe |
| 1 | 1 | 2 | Bob |
| 1 | 1 | 3 | Cat |
| | | | |
| 2 | 1 | 1 | Abe |
| 2 | 1 | 2 | Bob |
| 2 | 1 | 3 | Cat |
| | | | |
| 3 | 1 | 1 | Abe |
| 3 | 1 | 2 | Cat |
| 3 | 1 | 3 | Bob |
| | | | |
| 4 | 1 | 1 | Abe |
| 4 | 1 | 2 | Bob |
| 4 | 1 | 3 | Cat |
| 4 | 2 | 2 | Dan |
---------------------------------------------------
I want to see where there are duplicate seating arrangements for a teacher. And by duplicates I mean where the row_num, seat_num, and child_name are the same among different seat_argmt_id for one teacher_id. So with the data provided above, only seat id 1 and 2 are what I would want to pull back, as they are duplicates on everything but the seat id. If all the children on the 2nd table are exact (sans the primary & foreign key, which is seat_argmt_id in this case), I want to see that.
My initial thought was to do a count(*) group by row#, seat#, and child. Everything with a count of > 1 would mean it's a dupe and = 1 would mean it's unique. That logic only works if you are comparing single rows though. I need to compare multiple rows. I cannot figure out a way to do it via SQL. The solution I have involves going outside of SQL and works (probably). I'm just wondering if there is a way to do it in DB2.
Does this do what you want?
select d.teacher_id, sa.row_num, sa.seat_num, sa.child_name
from seatingarrangement sa join
data d
on sa.seat_argmt_id = d.seat_argmt_id
group by d.teacher_id, sa.row_num, sa.seat_num, sa.child_name
having count(*) > 1;
EDIT:
If you want to find two arrangements that are the same:
select sa1.seat_argmt_id, sa2.seat_argmt_id
from seatingarrangement sa1 join
seatingarrangement sa2
on sa1.seat_argmt_id < sa2.seat_argmt_id and
sa1.row_num = sa2.row_num and
sa1.seat_num = sa2.seat_num and
sa1.child_name = sa2.child_name
group by sa1.seat_argmt_id, sa2.seat_argmt_id
having count(*) = (select count(*) from seatingarrangement sa where sa.seat_argmt_id = sa1.seat_argmt_id) and
count(*) = (select count(*) from seatingarrangement sa where sa.seat_argmt_id = sa2.seat_argmt_id);
This finds the matches between two arrangements and then verifies that the counts are correct.

PostgreSQL JOIN with array type with array elements

I have two tables tags and users
Table Name: tags
| id | name |
| 1 | one |
| 2 | two |
| 3 | three |
| 4 | four |
| 5 | five |
Table Name: users
| id | fname | tags |
| 1 | Ram | {1,5} |
| 2 | Sham | {1,2,3,4} |
| 3 | Bham | {1,3} |
| 4 | Kam | {5,2} |
| 5 | Lam | {4,2} |
Expected output:
| id | fname | tags |
| 1 | Ram | one, five |
| 2 | Sham | one, two, three, four |
| 3 | Bham | one, three |
| 4 | Kam | five, two |
| 5 | Lam | four, two |
Trial-1 : using JOIN
SELECT I.id, I.fname, I.tags, J.name FROM users I
JOIN tags J ON J.id = ANY(I.cached_tag_ids)
LIMIT 1
Result:
| id | fname | tags |
| 1 | Ram | one |
| 1 | Ram | five |
Expected:
| id | fname | tags |
| 1 | Ram | one, five |
Your tags should have a INTEGER[] type.
CREATE TABLE users(
id SERIAL,
fname VARCHAR(50),
tags INTEGER[]
);
Then,
SELECT I.id, I.fname, array_agg(J.name)
FROM users I
LEFT JOIN tags J
ON J.id = ANY(I.tags)
GROUP BY fname,I.id ORDER BY id
should work. See sqlfiddle
This question may help.

SQL filter from many-to-many relationship

I have basic many to many relation set with tables categories, locations trough categories_locations table. Example:
Categories table
| ID | Name |
| 1 | Category 1 |
| 2 | Category 2 |
| 3 | Category 3 |
Locations table
| ID | Name |
| 1 | Location 1 |
| 2 | Location 2 |
| 3 | Location 3 |
Categories_Locations table
| category_id | location_id |
| 1 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 3 |
How to get all location that belong to category 2 and at the same time also belong to category 3? In above example that would result only to location 3!
Filtering with OR is simple. Just a normal left join where category_id IN (matched categories). But how to filter when I want to get only those relation that belong to category1 and at the same time also to category2 (and so on)?
select
Location_ID
from CategoryLocations
where Category_ID in (2,3)
group by Location_ID
having COUNT(distinct Category_ID) = 2 -- this 2 is the number of items in the IN list above