Get multiple rows from a table to per row of another table - mysql

I have a problem here, I wonder if it is possible to do this in just one query instead of having the server do several queries.
So here's the thing, what I want is, in each category, to search all stores belonging to that category within a limit of 24 stores. That is, imagining that I have 5 categories registered, I will search 24 stores belonging to that category, which in the conclusion I will have a total of 120 stores in the query result.
The following code is an example, but this will only fetch the first 24 stores of any category without iterating through each category.
SELECT *
FROM categories c
LEFT JOIN (SELECT * FROM stores_categories LIMIT 24 OFFSET 0) sc ON sc.id_category = c.id
WHERE type = 'STORE' OR type = 'ALL';
Someone sent me this and it is very similar to my problem, How to SELECT the newest four items per category?, It is kind of like that, but I would like to be able to limit and with offset to make pages and not just the first 24 most recent in each category.
The example code from the link I was given:
SELECT sc1.*
FROM stores_categories sc1
LEFT OUTER JOIN stores_categories sc2 ON (sc1.id_category = sc2.id_category AND sc1.id_store < sc2.id_store)
GROUP BY sc1.id_store
HAVING COUNT(*) < 24
ORDER BY sc1.id_category;
And the database server which one I'm is the version 5.5.64-MariaDB. An fiddle example where is possible to make tests with same values from my database. https://www.db-fiddle.com/f/jcowJL9S4FQXqKg2yMa8kf/0

