Mysql: how to find users that currently in the room? - mysql

I have table entrances that logs times when users came into the room and came out of the room. Something like that:
user | action | time
-------------------------------------------
Ivan | in | 2016-08-28 12:00:00
John | in | 2016-08-28 12:00:01
Ann | in | 2016-08-28 12:00:02
Ivan | out | 2016-08-28 12:00:03
Ivan | in | 2016-08-28 12:00:04
Ann | out | 2016-08-28 12:00:05
Ivan | out | 2016-08-28 12:00:06
Mike | in | 2016-08-28 12:00:07
John | out | 2016-08-28 12:00:08
Ann | out | 2016-08-18 12:00:09
John | in | 2016-08-18 12:00:10
John | out | 2016-08-18 12:00:11
Ann | in | 2016-08-18 12:00:12
Users actions are independent. Only is known is that first action is always in and user cannot in twice witout out (and reverse).
My goal is to find all users that currently in room.
I have two ideas:
select users that have no out after latest in
select users that has count in more that count out
How to implement this on mysql? Or any other ideas?
SQL for testing:
CREATE TABLE `entrances` (
`id` int(11) NOT NULL,
`user` varchar(10) COLLATE utf8_bin NOT NULL,
`action` varchar(3) COLLATE utf8_bin NOT NULL,
`time` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `entrances` (`id`, `user`, `action`, `time`) VALUES
(1, 'Ivan', 'in', '2016-08-28 12:00:00'),
(2, 'John', 'in', '2016-08-28 12:00:01'),
(3, 'Ann', 'in', '2016-08-28 12:00:02'),
(4, 'Ivan', 'out', '2016-08-28 12:00:03'),
(5, 'Ivan', 'in', '2016-08-28 12:00:04'),
(6, 'Ann', 'out', '2016-08-28 12:00:05'),
(7, 'Ivan', 'out', '2016-08-28 12:00:06'),
(8, 'Mike', 'in', '2016-08-28 12:00:07'),
(9, 'John', 'out', '2016-08-28 12:00:08'),
(10, 'Ann', 'out', '2016-08-28 12:00:09'),
(11, 'John', 'in', '2016-08-28 12:00:10'),
(12, 'John', 'out', '2016-08-28 12:00:11'),
(13, 'Ann', 'in', '2016-08-28 12:00:12');
ALTER TABLE `entrances` ADD PRIMARY KEY (`id`);

First get the last action time from each user in the subquery
Then join against that subquery to have only the last record for each user
Then take only those records having the action = in with the where clause
Like this
select e.*
from entrances e
join
(
select user, max(time) as mtime
from entrances
group by user
) t on t.user = e.user
and t.mtime = e.time
where e.action = 'in'

Join all row with the action out and select the ones that have no out action or the in action is newer
SELECT * FROM entrances e
LEFT JOIN entrances e1 ON e.user = e1.user AND e1.action = 'out'
WHERE e.action = 'in' AND (e1.user IS NULL OR e.time > e1.time)
GROUP BY e.user, e.action

I will try something like this however it's better to test on real data.
select user from entrances e inner join
(select user, count(*) cnt from entrances where action='in') as e_in
on e.user=e_in.user left join
(select user, count(*) cnt from entrances where action='out') as e_out
on e.user=e_out.user
where e_out.user is null or (e_in.cnt - e_out.cnt) = 1

Mysql implementation of my idea number 1:
SELECT e.* FROM entrances e
WHERE NOT EXISTS (
SELECT * FROM entrances WHERE e.user=user AND action="out" AND time > (
SELECT MAX(time) FROM entrances WHERE e.user=user AND action="in"
)
)
This works literally as written: find users that have no out after last in. Utilizes NOT EXISTS feature. Advantage is readability without magic, plain algorythm.

Related

MySQL Update a table by min of another table

I would like to update Table Lease from Table History
CREATE TABLE Lease
(`LeaseID` int, `Name` varchar(3), `Users` varchar(15), `WhoSignID` int, `NoteDate` date)
;
INSERT INTO Lease
(`LeaseID`, `Name`, `Users`, `WhoSignID`, `NoteDate`)
VALUES
(1, 'AAA', '1000,1001', NULL, NULL),
(2, 'BBB', '1002', NULL, NULL),
(3, 'CCC', '1003,1004', NULL, NULL),
(4, 'DDD', '1005,1006, 1007', NULL, NULL)
;
CREATE TABLE History
(`HistoryID` int, `LeaseID` int, `User` int, `SignDate` date)
;
INSERT INTO History
(`HistoryID`, `LeaseID`, `User`, `SignDate`)
VALUES
(1, 1, 1000, '2020-01-05'),
(2, 1, 1001, '2020-01-04'),
(3, 1, 1001, '2020-01-02'),
(4, 1, 1000, '2020-01-03'),
(6, 2, 1002, '2020-05-01'),
(7, 2, 1002, '2020-05-03')
;
I looking of a Mysql Update to update Table Lease :
NoteDate and WhoSignID based on SignDate and User
where Minimum of SignDate of User
Table Lease After Update
LeaseID | Name | Users | WhoSignID | NoteDate
1 | AAA | 1000,1001 | 1001 | 2020-01-02
2 | BBB | 1002 | 1002 | 2020-05-01
...
I appreciate any assist
Your Lease table has a serious design problem, because it is storing users as a CSV list. Instead, you should have each user value on a separate record. That being said, it appears that the CSV user list is immaterial to your current problem, which only required finding the earliest date for each lease. If so, then a simple update join should suffice:
UPDATE Lease l
INNER JOIN
(
SELECT h1.LeaseID, h1.User, h2.MinSignDate
FROM History h1
INNER JOIN
(
SELECT LeaseID, MIN(SignDate) AS MinSignDate
FROM History
GROUP BY LeaseID
) h2
ON h2.LeaseID = h1.LeaseID AND
h2.MinSignDate = h1.SignDate
) h
ON h.LeaseID = l.LeaseID
SET
WhoSignID = h.User,
NoteDate = h.MinSignDate;

How do I select the max(timestamp) from a relational mysql table fast

We are developing a ticket system and for the dashboard we want to show the tickets with it's latest status. We have two tables. The first one for the ticket itself and a second table for the individual edits.
The system is running already, but the performance for the dashboard is very bad (6 seconds for ~1300 tickets). At first we used a statemant which selected 'where timestamp = (select max(Timestamp))' for every ticket. In the second step we created a view which only includes the latest timestamp for every ticket, but we are not able to also include the correct status into this view.
So the main Problem might be, that we can't build a table in which for every ticket the lastest ins_date and also the latest status is selected.
Simplyfied database looks like:
CREATE TABLE `ticket` (
`id` int(10) NOT NULL,
`betreff` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `ticket_relation` (
`id` int(11) NOT NULL,
`ticket` int(10) NOT NULL,
`info` varchar(10000) DEFAULT NULL,
`status` int(1) NOT NULL DEFAULT '0',
`ins_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`ins_user` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `ticket` (`id`, `betreff`) VALUES
(1, 'Technische Frage'),
(2, 'Ticket 2'),
(3, 'Weitere Fragen');
INSERT INTO `ticket_relation` (`id`, `ticket`, `info`, `status`, `ins_date`, `ins_user`) VALUES
(1, 1, 'Betreff 1', 0, '2019-05-28 11:02:18', 123),
(2, 1, 'Betreff 2', 3, '2019-05-28 12:07:36', 123),
(3, 2, 'Betreff 3', 0, '2019-05-29 06:49:32', 123),
(4, 3, 'Betreff 4', 1, '2019-05-29 07:44:07', 123),
(5, 2, 'Betreff 5', 1, '2019-05-29 07:49:32', 123),
(6, 2, 'Betreff 6', 3, '2019-05-29 08:49:32', 123),
(7, 3, 'Betreff 7', 2, '2019-05-29 09:49:32', 123),
(8, 2, 'Betreff 8', 1, '2019-05-29 10:49:32', 123),
(9, 3, 'Betreff 9', 2, '2019-05-29 11:49:32', 123),
(10, 3, 'Betreff 10', 3, '2019-05-29 12:49:32', 123);
I have created a SQL Fiddle: http://sqlfiddle.com/#!9/a873b6/3
The first three Statements are attempts that won't work correct or way too slow. The last one is the key I think, but I don't understand, why this gets the status wrong.
The attempt to create the table with latest ins_date AND status for each ticket:
SELECT
ticket, status, MAX(ins_date) as max_date
FROM
ticket_relation
GROUP BY
ticket
ORDER BY
ins_date DESC;
This query gets the correct (latest) ins_date for every ticket, but not the latest status:
+--------+--------+----------------------+
| ticket | status | max_date |
+--------+--------+----------------------+
| 3 | 1 | 2019-05-29T12:49:32Z |
+--------+--------+----------------------+
| 2 | 0 | 2019-05-29T10:49:32Z |
+--------+--------+----------------------+
| 1 | 0 | 2019-05-28T12:07:36Z |
+--------+--------+----------------------+
Expected output would be this:
+--------+--------+----------------------+
| ticket | status | max_date |
+--------+--------+----------------------+
| 3 | 3 | 2019-05-29T12:49:32Z |
+--------+--------+----------------------+
| 2 | 1 | 2019-05-29T10:49:32Z |
+--------+--------+----------------------+
| 1 | 3 | 2019-05-28T12:07:36Z |
+--------+--------+----------------------+
Is there a efficient way to select the latest timestamp and status for every ticket in the tiket-table?
Other approach is to think filtering not GROUPing..
Query
SELECT
ticket_relation_1.ticket
, ticket_relation_1.status
, ticket_relation_1.ins_date
FROM
ticket_relation AS ticket_relation_1
LEFT JOIN
ticket_relation AS ticket_relation_2
ON
ticket_relation_1.ticket = ticket_relation_2.ticket
AND
ticket_relation_1.ins_date < ticket_relation_2.ins_date
WHERE
ticket_relation_2.id IS NULL
ORDER BY
ticket_relation_1.id DESC
Result
| ticket | status | ins_date |
| ------ | ------ | ------------------- |
| 3 | 3 | 2019-05-29 12:49:32 |
| 2 | 1 | 2019-05-29 10:49:32 |
| 1 | 3 | 2019-05-28 12:07:36 |
see demo
This query would require a index KEY(ticket, ins_date, id) to get max performance..
One solution would be to use a subquery to compute the latest insert date for each ticket, and then to join the results with the original table, like:
SELECT t.ticket, t.status, t.ins_date
FROM ticket_relation t
INNER JOIN (
SELECT ticket, max(ins_date) max_ins_date
FROM ticket_relation
GROUP BY ticket
) x ON t.ticket = x.ticket AND t.ins_date = x.max_ins_date
For better performance with this query, you want an index on (ticket, ins_date).
Anoter option would be to use a NOT EXISTS condition to ensure that only the latest record is selected, like:
SELECT t.ticket, t.status, t.ins_date
FROM ticket_relation t
WHERE NOT EXISTS (
SELECT 1
FROM ticket_relation t1
WHERE t1.ticket = t.ticket AND t1.ins_date > t.ins_date)
)
NB: when dealing with GROUP BY, all non-aggregated columns must appear in the GROUP BY clause. Else, you will get either an error or unprectictable results (depending on whether server option ONLY_FULL_GROUP_BY is, respectively, enabled or disabled).
If you are able to upgrade to a recent version of mysql (8.0), then window functions can be used to simplify the query and possibly increase its performance, like:
SELECT ticket, status, ins_date
FROM (
SELECT
ticket,
status,
ins_date,
row_number() over(partition by ticket order by ins_date desc) rn
FROM ticket_relation
) x WHERE rn = 1
You can try below query -
SELECT
ticket, status, ins_date as max_date
FROM ticket_relation a
where ins_date in (select max(ins_date) from ticket_relation b where a.ticket=b.ticket)

MySQL joining tables of different structure

I am having trouble either getting any result or a correct result in the following problem - http://www.sqlfiddle.com/#!9/696ed2/4
Overall goal is to list all transactions of users who are linked together as 'Customers'. So if John is looking at his dashboard, he will see which books Alice (his customer) has rented (including book title), and which books were sold (he won't be able to see the title of that book).
When two multiple tables are joined to the parent table, where both depending tables have an 'active' flag set against each row, I can't seem to get only active rows.
# USERS
CREATE TABLE `users` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`active` boolean DEFAULT NULL
);
INSERT INTO `users` (`id`, `name`, `active`) VALUES
(1, 'John', 1),
(2, 'Alice', 1),
(3, 'Jess', 1),
(4, 'Bob', 1);
# BOOKS
CREATE TABLE `books` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`active` boolean DEFAULT NULL
);
INSERT INTO `books` (`id`, `name`, `active`) VALUES
(1, 'On the Road', 1),
(2, 'Neuromancer', 0),
(3, 'Modern History', 1),
(4, 'Red Mars', 1);
# TRANSACTIONS
CREATE TABLE `transactions` (
`id` int(11) NOT NULL,
`user_1_id` int(11) NOT NULL,
`user_2_id` int(11) DEFAULT NULL,
`book_id` int(11) DEFAULT NULL,
`timestamp` varchar(20) DEFAULT NULL,
`type` enum('Rent', 'Sold') NOT NULL
);
INSERT INTO `transactions` (`id`, `user_1_id`, `user_2_id`, `book_id`, `timestamp`, `type`) VALUES
(1, 1, 2, 1, '1465238591', 'Rent'),
(2, 2, 1, 2, '1465238592', 'Rent'),
(3, 2, 3, 3, '1465238593', 'Rent'),
(4, 3, 4, NULL, '1465238594', 'Sold'),
(5, 2, 3, NULL, '1465238595', 'Sold'),
(6, 3, 4, NULL, '1465238596', 'Sold'),
(7, 2, 2, 4, '1465238597', 'Rent'),
(8, 1, 3, 1, '1465238598', 'Rent'),
(9, 2, 4, 2, '1465238598', 'Rent');
# RELATIONSHIPS
CREATE TABLE `relationships` (
`id` int(11) NOT NULL,
`user_1_id` int(11) DEFAULT NULL,
`user_2_id` int(11) NOT NULL,
`type` enum('Customer', 'Supplier') NOT NULL
);
INSERT INTO `relationships` (`id`, `user_1_id`, `user_2_id`, `type`) VALUES
(1, 1, 2, 'Customer'),
(2, 2, 1, 'Customer'),
(3, 2, 4, 'Customer'),
(3, 1, 4, 'Supplier'),
(3, 3, 1, 'Customer');
Query:
SELECT DISTINCT
t.id,
t.type,
t.timestamp,
u1.name as user_1_name,
u2.name as user_2_name,
b.name as book_name
FROM transactions t
LEFT JOIN relationships r ON (r.user_1_id = 1 AND r.type = 'Customer')
LEFT JOIN books b ON (b.id = t.book_id AND b.active)
LEFT JOIN users u1 ON (u1.id = t.user_1_id) # AND u1.active
LEFT JOIN users u2 ON (u2.id = t.user_2_id) # AND u2.active
WHERE (r.user_2_id = t.user_1_id OR t.user_2_id = 1 AND t.user_1_id != 1)
# AND b.active AND u1.active AND u2.active
[Result]:
| id | type | timestamp | user_1_name | user_2_name | book_name |
|----|------|------------|-------------|-------------|----------------|
| 3 | Rent | 1465238593 | Alice | Jess | Modern History |
| 2 | Rent | 1465238592 | Alice | John | (null) | <<< Should not be here
| 7 | Rent | 1465238597 | Alice | Alice | Red Mars |
| 5 | Sold | 1465238595 | Alice | Jess | (null) | <<< Correct
| 9 | Rent | 1465238598 | Alice | Bob | (null) | <<< Should not be here
In the above result, the problem is that book Neuromancer has flag 'active' set to 0, so should not appear in the result. I have played with placing AND b.active at different places, but the results are always wrong. (See http://www.sqlfiddle.com/#!9/696ed2/5)
Looking at the mess above, I am not even sure my approach is any good, any suggestions are welcome.
As D. Smania mentioned in comments you need to make sure b.active is either 1 or NULL but based on your LEFT JOIN condition b.active will always be 1 so you need to do the join only on id and rely on the WHERE condition for comparison. This should yield the results you asked for:
SELECT DISTINCT
t.id,
t.type,
t.timestamp,
u1.name AS user_1_name,
u2.name AS user_2_name,
b.name AS book_name
FROM transactions t
LEFT JOIN relationships r ON (r.user_1_id = 1 AND r.type = 'Customer')
LEFT JOIN books b ON (b.id = t.book_id)
LEFT JOIN users u1 ON (u1.id = t.user_1_id)
LEFT JOIN users u2 ON (u2.id = t.user_2_id)
WHERE (r.user_2_id = t.user_1_id OR t.user_2_id = 1 AND t.user_1_id != 1)
AND (b.active OR b.active IS NULL)
AND u1.active AND u2.active
SQL Fiddle
One note - in your first WHERE condition it's not clear to me whether you mean:
(r.user_2_id = t.user_1_id OR (t.user_2_id = 1 AND t.user_1_id != 1))
or
((r.user_2_id = t.user_1_id OR t.user_2_id = 1) AND t.user_1_id != 1)
It's always best to be explicit with your logic grouping when you have adjacent AND and OR conditions.

SQL for filtering

By referencing Collaborative filtering in MySQL? , I have created the following ones:
CREATE TABLE `ub` (
`user_id` int(11) NOT NULL,
`book_id` varchar(10) NOT NULL,
`rate` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`book_id`),
UNIQUE KEY `book_id` (`book_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
insert into ub values (1, 'A', '8'), (1, 'B', '7'), (1, 'C', '10');
insert into ub values (2, 'A', '8'), (2, 'B', '7'), (2, 'C', '10'), (2,'D', '8'), (2,'X', '7');
insert into ub values (3, 'X', '10'), (3, 'Y', '8'), (3, 'C', '10'), (3,'Z', '10');
insert into ub values (4, 'W', '8'), (4, 'Q', '8'), (4, 'C', '10'), (4,'Z', '8');
Then, I can able to get the following table and understand how it works.
create temporary table ub_rank as
select similar.user_id,count(*) rank
from ub target
join ub similar on target.book_id= similar.book_id and target.user_id != similar.user_id and target.rate= similar.rate
where target.user_id = 1
group by similar.user_id;
select * from ub_rank;
+---------+------+
| user_id | rank |
+---------+------+
| 2 | 3 |
| 3 | 1 |
| 4 | 1 |
+---------+------+
However, I start to be confused after the following code.
select similar.rate, similar.book_id, sum(ub_rank.rank) total_rank
from ub_rank
join ub similar on ub_rank.user_id = similar.user_id
left join ub target on target.user_id = 1 and target.book_id = similar.book_id and target.Rate= similar.Rate
where target.book_id is null
group by similar.book_id
order by total_rank desc, rate desc;
+---------+------------+
| book_id | total_rank |
+---------+------------+
| X | 4 |
| D | 3 |
| Z | 2 |
| Y | 1 |
| Q | 1 |
| W | 1 |
+---------+------------+
(1, 'A', '8'), (1, 'B', '7'), (1, 'C', '10');
(2, 'A', '8'), (2, 'B', '7'), (2, 'C', '10'), (2,'D', '8'), (2,'X', '7');
What I wanna do is that, suppose user 1 and 2 have similar behavior ( chosen A,B,C before with matched rating), thus I will recommend D to user A , as it has a higher rate.
Seems the code above not to do so? As, the first ranked is X. How can I change the code in order to achieve the goal mentioned?
Or, actually does the existing method is a better/more accuracy for recommendation?
The existing query is ranking the results based on the total value of rank for each book, and then using rate as a tie-break for books which have the same total rank. (Also, rate will essentially be random since similar.rate is not aggregated, grouped on or functionally dependent on a grouping item in the query.)
As such, X will be ranked higher than D because it has been chosen by one user of rank 3 and one user of rank 1, giving a total rank of 4, whereas D has only been chosen by one user of rank 3.
You could change the query to include a rating element weighted by ranking - for example:
select similar.book_id,
sum(ub_rank.rank) total_rank,
sum(ub_rank.rank*similar.rate) wtd_rate
from ub_rank
join ub similar on ub_rank.user_id = similar.user_id
left join ub target on target.user_id = 1 and target.book_id = similar.book_id and target.Rate= similar.Rate
where target.book_id is null
group by similar.book_id
order by wtd_rate desc, total_rank desc
- although in this case this will still rank X higher, as it has a rating of 7 from a user of rank 3 plus a rating of 10 from a user of rank 1, giving a total rank of 31, compared with D's total rank of 24.
(SQLFiddle here)
If you want X to rank higher than D, you need to decide what criteria you are going to use that would rank X higher than D.

EDITED Looking for SQL improvement

By referencing Collaborative filtering in MySQL? , I have created the following ones:
CREATE TABLE `ub` (
`user_id` int(11) NOT NULL,
`book_id` varchar(10) NOT NULL,
`rate` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`book_id`),
UNIQUE KEY `book_id` (`book_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
insert into ub values (1, 'A', '8'), (1, 'B', '7'), (1, 'C', '10');
insert into ub values (2, 'A', '8'), (2, 'B', '7'), (2, 'C', '10'), (2,'D', '8'), (2,'X', '7');
insert into ub values (3, 'X', '10'), (3, 'Y', '8'), (3, 'C', '10'), (3,'Z', '10');
insert into ub values (4, 'W', '8'), (4, 'Q', '8'), (4, 'C', '10'), (4,'Z', '8');
Then, I can able to get the following table and understand how it works.
create temporary table ub_rank as
select similar.user_id,count(*) rank
from ub target
join ub similar on target.book_id= similar.book_id and target.user_id != similar.user_id and target.rate= similar.rate
where target.user_id = 1
group by similar.user_id;
select * from ub_rank;
+---------+------+
| user_id | rank |
+---------+------+
| 2 | 3 |
| 3 | 1 |
| 4 | 1 |
+---------+------+
However, I start to be confused after the following code.
select similar.rate, similar.book_id, sum(ub_rank.rank) total_rank
from ub_rank
join ub similar on ub_rank.user_id = similar.user_id
left join ub target on target.user_id = 1 and target.book_id = similar.book_id and target.Rate= similar.Rate
where target.book_id is null
group by similar.book_id
order by total_rank desc, rate desc;
+---------+------------+
| book_id | total_rank |
+---------+------------+
| X | 4 |
| D | 3 |
| Z | 2 |
| Y | 1 |
| Q | 1 |
| W | 1 |
+---------+------------+
(SOLVED) First, I wondering the total rank of X and D why not the same (i.e. 3). Isn't it count the number of books which the same as user A for user B? So, D and X should be 3?!
(SOLVED) Second, how should I modify the code such as the rate can act as an element for the ranking. That is, if the rank of 2 books are the same, then the one with higher marks will place higher rank.
Thanks
EDITED
(1, 'A', '8'), (1, 'B', '7'), (1, 'C', '10');
(2, 'A', '8'), (2, 'B', '7'), (2, 'C', '10'), (2,'D', '8'), (2,'X', '7');
What I wanna do is that, suppose user 1 and 2 have similar behavior ( chosen A,B,C before with matched rating), thus I will recommend D to user A , as it has a higher rate.
Seems the code above not to do so? As, the first ranked is X.
First, I wondering the total rank of X and D why not the same (i.e.
3). Isn't it count the number of books which the same as user A for
user B? So, D and X should be 3?!
X has a greater rank as its present for the second user_id and the third user_id, the query gets the total of the rank, in this case 3 (user_id = 2) + 1 (user_id = 3)
Second, how should I modify the code such as the rate can act as an
element for the ranking. That is, if the rank of 2 books are the same,
then the one with higher marks will place higher rank.
Use the same query and order it by rate desc after the rank, like
select similar.book_id, sum(ub_rank.rank) total_rank
from ub_rank
join ub similar on ub_rank.user_id = similar.user_id
left join ub target on target.user_id = 1 and target.book_id = similar.book_id and target.Rate= similar.Rate
where target.book_id is null
group by similar.book_id
order by total_rank desc, rate desc;
Update: As per your requirement, you need to the get the list of books that have the closest match with other users and have the maximum price, try the below query for the same
SELECT
temp.book_id,
temp.rate as book_rate
FROM (
SELECT
similar.user_id,
COUNT( similar.book_id ) as book_match_count
FROM
ub target
JOIN ub similar ON target.book_id= similar.book_id AND target.user_id != similar.user_id
WHERE
target.user_id = 1
GROUP BY
similar.user_id
) AS users_with_book_matches
JOIN ub temp ON ( temp.user_id =users_with_book_matches.user_id AND temp.book_id NOT IN ( SELECT book_id FROM ub WHERE ub.user_id = 1 ) )
GROUP BY
temp.book_id
ORDER BY
users_with_book_matches.book_match_count DESC,
temp.rate DESC
limit 5
The above query gets the top 5 closest book matches
Here's the SqlFiddle, make sure to change the user_id at 2 places, hope this serves your purpose