How can i query in "mulitple" many to many tables? - sql

I have three tables with a many-to-many relationships
CREATE TABLE plate(
pid integer NOT NULL,
pname text
);
CREATE TABLE vegetables(
vid integer NOT NULL,
vname text
);
CREATE TABLE meat(
mid integer NOT NULL,
mname text
);
Many to Many relationship of the three tables:
+------+-----+
| pid | vid |
+------+-----+
| 1 | 13 |
| 1 | 12 |
| 2 | 12 |
+------------+
and:
+-------+---+
| pid |mid|
+-------+---+
| 1 | 2 |
| 1 | 3 |
| 2 | 3 |
+-------+---+
The **query** i need is to check :
when the user enter the ingrediants of the plate,
example:
vid"13","12"
and
mid"2","3"
then the query will check wether the ingrediants can form a plate or not, by checking the many to many relationship table.
i tried using the IN statment, but find no results
any help?

I believe you need to find plates having all the meats and then plates having all the vegetables. Once you have those simply process a JOIN to obtain an intersect.
select pid
from
(
-- plates having all the meats
select pid
from platemeat pm
where pm.mid in (12,13)
group by pid
having count(distinct mid) = 2
) t1
join
(
-- plates having all the vegetables
select pid
from plateveget pv
where pv.vid in (2,3)
group by pid
having count(distinct vid) = 2
) t2 on t1.pid = t2.pid

Related

PostgreSQL Waiting List Query

I have a table that contains WaitingListPosition records, which is essentially a ticket to signify a user is waiting to join a membership for a Club, which has its own record in the Clubs table. In the WaitingListPositions table there are multiple positions for various Clubs. This is what the table currently looks like with a SELECT query:
id | club_id | location_id | user_id | child_id | created_at
----+---------+-------------+---------+----------+-------------------------------
2 | 8 | 3 | 10 | | 2021-07-05 12:49:24.036091+00
4 | 8 | 3 | 33 | | 2021-07-05 12:55:57.54674+00
5 | 9 | 5 | | 5 | 2021-07-05 12:58:16.319837+00
I have an API that returns a list of WaitingListPosition objects in array, which returns all the WaitingListPositions that user has. I need to get the following data out but have been unsuccessful:
Each object must contain all the WaitingListPosition data
The current position in the waiting list (the list is ordered by created_at (timestamp) field)
The total amount of WaitingListPositions that correspond to the same Club of the current WaitingListPosition object
I need to figure out a query that would produce the following results for the API (using the previous data as example):
id | club_id | location_id | user_id | child_id | created_at | position | total_count
----+---------+-------------+---------+----------+-------------------------------+-----------+---------------
2 | 8 | 3 | 10 | | 2021-07-05 12:49:24.036091+00 | 1 | 2
4 | 8 | 3 | 33 | | 2021-07-05 12:55:57.54674+00 | 2 | 2
5 | 9 | 5 | | 5 | 2021-07-05 12:58:16.319837+00 | 1 | 1
I was able to successfully get the total_count into the query without any problems, perhaps not the most efficient way, but by running a subquery of all the memberships where the user/child is involved, and then getting the count for each location. Worked fine. I just cant for the life of me get the position...
This is what I've got so far, obviously the position is incorrect and is taking the row number of the wrong data.
WITH total_counts AS
(SELECT club_id,
COUNT(id)
FROM ClubWaitingListPositions
WHERE club_id = ANY((SELECT DISTINCT(club_id)
FROM ClubWaitingListPositions
WHERE user_id = $1
OR (child_id = ANY(SELECT id
FROM Children
WHERE $1 = ANY(parents)))))
GROUP BY club_id),
positions AS (SELECT results.*,
ClubLocations.location_name AS location_name,
Clubs.name AS club_name
FROM (SELECT ClubWaitingListPositions.*,
ROW_NUMBER() OVER(ORDER BY created_at) AS position,
(SELECT count
FROM total_counts
WHERE club_id = ClubWaitingListPositions.club_id)
AS total_count
FROM ClubWaitingListPositions) results
INNER JOIN ClubLocations ON ClubLocations.id = results.location_id
INNER JOIN Clubs ON ClubLocations.club_id = Clubs.id
WHERE (user_id = $1)
OR (child_id = ANY(SELECT id FROM Children WHERE $1 = ANY(parents))))
SELECT positions.*,
CONCAT(Users.first_name, ' ', Users.last_name) AS member_name
FROM positions
INNER JOIN Users ON Users.id = positions.user_id
WHERE user_id IS NOT NULL
UNION
SELECT positions.*,
CONCAT(Children.first_name, ' ', Children.last_name) AS member_name
FROM positions
INNER JOIN Children ON Children.id = positions.child_id
WHERE child_id IS NOT NULL
ORDER BY created_at DESC
Any help appreciated ! :)

Combining duplicate SQLite columns after joining parent with multiple children

