MySQL linking table intersection query - mysql

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

Related

Collect all rows from different tables with true/false value depending on tables they came from

If I have these different tables:
Participants_1
name | age
James | 18
Participants_2
name | age
Daniel | 20
James | 18
How can I insert it to another table like this?
All_Participants
name | age | in_participants_1 | in_participants_2
James | 18 | 1 | 1
Daniel | 20 | 0 | 1
So basically the All_Participants tables is the collection of all rows from other Participants tables with additional fields of is_participants_* if they came from specific tables.
You need a UNION ALL with GROUP BY:
-- INSERT INTO ...
SELECT name, age, MAX(in_participants_1), MAX(in_participants_2)
FROM (
SELECT name, age, 1 AS in_participants_1, 0 AS in_participants_2 FROM participants_1
UNION ALL
SELECT name, age, 0, 1 FROM participants_2
) AS u
GROUP BY name, age
Ideally you would store all participants in one table and store their ids in participants 1 and 2 table. Or better, create one participation table that stores participant id and event id.

SQL Query not consistent across different tables

I have 3 tables.
Team name, Team win ratio, Team location.
Team name, Team win ratio, Team location, Team value, Team fan count.
Team name, Player name, Player salary.
Table 1
Table 2
Table 2
Team name
Team name
Team name
Team win ratio
Team win ratio
Team location
Team location
Team value
Team fan count
Player name
Player salary
I need to combine 2 tables out of the 3:
either Table 1 combine Table 3 based on same primary key Team name, or
Table 2 join Table 3 based on same primary key Team name.
When I combine table 1 and 3 by team name, the join works perfectly, resulting in results with columns: Team name, Team win ratio, Team location, Player name, Player salary. However, when doing table 2 and 3, I get no results, despite table 2 and table 1 being virtually the same thing.
I tried checking to see if Team name in table 1 and Team name in table 2 are the same by doing:
SELECT TABLE1.TEAMNAME, TABLE2.TEAMNAME
FROM TABLE1
JOIN TABLE2 ON TABLE1.TEAMNAME = TABLE2.TEAMNAME
Or something along those lines, and I get the table with columns: table1teamname and table2teamname side by side with no issues, and they are exactly the same. However, when I do something along the lines of:
SELECT TABLE2.TEAMNAME, TABLE3.TEAMNAME
FROM TABLE2
JOIN TABLE3 ON UPPER(TABLE3.TEAMNAME) LIKE ('%'||UPPER(TABLE2.TEAMNAME)||'%')
I get no results, whereas if trying something like:
SELECT TABLE1.TEAMNAME, TABLE3.TEAMNAME
FROM TABLE1
JOIN TABLE3 ON UPPER(TABLE3.TEAMNAME) LIKE ('%'||UPPER(TABLE1.TEAMNAME)||'%')
It works completely fine, and I get the two columns table2.teamname and table 3.teamname that are all similar (team name in table 3 is a longer version and capitalised) side by side but matching exactly.
Why would this issue occur? Table 1 and Table 2 are basically the same other than the addition of a couple columns in the table, with the same results, but one table having a little more columns per row.
So to recap, table 1 and 2 are exactly the same, but table 1 fuzzy match on team name with table 3 works and gives rows of data, whereas table 2 fuzzy match with table 3 gives no rows with no matched data. Why is this?
Maybe there is unexpected space padding. Check out the following:
CREATE TABLE names (
id int
, name varchar(20)
) charset=UTF8;
INSERT INTO names VALUES
(1, 'name1')
, (2, 'name1 ')
, (3, 'name2')
;
SELECT names.*
, 'name1' = names.name
, 'name1' LIKE CONCAT('%', names.name, '%')
FROM names
;
Result:
+------+--------+----------------------+-------------------------------------------+
| id | name | 'name1' = names.name | 'name1' LIKE CONCAT('%', names.name, '%') |
+------+--------+----------------------+-------------------------------------------+
| 1 | name1 | 1 | 1 |
| 2 | name1 | 1 | 0 |
| 3 | name2 | 0 | 0 |
+------+--------+----------------------+-------------------------------------------+
Full test case here
The extra trailing space pad on the name in row with id = 2 causes the equals comparison to match, but the LIKE comparison does not!
This behavior is supported by the SQL Standard for certain collations. See <character set specification> and "PAD SPACE characteristic".
Updated:
SELECT names.*
, 'name1' = names.name
, 'name1' LIKE CONCAT('%', names.name, '%')
, 'name1' LIKE names.name
FROM names
;
+------+--------+----------------------+-------------------------------------------+-------------------------+
| id | name | 'name1' = names.name | 'name1' LIKE CONCAT('%', names.name, '%') | 'name1' LIKE names.name |
+------+--------+----------------------+-------------------------------------------+-------------------------+
| 1 | name1 | 1 | 1 | 1 |
| 2 | name1 | 1 | 0 | 0 |
| 3 | name2 | 0 | 0 | 0 |
+------+--------+----------------------+-------------------------------------------+-------------------------+
Updated test case here

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;

UNION SELECT with dummy data

I want to create a limit of 6 rows with this logic:
Select user_id's that are in the friends list (and also in the same table)
if there are less that 6, select then another RANDOM users,but not in friends list (until limit of 6)
if there are not 6 user_id's, add some "dummy" users id (with id 0)
all "real" users must be distinct (with id > 0)
id | friends_list | name
1 2,3,5 John
2 1,7,9 Michael
3 1,2,5 Tom
4 3,2,6 Larry
The expected result must be something like this (for a given user e.g. id=1):
2, 3, 4, 0, 0, 0
You should probably store your data differently and use joins to select the data out of multiple tables. There are also other ways you might think about storing data also depending on your use cases.
PEOPLE TABLE
id | name
1 | John
2 | Michael
3 | Tom
4 | Larry
PEOPLE_FRIENDS TABLE
id | person_id | friend_id
1 | 1 | 2 //In this case John is friends with Michael
2 | 3 | 1 //In this case Tom is friends with John.
The following select would pull id's for John's friends.
SELECT * FROM PEOPLE `P` INNER JOIN PEOPLE_FRIENDS `PF` ON P.id = P.person_id WHERE P.id = 1
Again a million different ways to go about writing that query as well, but this will get you pointed in the right direction I think.

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.