I am writing an app that interacts with a MySQL database.
I have 3 tables, 'categories', 'books' and 'book_category'.
/* stores properties for categories */
`categories` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(140) NOT NULL UNIQUE,
PRIMARY KEY (`id`)
);
/* stores properties for books */
`books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` int(11) NOT NULL,
`read` tinyint(1) NOT NULL default 0,
`creation_date` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
/* matches books against categories */
`book_category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
I have 2 variables to perform the search.
<?php
$categoryString = 'python'; // books saved under python
$readBool = 1; // all books that have been read
?>
I would like to write the following SQL query:
Select all python books that have been read(i.e, book.read = 1).
My SQL knowledge is very limited, please help.
This should do the trick:
SELECT books.id, books.title, books.`read`, categories.name
FROM books
INNER JOIN book_category ON books.id = book_category.item_id
INNER JOIN categories ON book_category.category_id = categories.id
WHERE books.read = 1
AND categories.name = 'python'
And here's an SQL fiddle to demonstrate.
Just try this-
select * from book_category
LEFT JOIN books ON book_category.item_id = books.id
LEFT JOIN category ON category.id = book_category.category_id
WHERE category.name = 'python' AND
book.read = 1
select b.title
from books b
left join book_category bc on (b.id=bc.item_d)
left join categories c on (c.id=bc.category_id)
where b.read=1 and c.name='python'
SELECT * FROM books WHERE id IN (
SELECT item_id FROM book_category WHERE category_id IN (
SELECT id FROM category WHERE name = 'Python'
)
)
SELECT *
FROM books AS b
INNER JOIN book_category AS bc
ON bc.item_id = b.id
INNER JOIN categories AS c
ON c.id = bc.category_id
WHERE b.read = 1 AND c.name='python'
SELECT b.title
FROM books b, categories c, book_category bc
WHERE b.id = bc.item_id
AND c.id = bc.category_id
AND b.read
AND c.name = 'python'
To make sure that your query works fast, make sure that you have following indexes:
books(id)
categories(id, name)
book_category(item_id)
book_category(caregory_id)
Try this
"select b.title from category c INNER JOIN book_category bc on bc.category_id = c.id INNER JOIN book b on b.id = b.id where b.read = '1' and c.name = 'python'";
Related
I would like to display the image of 4 best selling items on the index page but the sql I'm using does not work. I think the syntax is correct but doesn't show anything on the page. I have an order_items table and products table.
Products table
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL,
`date` datetime NOT NULL,
`category` int(11) NOT NULL,
`name` varchar(100) NOT NULL,
`image` varchar(100) CHARACTER SET utf8 NOT NULL,
`description` text NOT NULL,
`author` varchar(100) NOT NULL,
`price` decimal(5,2) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=27 ;
Order_items table
CREATE TABLE IF NOT EXISTS `order_items` (
`id` int(11) NOT NULL,
`order` int(11) NOT NULL,
`product` int(11) NOT NULL,
`price` decimal(8,2) NOT NULL,
`qty` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=68 ;
NOTE: in order_items table the field product corresponds to the ID of the product.
Finally, this is the sql statement I'm using but doesn't show anything.
public function bestSellingItems(){
$sql = "SELECT * FROM products AS p
INNER JOIN order_items AS od ON p.id = od.id
GROUP BY p.id, SELECT SUM(od.qty) AS total
ORDER BY SUM(od.qty) DESC limit 4";
}
I would appreciate any help.
You said it your self, product correspond to id and you used p.id = od.id , also, I didn't understand the part of the second select which I believe should throw an error(no from..) so I adjusted it a little bit - select the sum, alias it and order by that alias.
public function bestSellingItems(){
$sql = "SELECT p.*,od.*,sum(od.qty) as sum_qty FROM products AS p
INNER JOIN order_items AS od ON p.id = od.product
GROUP BY p.id
ORDER BY sum_qty DESC limit 4";
}
your join "INNER JOIN order_items AS od ON p.id = od.id " should be INNER JOIN order_items AS od ON p.id = od.Product
In my database, I have 3 tables.
Jokes:
CREATE TABLE IF NOT EXISTS `jokes` (
`joke_id` int(11) NOT NULL AUTO_INCREMENT,
`joke` varchar(1024) NOT NULL,
`category_id` int(11) NOT NULL,
`vote` int(255) NOT NULL,
`date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`joke_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Category:
CREATE TABLE IF NOT EXISTS `category` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(51) NOT NULL,
PRIMARY KEY (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
And finally, Comments:
CREATE TABLE IF NOT EXISTS `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
`comment` text NOT NULL,
`joke_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
I need to join all three tables together to get the category name of a joke, the joke itself, and the unique comments correlating to that specific joke_id.
For the moment I can only join two tables where the joke_id = 1. This will return the comments for that joke.
This function is stored in my read controller, with the name of Joke:
public function joke($joke_id = FALSE)
{
if ($joke_id === FALSE) redirect('main'); //go to default controller
$data['results'] = $this->comments_m->getComments($joke_id, $this->uri->segment(2));
$this->load->view('template/header');
$this->load->view('template/sidebar');
$this->load->view('content/read', $data);
$this->load->view('template/footer');
}
The model (getjokes_m) has a function called readJokes which grabs only the category, the joke, and the joke_id to read that one specific joke:
function readJokes($joke_id)
{
$query = $this->db->query("SELECT j.*, c.name FROM jokes j LEFT JOIN category c ON c.category_id = j.category_id WHERE joke_id = '$joke_id'") or die("No results found" );
//displays the results
return $query->result();
}
For the moment, this results in only getting the category name of a joke, and the joke itself. What i want is to grab the comments for this one joke as well. How can I alter my query to return the information that I need?
Edit:
This query seems to work:
SELECT c.name, j.*, co.* FROM jokes j LEFT JOIN category c ON c.category_id = j.category_id LEFT JOIN comments co ON co.joke_id = j.joke_id WHERE j.joke_id = '$joke_id'"
but at the same time, it grabs the joke as many times as the joke has been commented for some reason. I.e the joke has 6 comments, the joke is shown 6 times.
EDIT:
SELECT j.joke AS 'Joke', c.name AS 'Category', 'Comments : ' AS 'Comments'
FROM jokes j
LEFT JOIN category c ON c.category_id = j.category_id
WHERE j.joke_id = '1'
UNION
SELECT '' AS 'Joke', '' AS 'Category', c.comment AS 'Comments'
FROM comments c
WHERE c.joke_id = '1'
LIMIT 0 , 30
I don't have your data, it's hard to test, but you may want to try this one.
It's one query. A cleaner way would be to get the joke then for that joke id get the comments.
SELECT c.*, j.*, co.*
FROM jokes j
INNER JOIN category c ON c.category_id = j.category_id
INNER JOIN comments co ON co.joke_id = j.joke_id
WHERE j.joke_id = '1'
SQLFIDDLE DEMO
The query you show here is what you need
SELECT c.name, j.*, co.* FROM jokes j LEFT JOIN category c ON c.category_id = j.category_id LEFT JOIN comments co ON co.joke_id = j.joke_id WHERE j.joke_id = '$joke_id'"
or you can hit a separate query for comments by providing joke id. in fact using a separate query is good,as it reduce the fetch data volume if the number of comments is more.
I want to counting from multiple tables and return all count values in one query. I've created SQL and It feels like this query is slow. Is this the best way to do as i said? If not, please suggest me for better solutions. Because sometimes it took more than 15 seconds to finish querying. Thank you.
And here is my database approximate info.
table post 400 rows.
table comment 3000 rows.
table like 1000 rows.
table view 6000 rows.
SQL
SELECT p.*,
COUNT(c.id) as commentCount,
COUNT(l.id) as likeCount,
COUNT(c.id) as viewCount,
FROM post p
LEFT JOIN comment c
ON (p.id = c.postid)
LEFT JOIN like l
ON (p.id = l.postid)
LEFT JOIN view v
ON (p.id = v.postid)
GROUP BY u.id
ORDER BY postCount DESC
TABLE post
CREATE TABLE `post` (
`id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`details` VARCHAR( 500 ) NOT NULL ,
`datetime` DATETIME NOT NULL
) ENGINE = MYISAM ;
TABLE comment
CREATE TABLE `comment ` (
`id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`postid` INT( 10 ) NOT NULL ,
`details` VARCHAR( 500 ) NOT NULL ,
`datetime` DATETIME NOT NULL
) ENGINE = MYISAM ;
TABLE like
CREATE TABLE `like` (
`id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`postid` INT( 10 ) NOT NULL ,
`datetime` DATETIME NOT NULL
) ENGINE = MYISAM ;
TABLE view
CREATE TABLE `view` (
`id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`postid` INT( 10 ) NOT NULL ,
`ip` VARCHAR( 30) NOT NULL ,
`datetime` DATETIME NOT NULL
) ENGINE = MYISAM ;
The query is slow because you are aggregating along multiple dimensions. This greatly multiplies the size of the data. One solution is to pre-aggregate the data:
SELECT p.*, c.cnt as commentCount, l.cnt as likeCount, v.cnt as viewCount,
FROM post p LEFT JOIN
(select c.postid, count(*) as cnt
from comment c
group by c.postid
) c
ON p.id = c.postid LEFT JOIN
(select l.postid, count(*) as cnt
from like l
group by l.postid
) l
ON p.id = l.postid LEFT JOIN
(select v.postid, count(*) as cnt
from view v
group by v.postid
) v
ON p.id = v.postid
GROUP BY p.id
ORDER BY postCount DESC;
In addition, your query would return about the same values for the three counts -- and they would probably not be accurate.
I am adding a tags feature to a polling system using MySql and PHP. The user will click a tag to list all polls (AKA propositions) with the respective tag. The link looks like this, "/propositions/tagged/baseball".
I use a mod_rewrite to assign the "baseball" tag to a variable called $tag.
RewriteRule ^propositions/tagged/(.*)$ /propositions/index.php?tag=$1 [L]
It does not return the tag names of the tags associated with the poll. How would I get those names?
The query below works, but it does not return the tag names of the tags associated with the poll. How would I get those names? Also, can it be done more efficiently. One more thing, would it be better to store the tags with the poll data. I saw a while back where SO does this like including the tags in a table column with the question. Don't know if they still do that or ever did that but saw or read it somewhere a while back.
Thanks so much!
=======================================================
pb_prop (
id int(11) NOT NULL auto_increment,
active tinyint(1) NOT NULL default '1',
submit_by int(11) NOT NULL,
total_votes int(11) NOT NULL default '0',
removed tinyint(1) NOT NULL default '0',
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Described below as ".PROP_TABLE." p
=======================================================
pb_prop_tags (
id int(11) NOT NULL auto_increment,
prop_id int(11) NOT NULL,
tag_id int(11) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Described below as ".PROP_TAGS_TABLE." pt
=======================================================
pb_tags (
tag_id int(11) NOT NULL auto_increment,
tag_name varchar(64) NOT NULL,
PRIMARY KEY (tag_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Described below as ".TAGS_TABLE." t
=======================================================
pb_prop_answers (
id int(11) NOT NULL auto_increment,
prop_id int(11) NOT NULL,
num_votes int(11) NOT NULL default '0',
PRIMARY KEY (id),
KEY poll_id (prop_id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Described below as ".PROP_ANSWERS_TABLE." a
=======================================================
Working SQL below ...could be improved? Since posting the original SQL, I added specific columns to the select ...got rid of some wild cards. Also, added more to the WHERE to filter the results better.
Original SQL shown after.
SELECT
u.username, u.userid,
p.*,
pt.prop_id,
pt.tag_id,
t.*,
(
SELECT
SUM(num_votes)
FROM
".PROP_ANSWERS_TABLE." a
WHERE
a.prop_id = p.id
) AS total_votes,
(
SELECT
count(*)
FROM
".PROP_ANSWERS_TABLE." a
WHERE
a.prop_id = p.id
) AS total_answers
FROM
".PROP_TABLE." p
INNER JOIN
".TAGS_TABLE." t
ON
t.tag_name LIKE '%".$tag."%'
INNER JOIN
".PROP_TAGS_TABLE." pt
ON
pt.tag_id = t.tag_id
LEFT JOIN
".USERS_TABLE." u
ON
u.userid = p.submit_by
WHERE
pt.tag_id = t.tag_id
AND
pt.prop_id = p.id
AND
p.removed = 0
AND
p.active = 1
AND
t.tag_id = pt.tag_id
ORDER BY
{$sort} {$dir}
Original posted SQL:
SELECT
u.username, u.userid,
p.*,
pt.*,
(
SELECT
SUM(num_votes)
FROM
".PROP_ANSWERS_TABLE." a
WHERE
a.prop_id = p.id
) AS total_votes,
(
SELECT
count(*)
FROM
".PROP_ANSWERS_TABLE." a
WHERE
a.prop_id = p.id
) AS total_answers
FROM
".PROP_TABLE." p
INNER JOIN
".PROP_TAGS_TABLE." pt
ON
pt.tag_id = p.id
INNER JOIN
".TAGS_TABLE." t
ON
t.tag_name LIKE '%".$tag."%'
LEFT JOIN
".USERS_TABLE." u
ON
u.userid = p.submit_by
WHERE
p.removed = 0
AND
p.active = 1
AND
t.tag_id = pt.tag_id
ORDER BY
{$sort} {$dir}
I think you could do this without the subqueries, just joining against the tables used in the subqueries and using the aggregate functions in the main select. You would need a suitable (and likely quite long!) GROUP BY clause to go with it.
Something like this (and no doubt there are typos in it)
SELECT u.username, u.userid,
p.*,
pt.*,
SUM(pat.num_votes) AS calc_total_votes,
COUNT(pat.num_votes) AS calc_total_answers
FROM ".PROP_TABLE." p
INNER JOIN ".PROP_TAGS_TABLE." pt ON pt.tag_id = p.id
INNER JOIN ".TAGS_TABLE." t ON t.tag_id = pt.tag_id AND t.tag_name LIKE '%".$tag."%'
LEFT JOIN ".PROP_ANSWERS_TABLE." pat ON pat.prop_id = p.id
LEFT JOIN ".USERS_TABLE." u ON u.userid = p.submit_by
WHERE p.removed = 0
AND p.active = 1
GROUP BY u.username, u.userid, p.id, p.active, p.submit_by, p.total_votes , p.removed, pt.id, pt.prop_id, pt.tag_id
ORDER BY {$sort} {$dir}
Amended SQL below, based on your updated SQL. This is doing a JOIN against a subselect which should be more efficient than a subselect per row.
SELECT u.username, u.userid, p.*, pt.prop_id, pt.tag_id, t.*, Sub1.total_votes, Sub1.total_answers
FROM ".PROP_TABLE." p
INNER JOIN ".PROP_TAGS_TABLE." pt ON pt.prop_id = p.id
INNER JOIN ".TAGS_TABLE." t ON pt.tag_id = t.tag_id
LEFT OUTER JOIN (SELECT prop_id, SUM(num_votes) AS total_votes, count(*) AS total_answers
FROM ".PROP_ANSWERS_TABLE."
GROUP BY prop_id ) AS Sub1 ON p.id = Sub1.prop_id
LEFT OUTER JOIN ".USERS_TABLE." u ON u.userid = p.submit_by
WHERE t.tag_name LIKE '%".$tag."%'
AND p.removed = 0
AND p.active = 1
ORDER BY {$sort} {$dir}
However it should be possible to do this select with just an overall group by and no need for a subselect.
Use EXPLAIN to check your performance and which indices are used when it comes to mysql performance analysis.
http://dev.mysql.com/doc/refman/5.0/en/explain.html
How to interpret the results of EXPLAIN can be seen here:
http://dev.mysql.com/doc/refman/5.0/en/explain-output.html
Avoid things like:
possible keys is "null"
type column shows "ALL"
extra column shows "using filesort" or "using temporary"
I noticed that "using temporary" or "using filesort" is often a result of an additional SORT BY statement at the end of the query.
Check that indices are used for all your joins. Removing the SORT BY might also speed up the process if the sort direction is not reflecting the sort direction of the used indices. But then you would have to check if it is faster to sort the (small?) result set in your code or in mysql.
Say I have three tables in my database:
CREATE TABLE `users` (
`user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR(16) NOT NULL
);
CREATE TABLE `users_meta` (
`meta_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`key` VARCHAR(200) NOT NULL ,
`value` TEXT NOT NULL
);
CREATE TABLE `posts` (
`post_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`content` TEXT NOT NULL
);
The table users_meta is just a key-value store of information about users, such that we can add any piece of information we want.
Say I added a key => value pair to the users_meta table for each user where the key was "age", and the value was a number representing their age.
Given this set of circumstances, what's the best way to select the first 10 posts ordered by user age?
I like putting the condition of the join in the join itself to be clear that I want a limited join:
SELECT p.post_id, p.content
FROM users u
INNER JOIN users_meta um
ON (u.user_id = um.user_id) AND um.key = 'age'
INNER JOIN posts p
ON (p.user_id = u.user_id)
ORDER BY um.value
limit 10
If you order by user age only, you will select 10 posts of the same user (the youngest one).
I would suggest to denormalize and store age in users table directly.
Agree with #KOHb, but if that's exactly what you want, here is the query:
SELECT TOP 10 p.id, p.content
FROM users u JOIN users_meta um ON (u.user_id = um.user_id)
JOIN posts p ON (p.user_id = u.user_id)
WHERE um.key = 'age'
ORDER BY um.value