How to use variable from another table for UNION - mysql

I have two sql commands I want to combine. I have changed the variables I am actually using in an attempt make it simpler to explain.
I would like to get the name of all fruits and vegetables with the colors that are a favoriteColor of everyone who's age is equal to the given value.
Currently I have these queries split up and I get the favorite color of people with SELECT favoriteColor FROM people WHERE age = ? and then I get all the fruits and vegetables where the color matches the favoriteColor of each person.
I get the matching fruits and vegetables like this:
SELECT * FROM ((SELECT 1 as type, name FROM fruits WHERE color = ?)
UNION ALL
(SELECT 2 as type, name FROM vegetables WHERE color = ?)) results
I basically want something like this, but I haven't been able to get it to work and I also do not want to have to run the same SELECT query twice:
SELECT * FROM ((SELECT 1 as type, name FROM fruits WHERE color =
(SELECT favoriteColor FROM people WHERE age = ?))
UNION ALL
(SELECT 2 as type, name FROM vegetables WHERE color =
(SELECT favoriteColor FROM people WHERE age = ?))) results
And I don't mind if I get duplicated fruits and vegetables, I need the duplicates for my situation.
For example:
If there are 2 people who are 30 years old and both of them like the color red, I want to get all fruits and vegetables that are red twice.
If there are 2 people who are 10 years old and one of them likes the color red and the other one also likes the color green, I want to get all fruits and vegetables that are red and green.

Not sure why you thought you had to test the colour in the union since the driver is people. And I have guessed what our output should be.
create table people(id int,name varchar(10),colour varchar(1),age int);
insert into fruits values
(1,'a','a'),(2,'b','a'),(3,'b','b'),(4,'b','c');
insert into vegetables values
(1,'t','a'),(2,'t','u'),(3,'v','v'),(4,'v','w');
insert into people values
(1,'aa','a',10),(2,'bb','b',10),(3,'cc','c',10),(4,'dd','c',11);
select p.name,p.age,p.name,s.`type`,s.name,s.colour
from people p
join
(
select 1 as type, name,colour from fruits
union
select 2 as type, name,colour from vegetables
) s
on s.colour = p.colour
where p.age = 10;
+------+------+------+------+------+--------+
| name | age | name | type | name | colour |
+------+------+------+------+------+--------+
| aa | 10 | aa | 2 | t | a |
| aa | 10 | aa | 1 | b | a |
| aa | 10 | aa | 1 | a | a |
| bb | 10 | bb | 1 | b | b |
| cc | 10 | cc | 1 | b | c |
+------+------+------+------+------+--------+
5 rows in set (0.00 sec)

i don't know but the simplied version may be
SELECT * FROM ((SELECT 1 as type, name, color FROM fruits WHERE color = ?) UNION ALL
(SELECT 2 as type, name, color FROM vegetables WHERE color = ?)) results
where results.color= (SELECT favoriteColor FROM people WHERE age = ?)
sorry for indentation

I'd do it as a pair of unions to create one unified dataset, joined to another (then filtered) dataset:
SELECT * FROM
(
SELECT 1 as type, name, color FROM fruits
UNION ALL
SELECT 2 as type, name, color FROM vegetables
) plants pl
INNER JOIN
people pe ON pl.color = pe.favoriteColor
WHERE
pe.age = 30
If you want different columns out of fruit and veg, and there might not be a fruit or veg row for a given color:
SELECT * FROM
people pe
LEFT JOIN fruits f on pe.favoriteColor = f.color
LEFT JOIN veg v on pe.favoriteColor = f.color
WHERE
pe.age = 30
But bear in mind that multiple fruits or veg of a given color will cause the result set to multiply in duplicate for the other plant, which could become a nightmare to deal with on the front end

Related

WHERE/GROUP By Condition - One Name but multiple values

