SQL Joins Excluding Data - sql

Say I have three tables:
Fruit (Table 1)
------
Apple
Orange
Pear
Banana
Produce Store A (Table 2 - 2 columns: Fruit for sale => Price)
-------------------------
Apple => 1.00
Orange => 1.50
Pear => 2.00
Produce Store B (Table 3 - 2 columns: Fruit for sale => Price)
------------------------
Apple => 1.10
Pear => 2.50
Banana => 1.00
If I would like to write a query with Column 1: the set of fruit offered at Produce Store A UNION Produce Store B, Column 2: Price of the fruit at Produce Store A (or null if that fruit is not offered), Column 3: Price of the fruit at Produce Store B (or null if that fruit is not offered), how would I go about joining the tables?
I am facing a similar problem (with more complex tables), and no matter what I try, if the "fruit" is not at "produce store a" but is at "produce store b", it is excluded (since I am joining produce store a first). I have even written a subquery to generate a full list of fruits, then left join Produce Store A, but it is still eliminating the fruits not offered at A. Any Ideas?

The key is to use a left join. This will include columns from the left side table even if there is no matching row in the right side table.
For example:
select f.name, a.price, b.price
from Fruit f
left join ProduceStoreA a on a.Name = f.Name
left join ProduceStoreB b on b.Name = f.Name
If a fruit is not found in Store A, the second column will be null. If it's not found in Store B, the third column will be null. If neither sells the fruit, both column two and three will be null.

Related

Getting ranges of arbitrary strings in SQL based on sequence dictated in a separate table

Consider the following dataset (may look weird but want to land my point that the strings are arbitrary):
Table A
TicketId
StartAnimal
EndAnimal
1
Monkey
Bee
1
Lion
Buffalo
Table B
Animal
Sequence
Monkey
1
Zebra
2
Bee
3
Turtle
4
Lion
5
Buffalo
6
Is it possible to retrieve the animals that correspond to Ticket ID 1 based on the different "ranges" in each of its rows? For example,for Ticket ID 1 the following animals should be retrieved: Monkey, Zebra, Bee, Lion, Buffalo.
As you can see the animal strings themselves have no order logic to it, but the sequence can be leveraged for it. I'm just failing to come up with how to reference it for each row in a single query.
Edit
As an edge case, sometimes the EndAnimal might not even have a sequence to start with, in which case only the StartAnimal should be returned. As an example, assuming Bee is not in the sequence table, we should only get Monkey, Lion and Buffalo. Is that something SQL can handle?
Thanks!
There are numerous ways, one such way is to inner join the tables to find the corresponding start and end sequences and then find those rows that qualify:
with s as (
select bs.Sequence s1, IsNull(be.sequence,1) s2, a.ticketId
from a left join b bs on bs.animal = a.StartAnimal
left join b be on be.Animal = a.EndAnimal
)
select b.Animal
from b
join s on b.Sequence >=s1 and b.Sequence <= s2
where s.ticketId = 1
order by b.Sequence;
Example Fiddle

How to use SQL to label each row based on certain criteria?

I need to correctly label each row based on certain criteria. For example the data I have is like this:
Table Product
Group product_id product_name category
1 123 Egg A
1 456 Egg A
1 456 Milk A
1 789 Milk A
2 135 Apple B
2. 137 Orange B
2. 137 Banana B
2. 139 Strawberry B
3. 235 Egg A
3. 237 Apple B
3. 237 Egg B
3. 239 Orange B
3. 239 Egg B
Since product egg can be found in more than 1 product IDs and milk can be found in more than 1 product IDs, 123,456 and 789 should be marked as A. Basically if a product name appears more than once in a group, then it is marked as A, otherwise B.
I was trying to use array functions and compare them, but it doesn't work for this scenario. For example,
select product_id,array_agg(product_name) as p1 from product
Then compare p1 with another array (p2) from the self inner join.
Any hints or help would be greatly appreciated!
Have you considered using a Case When statement?
Case
when product_name = 'egg' and category = 'a' then label = 'egg1'
when product_name = 'egg' and category = 'b' then label = 'egg2'
else 'no label'
End
I am referencing this post https://dba.stackexchange.com/questions/82487/case-with-multiple-conditions for clarity. - J
I am confused with your requirement. You state product name appears only once in a group, then it is marked as A, otherwise B. However, the data you show contains the exact opposite. The following produces what you said you wanted, not the values you posted. It will be correct or exactly the reverse. (See demo)
-- if a product name appears only once in a group, then it is marked as A, otherwise B.
with prod_group (group_id, product_name, cnt) as
( select group_id, product_name, count(*)
from products
group by group_id, product_name
) -- select * from prod_group ;
update products p
set category = case when grp.cnt = 1 then 'A' else 'B' end
from prod_group grp
where ( p.group_id, p.product_name) = ( grp.group_id, grp.product_name);
How it works: The prod_group CTE simply counts the number of times a product name appears in a group. The main "query" then uses this result to update category. Contrary to to your statement case isn't really going to help the CASE expression is exactly what you need.
Note: GROUP is an extremely poor choice for a column name as it is both a Postgres (conditional) and a SQL Standard reserved word.