It could make use of a calculated row_number per category.
--
-- Emulated row_number via variables
--
SELECT sc.id, id_store
, cat.name AS cat_name
, cat.type AS cat_type
, rn
FROM
(
SELECT sc.*
, #rn := CASE
WHEN #cat_id = sc.id_category
THEN #rn + 1 ELSE 1
END AS rn
, #cat_id := sc.id_category as cat_id
FROM stores_categories sc
CROSS JOIN (SELECT #cat_id:=0, #rn:=0) vars
ORDER BY sc.id_category, sc.id_store DESC, sc.id DESC
) sc
LEFT JOIN categories cat
ON cat.id = sc.id_category
WHERE rn BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC, sc.id DESC
Or
--
-- Grouped self-join
--
SELECT sc.id, sc.id_store
, cat.name AS cat_name
, cat.type AS cat_type
, COUNT(sc2.id_store) AS cnt
FROM stores_categories sc
LEFT JOIN stores_categories sc2
ON sc2.id_category = sc.id_category
AND sc2.id_store >= sc.id_store
LEFT JOIN categories cat
ON cat.id = sc.id_category
GROUP BY sc.id
HAVING cnt BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC;
Or
--
-- Correlated sub-query
--
SELECT sc.id, id_store
, cat.name AS cat_name
, cat.type AS cat_type
, ( select count(*)
from stores_categories sc2
where sc2.id_category = sc.id_category
and sc2.id_store >= sc.id_store
) as rn
FROM stores_categories AS sc
LEFT JOIN categories cat
ON cat.id = sc.id_category
HAVING rn BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC;
Demo on db<>fiddle here

Related

Simple incremental counter in mysql query

My Query:
SET #rank = 0;
SELECT Jobs.ID, Jobs.StatusID, (#rank:=#rank+1) AS Rank
FROM Jobs
INNER JOIN JobStatuses ON Jobs.StatusID = JSID
INNER JOIN JobStatusGroups ON Jobs.SGrID = JSGID
WHERE Jobs.StatusID = 3
ORDER BY JobTitle DESC
LIMIT 5
Results in:
1010 3 1
1011 3 2
1013 3 4
1014 3 5
1016 3 7
Should result in:
1010 3 1
1011 3 2
1013 3 3
1014 3 4
1016 3 5
How can I make #rank only increment if row is actually being inserted? I tried cross join did not work. I also tried the following:
SELECT Jobs.ID, Jobs.StatusID, (#rank:=#rank+1) AS Rank
FROM Jobs, (SELECT #rank := 0) tempRank
INNER JOIN JobStatuses ON Jobs.StatusID = JSID
INNER JOIN JobStatusGroups ON Jobs.SGrID = JSGID
WHERE Jobs.StatusID = 3
ORDER BY JobTitle DESC
LIMIT 5
But gives me an error saying Jobs.StatusID does not exist.
instead of setting it outside cross join it in the select (i prefer cross join just so its easier to read aka i know where im creating the variable) that way the count wont be messed up
INSERT IGNORE INTO MyTable(MyID, UserID, JobID, StatusID, SortOrder)
SELECT :myid, :userid, Jobs.ID, Jobs.StatusID, (#rank:=#rank+1) AS Rank
FROM Jobs
INNER JOIN JobStatuses ON Jobs.StatusID = JSID
CROSS JOIN (SELECT #rank := 0) temp
LEFT JOIN JobStatusGroups ON Jobs.SGrID = JSGID
WHERE ...
ORDER BY JobTitle DESC
on terms of performance I haven't ever noticed performance issues with using the variables instantiation inside a join on the table
one thing you can try is maybe the ORDER BY is messing up the order / count so you can try encapsulating it in a subselect
SELECT ID, StatusID, (#rank:=#rank+1) AS Rank
FROM
( SELECT Jobs.ID, Jobs.StatusID
FROM Jobs
INNER JOIN JobStatuses ON Jobs.StatusID = JSID
INNER JOIN JobStatusGroups ON Jobs.SGrID = JSGID
WHERE Jobs.StatusID = 3
ORDER BY JobTitle DESC
LIMIT 5
)t
CROSS JOIN (SELECT #rank := 0) temp
Try This updated one:
SET #rank = 0;
INSERT INTO MyTable(MyID, UserID, JobID, StatusID, SortOrder)
SELECT myid, userid, Jobs.ID, Jobs.StatusID, (#rank:=#rank+1) AS Rank
FROM Jobs
INNER JOIN JobStatuses ON Jobs.StatusID = JobStatuses.JSID
LEFT JOIN JobStatusGroups ON Jobs.SGrID = JobStatusGroups.JSGID
ON DUPLICATE KEY update MyID=IF((#rank:=#rank-1) <> NULL IS NULL, VALUES(MyID), NULL);

can we have a query of "10 posts from every category in one request"?

We have a list of posts that belong a category.
can we have a query of "10 posts from every category in one request" ?
Or
We have to query 10 posts for every category separately ?
thx
A fudge involving using GROUP_CONCAT to put all the post ids for a category together (you can add an order clause to this if you want), then substring_index to get the first 10 posts.
This is then joined back to the original table using FIND_IN_SET
SELECT a.*
FROM some_table a
INNER JOIN
(
SELECT category_id, SUBSTRING_INDEX(GROUP_CONCAT(post_id), ',', 10) AS posts
FROM some_table
GROUP BY catgegory_id
) sub0
ON a.category_id = sub0.category_id
AND FIND_IN_SET(a.post_id, sub0.posts)
Or using variables:-
SELECT a.*
FROM some_table a
INNER JOIN
(
SELECT category_id, post_id, #cnt:=IF(#category_id=category_id, #cnt + 1, 1) AS cnt, #category_id:=category_id
FROM
(
SELECT category_id, post_id
FROM some_table
ORDER BY category_id, post_id
) sub0
CROSS JOIN (SELECT #cnt:=0, #category_id:=0) sub1
) sub2
ON a.category_id = sub2.category_id
AND a.post_id = sub2.post_id
AND cnt <= 10
This pseudo code should point you in the right direction.
SELECT DISTINCT CATEGORY FROM T AS T1
CROSS JOIN (SELECT * FROM T WHERE T.CATEGORY = T1.CATEGORY ORDER BY CATEGORY DESC LIMIT 10)

My Odd SubSelect, Need a LEFT JOIN Improvement

Here is a sample SQL dump: https://gist.github.com/JREAM/99287d033320b2978728
I have a SELECT that grabs a bundle of users.
I then do a foreach loop to attach all the associated tree_processes to that user.
So I end up doing X Queries: users * tree.
Wouldn't it be much more efficient to fetch the two together?
I've thought about doing a LEFT JOIN Subselect, but I'm having a hard time getting it correct.
Below I've done a query to select the correct data in the SELECT, however I would have to do this for all 15 rows and it seems like a TERRIBLE waste of memory.
This is my dirty Ateempt:
-
SELECT
s.id,
s.firstname,
s.lastname,
s.email,
(
SELECT tp.id FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
WHERE subscribers_id = s.id
ORDER BY tp.id DESC
LIMIT 1
) AS newest_tree_id,
#
# Don't want to have to do this below for every row
(
SELECT t.type FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
WHERE subscribers_id = s.id
ORDER BY tp.id DESC
LIMIT 1
) AS tree_type
FROM subscribers AS s
INNER JOIN scenario_subscriptions AS ss ON (
ss.subscribers_id = s.id
)
WHERE ss.scenarios_id = 1
AND ss.completed != 1
AND ss.purchased_exit != 1
AND deleted != 1
GROUP BY s.id
LIMIT 0, 100
This is my LEFT JOIN attempt, but I am having trouble getting the SELECT values
SELECT
s.id,
s.firstname,
s.lastname,
s.email,
freshness.id,
# freshness.subscribers_id < -- Cant get multiples out of the LEFT join
FROM subscribers AS s
INNER JOIN scenario_subscriptions AS ss ON (
ss.subscribers_id = s.id
)
LEFT JOIN ( SELECT tp.id, tp.subscribers_id AS tp FROM tree_processes AS tp
JOIN tree AS t ON (
t.id = tp.tree_id
)
ORDER BY tp.id DESC
LIMIT 1 ) AS freshness
ON (
s.id = subscribers_id
)
WHERE ss.scenarios_id = 1
AND ss.completed != 1
AND ss.purchased_exit != 1
AND deleted != 1
GROUP BY s.id
LIMIT 0, 100
In the LEFT JOIN you are using 'freshness' as the table alias. This in you select you need to additionally state what column(s) you want from it. Since there is only one column (id) you need to add:
freshness.id
to the select clause.
Your ON clause of the left join looks pretty dodgy too. Maybe freshness.id = ss.subscribers_id?
Cheers -

SQL Query with Joins Counting multiple Results Per Record Ordering By Count

I have a table called Request.
Other tables are linked to the Request table through a request id.
There is a TwitterTweet table and a FacebookPost table.
So a single request can have 50 TwitterTweets and/or 20 FacebookPosts or any amount of Tweets/Posts
We can add them together for a total count of 70.
I'm trying to create a query that could tell me what is the request with the highest total count.
I know this is wrong:
(I attempted to just order them by the counts within the TwitterTweet, but it would not let me do an OUTER JOIN which I thought
would bring back the Count.count column. It forced me to do a Left Join for it to compile. My Logic was to do a join so
that the results were calculated for each row by the requestid)
SELECT r1.`id` AS requestid, r1 . *
FROM `Request` AS r1
LEFT JOIN
(SELECT COUNT( * ) AS count, rid
FROM
((SELECT `TwitterTweet`.`id` AS `smid` , `TwitterTweet`.`requestid` AS rid
FROM `TwitterTweet`
WHERE `TwitterTweet`.`requestid` = requestid
AND `TwitterTweet`.`active` =1) AS talias
)) AS Count ON ( Count.rid = requestid )
ORDER BY Count.count
*When I tried to add in the Facebook side it would not compile any more
(The concept is that the results are added from TwitterTweet with the results from FacebookPost
that are attached to the specific requestid which would give us a count. The entire result
set should be ordered by that count)
SELECT r1.`id` AS requestid, r1 . *
FROM `Request` AS r1
LEFT JOIN
(SELECT COUNT( * ) AS count, rid
FROM
((SELECT `TwitterTweet`.`id` AS `smid` , `TwitterTweet`.`requestid` AS rid
FROM `TwitterTweet`
WHERE `TwitterTweet`.`requestid` = requestid
AND `TwitterTweet`.`active` =1 ) AS talias
UNION All
(SELECT `FacebookPost`.`id` AS `smid`, `FacebookPost`.`requestid` AS rid
FROM `FacebookPost`
WHERE `FacebookPost`.`requestid` = requestid
AND `FacebookPost`.`active` = 1) as falias
)) AS Count ON ( Count.rid = requestid )
ORDER BY Count.count
I updated the Query with an attempt to add an alias:
SELECT rid, SUM(count) total_count
FROM
(
(SELECT COUNT(*) AS count, r.rid
FROM request r
JOIN TwitterTweet tt
ON r.id = tt.requestid
WHERE tt.active = 1
GROUP BY r.rid) AS twitter
UNION ALL
(SELECT COUNT(*) AS count, r.rid
FROM request r
JOIN FacebookPost fp
ON r.id = fp.requestid
WHERE fp.active = 1
GROUP BY r.rid ) AS fbook
)
GROUP BY rid
ORDER BY SUM(count) DESC
I made another adjustment to give the middle subquery an alias, but now I only get one row returned with a zero in the rid column and 5686 in the total_count column...the 5686 might be all of the results.
SELECT counts.rid, SUM(count) total_count
FROM
(
SELECT COUNT(*) AS count, r.requestid AS rid
FROM request r
JOIN TwitterTweet tt
ON r.id = tt.requestid
WHERE tt.active = 1
GROUP BY r.requestid
UNION ALL
SELECT COUNT(*) AS count, r.requestid AS rid
FROM request r
JOIN FacebookPost fp
ON r.id = fp.requestid
WHERE fp.active = 1
GROUP BY r.requestid
) AS counts
GROUP BY counts.rid
ORDER BY SUM(count) DESC
Got it!!!
Thanks for your help guys, I had to remove those joins on the request:
SELECT counts.rid, SUM(count) total_count
FROM
(
SELECT COUNT(*) AS count, tt.requestid AS rid
FROM TwitterTweet tt
WHERE tt.active = 1
GROUP BY tt.requestid
UNION ALL
SELECT COUNT(*) AS count, fp.requestid AS rid
FROM FacebookPost fp
WHERE fp.active = 1
GROUP BY fp.requestid
) AS counts
GROUP BY counts.rid
ORDER BY SUM(count) DESC
SELECT id, SUM(count) total_count
FROM
(
SELECT COUNT(*) AS count, r.id
FROM request r
JOIN TwitterTweet tt
ON r.id = tt.requestid
WHERE tt.active = 1
GROUP BY r.id
UNION ALL
SELECT COUNT(*) AS count, r.id
FROM request r
JOIN FacebookPost fp
ON r.id = fp.requestid
WHERE fp.active = 1
GROUP BY r.id
) sub
GROUP BY id
ORDER BY SUM(count) DESC
;

Followers of a user and ids of last 2 comments liked by each follower(if any), also the count of follower's followers, using mysql

I have social application let's say like twitter, where user can follow other users and can likes some comments.
What I need to fetch is followers of a user and ids of last 2 comments liked by each follower(if any), also the count of follower's followers, using MySQL.
Here are the tables
Table user_follower
User_id follower_id
1 2
2 3
1 5
1 6
1 7
Table user_likes
comment_id User_id date
41 2 some_date
42 2 some_date
41 5 some_date
42 5 some_date
43 5 some_date
43 2 some_date
43 6 some_date
how can we do this in a single mysql query?
so far i am able to get the followers and count of follower's follower and following both.
select uf.follower_id,
(select count(*) from user_followers uf1 where uf1.follower_id = uf.follower_id) as following_count,
(select count(*) from user_followers uf2 where uf2.user_id = uf.follower_id) as follower_count,
from user_followers uf
join users u on u.id = uf.follower_id
where uf.user_id = 1
what i want is now to get the 2 latest comment_ids for each follower, i.e. uf.follower_id here.
if not possible in the same query,
i am fine even with another query as will passing the follower_ids as in parameter, but it should give me 2 latest comment for each passed id..
I think this will work. I have not tested it so it may have some syntax errors. Given the level of nesting used in this query I suspect it will perform badly with a very large dataset.
SELECT follower_id, num_followers, GROUP_CONCAT(comment_id)
FROM (
SELECT t.*,
#r := IF(#g = t.follower_id, #r+1, 1) RowNum,
#g := t.follower_id
FROM (select #g:=null) AS initvars
INNER JOIN (
SELECT followers.*, ul.comment_id
FROM (
SELECT
uf1.user_id,
uf1.follower_id,
COUNT(uf2.follower_id) AS num_followers
FROM user_follower uf1
LEFT JOIN user_follower uf2
ON uf1.follower_id = uf2.user_id
WHERE uf1.user_id = 1
GROUP BY uf1.user_id, uf1.follower_id
) AS followers
LEFT JOIN user_likes ul
ON followers.follower_id = ul.user_id
ORDER BY followers.follower_id ASC, comment_id DESC
) AS t
) AS final
WHERE RowNum < 3
GROUP BY follower_id, num_followers;
UPDATE Here is the other query using the inequality join -
SELECT tmp.follower_id, COUNT(uf2.follower_id) AS num_followers, tmp.comments
FROM (
SELECT follower_id, GROUP_CONCAT(comment_id ORDER BY comment_id DESC) AS comments
FROM (
SELECT uf.follower_id, ul1.*
FROM user_follower uf
LEFT JOIN user_likes ul1
ON uf.follower_id = ul1.user_id
LEFT JOIN user_likes ul2
ON uf.follower_id = ul2.user_id
AND ul1.comment_id < ul2.comment_id
WHERE uf.user_id = 1
GROUP BY ul1.user_id, ul1.comment_id
HAVING COUNT(ul2.comment_id) < 2
) AS tmp
GROUP BY tmp.follower_id
) AS tmp
LEFT JOIN user_follower uf2
ON tmp.follower_id = uf2.user_id
GROUP BY tmp.follower_id