I have the following table:
Name Product
Bob Car
Bob Apples
Bob Pears
Bob Car
John Apples
John Pears
Whoever has bought a Product Car, I want to keep separate from everyone else. So, I create a flag:
Name Product Flag
Bob Car 1
Bob Apples 0
Bob Pears 0
Bob Car 1
John Apples 0
John Pears 0
But the problem with my flag is that even if I do a where condition and say, show me the consumer WHERE flag !=1, it'll pick Bob. Which is incorrect as Bob owns a car.
I would still like to GROUP by Product.
How do I separate the above table into two groups?
Thanks!
Use below query :-
select name from table where flag!=1
and name not in (select name from table where flag = 1)
group by name
"show me the consumer WHERE flag !=1, it'll pick Bob" that is because you are asking for rows where flag != 1. Instead you'll need something a little more complicated, like:
SELECT DISTINCT Name
FROM tableTable
WHERE Name NOT IN (SELECT Name FROM theTable WHERE Product = 'Car')
alternatively, you can do a LEFT JOIN, which may or may not be faster depending on the amount of data you have and how its values are distributed.
SELECT DISTINCT a.Name
FROM theTable a
LEFT JOIN theTable b ON a.Name = b.Name AND b.Product = 'Car'
WHERE a.Product != 'Car' AND b.Product IS NULL
;
This gets all the rows with products other than cars, and then uses the LEFT JOIN in conjunction with the IS NULL condition to find which did not also have a 'Car' row.
I think you want your table's data displayed, just with "People who bought cars" partitioned (not grouped) separately somehow - this could be done with an ORDER BY OwnsACar clause, for example.
Step 1: Identify the people who have bought cars:
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
Step 2: Join on this data to generate a calculated "OwnsACar" column:
SELECT
yourTable.Name,
yourTable.Product,
ISNULL( carowners.Name ) AS OwnsACar
FROM
yourTable
LEFT OUTER JOIN
(
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
) AS carowners ON carowners.Name = yourTable.Name
ORDER BY
OwnsACar ASC,
yourTable.Name ASC
You can use these two queries. The additional Flag column is not required.
-- do not have Car
SELECT *
FROM products
WHERE Name not in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
-- have Car
SELECT *
FROM products
WHERE Name in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
Illustration:
-- table
SELECT * FROM products;
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
| John | Apples |
| John | Pears |
+------+---------+
-- query for people that do not have Car
+------+---------+
| Name | Product |
+------+---------+
| John | Apples |
| John | Pears |
+------+---------+
-- query for people having 'Car'
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
+------+---------+
Try with :
SELECT `t`.`Name`, `t`.`Product`, SUM(`t`.`Flag`) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;
Although you can go without the flag column by going :
SELECT `t`.`Name`, `t`.`Product`, SUM(IF(`t`.`Product` = 'Car', 1, 0)) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;

Group results from different tables by rank

I'm sure I'm not the first to need to do this, but I couldn't find a similar question that accounted for the nuance.
I have 3 tables (fav_food, fav_color, and fav_place) that all follow a similar pattern:
userid | label | rank |
=================================
1 | red | 1
1 | green | 2
1 | orange | 3
2 | blue | 1
2 | red | 2
...
Each table will have at most 3 items per userid, but some users might have fewer than 3, and other users might have none for a given table. i.e., it's possible user 10 has 2 favorite colors, 1 favorite food, and 0 favorite places.
I'm looking for a query that can output my data like so:
userid | fav_food | fav_place | fav_color | rank
===========================================================
1 | pizza | New York | red | 1
1 | burgers | NULL | green | 2
1 | NULL | NULL | orange | 3
2 | tacos | Chicago | blue | 1
2 | burgers | Orlando | red | 2
...
Basically, all ranked 1 items together, ranked 2 items together, and ranked 3 items together (NULLs were no item of that rank exists).
I was able to get it working using 3 separate queries (one for each table) + post processing at the application layer, but for the sake of my personal knowledge base, I was wondering if anyone knew how to do it in a single query.
Many thanks!
#Isick,
You can do this with a LEFT OUTER JOIN on each table to a table containing just the userid and rank. DEMO
select user_rank.userid, user_rank.rank, f.food, p.place, c.color from
(
select userid, rank from fav_food
union
select userid, rank from fav_place
union
select userid, rank from fav_color
) user_rank
left outer join
( select userid, rank, label as food from fav_food) f
on user_rank.userid = f.userid and user_rank.rank = f.rank
left outer join
( select userid, rank, label as place from fav_place) p
on user_rank.userid = p.userid and user_rank.rank = p.rank
left outer join
( select userid, rank, label as color from fav_color) c
on user_rank.userid = c.userid and user_rank.rank = c.rank
order by userid, rank

Outliers of data by groups