I've got a SQLite database set up which looks like this:
I'm currently looking to join all of the child tables into the parent table. As you can see, all of the children are designed in the same way, so I was hoping that they would merge nicely together.
Unfortunately, joining the tables with a left join results in this:
With SQLite, is it possible to merge the columns so that the null values are ignored, and the remaining starting_item_ids and item_ids all display in the same column?
Any help would be greatly appreciated.
Edit: Here's a minimal reproducible example
CREATE TABLE `starting_item` (
`starting_item_id` integer PRIMARY KEY AUTOINCREMENT,
`quantity` integer
);
CREATE TABLE `starting_weapon` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_armor` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_gear` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
CREATE TABLE `starting_tool` (
`starting_item_id` integer,
`item_id` integer,
FOREIGN KEY (`starting_item_id`) REFERENCES `starting_item` (`starting_item_id`)
);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_item (quantity) VALUES (1);
INSERT INTO starting_weapon (starting_item_id, item_id) VALUES (1, 4);
INSERT INTO starting_armor (starting_item_id, item_id) VALUES (2, 7);
INSERT INTO starting_gear (starting_item_id, item_id) VALUES (3, 30);
INSERT INTO starting_tool (starting_item_id, item_id) VALUES (4, 20);
select * from starting_item
left join starting_weapon on starting_weapon.starting_item_id = starting_item.starting_item_id
left join starting_armor on starting_armor.starting_item_id = starting_item.starting_item_id
left join starting_gear on starting_gear.starting_item_id = starting_item.starting_item_id
left join starting_tool on starting_tool.starting_item_id = starting_item.starting_item_id
Current result:
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| starting_item_id | quantity | starting_item_id | item_id | starting_item_id | item_id | starting_item_id | item_id | starting_item_id | item_id |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 1 | 1 | 1 | 4 | | | | | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 2 | 1 | | | 2 | 7 | | | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 3 | 1 | | | | | 3 | 30 | | |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
| 4 | 1 | | | | | | | 4 | 20 |
+------------------+----------+------------------+---------+------------------+---------+------------------+---------+------------------+---------+
Intended Result:
+------------------+----------+------------------+---------+
| starting_item_id | quantity | starting_item_id | item_id |
+------------------+----------+------------------+---------+
| 1 | 1 | 1 | 4 |
+------------------+----------+------------------+---------+
| 2 | 1 | 2 | 7 |
+------------------+----------+------------------+---------+
| 3 | 1 | 3 | 30 |
+------------------+----------+------------------+---------+
| 4 | 1 | 4 | 20 |
+------------------+----------+------------------+---------+
You can have the stated desired output (minus frames, but that is just a config thing), with this query:
select * from starting_item a
join ( select * from starting_weapon
union select * from starting_armor
union select * from starting_gear
union select * from starting_tool) b
on a.starting_item_id=b.starting_item_id;
It uses a multi "union" of the identically structured subtables and joins it with the starting item table, using aliases "a" and "b".
The result is quite exactly the stated desired output:
starting_item_id quantity starting_item_id item_id
---------------- ---------- ---------------- ----------
1 1 1 4
2 1 2 7
3 1 3 30
4 1 4 20
However, in my opinion avoiding the redundant column (twice the ID) is desireable.
And I also feel somehow that a little trick to add an indicator (what kind of item we are seeing) is helpful. So I offer this for prettiness:
select * from starting_item
join ( select *, "weapon" kind from starting_weapon
union select *, "armor" kind from starting_armor
union select *, "gear" kind from starting_gear
union select *, "tool" kind from starting_tool)
using(starting_item_id);
It gets you, in case you like it:
starting_item_id quantity item_id kind
---------------- ---------- ---------- ----------
1 1 4 weapon
2 1 7 armor
3 1 30 gear
4 1 20 tool
The first change is to join with using() instead of on ..., which (in spite of using * for convenience) gets you only one ID column.
The second change is to add a column named "kind" with explicitly given content in each "unioned" part. This "unions" and "joins" up nicely and gets you a named indicator column. The advantage is that you can e.g. order by that indicator column for some tidyness.
Finally, please consider whether the unioned table used here is not actually something which you can use in your database design. I.e. use one table for all items, with an additional "kind" column.

Postgres query IN query

