Limit in subquery - mysql

When I use the following query without LIMIT nested in a subquery
SELECT `c`.*,
GROUP_CONCAT(g.photo SEPARATOR "|") AS `photos_list`
FROM `contests` AS `c`
LEFT JOIN
(
SELECT `gallery`.`contest_id`,
`gallery`.`photo`
FROM `gallery`
) AS `g` ON c.id = g.contest_id
GROUP BY `c`.`id`
all works fine
id title photos_list
1 title1 50026c35632eb.jpg
2 title2 50026ac53567f.jpg|50026ac5ec82e.jpg|500e71557270f....
Bun when I add LIMIT, I get "photos_list" in only one row. Following query
SELECT `c`.*,
GROUP_CONCAT(g.photo SEPARATOR "|") AS `photos_list`
FROM `contests` AS `c`
LEFT JOIN
(
SELECT `gallery`.`contest_id`,
`gallery`.`photo`
FROM `gallery`
LIMIT 0, 2
) AS `g` ON c.id = g.contest_id
GROUP BY `c`.`id`
will return
id title photos_list
1 title1 NULL
2 title2 50026ac46ea05.jpg|50026ac53567f.jpg
Item with an id = 1 has to contain photos_list, but it doesn't. Noteworthy that LIMIT does work for item with an id = 2.
What should I do to get a correct result?

SELECT `c`.*,
GROUP_CONCAT(g.photo SEPARATOR "|") AS `photos_list`
FROM `contests` AS `c`
LEFT JOIN
(
SELECT `gallery`.`contest_id`,
`gallery`.`photo`
FROM `gallery`
) AS `g` ON c.id = g.contest_id
GROUP BY `c`.`id`
Change GROUP_CONCAT to this:
SUBSTRING_INDEX(GROUP_CONCAT(g.photo SEPARATOR "|"),'|',2) AS `photos_list`

You can do similar things with timestamps (e.g. AND photo_date > gsub.photo_date) or more complex criteria. The only caveat is that if there are several rows that all match the conditions (e.g. several photos have identical timestamps), all of them will be included. That's why I chose photo_id, which is assumably unique.
Insert it into your original query like so:
SELECT c.id, c.title,
GROUP_CONCAT(g.photo SEPARATOR "|") AS photos_list
FROM contests AS c
LEFT JOIN (
//put query from above here
) AS g
ON c.id = g.contest_id GROUP BY c.id

This works as well. However, without wrapping another SELECT clause around it, if there are no photos for a contest, the contest will not show up.
SELECT c.*, GROUP_CONCAT(g.photo SEPARATOR "|") AS photo_list
FROM
contests c
LEFT JOIN
(SELECT *, #num:= if(#contest = contest_id, #num + 1,1) as row_num,
#contest := contest_id as c_id
FROM gallery
ORDER BY contest_id) AS g
ON c.id = g.contest_id
WHERE g.row_num <= 2
GROUP BY c.id, c.title

SELECT c.*, ((
SELECT GROUP_CONCAT(temp.photo SEPARATOR "|")
FROM (SELECT photo FROM gallery g WHERE c.id = g.contest_id LIMIT 2) temp
)) AS photo_list
FROM contests c
Sorry for the incorrect answer. I'm not saying that the following solution is the optimum one but at least it works. BTW, in this new solution I've assumed that you gallery table has a primary key named id.
SELECT c.*, GROUP_CONCAT(g.photo SEPARATOR "|") AS photos_list
FROM contests AS c
LEFT JOIN (
SELECT
g_0.*
FROM (
SELECT
g_1.*
, ((SELECT COUNT(*) FROM gallery g_2 WHERE g_2.contest_id = g_1.contest_id AND g_2.id <= g_1.id)) AS i
FROM gallery g_1
) g_0
WHERE
g_0.i <= 2
) g ON (c.id = g.contest_id)
GROUP BY c.id

How do you decide which 2 of the possible set of photos for a particular contest should be returned? Is it meant to be a random thing? Or is it the 2 most recent photos, or the 2 highest rated photos, or some other criteria? Once you can set a condition for choosing the photos, the rest is straighforward. This query would get you the 2 photos with the highest photo_ids for each contest_id:
SELECT contest_id, photo, photo_id
FROM gallery gsub
WHERE (
SELECT COUNT(*) FROM gallery
WHERE contest_id=gsub.contest_id //for each category
AND photo_id > gsub.photo_id
) < 2 //if number of photo_ids > than this photo_id < 2, keep this photo
ORDER BY contest_id
You can do similar things with timestamps (e.g. AND photo_date > gsub.photo_date) or more complex criteria. The only caveat is that if there are several rows that all match the conditions (e.g. several photos have identical timestamps), all of them will be included. That's why I chose photo_id, which is assumably unique.
Insert it into your original query like so:
SELECT c.id, c.title,
GROUP_CONCAT(g.photo SEPARATOR "|") AS photos_list
FROM contests AS c
LEFT JOIN (
//put query from above here
) AS g
ON c.id = g.contest_id GROUP BY c.id

Related

Mysql query to sum a quantity from another table and under its group name if needed

