MySQL: Finding the most efficient use of INNER JOIN with subquery - mysql

I have a working query using INNER JOIN and a subquery but was wondering if there is a more effient way of writing it.
with prl
as
(
SELECT `number`, creator, notes
FROM ratings
INNER JOIN
projects on ratings.project_id = projects.project_id
WHERE ratings.rating = 5 AND projects.active = 1
)
SELECT prl.`number`, creator, notes
FROM prl
INNER JOIN(
SELECT `number`
HAVING COUNT(creator) > 1
)temp ON prl.`number` = temp.`number`
ORDER BY temp.`number`
projects table
project_id| number | creator | active |
| 1 | 3 | bob | 1 |
| 2 | 4 | mary | 1 |
| 3 | 5 | asi | 1 |
rating table
project_id| notes | rating |
| 1 | note1 | 5 |
| 1 | note2 | 5 |
| 3 | note3 | 5 |
| 1 | note4 | 1 |
| 2 | note5 | 5 |
| 3 | note6 | 2 |
result
| number | creator | notes |
| 3 | bob | note1 |
| 3 | bob | note2 |

It seems like you're using MySQL version that support window function. If so, then try this:
SELECT number, creator, notes
FROM
(SELECT p.number, p.creator, r.notes,
COUNT(creator) OVER (PARTITION BY creator) AS cnt
FROM project p
JOIN rating r ON p.project_id=r.project_id
WHERE r.rating=5
AND p.active = 1) v
WHERE cnt=2;
As far as whether this is more efficient, I'm not really sure because it depends in your table indexes but for a small dataset, I assume this will do well.
Demo fiddle

Related

Selecting COUNT and MAX columns with 2 tables and a bridge table

so what I am trying to do is having 3 tables (pictures, collections, and bridge) with the following columns:
Collections Table:
| id | name |
------------------
| 1 | coll1 |
| 2 | coll2 |
------------------
Pictures Table: (timestamps are unix timestamps)
| id | name | timestamp |
-------------------------
| 5 | Pic5 | 1 |
| 6 | Pic6 | 19 |
| 7 | Pic7 | 3 |
| 8 | Pic8 | 892 |
| 9 | Pic9 | 4 |
-------------------------
Bridge Table:
| id | collection | picture |
-----------------------------
| 1 | 1 | 5 |
| 2 | 1 | 6 |
| 3 | 1 | 7 |
| 4 | 1 | 8 |
| 5 | 2 | 5 |
| 6 | 2 | 9 |
| 7 | 2 | 7 |
-----------------------------
And the result should look like this:
| collection_name | picture_count | newest_picture |
----------------------------------------------------
| coll1 | 4 | 8 |
| coll2 | 3 | 9 |
----------------------------------------------------
newest_picture should always be the picture with the heighest timestamp in that collection and I also want to sort the result by it. picture_count is obviously the count of picture in that collection.
Can this be done in a single statement with table joins and if yes:
how can I do this the best way?
A simple method uses correlated subqueries:
select c.*,
(select count(*)
from bridge b
where b.collection = c.id
) as pic_count,
(select p.id
from bridge b join
pictures p
on b.picture = b.id
where b.collection = c.id
order by p.timestamp desc
limit 1
) as most_recent_picture
from collections c;
A more common approach would use window functions:
select c.id, c.name, count(bp.collection), bp.most_recent_picture
from collections c left join
(select b.*,
first_value(p.id) over (partition by b.collection order by p.timestamp desc) as most_recent_picture
from bridge b join
pictures p
on b.picture = p.id
) bp
on bp.collection = c.id
group by c.id, c.name, bp.most_recent_picture;

join with count on a joined table with group clause in mysql

I have 3 tables:
applications (has many votes)
votes (belongs to applications and questions)
questions (has many votes)
I need to get number of votes per application per question.
So, my attempt was:
SELECT applications.id, COUNT(votes.id), votes.question_id
FROM applications
LEFT OUTER JOIN votes ON (votes.application_id = application.id)
GROUP BY votes.question_id
However, it displays data only for a single application, so I assume my query is malformed:
+----+-----------------+-------------+
| id | COUNT(votes.id) | question_id |
+----+-----------------+-------------+
| 1 | 1185 | 1 |
| 1 | 1170 | 2 |
| 1 | 1209 | 3 |
| 1 | 1230 | 4 |
| 1 | 1213 | 5 |
+----+-----------------+-------------+
What I need:
+----+-----------------+-------------+
| id | COUNT(votes.id) | question_id |
+----+-----------------+-------------+
| 1 | 1185 | 1 |
| 1 | 1170 | 2 |
| 1 | 1209 | 3 |
| 1 | 1230 | 4 |
| 1 | 1213 | 5 |
| 2 | null | 1 |
| 2 | 50 | 2 |
| 2 | 333 | 3 |
| 2 | 1230 | 4 |
| 2 | 1213 | 5 |
| 3 | null | 1 |
| 3 | 50 | 2 |
| 3 | 333 | 3 |
| 3 | null | 4 |
| 3 | 5555 | 5 |
+----+-----------------+-------------+
The group by clause was missing applications.id.
SELECT applications.id, COUNT(votes.id), votes.question_id
FROM applications
LEFT OUTER JOIN votes ON votes.application_id = application.id
group by applications.id, votes.question_id
You should be grouping by the applications.id as well as the questions.id:
SELECT a.id, COUNT(votes.id), votes.question_id
FROM applications a LEFT OUTER JOIN
votes v
ON v.application_id = a.id
GROUP BY a.id, v.question_id;
However, this will not produce exactly what you want. You seem to want all the questions for the applications, regardless of whether or not there are any votes. If so, this is probably what you want:
SELECT a.id, q.question_id, COUNT(v.application_id)
FROM applications a CROSS JOIN
(SELECT DISTINCT question_id FROM votes) q LEFT JOIN
votes v
ON v.application_id = a.id and v.question_id = q.question_id
GROPU BY a.id, q.question_id;