I want to analyse outliers a of grouped data. Lets say I have data:
+--------+---------+-------+
| fruit | country | price |
+--------+---------+-------+
| apple | UK | 1 |
| apple | USA | 3 |
| apple | LT | 2 |
| apple | LV | 5 |
| apple | EE | 4 |
| pear | SW | 6 |
| pear | NO | 2 |
| pear | FI | 3 |
| pear | PL | 7 |
+--------+---------+-------+
Lets take pears. If my method of finding outliers would be to take 25% highest prices of pears and lowest 25%, outliers of pears would be
+--------+---------+-------+
| pear | NO | 2 |
| pear | PL | 7 |
+--------+---------+-------+
As for apples:
+--------+---------+-------+
| apple | UK | 1 |
| apple | LV | 5 |
+--------+---------+-------+
That I want is to create a view, which would show table of all fruits outliers union. If I had this view, I could analyse only tails, also intersect view with main table to get table without outliers - that's my goal. Solution to this would be:
(SELECT * FROM fruits f WHERE f.fruit = 'pear' ORDER BY f.price ASC
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0)
FROM fruits f2
WHERE f2.fruit = 'pear')
)
union all
(SELECT * FROM fruits f WHERE f.fruit = 'pear' ORDER BY f.price DESC
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0)
FROM fruits f2
WHERE f2.fruit = 'pear')
)
union all
(SELECT * FROM fruits f WHERE f.fruit = 'apple' ORDER BY f.price ASC
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0)
FROM fruits f2
WHERE f2.fruit = 'apple')
)
union all
(SELECT * FROM fruits f WHERE f.fruit = 'apple' ORDER BY f.price DESC
LIMIT (SELECT ROUND(COUNT(*) * 0.25,0)
FROM fruits f2
WHERE f2.fruit = 'apple')
)
This would give me a table I want, however code after LIMIT doesn't seem to be correct... Another problem is number of groups. In this example there are only two groups(pears,apples), but in my actual data there are around 100 groups. So 'union all' should somehow automatically go thru all unique fruits without writing code for each unique fruit, find number of outliers of each unique fruit, take only that numbe of rows and show it all in another table(view).
You can't supply LIMIT with a value from a subquery, in any RDBMS I'm aware of. Some dbs don't even allow host variables/parameters in their versions of the clause (I'm thinking of iSeries DB2).
This is essentially a greatest-n-per-group problem. Similar queries in most other RDBMSs are solved with what are called Windowing functions - essentially, you're looking at a movable selection of data.
MySQL doesn't have this functionality, so we have to counterfeit it. The actual mechanics of the query will depend on the actual data you need, so I can only speak to what you're attempting here. The techniques should be generally adaptable, but may require rather more creativity than otherwise.
To start with you want a function that will return a number indicating it's position - I'm assuming duplicate prices should be given the same rank (ties), and that doing so won't create a gap in the number. This is essentially the DENSE_RANK() windowing function. We can get these results by doing the following:
SELECT fruit, country, price,
#Rnk := IF(#last_fruit <> fruit, 1,
IF(#last_price = price, #Rnk, #Rnk + 1)) AS Rnk,
#last_fruit := fruit,
#last_price := price
FROM Fruits
JOIN (SELECT #Rnk := 0) n
ORDER BY fruit, price
Example Fiddle
... Which generates the following for the 'apple' group:
fruit country price rank
=============================
apple UK 1 1
apple LT 2 2
apple USA 3 3
apple EE 4 4
apple LV 5 5
Now, you're trying to get the top/bottom 25% of rows. In this case, you need a count of distinct prices:
SELECT fruit, COUNT(DISTINCT price)
FROM Fruits
GROUP BY fruit
... And now we just need to join this to the previous statement to limit the top/bottom:
SELECT RankedFruit.fruit, RankedFruit.country, RankedFruit.price
FROM (SELECT fruit, COUNT(DISTINCT price) AS priceCount
FROM Fruits
GROUP BY fruit) CountedFruit
JOIN (SELECT fruit, country, price,
#Rnk := IF(#last_fruit <> fruit, 1,
IF(#last_price = price, #Rnk, #Rnk + 1)) AS rnk,
#last_fruit := fruit,
#last_price := price
FROM Fruits
JOIN (SELECT #Rnk := 0) n
ORDER BY fruit, price) RankedFruit
ON RankedFruit.fruit = CountedFruit.fruit
AND (RankedFruit.rnk > ROUND(CountedFruit.priceCount * .75)
OR RankedFruit.rnk <= ROUND(CountedFruit.priceCount * .25))
SQL Fiddle Example
...which yields the following:
fruit country price
=======================
apple UK 1
apple LV 5
pear NN 2
pear NO 2
pear PL 7
(I duplicated a pear row to show "tied" prices.)
Does round not need 2 / 3 arguments? I.e. do you not need to put in, to what decimal place you wish to round?
so
...
LIMIT (SELECT ROUND(COUNT(*) * 0.25)
FROM #fruits f2
WHERE f2.fruit = 'apple')
becomes
...
LIMIT (SELECT ROUND(COUNT(*) * 0.25,2)
FROM #fruits f2
WHERE f2.fruit = 'apple')
also, just having a quick look at lunch, but it looks like you're just expecting the min / max values. Could you not just use those functions instead?

MySQL linking table intersection query

Sorry if my question sounds misleading, I'm not fully sure how to formulate it.
Consider the following tables: Fruit which has an ID and Name, Person which also has an ID and Name, and Person_has_Fruit (many to many linking table) which has a Person_ID and a Fruit_ID.
What transaction can retrieve the people which have two or more specific fruits? Basically how can I intersect the results?
Example:
Fruit table
ID | Name
-----------------
1 | Apple
2 | Pineapple
3 | Banana
4 | Lemon
Person table
ID | Name
-----------------
1 | Tom
2 | Bill
3 | John
Many to many table
PersonID | FruitID
-----------------
3 | 1
1 | 2
3 | 2
2 | 3
3 | 3
I want a query to retrieve me John when I ask for the person which has Apple, Pineapple and Banana. Any suggestions?
I've tried 'SELECT * FROM Person_has_Fruit WHERE FruitID in ('1', '2', '3')' but that is incorrect as it retrieves all the person IDs which have one of them, so basically it implies an or relationship between the values.
You are looking to perform a relational division between the some tuples of Fruit table and the Person table.
So:
Select * From Person p
Where Not Exists ( Select * from Fruit f
Where (Name = 'Apple' Or Name = 'Pinneapple' or Name = 'Banana')
And Not Exists ( Select * from Person_has_Fruit pf
Where pf.PersonId = p.ID and pf.FruitId=f.ID))
Try create a view first.
CREATE VIEW fruitowners AS
( SELECT personfruit.personID, fruittable.id, persontable.person, fruittable.fruit
FROM persontable, fruittable, personfruit
WHERE personfruit.personID=persontable.id AND personfruit.fruitID=fruittable.id )
Then:
SELECT fruit FROM fruitowners
WHERE person='John'
returns all the fruit types John owns
And:
SELECT person FROM fruitowners
WHERE fruit='Banana'
returns all the banana owners.
And:
SELECT COUNT(*) FROM fruitowners WHERE person='John'
returns the number of fruits John owns
And:
SELECT COUNT('fruit') FROM fruitowners WHERE person='John' and fruit='Banana'
returns how many bananas John owns

Query to display the order of items

I am trying to get a list of fruits eaten by different people.
+-----------+
ID Name
+----+------+
1 Paul
2 John
3 Nick
+----+------+
Table: users
+--------+--------+
userID Fruit
+--------+--------+
1 Apple
2 Peach
2 Orange
2 Apple
3 Apple
3 Peach
+--------+--------+
Table: eats
Now I can easily get a list of who ate what. But if I want to know who ate what fruit 2nd? Result would be:
+--------+----------+
Name Fruit
+--------+----------+
John Orange
Nick Peach
+--------+----------+
Result
SELECT Name, Fruit FROM users, eats WHERE ID = userID ... ???
As #lanzz points out, you can't count on the order. Maintaining a sequence would be expensive, but let's say you're storing the timestamp and the rows are returned in order...
Use a variable to remember how many rows you've seen for each name. Iterate the variable while looking at the same person, and reset it when you hit a new person. Then spit out only the records that have the desired sequence. (This has the added benefit of being able to select the user's nth row for any value of n).
set #seen_name = null;
set #seq = 1;
select * from (
SELECT Name, Fruit,
case when #seen_name is null OR #seen_name != name then #seq := 1 else #seq := #seq + 1 end as seq_formula,
#seen_name := name,
#seq as seq
FROM users, eats
WHERE ID = userID
) foo
where seq = 2;
and voila:
+------+--------+-------------+--------------------+------+
| Name | Fruit | seq_formula | #seen_name := name | seq |
+------+--------+-------------+--------------------+------+
| John | Orange | 2 | John | 2 |
| Nick | Peach | 2 | Nick | 2 |
+------+--------+-------------+--------------------+------+
This query ought to join the tables:
SELECT eats.Fruit, users.Name FROM eats, users WHERE users.ID = eats.userID
You cannot rely on row order in a relational database without ordering the rows explicitly, as the order of the returned rows will vary as you delete and insert new records in your table. You will need to have an additional column, say sequence, that you will explicitly store the order of the fruits consumed. Then, it is straightforward to select the fruit with sequence = 2.