SQL Query not consistent across different tables - mysql

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

Related

Mysql IN function

class_table
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1,2,3,4 |
+----+-------+--------------+
student_mark
+----+----------+--------+
| id |student_id| marks |
+----+----------+--------+
| 1 | 1 | 12 |
+----+----------+--------+
| 2 | 2 | 80 |
+----+----------+--------+
| 3 | 3 | 20 |
+----+----------+--------+
I have these two tables and i want to calculate the total marks of student and my sql is:
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN
(SELECT `student_id` FROM `class_table` WHERE `teac_id` = '1')
But this will return null, please help!!
DB fiddle
Firstly, you should never store comma separated data in your column. You should really normalize your data. So basically, you could have a many-to-many table mapping teacher_to_student, which will have teac_id and student_id columns.
In this particular case, you can utilize Find_in_set() function.
From your current query, it seems that you are trying to getting total marks for a teacher (summing up marks of all his/her students).
Try:
SELECT SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
In case, you want to get total marks per student, you would need to add a Group By. The query would look like:
SELECT sm.`student_id`,
SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
GROUP BY sm.`student_id`
Just in case you want to know why, The reason it returned null is because the subquery returned as '1,2,3,4' as a whole. What you need is to make it returned 1,2,3,4 separately.
What your query returned
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN ('1,2,3,4')
What you expect is
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN (1,2,3,4)
The best way is it normalize as #madhur said. In your case you need to make the teacher and student as one to many link
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1 |
+----+-------+--------------+
| 2 | 1 | 2 |
+----+-------+--------------+
| 3 | 1 | 3 |
+----+-------+--------------+
| 4 | 1 | 4 |
+----+-------+--------------+
If you want to filter your table based on a comma separated list with ID, my approach is to
append extra commas at the beginning and at the end of a list as well as at the beginning and at the end of an ID, eg.
1 becomes ,1, and list would become ,1,2,3,4,. The reason for that is to avoid ambigious matches like 1 matches 21 or 12 in a list.
Also, EXISTS is well-suited in that situation, which together with INSTR function should work:
SELECT SUM(`marks`)
FROM `student_mark` sm
WHERE EXISTS(SELECT 1 FROM `class_table`
WHERE `teac_id` = '1' AND
INSTR(CONCAT(',', student_id, ','), CONCAT(',', sm.student_id, ',')) > 0)
Demo
BUT you shouldn't store related IDs in one cell as comma separated list - it should be foreign key column to form proper relation. Joins would become trivial then.

MySQL Error to selecting data

I have two column one column associated with another...
Table:base_data
id |---name----|-----des
1 | some name1 | The description1
2 | some name2 | The description2
Table: photos
id |---p_id----|-----photo
1 | 1 | img1s.jpg
2 | 1 | img1w.jpg
3 | 2 | img2.jpg
4 | 2 | img14.jpg
5 | 2 | img15.jpg
I want to select all data from table 1(base_data) and one row from associated row from photos: table how can I do that ????
I don't want to select by greatest n per group I want to select all data from the first table and only one row of the second table which matches with the first table row id, just first match not other.
The Result I want...
id |---name----|---des----|---p_id----|---photo----|
1 | some name |the des..1| 1 | img1s.jpg|
2 | some name |the des..2| 2 | img2.jpg|
I suppose you want to associate base_data with the first photo taken, which should be the one with the lowest photos.id. In MySQL, you could write this as follows: Create an intermediate query which gives - for any p_id - the corresponding record with the lowest id. Then, left join base_data with this intermediate query result. Hope there are not to many typos in it :-) :
select b.id, p2.photo
from base_data b left join
(select p.photo, p.p_id, min(id) from photos p group by p.p_id) p2 on b.id = p2.p_id
If you want the alphanumerically lowest photo name, in MySQL you can do this:
select
t1.*,
t2.photo
from
base_data as t1
left join (
select
p_id,
min(photo) as photo
from
photos
group by
p_id
) as t2 on t2.p_id = t1.id;

Convert a column of set values to a column of individual values in MySQL