mysql join 3 tables by id

I have 3 tables to join and need some help to make it work, this is my schema:
donations:
+--------------------+------------+
| uid | amount | date |
+---------+----------+------------+
| 1 | 20 | 2013-10-10 |
| 2 | 5 | 2013-10-03 |
| 2 | 50 | 2013-09-25 |
| 2 | 5 | 2013-10-01 |
+---------+----------+------------+
users:
+----+------------+
| id | username |
+----+------------+
| 1 | rob |
| 2 | mike |
+----+------------+
causes:
+--------------------+------------+
| id | uid | cause | <missing cid (cause id)
+---------+----------+------------+
| 1 | 1 | stop war |
| 2 | 2 | love |
| 3 | 2 | hate |
| 4 | 2 | love |
+---------+----------+------------+
Result I want (data cropped for reading purposes)
+---------+-------------+---------+-------------+
| id | username | amount | cause |
+---------+-------------+---------+-------------+
| 1 | rob | 20 | stop war |
| 2 | mike | 5 | love |
+---------+-------------+-----------------------+
etc...
This is my current query, but returns double data:
SELECT i.*, t.cause as tag_name
FROM users i
INNER JOIN donations tti ON (tti.uid = i.id)
INNER JOIN causes t ON (t.uid = tti.uid)
EDIT: fixed sql schema on fiddle
http://sqlfiddle.com/#!2/0e06c/1 schema and data
How I can do this?
It seems your table's model is not right. There should be a relation between the Causes and Donations.
If not when you do your joins you will get duplicated rows.
For instance. Your model could look like this:
Donations
+--------------------+------------+
| uid | amount | date | causeId
+---------+----------+------------+
| 1 | 20 | 2013-10-10 | 1
| 2 | 5 | 2013-10-03 | 2
| 2 | 50 | 2013-09-25 | 3
| 2 | 5 | 2013-10-01 | 2
+---------+----------+------------+
causes:
+----------------------+
| id | cause |
+---------+------------+
| 1 | stop war |
| 2 | love |
| 3 | hate |
+---------+------------+
And the right query then should be this
SELECT i.*, t.cause as tag_name
FROM users i
INNER JOIN donations tti ON (tti.uid = i.id)
INNER JOIN causes t ON (t.id = tti.causeId)
Try this
SELECT CONCAT(i.username ,' ',i.first_name) `name`,
SUM(tti.amount),
t.cause AS tag_name
FROM users i
LEFT JOIN donations tti ON (tti.uid = i.id)
INNER JOIN causes t ON (t.uid = tti.uid)
GROUP BY i.id
Fiddle
You need to match the id from both the users and causes table at the same time, like so:
SELECT i.*, t.cause as tag_name
FROM users i
INNER JOIN donations tti ON (tti.uid = i.id)
INNER JOIN causes t ON (t.uid = tti.uid and t.id = i.id)
Apologies for formatting, I'm typing this on a phone.

Mysql join request

