Mysql Many to Many Query - sql

I have a many to many table setup in my mysql database. Teams can be in many games and each game has 2 teams. There is a table in between them called teams_games. SHOW CREATE TABLE information follows.
I have been messing with this query for a while. At this point I don't care if it requires sub-queries, joins, or unions. I have tried all of them but nothing has been satisfactory and I think i'm missing something. The disconnect I keep having is finding the two team ids from each game and then using the tid to grab the team information.
What I would like to do is if given a game id (gid) I can query to find:
home_team_name, home_team_id, away_team_name, away_team_id, team_league(away and home will be the same league), all the information from games table
Table Create Table
teams CREATE TABLE `teams` (
`tid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(60) NOT NULL,
`league` varchar(2) NOT NULL,
`active` tinyint(1) NOT NULL,
PRIMARY KEY (`tid`)
)
CREATE TABLE teams_games (
`tid` int(10) unsigned NOT NULL,
`gid` int(10) unsigned NOT NULL,
`homeoraway` tinyint(1) NOT NULL,
PRIMARY KEY (`tid`,`gid`),
KEY `gid` (`gid`),
CONSTRAINT `teams_games_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `teams` (`tid`),
CONSTRAINT `teams_games_ibfk_2` FOREIGN KEY (`gid`) REFERENCES `games` (`gid`)
)
CREATE TABLE games (
`gid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`location` varchar(60) NOT NULL,
`time` datetime NOT NULL,
`description` varchar(400) NOT NULL,
`error` smallint(2) NOT NULL,
`home_score` smallint(2) DEFAULT NULL,
`away_score` smallint(2) DEFAULT NULL,
PRIMARY KEY (`gid`)
)

Why not just drop the teams_games table and alter games:
CREATE TABLE games (
`gid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`location` varchar(60) NOT NULL,
`time` datetime NOT NULL,
`description` varchar(400) NOT NULL,
`error` smallint(2) NOT NULL,
`home_score` smallint(2) DEFAULT NULL,
`away_score` smallint(2) DEFAULT NULL,
`home_tid` int(10) unsigned NOT NULL,
`away_tid` int(10) unsigned NOT NULL,
PRIMARY KEY (`gid`)
)
Then you can write a simple join like:
SELECT
g.*,
h.name as home_team,
a.name as away_team,
h.league as league
FROM games AS g
INNER JOIN teams AS h ON g.home_tid = h.tid
INNER JOIN teams as a ON g.away_tid = a.tid
WHERE gid = ?

I assumed homeoraway = 1 for home and homeoraway = 0 for away.
SELECT g.*, ht.name, ht.tid, at.name, at.tid, ht.league
FROM games g
JOIN team_games htg ON htg.gid = g.gid AND htg.homeoraway = 1
JOIN team ht ON ht.tid = htg.tid
JOIN team_games atg ON atg.gid = g.gid AND atg.homeoraway = 0
JOIN team at ON at.tid = atg.tid
This works by joining games to team_games for the home team, then to teams for the team info, then doing the same thing for the away team.

I assumed that 1 = home, 2 = away. You can change appropriately.
SELECT
HT.name AS home_team_name,
HT.tid AS home_team_id,
AT.name AS away_team_name,
AT.tid AS away_team_id,
HT.league AS team_league
FROM
teams_games HTG
INNER JOIN teams_games ATG ON
ATG.gid = HTG.gid AND
ATG.homeoraway = 2
INNER JOIN teams HT ON
HT.tid = HTG.tid
INNER JOIN teams AT ON
AT.tid = ATG.tid
WHERE
HTG.gid = ???
HTG.homeoraway = 1

select
th.name as home_team_name,
th.tid as home_team_id,
ta.name as away_team_name,
ta.tid as away_team_id,
th.league as team_league,
g.*
from games g
inner join teams_games tgh on (g.gid = tgh.gid and tgh.homeoraway = <HOME_VALUE>)
inner join teams_games tga on (g.gid = tga.gid and tga.homeoraway = <AWAY_VALUE>)
inner join teams th on (tgh.tid = th.tid)
inner join teams ta on (tga.tid = ta.tid)
where
g.gid = <GAME_ID>

Related

Groupby Count Query On Two Tables Filtered by Condition on Third Table

