Help with SELECT statement - sql

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

Related

Sort by column from another table

I need a list over all Books in my db.
The list should contain:
Name of the Publisher , Book Title , year of publication for all books.
The list must be sorted alphabetically in order of publisher and then title.
I am not able to sort the list for BOTH condition, just for one.
Here the tables:
Publishers table
cursor.execute("DROP TABLE IF EXISTS `Publishers`;")
cursor.execute('''
CREATE TABLE `Publishers` (
`PublisherID` INTEGER PRIMARY KEY AUTOINCREMENT,
`Name` VARCHAR(45) NULL,
`City` TEXT NULL,
`Country` TEXT NULL
);
Books table
cursor.execute("DROP TABLE IF EXISTS `Books`;")
cursor.execute('''
CREATE TABLE `Books` (
`BookID` INTEGER PRIMARY KEY AUTOINCREMENT,
`Title` VARCHAR(45) NULL,
`ISBN` VARCHAR(45) NULL,
`ISBN13` TEXT NOT NULL CHECK (length(ISBN13)=13),
`PublisherID` INT NOT NULL,
`Year_Published` VARCHAR(45) NULL,
`Price` DOUBLE NULL,
FOREIGN KEY (`PublisherID`) REFERENCES Publishers(PublisherID) ON DELETE CASCADE ON UPDATE CASCADE
);
''')
I was able to write this but not to sort it for BOTH conditions
cursor = conn.cursor()
for row in cursor.execute('''SELECT Publishers.name,title,Year_Published
FROM books
INNER JOIN publishers on publishers.PublisherID = books.PublisherID
ORDER BY
title ASC, name.publisher ASC;'''):
print(row)
cursor.close()
You seem to be looking for a join:
select p.name, b.title, b.year_published
from books b
inner join publishers p on p.publisherID = b.publisherID
order by p.name, b.title
I had written a wrong conditions. Answer seems to be the following thanks to all!1
cursor = conn.cursor()
for row in cursor.execute('''SELECT Publishers.name,title,Year_Published
FROM books
INNER JOIN publishers on publishers.PublisherID = books.PublisherID
ORDER BY
publishers.name ASC, books.title ASC;'''):
print(row)
cursor.close()

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 ;

how to create a query two or more values?

I would need if I could help with the following code:
CREATE TABLE `car` (
`car_id` int(10) unsigned NOT NULL auto_increment,
`car_name` varchar(25) NOT NULL,
`car_year` date NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB;
CREATE TABLE `people` (
`peo_id` int(10) unsigned NOT NULL auto_increment,
`peo_name` varchar(50) NOT NULL,
`peo_surname` varchar(200) NOT NULL,
`car_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`fav_id`),
KEY `FK_Favorites` (`user_id`),
CONSTRAINT `FK_Favorites` FOREIGN KEY (`car_id`) REFERENCES `car` (`car_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
I have questions, show all cars that are more than 3 people ordered by year of car.
Thank you very much, sorry for my bad English
SELECT
c.car_id
,c.car_year
,COUNT(p.peo_id)
FROM car c
INNER JOIN people p on c.card_id = p.car_id
GROUP BY c.car_id ,c.car_year
HAVING COUNT(p.peo_id) > 3
ORDER BY c.car_year
First, find number of cars with more than three people
select car_id
from people
group by car_id
having count(*) > 3
Now, get the car details
select * from car
where car_id in
(
select car_id
from people
group by car_id
having count(*) > 3
)
order by car_year

Cant figure out how to join these tables

Need help with some kind of join here. Cant figure it out.
I want to loop out the forum boards, but I also want to get last_post and last_posterid from posts, and based on last_posterid get username from users.
This is how far I've come yet (:P):
SELECT name, desc, position FROM boards b
INNER JOIN posts p ON ???
INNER JOIN users u ON ???
ORDER BY b.position ASC
Help would be greatly appreciated.
Cheers
CREATE TABLE `posts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) DEFAULT NULL,
`message` text,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`edited` int(10) unsigned DEFAULT NULL,
`edited_by` int(10) unsigned NOT NULL DEFAULT '0',
`icon` varchar(255) DEFAULT NULL,
`topicid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `boards` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`desc` varchar(255) NOT NULL,
`position` int(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `users` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(200) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
CREATE TABLE `topics` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`posterid` int(10) unsigned NOT NULL DEFAULT '0',
`subject` varchar(255) NOT NULL,
`posted` int(10) unsigned NOT NULL DEFAULT '0',
`last_post` int(10) unsigned NOT NULL DEFAULT '0',
`last_poster` int(10) unsigned NOT NULL DEFAULT '0',
`num_views` mediumint(8) unsigned NOT NULL DEFAULT '0',
`num_replies` mediumint(8) unsigned NOT NULL DEFAULT '0',
`closed` tinyint(1) NOT NULL DEFAULT '0',
`sticky` tinyint(1) NOT NULL DEFAULT '0',
`icon` varchar(40) DEFAULT NULL,
`boardid` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=16 DEFAULT CHARSET=utf8
Assuming posts.topicid refers to boards.id, and posts.posterid refers to users.id, try something like
SELECT b.name, d.desc, b.position
FROM boards b
LEFT JOIN posts p
ON b.id = p.topicid
LEFT JOIN users u
ON (p.posterid = u.id) AND (p.posted = (SELECT MAX(sp.posted) FROM posts sp GROUP BY))
ORDER BY
b.position ASC
Another remark: try to name your fields such that it is clear what they refer to, for instance:
CREATE TABLE `foo` (
foo_ID UNSIGNED INTEGER NOT NULL AUTO_INCREMENT,
-- more stuff
PRIMARY KEY (`foo_ID`)
);
This allows you to re-use the field name foo_ID in another table, which makes things very easy when performing a join.
Nothing stands out, so this is mostly a guess:
SELECT b.name, b.desc, b.position
FROM boards AS b
INNER JOIN posts AS p
ON b.id = p.topicid
INNER JOIN users AS u
ON p.posterid = u.id
ORDER BY b.position ASC
SELECT username, message, posts.id
FROM users, posts
WHERE posterid=users.id
GROUP BY username, message, posts.id
HAVING posted=MAX(posted)
This might just do to get each user's last post concisely.
Ok, lack of a schema not withstanding, it looks like you could use nested select. Something like:
DECLARE #posts TABLE
(
postdate datetime,
postid int,
boardid int
)
DECLARE #users TABLE
(
userid int,
username varchar(10)
)
DECLARE #boards TABLE
(
boardid int
)
INSERT INTO #boards(boardid) VALUES (1)
INSERT INTO #users (userid, username) values (1, 'Adam')
insert into #posts (postdate, postid, boardid) values (GETDATE(), 1, 1)
SELECT b.*, p.*
from #boards b
inner join
(
SELECT TOP 1 postdate, postid, boardid, username FROM #posts
inner join #users on userid = postid WHERE boardid = 1
order by postdate desc
) as p on p.boardid = b.boardid
Oviously this is heavily generalised and uses some hard-coded values, but hopefully it should give you an option for implementing it on your own tables.
Of course, I infer no correctness in my implementation of the spec - SQL is not my first language lol
Was going to post this as a comment but it was too verbose and messy.
you'll want to use OUTER JOINs because otherwise boards with no posts in them will not show up
any single query that links those four tables is going to be a beast - consider using VIEWs to simplify things
your initial question and the tables you posted leave things a bit ambiguous: do you already have last_post and last_poster filled out in topics? If so, then your primary targets for the join should be boards and topics, and since you're already storing redundant data (I assume for speed?), why don't you also put in a last_post_time or something, which would simplify things a lot and curb load (no need to join in posts at all)?
mediumints? why on Earth? (If you don't need the date, and you think it'll never wrap, I suppose you can use the hack of trusting that a later post will have a higher ID due to autoincrementing key...)

Mysql Many to Many Query

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>