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

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

Related

Limit results to 1 row for each group in subquery

as a result of the execution of this query, situations are possible when two rows have the same minimum price, but i still need to select one. I understand perfectly well that the standard limit cannot be dispensed with here. I do not have enough knowledge to understand from which side to approach the solution of this issue. Thank you in advance for your attension.
UPDATE offers t1
SET t1.deleted_at = NOW()
WHERE t1.id
NOT IN
(
SELECT f.id
FROM (
SELECT name, MIN(net_price) as minprice
FROM offers
WHERE
supplier_id = (SELECT id FROM suppliers WHERE name = 'somename')
group BY name
)
AS x inner join (SELECT * FROM offers) AS f ON f.name = x.name AND f.net_price = x.minprice
)
AND
t1.supplier_id = (SELECT id FROM suppliers WHERE name = 'somename');
I don't know somehowe, but this works for me:
UPDATE offers t1
SET t1.deleted_at = NOW()
WHERE t1.id
NOT IN
(
SELECT f.id
FROM (
SELECT id, name, MIN(net_price) as minprice
FROM offers
WHERE
supplier_id = (SELECT id FROM suppliers WHERE name = 'somename')
group BY name
)
AS x inner join (SELECT * FROM offers) AS f ON f.id = x.id
)
AND
t1.supplier_id = (SELECT id FROM suppliers WHERE name = 'somename');
Just add id to subquery select and put it on inner join f.id = x.id

Match all keywords using like in group MySQL

I have a table of keywords (ID,ean,keyword) and another table with product details. I want the search to return EANs where all keywords match at least once, however the closest I have got is the following, but this returns matches that have the first term in them 3 times for example.
To give an example, let's say I have a product called 'Generic headphones - iPhone, iPad, iPod' and I searched 'gen%' 'hea%' 'ip%' it would come back as a match, but it would also match 'Apple headphones - iPhone, iPad, iPod' due to the 3 ip words, which is not desired.
SQL Fiddle
I want EAN 1 to match only, so matches need to be at least 1 for each term.
Any help would be much appreciated.
SELECT Count(keywords.ean) AS cc,
products.*
FROM keywords
INNER JOIN products
ON products.ean = keywords.ean
WHERE (
keyword LIKE 'gen%'
|| keyword like 'ip%'
|| keyword LIKE 'hea%')
GROUP BY (keywords.ean)
HAVING cc>=3
ORDER BY `products`.`ean` ASC
UPDATE: This gets the desired results, but there must be more efficient ways to do this.
SELECT products.*
FROM products
INNER JOIN (SELECT ean, count(*) as tc1
FROM keywords
WHERE ( keyword like 'gen%' )
GROUP BY ean
HAVING tc1 > 0 ) as t1 ON t1.ean = products.ean
INNER JOIN (SELECT ean, count(*) as tc2
FROM keywords
WHERE ( keyword like 'ip%' )
GROUP BY ean
HAVING tc2 > 0 ) as t2 ON t2.ean = products.ean
INNER JOIN (SELECT ean, count(*) as tc3
FROM keywords
WHERE ( keyword like 'hea%' )
GROUP BY ean
HAVING tc3 > 0 ) as t3 ON t3.ean = products.ean
ORDER BY products.ean
Perhaps you're after something more like this...
SELECT p.ean
, p.description
FROM products p
JOIN keywords k
ON k.ean = p.ean
WHERE k.keyword LIKE 'iP%'
OR k.keyword LIKE 'hea%'
OR k.keyword LIKE 'gen%'
GROUP
BY p.ean
HAVING COUNT(DISTINCT CASE WHEN k.keyword LIKE 'iP%' THEN 'iP'
WHEN k.keyword LIKE 'hea%' THEN 'hea'
WHEN k.keyword LIKE 'gen%' THEN 'gen'
ELSE keyword END) = 3;
http://sqlfiddle.com/#!9/270f9/25
This is how I'd do it in PostgreSQL. MySQL may have a slightly different syntax.
SELECT kc.cc AS cc,
products.*
FROM products
INNER JOIN ( SELECT ean, count(*) AS cc
FROM keywords
WHERE ( keyword like 'ip%'
OR keyword like 'ai%'
OR keyword like 'bei%' )
GROUP BY ean
HAVING count(*) >= 3 ) AS kc
ON kc.ean = products.ean
ORDER BY Products.ean;

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 -

GROUP_CONCAT multiple values from table

I just added a table which holds a url and description for each item in place. The below query works ok ... but it's only returning the url and I need the desc also. I m not sure how to make this work.
$query_str = "SELECT a.userid, a.cat, a.id, a.name, a.image,
a.desc, a.country, b.user_id, c.username,
c.fbook, d.cat_id,
(
SELECT GROUP_CONCAT(url)
FROM one_add o
WHERE a.id=o.one_id
) AS url,
(
SELECT COUNT(id)
FROM one_msg m
WHERE m.guest_id = ? AND
m.one_id=a.id
) AS count
FROM place a
LEFT OUTER JOIN wait b
ON a.id=b.post_id AND
b.user_id= ?
JOIN users c
ON a.userid=c.id
LEFT JOIN (
SELECT userid, user_id,
GROUP_CONCAT( cat_id ) AS cat_id
FROM user_cat
WHERE userid='$user_id'
GROUP BY user_id
) AS d ON d.userid='$user_id' AND
d.user_id=a.userid
WHERE a.cat != ? ORDER BY a.date desc";
This is what I want to accomplish: desc
You can achibe this by using CONCAT inside GROUP_CONCAT function as:
(SELECT GROUP_CONCAT(CONCAT('',desc,''))
FROM one_add o
WHERE a.id=o.one_id) AS url
full query:
(SELECT COUNT(id) FROM one_msg m WHERE m.guest_id = ? AND m.one_id=a.id ) AS count
FROM place a
LEFT OUTER JOIN wait b ON a.id=b.post_id AND b.user_id= ?
JOIN users c ON a.userid=c.id
LEFT JOIN ( SELECT userid, user_id, GROUP_CONCAT( cat_id ) AS cat_id FROM user_cat WHERE userid='$user_id' GROUP BY user_id ) AS d ON d.userid='$user_id' AND d.user_id=a.userid
WHERE a.cat != ? ORDER BY a.date desc";

Limit in subquery

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