Joining table mysql -- without double iteration - php

Let me start by saying this should be a relatively simple problem which is / was made unnecessary complicated by bad Database design (not by me) that said im also no expert in mysql.
Consider the following
Table Schedule
Note how the columns homeID and visitorID contains the names of the teams and not the actual teamID's
In a bid to fix this I created a new table with columns containing teamID AND teamName as can be seen by below image.
Table Teams
My Problem(s)
I must get the teamID from table Teams for BOTH home team AND away team
So I created the Teams table and this simple script:
SELECT schedule.*, teams.*
FROM schedule
JOIN teams ON schedule.homeID = teams.teamName OR schedule.visitorID = teams.teamName
WHERE schedule.gameID = 411
LIMIT 1 #added Limit1 else the code generates to rows
Output of mysql Script
Limit 1
Notice above how teamID is only generated for 1 team with Limit 1
No Limit Statement (Double Iteration)
Notice above how teamID can get retrieved for BOTH teams. Problem is its doing a double iteration.
TLDR; The above presents the following problems
Firstly the script will generate two outputs one for home team and once for away team. As to be expected however I cant have that.
As a workaround to Problem number 1 -- I added Limit 1 the problem I get with Limit though is that it only gives back a single teamID (as to be expected, I guess)
Question
How can I get BOTH teamID's from table teams with a single iteration? Hope this make sense....
Extra
A demo of application with hard coded team names looks like this (just to give an idea of what they are trying to achieve)

Sounds like you want to join teams twice to schedule.
SELECT s.*,
th.*,
ta.*
FROM schedule s
INNER JOIN teams th
ON s.homeid = th.teamname
INNER JOIN teams ta
ON s.visitorid = ta.teamname
WHERE s.gameid = 411;

I guess that you want to show both team in one row instead of two rows.
If yes, then you need to join the table teams twice.
Consider this demo: http://www.sqlfiddle.com/#!9/bb5e61/1
This join will collect both teams into one row:
SELECT s.*,
t1.teamId as homeId_teamId,
t1.teamCode as homeId_teamCode,
t1.teamName as homeId_teamName,
t2.teamId as visitorId_teamId,
t2.teamCode as visitorId_teamCode,
t2.teamName as visitorId_teamName
FROM Schedule s
JOIN Teams t1 ON s.homeId = t1.teamName
JOIN Teams t2 ON s.visitorId = t2.teamName;
| id | homeId | visitorId | homeId_teamId | homeId_teamCode | homeId_teamName | visitorId_teamId | visitorId_teamCode | visitorId_teamName |
|----|--------|-----------|---------------|-----------------|-----------------|------------------|--------------------|--------------------|
| 1 | Poland | Colombia | 1 | PL | Poland | 2 | CO | Colombia |
However you can also consider LEFT joins instead on INNER joins, which will work in a case where there is no relevant data in the TEAMS table:
SELECT s.*,
t1.teamId as homeId_teamId,
t1.teamCode as homeId_teamCode,
t1.teamName as homeId_teamName,
t2.teamId as visitorId_teamId,
t2.teamCode as visitorId_teamCode,
t2.teamName as visitorId_teamName
FROM Schedule s
LEFT JOIN Teams t1 ON s.homeId = t1.teamName
LEFT JOIN Teams t2 ON s.visitorId = t2.teamName;
| id | homeId | visitorId | homeId_teamId | homeId_teamCode | homeId_teamName | visitorId_teamId | visitorId_teamCode | visitorId_teamName |
|----|----------|-----------|---------------|-----------------|-----------------|------------------|--------------------|--------------------|
| 1 | Poland | Colombia | 1 | PL | Poland | 2 | CO | Colombia |
| 3 | Ya Majka | Poland | (null) | (null) | (null) | 1 | PL | Poland |
| 2 | Ya Majka | Rossija | (null) | (null) | (null) | (null) | (null) | (null) |
Here are the scripts that make up the tables from the examples
CREATE TABLE Schedule(
id int, homeId varchar(20),visitorId varchar(20)
);
INSERT INTO Schedule VALUES
(1, 'Poland', 'Colombia' ),(2,'Ya Majka','Rossija'),
(3,'Ya Majka','Poland');
CREATE TABLE Teams(
teamId int, teamCode varchar(10), teamName varchar(20)
);
INSERT INTO Teams VALUES
(1, 'PL', 'Poland' ),(2,'CO','Colombia'),(3,'US','United States');

You can use a subquery (two of them in the same query) to solve this:
select
gameID,
weekNum,
gameTimeEastern,
(select teamName from teams where teamID = schedule.homeID) as homeName,
homeScore,
(select teamName from teams where teamID = schedule.visitorID) as visitorName,
visitorScore from schedule;
This doesn't get all the columns from schedule, just an example to show how it works. If you need various queries (including select *, though this isn't a good practice except for testing), you could create a view based on a query like the above (with ALL columns from schedule, except homeID and visitorID that get replaced with sub-queries from the teams table). Then you can place queries against that view - and they will work like the original table where you had team names directly in it.

