I'm trying to combine 3 MySQL queries in to one.
This is what I am using to grab my news entries (with pagination) :
SELECT A.sid,
A.title,
A.time,
A.bodytext,
A.author,
A.url
FROM news A
INNER JOIN
(SELECT sid
FROM news
WHERE approved=1
ORDER BY sid DESC LIMIT $start, $limit) B USING (sid)
Now, I've recently added a comments feature and would like to get the total amount of comments for each "sid"
The part that gets confusing for me is that I need to match "sid" (from news.news) with "page_id" (from comments.pages) to grab it's unique "id".
+----+---------+
| id | page_id |
+----+---------+
| 1 | 87 |
| 2 | 86 |
| 41 | 85 |
| 3 | 84 |
| 13 | 83 |
+----+---------+
Now with that unique "id", I need to query "comments.comments" and match it with "page_id" column and count(*) how many comments it has - WHERE is_approved = 1
SELECT page_id,is_approved,count(*) FROM comments WHERE page_id = $id and is_approved = 1;
+---------+-------------+----------+
| page_id | is_approved | count(*) |
+---------+-------------+----------+
| 1 | 1 | 2 |
+---------+-------------+----------+
Is this possible?
Edited my response to conform to the updated question:
SELECT A.sid, A.title, A.time, A.bodytext, A.author, A.url,
D.page_id, D.num_comments
FROM news.news A
INNER JOIN (SELECT sid
FROM news
WHERE approved=1
ORDER BY sid desc
LIMIT $start, $limit) B USING (sid)
LEFT JOIN comments.pages C ON A.sid = C.id
LEFT JOIN (SELECT page_id,is_approved,count(*) as num_comments
FROM comments.comments
WHERE is_approved = 1) D ON C.page_id = D.page_id
Related
I have tables books and bookType which pose a 1 X n relationship.
books
+-----+------------------+----------+-------+
| id | title | bookType | price |
+-----+------------------+----------+-------+
| 1 | Wizard of Oz | 3 | 14 |
| 2 | Huckleberry Finn | 1 | 16 |
| 3 | Harry Potter | 2 | 25 |
| 4 | Moby Dick | 2 | 11 |
+-----+------------------+----------+-------+
bookTypes
+-----+----------+
| id | name |
+-----+----------+
| 1 | Fiction |
| 2 | Drama |
| 3 | Children |
+-----+----------+
How would I retrieve bookTypes where all books are more expensive than e.g. 12($)?
In this case, the expected output would be:
+-----+----------+
| id | name |
+-----+----------+
| 1 | Fiction |
| 3 | Children |
+-----+----------+
You can use not exists:
select t.*
from bookTypes t
where not exists (
select 1
from books b
where b.bookType = t.id and b.price < 12
)
If you want to select book types that also have at least one associated book:
select t.*
from bookTypes t
where
exists (select 1 from books b where b.bookType = t.id)
and not exists (select 1 from books b where b.bookType = t.id and b.price < 12)
Do a GROUP BY, use HAVING to return only booktypes having the lowest price > 12.
SELECT bt.name
FROM bookTypes bt
INNER JOIN books b ON b.bookType = bt.id
group by bt.name
HAVING SUM(b.price <= 12) = 0;
You can directly consider using having min(price) >= 12 with grouping by bookType
select t.id, t.name
from bookTypes t
join books b
on t.id = b.bookType
group by b.bookType
having min(price) >= 12
Moreover, if your DB's version is at least 10.2, then you can also use some window functions for analytical queries such as min(..) over (partition by .. order by ..) :
with t as
(
select t.id, t.name, min(price) over (partition by bookType) as price
from bookTypes t
join books b
on t.id = b.bookType
)
select id, name
from t
where price >= 12
in which min() over (..) window function determines minimum price for each booktype by use of partition by bookType
Demo
I think GMB's solution is likely the best so far. But for sake of completeness: You can also use the ALL operator with a correlated subquery. That's probably the most straight forward solution.
SELECT *
FROM booktypes bt
WHERE 12 < ALL (SELECT b.price
FROM books b
WHERE b.booktype = bt.id);
Can you not just select from books inner join bookTypes on id WHERE price > 12?
SELECT bt.*
FROM bookTypes bt
INNER JOIN books b ON b.bookType = bt.id
WHERE b.price > 12
Please see the picture for ERROR SCREENSHOT
Table: Candidate
+-----+---------+
| id | Name |
+-----+---------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
| 5 | E |
+-----+---------+
Table: Vote
+-----+--------------+
| id | CandidateId |
+-----+--------------+
| 1 | 2 |
| 2 | 4 |
| 3 | 3 |
| 4 | 2 |
| 5 | 5 |
+-----+--------------+
id is the auto-increment primary key, CandidateId is the id appeared in Candidate table.
Write a sql to find the name of the winning candidate, the above example will return the winner B.
+------+
| Name |
+------+
| B |
+------+
Notes:
You may assume there is no tie, in other words there will be at most one winning candidate.
Why this code can't work? Just try to use without limit
SELECT c.Name AS Name
FROM Candidate AS c
JOIN
(SELECT r.CandidateId AS can, MAX(r.Total_vote) AS big
FROM (SELECT CandidateId, COUNT(id) AS Total_vote
FROM Vote
GROUP BY CandidateId) AS r) AS v
ON c.id = v.can;
In your query, here: SELECT r.CandidateId AS can, MAX(r.Total_vote) AS big
you use MAX aggregate function, without group by, which is not correct SQL.
Try:
SELECT Candidate.* FROM Candidate
JOIN (
SELECT CandidateId, COUNT(id) AS Total_vote
FROM Vote
GROUP BY CandidateId
ORDER BY COUNT(id) DESC LIMIT 1
) v
ON Candidate.id = v.CandidateId
This is a join/group by query with order by:
select c.name
from candidate c join
vote v
on v.candidateid = c.id
group by c.id, c.name
order by count(*) desc
limit 1;
SELECT c.Name AS Name
FROM Candidate AS c JOIN (SELECT r.CandidateId AS can
FROM
(SELECT CandidateId, COUNT(id) AS Total_vote
FROM Vote
GROUP BY CandidateId) AS r
WHERE r.Total_vote = (SELECT MAX(r.Total_vote) FROM (SELECT
CandidateId, COUNT(id) AS Total_vote
FROM Vote
GROUP BY CandidateId) r)) AS v
ON c.id = v.can;
This is updated code
My code has two errors. The first one is "use of an aggregate like Max requires a Group By clause if there are any non-aggregated columns in the select list", but not sure why my previous code still can run and show no error. Maybe the system add the group by function automatically when it run.
The second one is that max can't be used with Group by in this format.
I've sql with NOT EXIST and it works very slowly in big db:
SELECT *
FROM
(
SELECT * FROM profiles ORDER BY id DESC
/* I need this order HERE! More info: https://stackoverflow.com/q/43516402/2051938 */
) AS users
WHERE
NOT EXISTS (
SELECT *
FROM request_for_friendship
WHERE
(
request_for_friendship.from_id = 1
AND
request_for_friendship.to_id = users.id
)
OR
(
request_for_friendship.from_id = users.id
AND
request_for_friendship.to_id = 1
)
)
LIMIT 0 , 1;
And I think I need to get request_for_friendship with some WHERE and after that check NOT EXIST, like this:
SELECT users.*
FROM
(
SELECT * FROM profiles ORDER BY id DESC
) AS users,
(
SELECT *
FROM request_for_friendship
WHERE
request_for_friendship.from_id = 1
OR
request_for_friendship.to_id = 1
) AS exclude_table
WHERE
NOT EXISTS
(
SELECT *
FROM exclude_table /* #1146 - Table 'join_test.exclude_table' doesn't exist */
WHERE
request_for_friendship.from_id = users.id
OR
request_for_friendship.to_id = users.id
)
LIMIT 0 , 1;
But it doesn't work: #1146 - Table 'join_test.exclude_table' doesn't exist
My tables:
1) profiles
+----+---------+
| id | name |
+----+---------+
| 1 | WILLIAM |
| 2 | JOHN |
| 3 | ROBERT |
| 4 | MICHAEL |
| 5 | JAMES |
| 6 | DAVID |
| 7 | RICHARD |
| 8 | CHARLES |
| 9 | JOSEPH |
| 10 | THOMAS |
+----+---------+
2) request_for_friendship
+----+---------+-------+
| id | from_id | to_id |
+----+---------+-------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 8 |
| 5 | 4 | 1 |
| 6 | 9 | 1 |
+----+---------+-------+
How to do some like this or better for perfomance?
p.s. I need to get only 1 row from table
Demo: http://rextester.com/DTA64368
I've already tried LEFT JOIN, but I've problem with order with him. mysql: how to save ORDER BY after LEFT JOIN without reorder?
First, do not use subqueries unnecessarily. Second, split the NOT EXISTS into two conditions:
SELECT p.*
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.from_id = 1 AND
rff.to_id = p.id
) AND
NOT EXISTS (SELECT 1
FROM request_for_friendship rff
WHERE rff.to_id = 1 AND
rff.from_id = p.id
)
ORDER BY id DESC;
This can now make use of two indexes: request_for_friendship(to_id, from_id) and request_for_friendship(from_id, to_id). Each index is needed for one of the NOT EXISTS conditions.
I still think there's ways to optimize this as 'in' is generally slower.
SELECT *
FROM profiles p
WHERE NOT EXISTS (SELECT 1
FROM request_for_friendship
WHERE (request_for_friendship.from_id,
request_for_friendship.to_id)
in ((1,p.id),
(p.id,1))
)
Get rid of the id in request_for_friendship. It wastes space and performance. The table has a "natural" PRIMARY KEY, which I will get to in a moment.
Since it seems that the relationship seems to commutative, let's make use of that by sorting the from and to -- put the smaller id in from and the larger is to. See LEAST() and GREATEST() functions.
Then you need only one EXISTS(), not two. And have
PRIMARY KEY(from_id, to_id)
Now to rethink the purpose of the query... You are looking for the highest id that is not "related" to id #1, correct? That sounds like a LEFT JOIN.
SELECT
FROM profiles AS p
LEFT JOIN request_for_friendship AS r ON r.to = p.id AND r.from = 1
WHERE r.to IS NULL
ORDER BY id DESC
LIMIT 1;
This may run about the same speed as the EXISTS -- Both walk through profiles from the highest id, reaching into the other table to see if a row is there.
If there is no such id, then the entire profiles table will be scanned, plus a the same number of probes into the other table.
I have two tables: contacts and client_profiles. A contact has many client_profiles, where client_profiles has foreign key contact_id:
contacts:
mysql> SELECT id,first_name, last_name FROM contacts;
+----+-------------+-----------+
| id | first_name | last_name |
+----+-------------+-----------+
| 10 | THERESA | CAMPBELL |
| 11 | donato | vig |
| 12 | fdgfdgf | gfdgfd |
| 13 | some random | contact |
+----+-------------+-----------+
4 rows in set (0.00 sec)
client_profiles:
mysql> SELECT id, contact_id, created_at FROM client_profiles;
+----+------------+---------------------+
| id | contact_id | created_at |
+----+------------+---------------------+
| 6 | 10 | 2014-10-09 17:17:43 |
| 7 | 10 | 2014-10-10 11:38:01 |
| 8 | 10 | 2014-10-10 12:20:41 |
| 9 | 10 | 2014-10-10 12:24:19 |
| 11 | 12 | 2014-10-10 12:35:32 |
+----+------------+---------------------+
I want to get the latest client_profiles for each contact. That means There should be two results. I want to use subqueries to achieve this. This is the subquery I came up with:
SELECT `client_profiles`.*
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
WHERE (client_profiles.id =
(SELECT `client_profiles`.`id` FROM `client_profiles` ORDER BY created_at desc LIMIT 1))
However, this is only returning one result. It should return client_profiles with id 9 and 11.
What is wrong with my subquery?
It looks like you were trying to filter twice on the client_profile table, once in the JOIN/ON clause and another time in the WHERE clause.
Moving everything in the where clause looks like this:
SELECT `cp`.*
FROM `contacts`
JOIN (
SELECT
`client_profiles`.`id`,
`client_profiles`.`contact_id`,
`client_profiles`.`created_at`
FROM `client_profiles`
ORDER BY created_at DESC
LIMIT 1
) cp ON `contacts`.`id` = `cp`.`contact_id`
Tell me what you think.
Should be something like maybe:
SELECT *
FROM `client_profiles`
INNER JOIN `contacts`
ON `contacts`.`id` = `client_profiles`.`contact_id`
GROUP BY `client_profiles`.`contact_id`
ORDER BY created_at desc;
http://sqlfiddle.com/#!2/a3f21b/9
You need to prequery the client profiles table grouped by each contact.. From that, re-join to the client to get the person, then again to the client profiles table based on same contact ID, but also matching the max date from the internal prequery using max( created_at )
SELECT
c.id,
c.first_name,
c.last_name,
IDByMaxDate.maxCreate,
cp.id as clientProfileID
from
( select contact_id,
MAX( created_at ) maxCreate
from
client_profiles
group by
contact_id ) IDByMaxDate
JOIN contacts c
ON IDByMaxDate.contact_id = c.id
JOIN client_profiles cp
ON IDByMaxDate.contact_id = cp.contact_id
AND IDByMaxDate.maxCreate = cp.created_at
I have such query that gives me results about bestseller items from shops, at the moment it works fine, but now I want to get only one product from each shop so to have a distinct si.shop_id only one bestseller product from a shop
SELECT `si`.`id`, si.shop_id,
(SELECT COUNT(*)
FROM `transaction_item` AS `tis`
JOIN `transaction` as `t`
ON `t`.`id` = `tis`.`transaction_id`
WHERE `tis`.`shop_item_id` = `si`.`id`
AND `t`.`added_date` >= '2014-02-26 00:00:00')
AS `count`
FROM `shop_item` AS `si`
INNER JOIN `transaction_item` AS `ti`
ON ti.shop_item_id = si.id
GROUP BY `si`.`id`
ORDER BY `count` DESC LIMIT 7
and that gives mu a result like:
+--------+---------+-------+
| id | shop_id | count |
+--------+---------+-------+
| 425030 | 38027 | 111 |
| 291974 | 5368 | 20 |
| 425033 | 38027 | 18 |
| 291975 | 5368 | 12 |
| 142776 | 5368 | 10 |
| 397016 | 38027 | 9 |
| 291881 | 5368 | 8 |
+--------+---------+-------+
any ideas?
EDIT
so I created a fiddle for it
http://sqlfiddle.com/#!2/cfc4c/1
Now the query returns best selling products I want it to return only one product from shopso the result of fiddle should be
+----+---------+-------+
| ID | SHOP_ID | COUNT |
+----+---------+-------+
| 1 | 222 | 3 |
| 4 | 333 | 2 |
| 8 | 555 | 1 |
| 9 | 777 | 1 |
+----+---------+-------+
Possibly something like this:-
SELECT si.shop_id,
SUBSTRING_INDEX(GROUP_CONCAT(CONCAT_WS(':', si.id, sub1.item_count) ORDER BY sub1.item_count DESC), ',', 1) AS `count`
FROM shop_item AS si
INNER JOIN
(
SELECT tis.shop_item_id, COUNT(*) AS item_count
FROM transaction_item AS tis
JOIN `transaction` as t
ON t.id = tis.transaction_id
AND t.added_date >= '2014-02-26 00:00:00'
GROUP BY tis.shop_item_id
) sub1
ON sub1.shop_item_id = si.id
GROUP BY si.shop_id
ORDER BY `count` DESC LIMIT 7
The sub query gets the count of items for each shop. Then the main query concatenates the item id and the item count together, group concatenates all those for a single shop together (ordered by the count descending) and then uses SUBSTRING_INDEX to grab the first one (ie, everything before the first comma).
You will have to split up the count field to get the item id and count separately (the separator is a : ).
This is taking a few guesses about what you really want, and with no table declares or data it isn't tested.
EDIT - now tested with the SQL fiddle example:-
SELECT SUBSTRING_INDEX(`count`, ':', 1) AS ID,
shop_id,
SUBSTRING_INDEX(`count`, ':', -1) AS `count`
FROM
(
SELECT si.shop_id,
SUBSTRING_INDEX(GROUP_CONCAT(CONCAT_WS(':', si.id, sub1.item_count) ORDER BY sub1.item_count DESC), ',', 1) AS `count`
FROM shop_item AS si
INNER JOIN transaction_item AS ti
ON ti.shop_item_id = si.id
INNER JOIN
(
SELECT tis.shop_item_id, COUNT(*) AS item_count
FROM transaction_item AS tis
JOIN `transaction` as t
ON t.id = tis.transaction_id
AND t.added_date >= '2014-02-26 00:00:00'
GROUP BY tis.shop_item_id
) sub1
ON sub1.shop_item_id = si.id
GROUP BY si.shop_id
) sub2
ORDER BY `count` DESC LIMIT 7;