MySQL. Average price, connecting two databases - mysql

I am learning MySQL and I currently do not understand how to do something.
I have two tables and I want to display some stuff out of it, it's pretty hard to explain so I'd rather show you.
These are my tables:
CREATE TABLE IF NOT EXISTS `proprietate` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`serie_buletin` varchar(8) NOT NULL,
`cnp` bigint(20) NOT NULL,
`nr_vehicul` int(11) NOT NULL,
`data_cumpararii` date NOT NULL,
`pret` int(11) NOT NULL,
`id_persoana` int(11) NOT NULL,
PRIMARY KEY (id),
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=16 ;
CREATE TABLE IF NOT EXISTS `vehicul` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nr_vehicul` int(11) NOT NULL,
`marca` varchar(30) NOT NULL,
`id_marca` int(11) NOT NULL,
`tip` varchar(15) NOT NULL,
`culoare` varchar(15) NOT NULL,
`capacitate_cilindrica` int(11) NOT NULL,
`id_proprietate` int(11) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (id_proprietate) REFERENCES proprietate(id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=16 ;
And these are my values inside my tables:
INSERT INTO `proprietate` (`id`, `serie_buletin`, `cnp`, `nr_vehicul`, `data_cumpararii`, `pret`, `id_persoana`) VALUES
(1, 'AK162332', 2006036035087, 4, '2014-05-01', 35000, 1),
(2, 'AK162332', 2006036035087, 10, '2014-05-02', 90000, 2),
(3, 'AK176233', 6548751520125, 2, '2014-05-03', 55000, 3),
(4, 'BZ257743', 6548751520125, 2, '2014-05-04', 25000, 4),
(5, 'BZ257743', 2006036035087, 15, '2014-05-05', 63000, 5),
(6, 'DC456542', 2003564784513, 7, '2014-05-06', 30000, 6),
(7, 'EN654872', 2012654879521, 6, '2014-05-07', 50000, 7);
INSERT INTO `vehicul` (`id`, `nr_vehicul`, `marca`, `id_marca`, `tip`, `culoare`, `capacitate_cilindrica`, `id_proprietate`) VALUES
(1, 4, 'Mercedes', 1, 'CLK 350', 'negru', 3500, 1),
(2, 10, 'Mercedes', 1, 'S 500', 'silver', 5000, 2),
(3, 2, 'Mercedes', 1, 'ML 550', 'alb', 5500, 3),
(4, 2, 'BMW', 2, '325', 'galben', 2500, 4),
(5, 15, 'BMW', 2, 'X5', 'negru', 3500, 5),
(6, 7, 'Audi', 3, 'R5', 'mov', 5000, 6),
(7, 6, 'Audi', 3, 'Q5', 'metalic', 3000, 7);
What I want to display is:
marca | nr_vehicul | average_price
Audi | 13 | 40000
BMW | 17 | 44000
Mercedes | 16 | 60000
How can I do that? So far I have managed to display the first two columns but I have no idea how to reference the first table in the second and calculate the average price.
This is what I have so far:
SELECT marca, SUM(nr_vehicul) AS nr_vehicul FROM vehicul GROUP BY marca
Can anyone help me please?

You should join your tables to get combined information from both of them:
SELECT marca, SUM(vehicul.nr_vehicul) AS nr_vehicul, avg(pret) as pret
FROM vehicul
LEFT OUTER JOIN proprietate on (id_proprietate = proprietate.id)
GROUP BY marca;
see this sql fiddle session for the output.
First you select the data (column names with appropriate functions used) you need: marca, SUM(vehicul.nr_vehicul), AVG(pret), then you construct the joined structure from where mysql should retrieve these informations: vehicul, proprietate.
For this structure you need primarily the vehicul table, by which you will group the result set. You want to join the proprietate table to the vehicul table properly, to make sure the correct data structure is created. Since you have foreign key from one table to the other, the easiest way to do it is to use that key: LEFT OUTER JOIN proprietate on (id_proprietate = proprietate.id).
For more information on understanding the different JOIN types, please see this article by Craig Buckler.

$query = "SELECT type, AVG(pret) FROM vehicul GROUP BY marca";
$result = mysql_query($query) or die(mysql_error());
// Print out result
while($row = mysql_fetch_array($result)){
echo "The average price of ". $row['type']. " is $".$row['AVG(price)'];}</code>
should return the average price per marca

Related

Mapping AppUsers to a Customer by AppUser information

I have database with ERD:
And sample data:
CREATE TABLE `AppUser` (
`AppUser_ID` bigint(20) NOT NULL,
`SomeFields` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`AppUser_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `AppUser` (`AppUser_ID`, `SomeFields`) VALUES
(1, 'values'),
(2, 'values'),
(3, 'values'),
(4, 'values'),
(5, 'values');
CREATE TABLE `IdpUser` (
`IdpUser_ID` bigint(20) NOT NULL,
`SomeFields` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
ADD PRIMARY KEY (`IdpUser_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `IdpUser` (`IdpUser_ID`, `SomeFields`) VALUES
(1, 'values'),
(2, 'values'),
(3, 'values'),
(4, 'values'),
(5, 'values');
CREATE TABLE `UserClaim` (
`Attribute_ID` bigint(20) NOT NULL,
`IdpUser_ID` bigint(20) DEFAULT NULL,
`AttributeKey` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
`AttributeValue` bigint(20) DEFAULT NULL,
PRIMARY KEY (`Attribute_ID`),
FOREIGN KEY (`IdpUser_ID`) REFERENCES `IdpUser` (`IdpUser_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `UserClaim` (`Attribute_ID`, `IdpUser_ID`, `AttributeKey`, `AttributeValue`) VALUES
(1, 1, 'Email_ID', 1),
(2, 2, 'Email_ID', 2),
(3, 3, 'Email_ID', 3),
(4, 4, 'Email_ID', 4),
(5, 5, 'Email_ID', 5),
(6, 2, 'Phone_ID', 4),
(7, 3, 'Phone_ID', 2),
(8, 4, 'Phone_ID', 3),
(9, 5, 'Phone_ID', 5),
(10, 2, 'PublicKey_ID', 1),
(11, 3, 'PublicKey_ID', 2),
(12, 1, 'PublicKey_ID', 1);
CREATE TABLE `UserInfo` (
`Attribute_ID` bigint(20) NOT NULL,
`AppUser_ID` bigint(20) DEFAULT NULL,
`AttributeKey` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
`AttributeValue` bigint(20) DEFAULT NULL,
PRIMARY KEY (`Attribute_ID`),
FOREIGN KEY (`AppUser_ID`) REFERENCES `AppUser` (`AppUser_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `UserInfo` (`Attribute_ID`, `AppUser_ID`, `AttributeKey`, `AttributeValue`) VALUES
(1, 1, 'Email_ID', 1),
(2, 2, 'Email_ID', 2),
(3, 3, 'Email_ID', 3),
(4, 4, 'Email_ID', 4),
(5, 5, 'Email_ID', 2),
(6, 2, 'Phone_ID', 1),
(7, 3, 'Phone_ID', 2),
(8, 4, 'Phone_ID', 3),
(9, 5, 'Phone_ID', 4);
CREATE TABLE `UserMapping` (
`Mapping_ID` int(11) NOT NULL,
`AppUser_ID` bigint(20) NOT NULL,
`IdpUser_ID` bigint(20) NOT NULL,
PRIMARY KEY (`Mapping_ID`),
FOREIGN KEY (`AppUser_ID`) REFERENCES `AppUser` (`AppUser_ID`),
FOREIGN KEY (`IdpUser_ID`) REFERENCES `IdpUser` (`IdpUser_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
INSERT INTO `UserMapping` (`Mapping_ID`, `AppUser_ID`, `IdpUser_ID`) VALUES
(1, 2, 2),
(2, 3, 3),
(3, 1, 1),
(4, 4, 5),
(5, 5, 4);
UserInfo holds internal User info, UserClaim holds external User info, my application has multiple login type. Internal User and External User is mapped.
I use both internal and external User attribute for mapping Users to a Customer. If Users have the same AttributeKey and AttributeValue will be grouped in a Customer and the following attributes of those Users must be added to the Customer
All User with the same AttributeKey and AttributeValue is
grouped into a group.
The Customer will have all attribute of the Users belong to that Customer.
For example - with above data we have Mapping_ID = 2 belongs to Customer1 with list of attributes :
Email_ID = [2]
Phone_ID = [1,4]
PublicKey_ID = [1]
And we also have Mapping_ID = 5 with list of attributes:
Email_ID = [2,5]
Phone_ID = [4,5]
PublicKey_ID = []
And we also have Mapping_ID = 1 with list of attributes:
Email_ID = [1]
Phone_ID = []
PublicKey_ID = [1]
Because Email_ID = 2, Phone_ID = 4 belong to Mapping_ID = [2,5] so Mapping_ID = [2,5] is mapped to Customer1.
Because PublicKey_ID = 1 belong to Mapping_ID = [2,1] so Mapping_ID = [2,1] is mapped to Customer1.
=> Mapping_ID = [1,2,5] are mapped to Customer1 and Customer1 attributes :
Email_ID = [1,2,5]
Phone_ID = [1,4,5]
PublicKey_ID = [1]
My approach is to group by AttributeKey and AttributeValue in each UserInfo and UserClaim to get temporary Customers and their attributes, then join their attributes together and group to a Customer with the same AttributeKey and AttributeValue. I saw my approach made thing difficult to intersect list of attributes to group the Customer and how to store Customer attributes for effectively add or remove attributes.
I am looking for an idea for this problem or any better approach to solve this puzzle.
UserInfo has approximately 100m rows.
UserClaim has approximately 300m rows.
I can use MySql, PDI (Pentaho Data Integration) tools to design and maintain ETL to get my result.

Most efficient way to count items under a nested tree

So I'm looking to provide a category (x) style product count on my website.
I'm currently using MySQL.
My categories table looks like:
Categories (Id, TreeLeft, TreeRight, Level, Name) - with 'Level' being the node depth.
Categories entered look like this:
My Item > Categories relation table looks like:
ItemCategories (ItemId, CategoryId)
Assuming I have:
1 item under 'TUBE'
2 items under 'LCD'
1 item under 'FLASH'
1 item under '2 WAY-RADIOs'
How can I most efficiently query my items (large db) + categories (4000 in db), to produce:
Electronics (5)
- Televisions (3)
- Tube (1)
- LCD (2)
- Portable Electronics (2)
- MP3 Players (1)
- Flash (1)
- 2 Way Radios (1)
Taking note to only return those categories which have products in them and also correctly counts them up the tree.
Any help most appreciated.
Edit: DB Code to recreate environment locally:
CREATE TABLE IF NOT EXISTS `Categories` (
`Id` int(11) NOT NULL auto_increment,
`TreeLeft` mediumint(7) NOT NULL,
`TreeRight` mediumint(7) NOT NULL,
`Level` tinyint(3) NOT NULL,
`Name` varchar(255) NOT NULL,
UNIQUE KEY `Id` (`Id`),
KEY `TreeLeft` (`TreeLeft`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
INSERT INTO `Categories` (`Id`, `TreeLeft`, `TreeRight`, `Level`, `Name`) VALUES
(1, 1, 20, 1, 'Electronics'),
(2, 2, 9, 2, 'Television'),
(3, 10, 19, 2, 'Portable Electronics'),
(4, 3, 4, 3, 'Tube'),
(5, 5, 6, 3, 'LCD'),
(6, 7, 8, 3, 'Plasma'),
(7, 11, 14, 3, 'MP3 Players'),
(8, 15, 16, 3, 'CD Players'),
(9, 11, 14, 3, '2 Way Radios'),
(10, 12, 13, 4, 'Flash');
CREATE TABLE IF NOT EXISTS `ItemCategories` (
`CategoryId` int(11) NOT NULL,
`ItemId` int(11) NOT NULL,
KEY `CategoryId` (`CategoryId`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO `ItemCategories` (`CategoryId`, `ItemId`) VALUES
(4, 3442),
(5, 3441),
(5, 3456),
(9, 5343),
(10, 5423);
Please see the subsection, entitled Aggregate Functions in a Nested Set, of Mike Hillyer's classic article on this subject at Managing Hierarchical Data in MySQL

Alternative to subquery

Given the following tables:
CREATE TABLE IF NOT EXISTS `rank` (
`rank_id` bigint(20) NOT NULL AUTO_INCREMENT,
`rank` int(10) NOT NULL DEFAULT '0',
`subject_id` int(10) NOT NULL DEFAULT '0',
`title_id` int(10) NOT NULL DEFAULT '0',
`source_id` int(10) NOT NULL DEFAULT '0'
PRIMARY KEY (`rank_id`)
) ENGINE=MyISAM;
INSERT INTO `rank` (`rank_id`, `rank`, `subject_id`, `title_id`, `source_id`) VALUES
(23, 0, 2, 1, 1),
(22, 0, 1, 1, 1),
(15, 0, 2, 2, 2),
(14, 0, 2, 2, 1),
(20, 0, 1, 3, 2),
(18, 0, 1, 4, 2),
(19, 0, 1, 5, 2),
(21, 0, 1, 3, 1),
(24, 0, 1, 6, 2);
CREATE TABLE IF NOT EXISTS `title` (
`title_id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`description` text,
`pre` varchar(255) DEFAULT NULL,
`last_modified_by` varchar(50) DEFAULT NULL,
`last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`title_id`)
) ENGINE=MyISAM;
INSERT INTO `title` (`title_id`, `title`, `last_modified`) VALUES
(1, 'new item', ' ', '2011-10-20 19:10:48'),
(2, 'another test', '2011-10-20 19:10:48'),
(3, 'and yet another', '2011-10-20 19:10:48'),
(4, 'one more', ' ', '2011-10-20 19:10:48'),
(5, 'adding more', ' ', '2011-10-20 19:10:48'),
(6, 'yes, another', ' ', '2011-10-20 19:10:48'),
(7, 'well, let''s see', ' ', '2011-10-20 19:10:48');
My need is for a query to select all titles that are not connected to a given subject in the rank table.
I have this working via a subquery:
SELECT title_id, title FROM title
WHERE title_id NOT IN (SELECT title_id FROM rank WHERE subject_id=2)
This returns the desired list:
+----------+-----------------+
| title_id | title |
+----------+-----------------+
| 3 | and yet another |
| 4 | one more |
| 5 | adding more |
| 6 | yes, another |
| 7 | well, let's see |
+----------+-----------------+
However, it gets a little slow when a large set of data is queried.
My question is if there is a way to return this result without the use of a subquery and if this alternative is any speedier.
Thanks in advance.
MySQL is usually faster with joins, though faster sub-queries are work in progress.
SELECT t.*
FROM title AS t
LEFT JOIN rank AS r ON (t.title_id = r.title_id AND r.subject_id = 2)
WHERE r.title_id IS NULL
As usual, you'll need to set up indexes on the foreign key (rank.title_id) and probably on the queried key (rank.subject_id).
You should read the MySQL documentation on [LEFT JOIN][1] if you want more details. There's also a nice trick with ONthat makes it different from WHERE.
The MySQL EXPLAIN command will tell you where your query needs help. e.g. EXPLAIN select title_id, title FROM title WHERE title_id NOT IN (select title_id from rank where subject_id=2)
My guess is that the true problem is that you don't have an index on rank.subject_id and that's causing a table scan (when rank has lots of rows).
Please create two indexes on subject_id and title_id and try the same.

Mysql - Help me alter this search query to get desired results

Following is a dump of the tables and data needed to answer understand the system:-
The system consists of tutors and classes.
The data in the table All_Tag_Relations stores tag relations for each tutor registered and each class created by a tutor. The tag relations are used for searching classes.
CREATE TABLE IF NOT EXISTS `Tags` (
`id_tag` int(10) unsigned NOT NULL auto_increment,
`tag` varchar(255) default NULL,
PRIMARY KEY (`id_tag`),
UNIQUE KEY `tag` (`tag`),
KEY `id_tag` (`id_tag`),
KEY `tag_2` (`tag`),
KEY `tag_3` (`tag`),
KEY `tag_4` (`tag`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `Tags` (`id_tag`, `tag`) VALUES
(1, 'Sandeepan'),
(2, 'Nath'),
(3, 'first'),
(4, 'class'),
(5, 'new'),
(6, 'Bob'),
(7, 'Cratchit');
CREATE TABLE IF NOT EXISTS `All_Tag_Relations` (
`id_tag` int(10) unsigned NOT NULL default '0',
`id_tutor` int(10) default NULL,
`id_wc` int(10) unsigned default NULL,
KEY `All_Tag_Relations_FKIndex1` (`id_tag`),
KEY `id_wc` (`id_wc`),
KEY `id_tag` (`id_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `All_Tag_Relations` (`id_tag`, `id_tutor`, `id_wc`) VALUES
(1, 1, NULL),
(2, 1, NULL),
(3, 1, 1),
(4, 1, 1),
(6, 2, NULL),
(7, 2, NULL),
(5, 2, 2),
(4, 2, 2),
(8, 1, 3),
(9, 1, 3);
Following is my query:-
This query searches for "first class" (tag for first = 3 and for class = 4, in Tags table) and returns all those classes such that both the terms first and class are present in the class name.
SELECT wtagrels.id_wc,SUM(DISTINCT( wtagrels.id_tag =3)) AS
key_1_total_matches,
SUM(DISTINCT( wtagrels.id_tag =4)) AS
key_2_total_matches
FROM all_tag_relations AS wtagrels
WHERE ( wtagrels.id_tag =3
OR wtagrels.id_tag =4 )
GROUP BY wtagrels.id_wc
HAVING key_1_total_matches = 1
AND key_2_total_matches = 1
LIMIT 0, 20
And it returns the class with id_wc = 1.
But, I want the search to show all those classes such that all the search terms are present in the class name or its tutor name
So that searching "Sandeepan class" (wtagrels.id_tag = 1,4) or "Sandeepan Nath" also returns the class with id_wc=1. And Searching. Searching "Bob First" should not return any classes.
Please modify the above query or suggest a new query, if possible using MyIsam - fulltext search, but somehow help me get the result.
I think this query would help you:
SET #tag1 = 1, #tag2 = 4; -- Setting some user variables to see where the ids go. (you can put the values in the query)
SELECT wtagrels.id_wc,
SUM(DISTINCT( wtagrels.id_tag =#tag1 OR wtagrels.id_tutor =#tag1)) AS key_1_total_matches,
SUM(DISTINCT( wtagrels.id_tag =#tag2 OR wtagrels.id_tutor =#tag2)) AS key_2_total_matches
FROM all_tag_relations AS wtagrels
WHERE ( wtagrels.id_tag =#tag1 OR wtagrels.id_tag =#tag2 )
GROUP BY wtagrels.id_wc
HAVING key_1_total_matches = 1 AND key_2_total_matches = 1
LIMIT 0, 20
It returns id_wc = 1.
For (6, 3) the query returns nothing.

MySQL Query does not use index in table join

I am trying to list all the book_sales information for a particular book author. So I have a query and it's not using Index to lookup records.
The following is my tables structure:
-- Table structure for table `books`
CREATE TABLE IF NOT EXISTS `books` (
`book_id` int(11) NOT NULL auto_increment,
`author_id` int(11) unsigned NOT NULL,
`book_type_id` int(11) NOT NULL,
`book_title` varchar(50) NOT NULL,
`book_price` smallint(4) NOT NULL,
`in_stock` char(1) NOT NULL,
PRIMARY KEY (`book_id`),
KEY `book_type_id` (`book_type_id`),
KEY `author_id` (`author_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
-- Dumping data for table `books`
INSERT INTO `books` (`book_id`, `author_id`, `book_type_id`, `book_title`, `book_price`, `in_stock`) VALUES
(1, 1, 1, 'My Book 1', 10, 'y'),
(2, 2, 1, 'My Book 2', 20, 'n'),
(3, 1, 2, 'My Book 3', 30, 'y'),
(4, 3, 3, 'My Book 4', 40, 'y'),
(5, 4, 2, 'My Book 5', 50, 'n'),
(6, 1, 1, 'My Book 6', 60, 'y'),
(7, 5, 3, 'My Book 7', 70, 'n'),
(8, 6, 2, 'My Book 8', 80, 'n'),
(9, 7, 1, 'My Book 9', 90, 'y'),
(10, 8, 3, 'My Book 10', 100, 'n');
-- Table structure for table `book_sales`
CREATE TABLE IF NOT EXISTS `book_sales` (
`sale_id` int(11) NOT NULL auto_increment,
`book_id` int(11) NOT NULL,
`sale_amount` decimal(8,2) NOT NULL default '0.00',
`time` datetime NOT NULL default '0000-00-00 00:00:00',
`price` smallint(8) NOT NULL,
PRIMARY KEY (`sale_id`),
KEY `book_id` (`book_id`),
KEY `price` (`price`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
-- Dumping data for table `book_sales`
INSERT INTO `book_sales` (`sale_id`, `book_id`, `sale_amount`, `time`, `price`) VALUES
(1, 1, '10.00', '2010-02-23 10:00:00', 20),
(2, 1, '20.00', '2010-02-24 11:00:00', 20);
My Query:
SELECT sale_amount, price
FROM book_sales
INNER JOIN books ON book_sales.book_id = books.book_id
WHERE books.author_id =1
An EXPLAIN on the above, shows me:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE books ref PRIMARY,author_id author_id 4 const 3 Using index
1 SIMPLE book_sales ALL book_id NULL NULL NULL 2 Using where
Clearly, book_sales is not using the key 'book_id', although I have it. What can I do to make the book_sales table use the Index?
Thank you.
Edits done based on suggestions (but the result is they still do not use index):
//Does not use the index in book_sales table
EXPLAIN SELECT sale_amount, price
FROM books, book_sales
FORCE INDEX ( book_id )
WHERE book_sales.book_id = books.book_id
AND books.author_id =1
//Does not use the index in book_sales table
EXPLAIN SELECT sale_amount, price
FROM book_sales, books
WHERE books.author_id = 1
AND book_sales.book_id = books.book_id
How to force the book_sale table with just 2 rows, to use the index ? Thank you.
As you can see in the EXPLAIN, "book_id" is listed as a possible key. If MySQL doesn't use it, it's just that the optimizer doesn't think it would speed up the query. Which is true if "book_sales" only has 2 rows, and 100% of those rows share the same "book_id". It's called cardinality btw. How to Avoid Table Scans (MySQL Manual)
Try filling it with more rows and you should see that MySQL will use an index for the join.
Edit: the query
SELECT sale_amount, price
FROM books, book_sales
FORCE INDEX ( book_id )
WHERE book_sales.book_id = books.book_id
AND books.author_id =1
...will not work either in that case because the optimizer still recognizes that reading the index is suboptimal and switches the table order to avoid doing so. You can force the table order by using STRAIGHT_JOIN. This is, however, a bit of a hack because it forces MySQL to execute the query in a way that is not the best.
EXPLAIN
SELECT sale_amount, price
FROM books
STRAIGHT_JOIN book_sales FORCE INDEX (book_id) ON book_sales.book_id = books.book_id
WHERE books.author_id = 1
Try this
SELECT sale_amount, price
FROM book_sales,books
LEFT JOIN books ON(book_sales.book_id = books.book_id)
WHERE books.author_id =1