Related

How to get 3 Users on one ROW using SQL ? (MySQL)

I have two arrays like this,
First Table (infos):
------------------------------
| ID | User1 | User2 | User3 |
------------------------------
| 1 | 20 | 30 | 12 |
------------------------------
Second Table (Users):
---------------------
| ID | Name | Email |
---------------------
| 12 | Test | Test# |
---------------------
| 20 | Bla | Test# |
---------------------
| 30 | Bate | Test# |
---------------------
I want to get the information of users on one row from the IDs on the first table.
I try by getting The row from the first table and fetching on users, but I want to optimize the function with just one Query.
SELECT * FROM infos;
SELECT * FROM Infos i,Users u WHERE u.ID = u.User1 (or 2 ...)
Is there any solution ?
You could use joining the table users 3 times, one for each userid you want show the related name (or other values):
select a.id
, a.user1
, b.Name as user1name
, a.user2
, c.name as user2name
, a.user3
, d.name as user3name
from infos a
inner join Users b on a.user1 = b.id
inner join Users c on a.user1 = c.id
inner join Users d on a.user1 = d.id
And just as suggested, you should not use old implicit join syntax based on comma-separated table names and where clause, you should use (since 1992) explicit joins. This syntax performs the same query, but is more clear.
This is a design error. Use a N:N relation (an additional table) to allow any number of users for the first table. With the relation, other queries will be easier.
A relation table looks like this:
create table relation
(
table1_id int unsigned not NULL,
table2_id int unsigned not NULL,
primary key(table1_id,table2_id)
);
A typical query (and I dislike a.* generally):
select a.*, b.*
from table1 a, table2 b, relation r
where r.table1_id = a.id
&& r.table2_id = b.id

MySQL: Replace all instances of specific foreign key with new value

I have a MySQL database with 1000s of personnel records, often with duplicates.
For each case with at least one duplicate I want to be able to delete all of the duplicates but one, then update any references to those deleted foreign keys with the one I did not.
For example, we see two instances of Star Lord below:
+-----------------------+
| `users` |
+------+----------------+
| id | name |
+------+----------------+
| 1 | Star Lord |
+------+----------------+
| 2 | Star Lord |
+------+----------------+
| 3 | Iron Man |
+------+-----+----------+
+-----------------------+
| `messages` |
+------+-----+----------+
| from | to | text |
+------+-----+----------+
| 1 | 5 | hi |
+------+-----+----------+
| 2 | 5 | how r u |
+------+-----+----------+
| 5 | 2 | Good, u? |
+------+-----+----------+
Those two tables should become:
+-----------------------+
| `users` |
+------+----------------+
| id | name |
+------+----------------+
| 1 | Star Lord |
+------+----------------+
| 3 | Iron Man |
+------+-----+----------+
+-----------------------+
| `messages` |
+------+-----+----------+
| from | to | text |
+------+-----+----------+
| 1 | 5 | hi |
+------+-----+----------+
| 1 | 5 | how r u |
+------+-----+----------+
| 5 | 1 | Good, u? |
+------+-----+----------+
Can this be done? I'm happy to use PHP as needed.
I found the following, but it's only for finding foreign key usage, not replacing instances for specific key values: MySQL: How to I find all tables that have foreign keys that reference particular table.column AND have values for those foreign keys?
Bonus Points
There may be additional data which needs to be merged in the users table. For example, Star Lord with ID #1 might have a phone field filled in, but Star Lord with ID #2 has an email field.
Worst case: they both have a field, with conflicting data.
I suggest:
Create a table of correct data. A good starting point might be:
CREATE TABLE users_new LIKE users;
ALTER TABLE users_new ADD UNIQUE (name);
INSERT INTO users_new
(id, name, phone, email)
SELECT MIN(id), name, GROUP_CONCAT(phone), GROUP_CONCAT(email)
FROM users
GROUP BY name;
Note that, due to your "worst case" observation under "Bonus Points", you may well want to manually verify the contents of this table before archiving the underlying users data (I advise against permanent deletion, just in case).
Update existing foreign relationships:
UPDATE messages
JOIN (users uf JOIN users_new unf USING (name)) ON uf.id = messages.from
JOIN (users ut JOIN users_new unt USING (name)) ON ut.id = messages.to
SET messages.from = unf.id,
messages.to = unt.id
If you have a lot of tables to update, you could cache the results of the join between users and users_new—either:
in a new_id column within the old users table:
ALTER TABLE users ADD new_id BIGINT UNSIGNED;
UPDATE users JOIN users_new USING (name)
SET users.new_id = users_new.id;
UPDATE messages
JOIN users uf ON uf.id = messages.from
JOIN users ut ON ut.id = messages.to
SET messages.from = uf.new_id,
messages.to = ut.new_id;
or else in a new (temporary) table:
CREATE TEMPORARY TABLE newid_cache (
PRIMARY KEY(old_id),
KEY(old_id, new_id)
) ENGINE=MEMORY
SELECT users.id AS old_id, users_new.id AS new_id
FROM users JOIN users_new USING (name);
UPDATE messages
JOIN newid_cache nf ON nf.old_id = messages.from
JOIN newid_cache nt ON nt.old_id = messages.to
SET messages.from = nf.new_id,
messages.to = nt.new_id;
Either replace users with users_new, or else modify your application to use the new table in place of the old one.
ALTER TABLE users RENAME TO users_old;
ALTER TABLE users_new RENAME TO users;
Update any foreign key constraints as appropriate.
I like to be really methodical about this, while you could write it all in one complex query, that's an optimisation, and unless it's obvious, an unnecessary one.
First backup your database :)
Create a table to hold the ids of the users you are going to keep.
Fill it with say
Insert into Keepers Select keep_id From (Select Min(id) as keep_id,`name` From `users`)
After that it's just some update with joins.
e.g.
UPDATE
`messages` m JOIN
keepers k
ON k.keeper_id = m.from
SET m.from = k.keeper_id
UPDATE
`messages` m JOIN
keepers k
ON k.keeper_id = m.to
SET m.to = k.keeper_id
Then get rid of the users you don't want
Delete `users`
from `users` u
outer join keepers on k.keeper_id = u.id
where i.id is null
When all is good e.g you have the same number of messages as you started with, no one is talking to themselves etc.
Delete the keepers table.
Syntax not checked, but it should be close.