Is it possible in Postgres to determine if at least one result of query 1 is inside query 2 results?
For example:
SELECT * FROM items
WHERE
(SELECT id FROM users) IN (SELECT user_id FROM user_items WHERE item_id = 1)
I know that this query can be a nonsense, I'm just asking how to do that check in the where clause. In my real query (more complex), I'm getting:
(Postgrex.Error) ERROR 21000 (cardinality_violation): more than one row returned by a subquery used as an expression
if there is more than one result from query1 (query1 IN query2)
EDIT
select user_id
from notification_token n
join notification_folder f on n.user_id = f.user_id
where ((SELECT tag_id FROM notification_folder_tag WHERE notification_folder_id = f.id) IN (SELECT tag_id FROM event_tag WHERE event_id = 1))
tables:
notification_token
| user_id | notification_token |
--------------------------------------------------
| 1 | token1 |
| 2 | token2 |
| 3 | token3 |
notification_folder
| user_id | data |
--------------------------------------------------
| 1 | "useless string" |
notification_folder_tag
| notification_folder_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 1 | 2 |
| 2 | 5 |
event_tag
| event_id | tag_id |
--------------------------------------------------
| 1 | 1 |
| 2 | 2 |
| 3 | 8 |
The result that I want is user_id 1 from notification_token.
"Where" should be true because at least one tag_id from the left side of the IN (result 1,2) is contained in the right side of the IN (result 1).
Anyways i get error when the left side of the IN is composed by more than one entry. It works properly with just one entry
Try this
SELECT * FROM items
WHERE
EXISTS (SELECT id FROM users)
IN
(SELECT user_id FROM user_items WHERE item_id = 1);
If this doesn't work, go for relational database queries.
You seem to want items anyone who ordered item_1 has also ordered. If this interpretation is correct, then here is one way to write the query:
select distinct i.*
from items i join
user_items ui
on ui.item_id = i.item_id
where ui.user_id in (select ui2.user_id
from user_items ui2
where ui2.item_id = 1
);

Tree with recursive and default

Using Postgres.
I have a pricelists
CREATE TABLE pricelists(
id SERIAL PRIMARY KEY,
name TEXT,
parent_id INTEGER REFERENCES pricelists
);
and another table, prices, referencing it
CREATE TABLE prices(
pricelist_id INTEGER REFERENCES pricelists,
name TEXT,
value INTEGER NOT NULL,
PRIMARY KEY (pricelist_id, name)
);
Parent pricelist id=1 may have 10 prices.
Pricelist id=2 as a child of parent 1 may have 5 prices which override parent 1 prices of the same price name.
Child Pricelist id=3 as as a child of pricelist 2 may have 2 price which override child 2 prices of the same price name.
Thus when I ask for child 3 prices, I want to get
all prices of child 3 and
those prices of his parent (child 2) that do not exists in child 3 and
all parent 1 prices that do not exists until now.
The schema can be changed in order to be efficient.
Example:
If
SELECT pl.id AS id, pl.parent_id AS parent, p.name AS price_name, value
FROM pricelists pl
JOIN prices p ON pl.id = p.pricelist_id;
gives
| id | parent | price_name | value |
|----------|:-------------:|------------:|------------:|
| 1 | 1 | bb | 10 |
| 1 | 1 | cc | 10 |
| 2 | 1 | aa | 20 |
| 2 | 1 | bb | 20 |
| 3 | 2 | aa | 30 |
then I'm looking for a way of fetching pricelist_id = 3 prices that'd give me
| id | parent | price_name | value |
|----------|:-------------:|------------:|------------:|
| 1 | 1 | cc | 10 |
| 2 | 1 | bb | 20 |
| 3 | 2 | aa | 30 |
WITH RECURSIVE cte AS (
SELECT id, name, parent_id, 1 AS lvl
FROM pricelists
WHERE id = 3 -- provide your id here
UNION ALL
SELECT pl.id, pl.name, pl.parent_id, c.lvl + 1
FROM cte c
JOIN pricelists pl ON pl.id = c.parent_id
)
SELECT DISTINCT ON (p.price_name)
c.id, c.parent_id, p.price_name, p.value
FROM cte c
JOIN prices p ON p.pricelist_id = c.id
ORDER BY p.price_name, c.lvl; -- lower lvl beats higher level
Use a recursive CTE like here:
Total children values based on parent
Recursive SELECT query to return rates of arbitrary depth?
There are many related answers.
Join to prices once at the end, that's cheaper.
Use DISTINCT ON the get the "greatest per group":
Select first row in each GROUP BY group?

SQL query by compound index

Let's say I have a table items with columns id type number room, id is primary key, (type, number) is a unique compound key; And a table inventory with columns id, item_type, item_number, owner, id is primary key, (type, number) is a unique compound key.
Example:
items
| id | type | number | room |
+----+---------+--------+------+
| 1 | laptop | 1 | 12 |
| 2 | laptop | 2 | 13 |
| 3 | desktop | 1 | 13 |
inventory
| id | item_type | item_number | owner |
+----+-----------+-------------+-------+
| 1 | laptop | 1 | Joe |
| 2 | laptop | 2 | Joe |
| 3 | desktop | 1 | Susan |
How do I query all items owned by Joe? If I do
SELECT *
FROM items
WHERE (type, number) IN (
SELECT item_type, item_number FROM inventory WHERE owner = 'Joe'
)
I only get one row in the result, though subquery returns multiple rows. I can't seem to do join on multiple columns either, like
SELECT *
FROM items
JOIN inventory ON inventory.item_type = items.type,
inventory.item_number = items.number`
WHERE inventory.owner = 'Joe'
You ought to combine the join conditions with AND, not with a comma.