I have 2 tables.
I am trying to get this result
Im having trouble with this query, the quantities seem t0o large, this is what i have and its wrong. I want the items-title and group to concat if they are in the same row, and the qtys to be summed up. Thank you.
SELECT g.`groupname`, SUM(i.`qty`) as qty 
FROM `items` AS i 
INNER JOIN `groups` AS g 
WHERE i.`groupid` = g.`groupid` OR i.`rownum` > 0 
GROUP BY g.`rownum`
Edit:
This may help if i've not explained correctly
Split this into two subqueries. One gets all the groups and their corresponding items using a left join. The other gets the items that have no corresponding group. Then combine them with UNION.
SELECT g1.id, CONCAT_WS(' ', g1.groupname, g2.titles) AS groupname, g1.qty + IFNULL(g2.qty, 0) AS qty
FROM (
SELECT g.id, g.groupname, SUM(qty) AS qty
FROM groups AS g
JOIN items AS i ON g.id = i.groupid
GROUP BY i.id
) AS g1
LEFT JOIN (
SELECT rownum, GROUP_CONCAT(title SEPARATOR ' ') AS titles, SUM(qty) AS qty
FROM items
WHERE rownum IS NOT NULL
GROUP BY rownum
) AS g2 ON g1.id = g2.rownum
UNION ALL
SELECT i.id, i.title, SUM(qty) AS qty
FROM items AS i
LEFT JOIN groups AS g ON g.id = i.groupid
WHERE g.id IS NULL AND i.rownum IS NULL
GROUP BY i.id
I haven't tested this. If it doesn't work and you'd like me to debug it, either create a db-fiddle or post the sample data as text so I can copy and paste it.
Although this is not the exact solution, I can work with this inside my web page...
https://www.db-fiddle.com/f/viFL7xnknyZodpchPHtBqp/3
SELECT g.rownum, groupname AS name, IFNULL(sum(qty), 0) AS qty, g.groupid
FROM groups AS g
LEFT JOIN items AS i ON g.groupid = i.groupid
WHERE (g.groupid = i.groupid AND i.rownum = 0)
OR (NOT EXISTS (SELECT 1 FROM items AS i2 WHERE i2.groupid = g.groupid) AND g.rownum > 0)
GROUP BY g.rownum
UNION ALL
SELECT rownum, title AS name, qty, groupid
FROM items
WHERE rownum > 0
ORDER BY rownum
Close Solution

How to fix query results not showing up?

I have a MySQL query which I want to execute to see who is the employee with the best skill X in a company I work for. To do this I randomly pick a company from my cv_profile (skill_cv_test) and find all users who work there for the same employer. And then I randomly choose a skill I have.
The result should either be zero or a list.
But when testing with PHPMyAdmin I get results where I don't see any row, but the status says there is at least one row.
Here's an example of the message I get: https://imgur.com/bVMH716
I have been trying different structures, even "walling" the query with another query, different joins.
SELECT
DISTINCT(sv.usr_id),
u.first_name AS fn,
u.last_name AS ln,
c.name AS company,
s.name AS skill
FROM
(
SELECT
MAX(last_change) as date,
id,
usr_id,
skill_id
FROM skill_valuations
GROUP BY usr_id, skill_id
ORDER BY date
) sv
LEFT JOIN skill_valuations skv ON skv.last_change = sv.date
INNER JOIN
(
SELECT
DISTINCT(skct.comp_id),
skct.usr_id AS usr_id,
skct.category
FROM skill_cv_test skct
WHERE skct.end_date IS NULL AND skct.comp_id IN (SELECT comp_id FROM (SELECT comp_id FROM skill_cv_test WHERE usr_id = 1 ORDER BY RAND() LIMIT 1) x)
) uqv ON uqv.usr_id = sv.usr_id
INNER JOIN
(
SELECT skill_id
FROM usr_skills
WHERE usr_id = $uid
ORDER BY RAND()
LIMIT 1
) usq ON usq.skill_id = sv.skill_id
LEFT JOIN companies c ON c.id = uqv.comp_id
LEFT JOIN skills s ON s.id = sv.skill_id
LEFT JOIN users u ON u.id = sv.usr_id
As mentioned before, I expect either no results or a result of at least one row.

Wrong MySQL query result using JOIN tables