Mysql - join 2 queries with limit

Im not very familiar with using 'join' in queries. I really tried solving this by my own, but it seems to be too hard.
I got 2 Tables:
Table 'users':
+-----------------+-----------------+
| member | online |
+-----------------+-----------------+
| mahran | 1 |
| peter | 1 |
| Jen | 1 |
| Steve | 0 |
+-----------------+-----------------+
Table 'tickets'
+-----------------+----------------+----------------+
| name | category | time |
+-----------------+----------------+----------------+
| mahran | silver | 1 |
| peter | blue | 1 |
| mahran | blue | 2 |
| peter | red | 3 |
| peter | green | 2 |
| Jen | silver | 1 |
+-----------------+----------------+----------------+
The chellange:
I need each member (users.member) who's online (users.online). The next thing is to get the category for each member (user.member = tickets.name) with the highest time (probably ORDER BY time DESC LIMIT 1).
So, for example:
Peter is online. Peters highest time is 3 at the position of category=red. So I want peter to show up in the result with his category 'red'. Mahran would show up with blue. Jen would get silver. And steve would be left out because he's not online.
I hope this was clear. In general I know how the queries would look like but theres no chance for me merging them together.
What needs to be merged:
SELECT member FROM users WHERE online = 1;
|
v for each member
SELECT category FROM tickets WHERE name=users.member ORDER BY time DESC.
So, any ideas how to solve this?
Here is a fiddle with a not working query: Click
You can do this easily with a correlated subquery:
select u.member,
(select t.category
from tickets t
where t.name = u.member
order by t.time desc
limit 1
) as MostRecentCategory
from users u
where u.online = 1;
This can make use of the following indexes: users(online, member) and ticket(member, time, category).
Here is the query you're looking for:
SELECT U.member
,T.category
FROM users U
INNER JOIN tickets T ON T.name = U.member
INNER JOIN (SELECT T2.name
,MAX(T2.time) AS [maxTime]
FROM tickets T2
GROUP BY T2.name) AS M ON M.name = T.name
AND M.maxTime = T.time
WHERE U.online = 1
The use of [name] to join the two tables is not a good practice, it's much better to use keys instead. But my query is just here to help you understanding the process of jointure.
Hope this will help you.
If i understand you correctly
SELECT DISTINCT users.member, tickets.category FROM tickets JOIN users ON users.member = tickets.name WHERE users.online = 1 ORDER BY tickets.time DESC
Can you make sql fiddle?
USE DISTINCT
stackoverflow.com/questions/11704012/mysql-distinct-join
try this
SELECT DISTINCT User.member,Ticket.category FROM users AS USER
INNER JOIN tickets AS Ticket ON (User.member = Ticket.name)
WHERE User.online = 1;
Sorry, but peter seems to be RED, It's time is 3. Don't you?
Depending on table definition, is not guaranteed to have one only result for each user.
For example, if peter has time 3 in two categories, you can get one different category depending of the SQL sorting method.
To be sure, tickets.Category and tickets.time must be in a unique key (both toghether, not a unike key for each field)
Assuming that, the Query could be this.
select t2.name, t2.category
from
tickets t2
INNER JOIN (Select
u.member, max(time)
from users u, tickets t
where
u.member = t.name
and u.online = 1
group by u.member
) as usermaxtime on t2.name = usermaxtime.member;