How to select group of distinct rows from the table

Let's assume, a table has the following rows
ID Name Value
1 Apple Red
1 Taste Sour
2 Apple Yellow
2 Taste Sweet
3 Apple Red
3 Taste Sour
4 Apple Green
4 Taste Tart
5 Apple Yellow
5 Taste Sweet
I wonder, how can I select ID's corresponding to distinct combination of Apple and Taste? For example, ID=1 corresponds to red sour apple and ID=3 can be omitted in the query result. Similarly, ID=2 is for yellow sweet apple and ID=5 can be excluded from the query result, etc. A valid query result can be any of the following ID sets: (1,2,4), (1,4,5), (2,3,4) etc.
The query or the model could be improved with more understanding of the problem.
But assuming the model is correct and the problem is presented as this, this would be my quick approach.
SELECT MIN(a.ID) as ID
FROM Table a
INNER JOIN Table b ON a.ID = b.ID AND a.Name > b.Name
GROUP BY a.Value, b.Value
This query is joining the table with itself using the ID. But because you would have four lines for each possible combination (Ex.: Apple-Apple, Taste-Taste, Apple-Taste and Taste-Apple), you need to state not only that they are different (Because you would still have Apple-Taste and Taste-Apple) but that one of them is bigger than the other (That way you choose to have Apples on one side of the join and Tastes in the other). That's why there is the a.Name > b.Name.
You then group by both the values, stating that you don't want to have more than one combination of Apple values and Taste values. Resulting in only three lines.
The Select I think it depends of the RDBMS (I used SQL Server syntax), and it's selecting the lowest ID. You don't care, so you could choose Min or Max. Min results in lines with 1,2,4. Max would result in 3,4,5.

Without using conjunctions in conditions of selection operators

Let's say there is a table call ITEM and it contains 3 attributes(name, id, price):
name id price
Apple 1 3
Orange 1 3
Banana 2 4
Cherry 3 5
Mango 1 3
How should I write a query to use a constants selection operator to select those item that have same prices and same ids ? The first thing come into my mind is use a rename operator to rename id to id', and price to price', then union it with the ITEM table, but since I need to select 2 tuples (price=price' & id=id') from the table, how can I select them without using the conjunctions operator in relational algebra ?
Thank you.
I'm not quite sure but for me, it would be something like this in relational calculus:
and then in SQL:
SELECT name FROM ITEM i WHERE
EXISTS ITEM u
AND u.name != i.name
AND u.price=i.price
AND u.id = i.id
But still, I think your assumption is right, you can still do it by renaming. I do believe it is a bit longer than what I did above.

Find the pairs that appear the most times in StoreID and each STOREID has only this pair SQL

I want to find the pairs of MOVIEID that appear more times in the STOREID.
Additionally, each STOREID should have only this pair as MOVIEIDs. My table has 2 columns: STOREID and MOVIEID.
For example:
STOREID | MOVIEID
--------|---------
1 | a
1 | b
1 | c
2 | a
2 | b
3 | a
3 | b
5 | a
5 | b
In this case the answer would be pair: (a,b) 3 times.
Thanks in advance!
As far as i understand you want to consider only stores that sell movie pairs. This makes it a lot simpler. First you group by stores and take only those results with two movies. Now to generate pairs of those would be tricky if there were more than two movies. You would need windowing functions. However, for two you get both movies with aggregation functions. One with min and the other with max. Further those functions ensure, the same pair always has the same order. For example the pair (a,b) will always be (a,b) and never (b,a).
SELECT COUNT(*), MOVIE_1, MOVIE_2
FROM (
SELECT MIN(MOVIEID) MOVIE_1
,MAX(MOVIEID) MOVIE_2
,STOREID
FROM STORE_MOVIES -- your table
GROUP BY STOREID
HAVING COUNT(*) = 2
) MOVIE_PAIRS
GROUP BY MOVIE_1, MOVIE_2
ORDER BY COUNT(*) DESC
FETCH FIRST ROW ONLY;
For HAVING COUNT(*) = 2 I assume MOVIEID together with STOREID is unique.
Although the request does not really make sense, that's not our design/implementation to concern. I have done with a 3-part self-join to your movies table. The first (m1) joins to second (m2) on the same store, but for second movie being greater than (m1) movie. This will prevent conditions of comparing (a,b) vs (b,a). Then, I am joining (m2) to (m3) by same store, but movie 3 greater than 2. This is an intentional LEFT-JOIN as not all stores will have more than 2. In this case, the value at (m3) will be NULL (non-existent). So, I am looking for where m3.storeID IS NULL. The JOIN between (m1) and (m2) requires the first and second to exist. Finally, tacking on the HAVING will show only those pairs that appear at multiple stores.
select
m1.movieID as Movie1,
m2.movieID as Movie2,
count(*) TimesPaired
from
Movies m1
JOIN movies m2
on m1.storeId = m2.storeId
AND m1.movieId < m2.movieId
LEFT JOIN movies m3
on m2.storeId = m3.storeId
AND m2.movieId < m3.movieId
where
m3.storeId IS NULL
group by
m1.movieID,
m2.movieID
having
count(*) > 1