The following MySQL query is suppose to rank the posts according to their views + rating + submit date in an ascending order:
select
cat ,
p.id ,
title ,
p.date ,
shares ,
source ,
cat ,
count(v.post_id) views ,
sum(r.ilike) rating,
r.module ,
r.module_id ,
#Rank := #Rank + 1 AS Rank
from
posts p
JOIN
rates r
on
r.module_id = p.id
AND r.module = 'posts'
JOIN
posts_views v
on
v.post_id = p.id
WHERE
p.date <= UNIX_TIMESTAMP(NOW())
AND p.state = '3'
AND
(
p.cat NOT REGEXP '[[:<:]]15[[:>:]]'
)
GROUP BY
r.module_id
ORDER BY
rating DESC ,
views DESC ,
p.date ASC LIMIT 0, 10
Gives the following result:
We have 3 problems in the result:
the views column values are doubled
the rating column values are copying the views' value
The Rank column in NULL
The query is generating a semi-Cartesian product. With multiple matching rows from r and multiple matching rows from v, those rows are getting matched together, inflating the results for rating and views. If we remove the GROUP BY and the aggregate functions, and get detail rows back, we can observe the "duplicate" rows that are causing the views count to be doubled, tripled...
One fix for this is to avoid the Cartesian product, by pre-aggregaing from at least one of the child tables in an inline view. Then we join the derived table to the posts table to get the aggregate to the outer query.
We probably want to consider using an outer join to handle the condition when there are no matching rows in views or rates, so we can return zero count for posts that don't have any views.
Initialize user defined variables, either as a separate statement, or within an inline view.
Also, we want to qualify all column references, both as an aid to the future reader (not force them to look at table definitions to figure out which table a column like cat or title or source is coming from), and to avoid the query breaking with an "ambiguous column" error, in the future when a column of the same name is added to one of the tables referenced in the query.
I suggest something like this:
SELECT p.cat
, p.id
, p.title
, p.date
, p.shares
, p.source
, p.cat
, IFNULL(v.cnt_views,0) AS views
, r.tot_rating AS rating
, r.module
, r.module_id
, #Rank := #Rank + 1 AS Rank
FROM ( SELECT #Rank := 0 ) i
CROSS
JOIN posts p
LEFT
JOIN ( SELECT ra.module_id
, MAX(ra.module) AS module
, SUM(ra.ilike) AS tot_rating
FROM rates ra
WHERE ra.module = 'posts'
GROUP
BY ra.module_id
) r
ON r.module_id = p.id
LEFT
JOIN ( SELECT pv.post_id
, SUM(1) AS cnt_views
FROM posts_views pv
GROUP
BY pv.post_id
) v
ON v.post_id = p.id
WHERE p.date <= UNIX_TIMESTAMP(NOW())
AND p.state = '3'
AND p.cat NOT REGEXP '[[:<:]]15[[:>:]]'
ORDER
BY r.tot_rating DESC
, v.cnt_views DESC
, p.date ASC
LIMIT 0, 10

How should I merge these selects and narrow the result set?

I have this huge query that filters results out of a series of keywords.
select distinct textures.id from textures
WHERE ((textures.id in (
select tt.texture_id
from tag_texture tt join tags t
on t.id = tt.tag_id
where t.name in ('tag1', 'tag2')
group by tt.texture_id
HAVING COUNT(DISTINCT t.id) = 2
)
) OR (textures.id in (
select ct.texture_id
from category_texture ct join categories c
on c.id = ct.category_id
where c.name in ('category1', 'category2')
group by ct.texture_id
HAVING COUNT(DISTINCT c.id) = 2
)
) OR (textures.id in (
select tex.id
from textures tex
where tex.name LIKE 'texturename'
group by tex.id
HAVING COUNT(DISTINCT tex.id) = 1
)
) ) AND textures.is_published = 1
The problem is that if I search for texturename tag1, all texturename results will be found, even if they have nothing to do with tags. However, if I search for "tag1 tag2", the resulting list is filtered out (less results than just searching tag1). Changing those ORs to AND widens the results even more, obviously.
What's the best way to merge these results so that each time a word is filtered the result set is narrowed down?
Changing all the OR to AND should solve the problem:
SELECT id, name
FROM textures
WHERE ((textures.id in (
select tt.texture_id
from tag_texture tt join tags t
on t.id = tt.tag_id
where t.name in ('1k', 'test')
group by tt.texture_id
HAVING COUNT(DISTINCT t.id) = 2
)
) AND (textures.id in (
select ct.texture_id
from category_texture ct join categories c
on c.id = ct.category_id
where c.name in ('mine')
group by ct.texture_id
HAVING COUNT(DISTINCT c.id) = 1
)
) AND (textures.id in (
select tex.id
from textures tex
where tex.name LIKE '%apple%'
group by tex.id
HAVING COUNT(DISTINCT tex.id) = 1
)
) ) AND textures.is_published = 1
SqlFiddle
There's no need to use DISTINCT in this query. You're not joining with any other tables, so nothing is going to cause the results to multiply.
If you want to search for the same keywords in all the fields, and require that at least one of them match each field, get rid of the GROUP BY and HAVING clauses.
select textures.id, textures.name from textures
WHERE ((textures.id in (
select tt.texture_id
from tag_texture tt join tags t
on t.id = tt.tag_id
where t.name in ('1k', 'test', 'apple', 'mine')
)
) AND (textures.id in (
select ct.texture_id
from category_texture ct join categories c
on c.id = ct.category_id
where c.name in ('1k' 'test', 'apple', 'mine')
)
) AND (textures.id in (
select tex.id
from textures tex
where tex.name LIKE '%1k%' OR tex.name LIKE '%test%' OR tex.name LIKE '%apple%'
OR tex.name LIKE '%mine%'
)
) ) AND textures.is_published = 1
I added mine to the list of keywords, because otherwise there was no match in the categories table.
SqlFiddle

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 -