I have inherited a table where one column is a comma-separated list of primary keys for a different table:
id | other_ids | value
---|-----------|-------
1 | a,b,c | 100
2 | d,e | 200
3 | f,g | 3000
I would like to convert this table to one where each other_id gets a column of its own:
id | other_id
---|---------
1 | a
1 | b
1 | c
2 | d
2 | e
3 | f
3 | g
However, I cannot think of a way to do this?
The table is > 10 GB in size, so I would like to do this inside the database, if possible.
first time post, please be kind.
Try this
select id,SUBSTRING_INDEX(other_ids,',',1) as other_id from reverseconcat
UNION
select id,SUBSTRING_INDEX(SUBSTRING_INDEX(other_ids,',',2),',',-1) as other_id from reverseconcat
UNION
select id,SUBSTRING_INDEX(SUBSTRING_INDEX(other_ids,',',3),',',-1) as other_id from reverseconcat
order by id
Although I cant really take any credit. Found this on http://www.programering.com/a/MzMyUzNwATg.html
Unsure how you will go on a huge dataset. Also you will need to add more unions if the other_ids are > 3
If you have the other table, then you can use a join and find_in_set():
select t.id, ot.pk as other_id
from t join
othertable ot
on find_in_set(ot.pk, t.other_ids) > 0;

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

MySQL conditionally populate column 3 based on DISTINCT involving 2 other columns in one table

Had a good read through similar topics but I can't quite a) find one to match my scenario, or b) understand others enough to fit / tailor / tweek to my situation.
I have a table, the important fields being;
+------+------+--------+--------+
| ID | Name | Price |Status |
+------+------+--------+--------+
| 1 | Fred | 4.50 | |
| 2 | Fred | 4.50 | |
| 3 | Fred | 5.00 | |
| 4 | John | 7.20 | |
| 5 | John | 7.20 | |
| 6 | John | 7.20 | |
| 7 | Max | 2.38 | |
| 8 | Max | 2.38 | |
| 9 | Sam | 21.00 | |
+------+------+--------+--------+
ID is an auto-incrementing value as records get added throughout the day.
NAME is a Primary Key field, which can repeat 1 to 3 times in the whole table.
Each NAME will have a PRICE value, which may or may not be the same per NAME.
There is also a STATUS field that need to be populated based on the following, which is actually the part I am stuck on.
Status = 'Y' if each DISTINCT name has only one price attached to it.
Status = 'N' if each DISTINCT name has multiple prices attached to it.
Using the table above, ID's 1, 2 and 3 should be 'N', whilst 4, 5, 6, 7, 8 and 9 should be 'Y'.
I think this may well involve some form of combination of JOINs, GROUPs, and DISTINCTs but I am at a loss on how to put that into the right order for SQL.
In order to get the count of distinct Price values per name, we must use a GROUP BY on the Name field, but since you also want to display all names ungrouped but with an additional Status field, we must first create a subselect in the FROM clause which groups by the name and determines whether the name has multiple price values or not.
When we GROUP BY Name in the subselect, COUNT(DISTINCT price) will count the number of distinct price values for each particular name. Without the DISTINCT keyword, it would simply count the number of rows where price is not null.
In conjunction with that, we use a CASE expression to insert N into the Status column if there is more than one distinct Price value for the particular name, otherwise, it will insert Y.
The subselect only returns one row per Name, so to get all names ungrouped, we join that subselect to the main table on the condition that the subselect's Name = the main table's Name:
SELECT
b.ID,
b.Name,
b.Price,
a.Status
FROM
(
SELECT Name, CASE WHEN COUNT(DISTINCT Price) > 1 THEN 'N' ELSE 'Y' END AS Status
FROM tbl
GROUP BY Name
) a
INNER JOIN
tbl b ON a.Name = b.Name
Edit: In order to facilitate an update, you can incorporate this query using JOINs in the UPDATE like so:
UPDATE
tbl a
INNER JOIN
(
SELECT Name, CASE WHEN COUNT(DISTINCT Price) > 1 THEN 'N' ELSE 'Y' END AS Status
FROM tbl
GROUP BY Name
) b ON a.Name = b.Name
SET
a.Status = b.Status
Assuming you have an unfilled Status column in your table.
If you want to update the status column, you could do:
UPDATE mytable s
SET status = (
SELECT IF(COUNT(DISTINCT price)=1, 'Y', 'N') c
FROM (
SELECT *
FROM mytable
) s1
WHERE s1.name = s.name
GROUP BY name
);
Technically, it should not be necessary to have this:
FROM (
SELECT *
FROM mytable
) s1
but there is a mysql limitation that prevents you to select from the table you're updating. By wrapping it in parenthesis, we force mysql to create a temporary table and then it suddenly is possible.