I need to get emtpy fields where data is repeated
For example an customer can have two or more contact persons, so query return (just shorted qyery resul):
CUSTOMER_NAME| CONTACT_PERSON|ETC..
dell | Ighor |etc..
dell | Dima |etc..
but I'm need :
CUSTOMER_NAME| CONTACT_PERSON|etc...
dell | Ighor |etc..
NULL | Dima |etc..
SELECT
`contact`.*,
`branch_has_equipment`.*,
`branch_has_contact`.*,
`equipment`.*,
`customer_has_branch`.*,
`branch`.*,
`customer`.*,
`ip`.*
FROM `customer`
INNER JOIN `customer_has_branch`
ON `customer`.`customer_id` = `customer_has_branch`.`customer_id`
INNER JOIN `branch`
ON `customer_has_branch`.`branch_id` = `branch`.`branch_id`
INNER JOIN `branch_has_equipment`
ON `branch`.`branch_id` = `branch_has_equipment`.`branch_id`
INNER JOIN `equipment`
ON `branch_has_equipment`.`equipment_id` = `equipment`.`equipment_id`
INNER JOIN `branch_has_contact`
ON `branch`.`branch_id` = `branch_has_contact`.`branch_id`
INNER JOIN `contact`
ON `branch_has_contact`.`contact_id` = `contact`.`contact_id`
INNER JOIN `equipment_has_ip`
ON `equipment`.`equipment_id` = `equipment_has_ip`.`equipment_id`
INNER JOIN `ip`
ON `equipment_has_ip`.`equipment_id` = `ip`.`ip_id`
WHERE `customer`.`inservice` = 'Yes'
ORDER BY `customer`.`customer_name`
in additional, tables ^
Customer
customer_id
customer_name
inservice
service_type
comment
Branch
branch_id
city
address
Equipment
equipment_id
brand
model
connection_param
connection_type
serial_number
id
release
Contact
contact_id
name
surname
phone_mobile
phone_work
phone_other
position
customer_has_branch_id
customer_id
branch_id
Since I have no idea how any of those tables relate to one another, my only answer to you is to use an OUTER JOIN, which will keep NULL results.
I'm not seriously advocating this solution because really i think this sort of thing should be handled in application level code (e.g. a bit of PHP), but anyway, consider the following:
SELECT * FROM my_table;
+------+--------+--------+
| game | points | player |
+------+--------+--------+
| 1 | 5 | Adam |
| 1 | 8 | Alan |
| 1 | 7 | Brian |
| 1 | 6 | John |
| 2 | 2 | Adam |
| 2 | 3 | Alan |
| 2 | 4 | Brian |
| 2 | 6 | John |
+------+--------+--------+
SELECT IF(game= #prev,'',game)
, #prev := game
FROM my_table ORDER BY game,player;
+-------------------------+---------------+
| IF(game= #prev,'',game) | #prev := game |
+-------------------------+---------------+
| 1 | 1 |
| | 1 |
| | 1 |
| | 1 |
| 2 | 2 |
| | 2 |
| | 2 |
| | 2 |
+-------------------------+---------------+

Join top 3 interest fields along with each user row

I'm trying to get the top 3 interests of each user, probably as a LEFT JOIN query.
The way the app is designed, each user has a set of interests which are no other than 'childs' (rows without parent) of the categories table.
Here are some simplified table schemas w/mock data (see SQL Fiddle demo)
-- Users table
| ID | NAME |
--------------
| 1 | John |
| 2 | Mary |
| 3 | Chris |
-- Categories table -- Interests table
| ID | NAME | PARENT | | ID | USER_ID | CATEGORY_ID |
-------------------------------------- ------------------------------
| 1 | Web Development | (null) | | 1 | 1 | 1 |
| 2 | Mobile Apps | (null) | | 2 | 1 | 1 |
| 3 | Software Development | (null) | | 3 | 1 | 1 |
| 4 | Marketing & Sales | (null) | | 4 | 2 | 1 |
| 5 | Web Apps | 1 | | 5 | 2 | 1 |
| 6 | CSS | 1 | | 6 | 3 | 1 |
| 7 | iOS | 2 | | 7 | 3 | 1 |
| 8 | Streaming Media | 3 | | 8 | 3 | 1 |
| 9 | SEO | 4 |
| 10 | SEM | 4 |
To get the top 3 interests of a given user, I've usually performed this query:
SELECT `c`.`parent` as `category_id`
FROM `interests` `i` LEFT JOIN `categories` `c` ON `c`.`id` = `i`.`category_id`
WHERE `i`.`user_id` = '2'
GROUP BY `c`.`parent`
ORDER BY count(`c`.`parent`) DESC LIMIT 3
This query returns the top 3 categories (parents) of user with id = 2
I would like to find out how I can query the users table and get their top 3 categories either in 3 different fields (preferred) or as a group_concat(..) in one field
SELECT id, name, top_categories FROM users, (...) WHERE id IN ('1', '2', '3');
Any ideas how I should go about doing this?
Thanks!
First build a groped query that lists on distinct rows, the top three skills for each user. Then pivot that into to pull the three skills for eah user out to the right. You will need to use the Max(isnull(skill,'')) expression on the skills in each skill column.
It is very crude way of doing it in MYSQL to get top 3 records for each user
SELECT u.id, c.name
FROM
users u,
categories c,
(SELECT i.id,
i.user_id,
i.category_id,
#running:=if(#previous=i.user_id,#running,0) + 1 as rId,
#previous:=i.user_id
FROM
(SELECT * FROM intersect ORDER BY user_id) i JOIN
(SELECT #running=0, #previous=0 ) r) i
WHERE
u.id = i.USER_ID AND
i.CATEGORY_ID = c.id AND
i.rId <= 3
group by u.id, c.name ;
Hope it helps
FIDDLE