I have three tables: Category, Mail and Classification. The relationship between the tables is outlined in this SQLFiddle with some sample data: http://sqlfiddle.com/#!9/118b24/3/0
CREATE TABLE `Category` (
`id` int(6) unsigned NOT NULL,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE `Mail` (
`id` int(6) unsigned NOT NULL,
`content` varchar(500) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE `Classification` (
`id` int(6) unsigned NOT NULL,
`mail_id` int(6) unsigned NOT NULL,
`category_id` int(6) unsigned NOT NULL,
FOREIGN KEY (mail_id) REFERENCES Mail(id),
FOREIGN KEY (category_id) REFERENCES Category(id),
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
I first run a query to get the count of emails assigned to each category:
SELECT Category.name, count(Classification.category_id) FROM Category LEFT OUTER JOIN Classification ON Classification.category_id = Category.id GROUP BY Category.name
Which works fine.
But I would now like to add a filter based on the date that is in the Email collection. If I had a join with a filter collection:
SELECT Category.name, count(Classification.category_id) FROM Category JOIN Mail ON Mail.date < '2019-03-24' LEFT OUTER JOIN Classification ON Classification.category_id = Category.id GROUP BY Category.name
But now the count only doubles and it seems the filter isn't even applied. Why isn't the filter working and what should I do to fix it?
You need a JOIN condition between Classification and Mail:
SELECT ca.name, count(cl.category_id)
FROM Category LEFT JOIN
Classification cl
ON cl.category_id = c.id LEFT JOIN
Mail m
ON m.id = cl.mail_id AND -- your JOIN key goes here
m.date < '2019-03-24'
GROUP BY c.name ;

Counting forum topics and replies

I have 2 tables: posts and forum_topics. Each post (a reply) is associated with another post (a forum topic, which is then associated with the forum_topics tables).
Problem: I need to count all forum topics and replies in the posts table. This is what I have so far:
SELECT ForumTopic.id, ForumTopic.title, ForumTopic.modified, COUNT(ReplyLeftOuterJoin.id) as replies_count
FROM forum_topics AS ForumTopic
LEFT OUTER JOIN posts AS PostLeftOuterJoin
ON PostLeftOuterJoin.object_id = ForumTopic.id
AND PostLeftOuterJoin.object_type = 'forum_topic'
AND PostLeftOuterJoin.status = 'approved'
LEFT OUTER JOIN posts AS ReplyLeftOuterJoin
ON ReplyLeftOuterJoin.object_id = PostLeftOuterJoin.id
AND ReplyLeftOuterJoin.object_type = 'post'
AND ReplyLeftOuterJoin.status = 'approved'
WHERE ForumTopic.forum_category_id = 'some_id'
Edit
Currently I'm only getting a count of all replies associated with a forum_topic (post) in the posts table. I would like to get a count of forum_topics in posts table associated with a forum topic in the forum_topics table.
NB FYI, a solution to this problem should use one query only.
Here is the schema of the two tables:
DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`context_id` bigint(20) unsigned DEFAULT NULL,
`context_type` enum('resource','module','kwik','user','assignment') COLLATE utf8_unicode_ci DEFAULT NULL,
`is_private` tinyint(1) NOT NULL,
`is_unread` tinyint(4) NOT NULL,
`last_replied` datetime NOT NULL,
`object_id` bigint(20) unsigned DEFAULT NULL,
`object_type` enum('forum_topic','forum','user','post') COLLATE utf8_unicode_ci DEFAULT NULL,
`status` enum('approved','unapproved','disabled') COLLATE utf8_unicode_ci NOT NULL,
`post` text COLLATE utf8_unicode_ci NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DROP TABLE IF EXISTS `forum_topics`;
CREATE TABLE `forum_topics` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`view_count` int(10) unsigned NOT NULL,
`forum_category_id` bigint(20) unsigned NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
SELECT
ForumTopic.forum_category_id,
COUNT(DISTINCT PostLeftOuterJoin.id) as forumtopics_count,
COUNT(ReplyLeftOuterJoin.id) as replies_count
FROM forum_topics AS ForumTopic
LEFT OUTER JOIN posts AS PostLeftOuterJoin
ON PostLeftOuterJoin.object_id = ForumTopic.id
AND PostLeftOuterJoin.object_type = 'forum_topic'
AND PostLeftOuterJoin.status = 'approved'
LEFT OUTER JOIN posts AS ReplyLeftOuterJoin
ON ReplyLeftOuterJoin.object_id = PostLeftOuterJoin.id
AND ReplyLeftOuterJoin.object_type = 'post'
AND ReplyLeftOuterJoin.status = 'approved'
WHERE ForumTopic.forum_category_id = 'some_id'
GROUP BY
ForumTopic.forum_category_id
;
maybe you can get the result in two queries
the following query will give you the count of posts in each forum
select f.id,f.title,f.modified,Type='Posts',Number=count(*)
from forum_topics as f
inner join posts p on p.forumid=f.id --u used in your query PostLeftOuterJoin.id ( is this the forum id or the posts id ?)
where p.status='approved' and p.object_type='forum_topic'
group by f.id,f.title,f.modified
union
select f.id,f.title,f.modified,Type='Replies',Number=count(*)
from forum_topics as f
inner join posts p on p.forumid=f.id --u used in your query PostLeftOuterJoin.id ( is this the forum id or the posts id ?)
where p.status='approved' and p.object_type='posts'
group by f.id,f.title,f.modified
to distinguish between the posts and replies, i added the Type
and from your application level you can get the forum post count when Type=Posts and same as for the replies
another method
select f.id,f.title,f.modified,Posts=(select count(*) from posts p where f.id=p.object_id and p.status='approved' and p.object_type='forum_topic'),
Replies=(select count(*) from posts p on where f.id=p.object_id
and p.status='approved' and p.object_type='posts')
from forum_topics f
where f.forum_category_id='someid'
hope that this will help you
regards

Which is the best way to perform this query? Maybe UNION

I have these four tables described bellow. Basically I have feeds with entries relationed with categories, and each category can be a main category or not (flag named as "principal").
Also each feed can be a partner feed or not (flag named as "parceiro").
I want to select all feed entries from partrners feeds, so I have this:
SELECT `e` . * , `f`.`titulo` AS `feedTitulo` , `f`.`url` AS `feedUrl`
FROM `feed_entries` AS `e`
INNER JOIN `feeds` AS `f` ON e.feed_id = f.id
INNER JOIN `entries_categorias` AS `ec` ON ec.entry_id = e.id
INNER JOIN `categorias` AS `c` ON ec.categoria_id = c.id
WHERE
e.deleted =0
AND
f.parceiro =1
GROUP BY `e`.`id`
ORDER BY `e`.`date` DESC
LIMIT 5
Now I need to include in this result all entries from no partners feeds that are in main categories, I mean, only entries in main categories. So, the query bellow do this:
SELECT `e` . * , `f`.`titulo` AS `feedTitulo` , `f`.`url` AS `feedUrl`
FROM `feed_entries` AS `e`
INNER JOIN `feeds` AS `f` ON e.feed_id = f.id
INNER JOIN `entries_categorias` AS `ec` ON ec.entry_id = e.id
INNER JOIN `categorias` AS `c` ON ec.categoria_id = c.id
WHERE
e.deleted =0
AND
c.principal =1
AND
f.parceiro =0
GROUP BY `e`.`id`
ORDER BY `e`.`date` DESC
LIMIT 5
I need to merge these results in one query with limit 5 ordened by date.
Is UNION the best solution, if so, how to write the query?
CREATE TABLE categorias (
id int(11) NOT NULL auto_increment,
nome varchar(100) collate utf8_unicode_ci NOT NULL,
principal int(1) NOT NULL default '0',
PRIMARY KEY (id),
UNIQUE KEY nome (nome)
)
CREATE TABLE entries_categorias (
id int(11) NOT NULL auto_increment,
entry_id int(11) NOT NULL,
categoria_id int(11) NOT NULL,
PRIMARY KEY (id),
KEY entry_id (entry_id),
KEY categoria_id (categoria_id)
)
CREATE TABLE feeds (
id int(11) NOT NULL auto_increment,
categoria_id int(11) NOT NULL,
titulo varchar(255) collate utf8_unicode_ci NOT NULL,
link varchar(255) collate utf8_unicode_ci NOT NULL,
url varchar(255) collate utf8_unicode_ci NOT NULL,
parceiro int(1) NOT NULL,
PRIMARY KEY (id),
KEY categoria_id (categoria_id)
)
CREATE TABLE feed_entries (
id int(11) NOT NULL auto_increment,
feed_id int(11) NOT NULL COMMENT 'Testando os comentários',
titulo varchar(255) collate utf8_unicode_ci NOT NULL,
descricao text collate utf8_unicode_ci NOT NULL,
slug varchar(255) collate utf8_unicode_ci NOT NULL,
link varchar(255) collate utf8_unicode_ci NOT NULL,
permaLink varchar(255) collate utf8_unicode_ci NOT NULL,
html text collate utf8_unicode_ci NOT NULL,
`date` datetime NOT NULL,
created_at datetime NOT NULL,
deleted int(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY permaLink (permaLink),
KEY feed_id (feed_id)
)
All that you should have to do is change the WHERE statement in the query:
SELECT `e`.*, `f`.`titulo` as `feedTitulo`, `f`.`url` as `feedUrl`
FROM `feed_entries` as `e`
INNER JOIN `feeds` as `f`
ON e.feed_id = f.id
INNER JOIN `entries_categorias` as `ec`
ON ec.entry_id = e.id
INNER JOIN `categorias` AS `c`
ON ec.categoria_id=c.id
WHERE (e.deleted = 0 AND f.parceiro = 1)
OR (e.deleted = 0 AND c.principal=1 AND f.parceiro = 0)
GROUP BY `e`.`id`
ORDER BY `e`.`date` desc
LIMIT 5
The new where statement has two conditions, so rather than querying the same set of tables/joins twice, we just query once and check both conditions!

SQL joins query not acting as wanted

I have the following tables:
CREATE TABLE `attendance_event_attendance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`talk_id` varchar(200) NOT NULL,
`membersAttended_id` varchar(200) NOT NULL,
PRIMARY KEY (`id`),
KEY `attendance_event_attendance_9ace4e5a` (`talk_id`),
KEY `attendance_event_attendance_3c0dadb7` (`membersAttended_id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
CREATE TABLE `attendance_member` (
`name` varchar(200) NOT NULL,
`telephone_number` varchar(200) NOT NULL,
`email_address` varchar(200) NOT NULL,
`membership_type` varchar(1) NOT NULL,
`membership_number` varchar(200) NOT NULL,
PRIMARY KEY (`membership_number`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `attendance_talk` (
`title` varchar(200) NOT NULL,
`speaker` varchar(200) NOT NULL,
`date_of_talk` date NOT NULL,
PRIMARY KEY (`title`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
I want to select all the members that have not attended the two latest talks. The query I have written looks like this:
SELECT m.name
from attendance_member as m
left outer join attendance_event_attendance as ea on (ea.membersAttended_id=m.membership_number)
join attendance_talk as t on (ea.talk_id = t.title)
where t.date_of_talk >= 2010-06-01
AND ea.membersAttended_id = null;
Is this correct? Or have I not understood joins correctly?
A somewhat horrible approach, I fear - but one that should work...
SELECT m.name
from attendance_member as m
left outer join (
SELECT ea.membersAttended_id
FROM attendance_event_attendance as ea
join attendance_talk as t on (ea.talk_id = t.title)
where t.date_of_talk >= 2010-06-01
GROUP BY ea.membersAttended_id
HAVING COUNT(*) = 2
) attendingmembers
ON attendingmembers.membersAttended_id = m.membership_number
WHERE attendingmembers.membersAttended_id IS NULL
Pretty much exactly like you would say it in English
Select Distinct m.name -- Select names
From attendance_member M -- of members
Where Not Exists -- who did not attend the last two talks
(Select * From attendance_event_attendance a
Join attendance_talk t
On a.talk_id = t.title
Where a.membersAttended_id = m.membership_number
And (Select Count(*) From attendance_talk
Where date_of_talk >= t. date_of_talk) <= 2)
NOTE: The subquery:
(Select * From attendance_event_attendance a
Join attendance_talk t
On a.talk_id = t.title
Where a.membersAttended_id = m.membership_number -- (correlated w/outer query)
And (Select Count(*) From attendance_talk
Where date_of_talk >= t. date_of_talk) <= 2)
returns the list of members who attended the talks which have 2 or fewer subsequent talks

Help with SELECT statement

I have two tables: players and cards2
In each cards2 row, there is at least one player id (pid, pid2, pid3, pid4). I'm trying to come up with a select statement to get all fname's and lname's if there is more than one player id (pid's that are not 0). There is always a pid, but not always a pid2, pid3, etc. Does this make sense?
Here are the structures.
Players table
CREATE TABLE IF NOT EXISTS `players` (
`player_id` mediumint(10) NOT NULL AUTO_INCREMENT,
`sport_id` tinyint(4) NOT NULL,
`fname` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`lname` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
`hof` tinyint(4) NOT NULL,
PRIMARY KEY (`player_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=421 ;
Cards2 table
CREATE TABLE IF NOT EXISTS `cards2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pid` mediumint(10) NOT NULL,
`pid2` mediumint(10) NOT NULL,
`pid3` mediumint(10) NOT NULL,
`pid4` mediumint(10) NOT NULL,
`num` smallint(4) NOT NULL DEFAULT '0',
`year` year(4) NOT NULL DEFAULT '0000',
`notes` text,
`new_manuf` varchar(50) NOT NULL,
`sportid` tinyint(4) NOT NULL DEFAULT '0',
`fname` varchar(25) NOT NULL,
`lname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=572 ;
Thanks in advance!
You could do it like this:
SELECT
id,
P1.fname AS fname1,
P1.lname AS lname1,
P2.fname AS fname2,
P2.lname AS lname2,
P3.fname AS fname3,
P3.lname AS lname3,
P4.fname AS fname4,
P4.lname AS lname4
FROM cards2
LEFT JOIN players P1 ON pid = P1.player_id
LEFT JOIN players P2 ON pid2 = P2.player_id
LEFT JOIN players P3 ON pid3 = P3.player_id
LEFT JOIN players P4 ON pid4 = P4.player_id
You might want to consider if it is a good idea to normalize your database though. Having four columns for four players seems like a bad idea. What if you later want to allow 6 players?
You might want to consider redesigning your tables if that's a possibility. The repeating player ID's in the CARD2 table will make querying difficult and artificially limit how many players can be associated with a particular card. If you're still in the design stage you can save yourself some grief later by redoing things now. Here's a quick shot at it:
First, redo the 'cards2' table to eliminate the player references:
CREATE TABLE IF NOT EXISTS `cards2` (
`card_id` int(11) NOT NULL AUTO_INCREMENT,
`num` smallint(4) NOT NULL DEFAULT '0',
`year` year(4) NOT NULL DEFAULT '0000',
`notes` text,
`new_manuf` varchar(50) NOT NULL,
`sportid` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`card_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=572;
Then create what's called a "junction table" to map the many-to-many relationship between cards and players (i.e. one player can appear on many cards, and many players can appear on one card):
CREATE TABLE IF NOT EXISTS 'card_player' (
'card_id' int(11) NOT NULL,
'player_id' mediumint(10) NOT NULL,
PRIMARY KEY ('card_id', 'player_id'),
FOREIGN KEY 'card_id' REFERENCES card2.card_id,
FOREIGN KEY player_id REFERENCES players.player_id
) ENGINE=MYISAM DEFAULT CHARSET=latin1;
Eliminating the repeated player_id's in the CARDS2 table should make querying easier. For example,
SELECT c.card_id, p.fname, p.lname
FROM players p,
cards2 c,
card_player x
WHERE x.card_id = c.card_id AND
p.player_id = x.player_id
ORDER BY c.card_id;
This also eliminates the limitation of having at most four players associated with a particular card, so if you have a team card with 20 players on it this new set of table definitions can handle it.
I hope this helps.
Use:
SELECT CONCAT(p1.fname, ' ', p1.lname) AS player1,
CONCAT(p2.fname, ' ', p2.lname) AS player2,
CONCAT(p3.fname, ' ', p3.lname) AS player3,
CONCAT(p4.fname, ' ', p4.lname) AS player4,
FROM CARDS2 c
JOIN PLAYERS p1 ON p1.playerid = c.pid
LEFT JOIN PLAYERS p2 ON p2.playerid = c.pid2
LEFT JOIN PLAYERS p3 ON p3.playerid = c.pid3
LEFT JOIN PLAYERS p4 ON p4.playerid = c.pid4
About making sense, wouldn't it be cleaner creating simple n:m players_tables_map table? http://en.wikipedia.org/wiki/First_normal_form