SQL inner join of multiple tables and search through database

I'm making a search function in PHP and I have three tables that I wish to join to a single one; the three tables looks as follow:
band
ID | bands
---+----------
1 | Muse
2 | Coldplay
3 | etc.
release
ID | releases
---+----------
1 | Showbiz
2 | Origin of Symmentry
3 | etc.
track
ID | tracks
---+-----------
1 | Sunburn
2 | Muscle Museum
3 | etc.
I want these tables to be put into this:
discografic
ID | band_id | release_id | track_id
---+----------+-------------+---------
1 | 1 | 1 | 1
2 | 1 | 1 | 2
3 | etc.
So that the table with the SQL code looks like this:
discografic
ID | bands | releases | tracks
---+----------+-------------+---------
1 | Muse | Showbiz | Sunburn
2 | Muse | Showbiz | Muscle Museum
3 | etc.
I want to INNER JOIN these tables. I joined one but I can't really figure out how the get the last joined as well.
SELECT *
FROM band
INNER JOIN discografic
ON band.id = discografic.band_id
This should probably have its own question; I also want to be able to search this database, but only have the result show up once, and also reference to the band every time. For example, if I search "Showbiz" it will give me "Muse", and only show it once.
Note: This is for testing purposes only, security is none of my concerns.
Try with this query:
select d.id,b.bands,r.releases,t.tracks from discografic as d INNER JOIN band as b on
d.band_id=b.id INNER JOIN release as r on d.release_id=r.id INNER JOIN track as t on
d.track_id=t.id GROUP BY d.id
Try This query
Select a.ID,b.bands,c.releases,d.tracks from discografic as a
inner join band as b on a.band_id = b.ID
inner join release as c on a.release_id = c.ID
inner join track as d on a.track_id = d.ID
where b.bands = 'Muse'
Use this query to insert the data like you wanted:
Insert into discograpy
(id,bands,releases,tracks)
SELECT band.ID,bands,releases,tracks
FROM band
INNER JOIN releases
ON band.id = releases.id
inner join track
on band.id = track.id
Use this query to show you only one band:
Declare #releases varchar(50)
Set #releases = 'showbiz'
SElect distinct bands from discograpy where releases = #releases
Here any variable can be passed or set in place of showbiz. This is an example

Select from two tables where rows from second table should be shown as columns in first

I have two tables in MySQL:
Table entry:
id | name | date
1 | Test Entry | 12/12/2013
2 | Test Entry 2 | 12/12/2013
Table note
id | entry_id | name | value
1 | 1 | note1 | value1
2 | 1 | note2 | value2
3 | 2 | note1 | value1
4 | 3 | note4 | value4
Where entry_id in note is a foreign key to id in entry.
Is there any solution I can create with a SELECT that will give me a result like the following?
entry_id | name | note1 | note2 | note3
1 | Test Entry | value1 | value2 | -
2 | Test Entry 2 | value 1 | - | value3
I want to avoid LEFT JOIN here (current implementation is working like this) and want to join note only once if that is possible. LEFT JOIN is not good here, because I do not know how many notes can be attached to one entry. My current implementation works that way that I first fetch all distinct notes by name that can be found in note, and then build a SELECT with foreach through PHP. Finally, the SELECT statement looks like this:
SELECT
E.id as entry_id,
E.name as name,
N1.value as note1_value,
N2.value as note2_value,
N3.value as note3_value
FROM entry E
JOIN LEFT note N1 ON E.id = N1.entry_id AND N1.name = 'note1'
JOIN LEFT note N2 ON E.id = N2.entry_id AND N2.name = 'note2'
JOIN LEFT note N3 ON E.id = N3.entry_id AND N3.name = 'note3'
Things get tricky when I join on note 20-30 times.
No, there is not a way to do that without joins.
I would recommend doing 2 queries.
select * from entry where id = id
select * from note where entry_id = id
and then join the results in your application code. You're right, the left joins are going to be bad.
Best would be to use a table with one note value and a note type (number) per each line.
id | value | note_no
Like this you can use as much notes as you like.
You can get the notes on one line using group_concat.
For an example, see here: http://lietoservetruth.wordpress.com/2010/12/13/mysql-group_concat-vertival-concat-concat-records/
This is faster than asking DB twice, and it's better DB design...

Categories