This question already has answers here:
MySQL query finding matches across multiple rows
(2 answers)
MySQL query to search multiple attributes and value_id
(1 answer)
Closed 3 years ago.
When I run the below query, I expect to get one result.
However, I get no results.
SELECT listings.*
FROM `listings`
LEFT JOIN users
ON listings.user_id = users.user_id
LEFT JOIN listing_attributes
ON listings.listing_id = listing_attributes.listing_id
WHERE users.account_status = 1
AND ( listing_attributes.name LIKE 'collection_city' AND listing_attributes.content LIKE '%d%' )
AND ( listing_attributes.name LIKE 'collection_post_code' AND listing_attributes.content LIKE '%e%' )
GROUP BY listings.listing_id
I have identified the problem as these two lines
AND ( listing_attributes.name LIKE 'collection_city' AND listing_attributes.content LIKE '%d%' )
AND ( listing_attributes.name LIKE 'collection_post_code' AND listing_attributes.content LIKE '%e%' )
If I remove either of the lines, I get the result I expect.
I am thinking something is wrong with the way I am encompassing the results in the ().
In essence I am wishing to check that multiple values exist in listing_attributes for a result to be as needed.
I think that your logic is not correct. You must use the operator OR for this condition. Otherwise the condition will be false.
{
( listing_attributes.name LIKE 'collection_city' AND listing_attributes.content LIKE '%d%' )
OR ( listing_attributes.name LIKE 'collection_post_code' AND listing_attributes.content LIKE '%e%' )
}
Please use below query, Try moving the condition to on clause
SELECT listings.*
FROM `listings`
LEFT JOIN users
ON listings.user_id = users.user_id AND users.account_status = 1
LEFT JOIN listing_attributes
ON listings.listing_id = listing_attributes.listing_id AND ( listing_attributes.name LIKE 'collection_city' AND listing_attributes.content LIKE '%d%' ) AND ( listing_attributes.name LIKE 'collection_post_code' AND listing_attributes.content LIKE '%e%' )
GROUP BY listings.listing_id;
Given that listings.listing_id is a key of listings you can use a HAVING clause checking for the sums of the logical expressions are greater then 0. This works because logical expressions in numeric context evaluate to 0 or 1 in MySQL.
Your join with user silently becomes an inner join because of the WHERE as NULLs cannot equal 1. Since the NULLs from the left join of listing_attributes cannot make the HAVING clause true you can also convert that join to an inner join.
SELECT listings.*
FROM listings
INNER JOIN users
ON listings.user_id = users.user_id
INNER JOIN listing_attributes
ON listings.listing_id = listing_attributes.listing_id
WHERE users.account_status = 1
GROUP BY listings.listing_id
HAVING sum(listing_attributes.name LIKE 'collection_city'
AND listing_attributes.content LIKE '%d%') > 0
AND sum(listing_attributes.name LIKE 'collection_post_code'
AND listing_attributes.content LIKE '%e%') > 0;
Related
I have a MySQL database with a media table, and a keywords table, and a many-to-many relationship between media and keywords via a media_keywords join table.
I want to fetch all records from the media table where the following set of conditions match:
'description' is like 'dog' OR
'media.keywords' includes the id for the 'dog' keyword [100]
And exclude from the found set any records where:
'description' is like 'cat' OR
'media.keywords' includes the id for the 'cat' keyword [400]
And also exclude any row where:
'media.keywords' includes the id for the 'monochrome' keyword [500]
I also want to return only distinct rows, so I'm using GROUP By 'media.id'
The SQL statement I have at the moment is as follows:
SELECT DISTINCT
`media`.`id`,`media`.`description`,
`keywords`.`id` AS `keywords.id`,
`keywords->media_keywords`.`id` AS `keywords.media_keywords.id`,
`keywords->media_keywords`.`media_id` AS `keywords.media_keywords.media_id`,
`keywords->media_keywords`.`keyword_id` AS `keywords.media_keywords.keyword_id`
FROM database.media
LEFT OUTER JOIN
(
`media_keywords` AS `keywords->media_keywords`
INNER JOIN `keywords` AS `keywords`
ON `keywords`.`id` = `keywords->media_keywords`.`keyword_id`
)
ON `media`.`id` = `keywords->media_keywords`.`media_id`
WHERE
(
(`media`.`description` LIKE '%dog%' )
OR `keywords`.`id` IN (100)
)
AND NOT
(
(`media`.`description` LIKE '%cat%' )
OR `keywords`.`id` IN (400,500)
)
GROUP BY `media`.`id` ;
This correctly fetches records where 'dog' is in the description or is a keyword, but ignores the exclusions completely.
Can anyone see what I'm doing wrong here?
I would use a where clause:
select m.*
from media m
where (m.description like '%dog%' or
exists (select 1
from keywords k
where k.media_id = m.id and
k.keyword_id = 100
)
) and
(m.description not like '%cat%' or
exists (select 1
from keywords k
where k.media_id = m.id and
k.keyword_id in (400, 500)
)
);
This is pretty much a direct translation of your conditions.
I don't work with mySQL much, but I would suggestion a different approach
Move the "DOG" condition inside the LEFT JOIN (so only get keywords
matching "DOG") and make it a JOIN. Now you'll have a list of all
matches.
add a subquery in the WHERE clause
WHERE id not in (SELECT id FROM ... WHERE LIKE '%cat%')
I am making the sql code. I have met the barrier, that is, so many select sentences in SQL query. Finally I want to get to the intersection of 9 select results
My sql code is same as below, just 1 select sentence. 8 select sentences are different from only search word, eg) cholera, diarrhea, fever, vomit, nausea, etc
First select sentence. Don't be suprised. That code is simple and repeatedly.
(SELECT code_co.code, code_co.disease_co, code_en.disease_en
FROM code_co
LEFT JOIN code_en ON code_en.code = code_co.code
LEFT JOIN note ON note.code = code_co.code
LEFT JOIN inclusion ON inclusion.code = code_co.code
LEFT JOIN exclusion ON exclusion.code = code_co.code
LEFT JOIN ds ON code_co.code = ds.code
LEFT JOIN tx ON code_co.code = tx.code
LEFT JOIN sx ON code_co.code = sx.code
WHERE
note LIKE CONCAT( '%', (
SELECT ds_word.ds_en
FROM ds_word
WHERE ds_co LIKE '%cholera%'
LIMIT 0 , 1
), '%' )
or
ds_content LIKE CONCAT( '%', (
SELECT ds_word.ds_en
FROM ds_word
WHERE ds_co LIKE '%cholera%'
LIMIT 0 , 1
), '%' )
...
inclusion LIKE CONCAT( '%', (
SELECT ds_word.ds_en
FROM ds_word
WHERE ds_co LIKE '%cholera%'
LIMIT 0 , 1
), '%' )
)
Below is the captured picture on phpmyadmin.
Really working code!
And 2nd select sentence is same as first sentence except cholera. Cholera is my search word.
In this way, I have 9 select sentences.
I want to get the intersection, but in MySQL, How can I care?
Intersect or minus can be used in just 2 sentences. (Right?)
(1st select sentence)
intersect
(2nd select sentence)
intersect
(3rd select sentence)
...
This way is right?
Please help me.
Thank you for your advice
You do the "intersect" by using and in the where clause. Using or is equivalent to a "union".
Also, you can simplify your expression by doing:
LEFT JOIN sx ON code_co.code = sx.code
CROSS JOIN (SELECT concat('%', ds_word.ds_en, '%') as pattern
FROM ds_word
WHERE ds_co LIKE '%cholera%'
LIMIT 0 , 1
) const
WHERE note LIKE const.pattern and
ds_content like const.pattern and
. . .
SELECT *
FROM (
`lecture` AS l
)
LEFT JOIN `professor` AS p ON `p`.`professor_id` = `l`.`professor_id`
WHERE `lecture_sem` = '20141'
AND (
lecture_name LIKE '%KEYWORD%'
OR lecture_code LIKE '%KEYWORD%'
OR p.professor_name LIKE '%KEYWORD%'
)
AND (
SELECT COUNT( DISTINCT s1.yoil, s1.start_time, s1.end_time )
FROM schedule AS s1
INNER JOIN schedule AS s2 ON ( s1.lecture_id
IN (
SELECT lecture_id
FROM timeitem
WHERE timetable_id =890
)
AND s2.yoil = s1.yoil
AND (
(
s1.start_time <= s2.start_time
AND s2.end_time <= s1.end_time
) )
AND s2.lecture_id = lecture_id # <-- HERE
)
) >0
LIMIT 0 , 30
I want to use where column like this:
s2.lecture_id = lecture_id
or,
s2.lecture_id = l.lecture_id
So I want to use parent column in subquery, but error occurs:
Column 'lecture_id' in on clause is ambiguous
I googled many answers about this problem ("on clause is ambiguous"), they said I should replace this query to joining two queries. But I don't have a clue how to transform this query.
I believe the following does the equivalent query, but I haven't tested it.
The technique is to move the correlated subquery into the FROM clause as a derived table so that it gets run only once, producing a result for each lecture_id (hence the GROUP BY).
I also factored out the subquery for timetable, which I believe can be rewritten as a JOIN.
And I suspect the join to professor may be properly an INNER JOIN -- how can you have a lecture without a professor?
SELECT l.*, p.*
FROM lecture AS l
INNER JOIN professor AS p ON p.professor_id = l.professor_id
INNER JOIN (
SELECT s2.lecture_id, COUNT( DISTINCT s1.yoil, s1.start_time, s1.end_time ) AS count
FROM schedule AS s1
INNER JOIN schedule AS s2 ON s2.yoil = s1.yoil
AND s1.start_time <= s2.start_time AND s2.end_time <= s1.end_time
INNER JOIN timeitem AS t ON s1.lecture_id = t.lecture_id
WHERE t.timetable_id = 890
GROUP BY s2.lecture_id
) AS c ON l.lecture_id = c.lecture_id
WHERE l.lecture_sem = '20141'
AND c.count > 0
AND (
l.lecture_name LIKE '%KEYWORD%'
OR l.lecture_code LIKE '%KEYWORD%'
OR p.professor_name LIKE '%KEYWORD%'
)
LIMIT 0 , 30
Anyway, even if the query isn't perfect, it demonstrates how one would refactor it to avoid a correlated subquery.
The line you identified in your code isn't an ON clause.
Instead, I think the error is referring to the following section.
AND (
SELECT COUNT( DISTINCT s1.yoil, s1.start_time, s1.end_time )
FROM schedule AS s1
INNER JOIN schedule AS s2 ON ( s1.lecture_id
IN (
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
SELECT lecture_id # <---- HERE
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FROM timeitem
WHERE timetable_id =890
)
You can fix this by creating an alias for the timeitem table, and prefixing the column with this prefix:
IN (
SELECT ti.lecture_id
FROM timeitem as ti
WHERE ti.timetable_id =890
)
But as Bill Karwin pointed out in his answer, you have other logical issues that need to be addressed.
This has been asked in different ways before, but I can't seem to get something that works for what I need exactly.
The goal here is to make a search query that returns Photos based on tags that are selected. Many tags can be applied to the filter simultaneously, which would need to make it so that the query only returns photos that have ALL of the tags selected. Think of any major web shop where you are narrowing down results after performing a basic keyword search.
Table1: Photos
ID|Title|Description|URL|Created
Table2: PhotosTagsXref
ID|PhotoId|TagId
Table3: PhotosTags
ID|Title|Category
What I have:
SELECT p.* FROM `PhotosTagsXref` AS pt
LEFT JOIN `Photos` AS p ON p.`ID` = pt.`PhotoId`
LEFT JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
WHERE p.`Description` LIKE "%test%" AND
????
GROUP BY p.`ID`
ORDER BY p.`Created` DESC LIMIT 20
The ???? is where I've tried a bunch of things, but stumped. Problem is I can easily find a result set that contains photos with one tag or another, but if applying 2, 3, or 4 tags we'd need to only return photos that have entries for all of those tags in the database. I think this will involve combining result sets but not 100% sure.
Example:
Photo 1 Tags: Blue, White, Red
Photo 2 Tags: Blue
Searching for a photo with tags of 'blue' returns both photos, searching for a photo with tags of 'blue' and 'white' returns only Photo 1.
Supposing the requested set of tags is (red,blue) you can do:
SELECT * FROM `Photos`
WHERE `Description` LIKE "%test%"
AND `ID` IN (
SELECT pt.`PhotoId` FROM `PhotosTagsXref` AS pt
JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
WHERE t.Title in ('red','blue') /* your set here */
GROUP BY pt.`PhotoId` HAVING COUNT(DISTINCT t.`TagId`)=2 /* # of tags */
)
ORDER BY `Created` DESC LIMIT 20
Apparently, the tag set needs to be created dynamically, as well as its count.
Note: I'm counting DISTINCT TagIDs because I don't know your table's constraints. If PhotosTagsXRef had a PK/UNIQUE (PhotoId,TagId) and PhotosTags had a PK/UNIQUE (TagId), then COUNT(*) would suffice.
Admittedly a bit ugly. But assuming that PhotosTags.Category has the 'Blue', 'White', etc, try something along this line.
SELECT p.*
From `Photos` AS p
WHERE p.`Description` LIKE "%test%" AND
AND Exists
( Select 1 FROM `PhotosTagsXref` AS pt
Inner JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
Where pt.`PhotoId` = p.`ID`
And t.Category = 'FirstCatToSearch'
)
AND Exists
( Select 1 FROM `PhotosTagsXref` AS pt
Inner JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
Where pt.`PhotoId` = p.`ID`
And t.Category = 'SecondCatToSearch'
)
AND Exists
( ...
)
...
SELECT p.* FROM `PhotosTagsXref` AS pt
LEFT JOIN `Photos` AS p ON p.`ID` = pt.`PhotoId`
LEFT JOIN `PhotosTags` AS t ON pt.`TagId` = t.`ID`
inner join (select PhotoId from PhotosTagsXref
LEFT JOIN `PhotosTags` AS t
ON pt.`TagId` = t.`ID`
where (t.title = 'cond 1' or t.title = 'cond 2' ...)
--where t.title in (list condition) **this works as well**
having count(1) = (count of conditions) ) filter
on filter.photoID = pt.PhotoID
WHERE p.`Description` LIKE "%test%"
GROUP BY p.`ID`
ORDER BY p.`Created` DESC LIMIT 20
That should work, I made some assumptions on what column to use for the filter and joins, you may need to retool...the inner join functions as a filter and should pull out only records that have the number of matches equal to the total of the number of matches submitted. Now you just need a language to plug in those conditions and condition count values.
I have a Query that works like a charm, but now I need to filter it by a value that is currently in a GROUP_CONCAT created field. i have learned that I can use HAVING to filter but I can't find the syntax to use as HAVING groupconcatname LIKE '%6%' will find 6, 16, 26 etc .. I need to have the statement search for specific values so it can find "8" within the groupconcat containing 1,5,8,18,30,58 and not find 8 AND 18 AND 58 because i used a LIKE '%%'.
My Query (works right now .. but finds too many results based on a partial match of %6%):
SELECT offers.*, s.short_name AS sponsorName, s.logo AS sponsorLogo, GROUP_CONCAT( mn.title) titles,
GROUP_CONCAT( mo.`name`) metas, GROUP_CONCAT( om.meta_option) AS meta_ids
FROM offers
INNER JOIN offer_metas as om ON om.offer_id = local_ld_offers.id
INNER JOIN meta_options as mo ON om.meta_option = mo.id
INNER JOIN meta_names as mn ON mo.category = mn.category AND mo.cat_seq = mn.seq
LEFT JOIN sponsors AS s ON s.id=offers.sponsor
GROUP BY offers.id
HAVING meta_ids LIKE '%6%'
ORDER BY end_date ASC
I need to replace HAVING meta_ids LIKE '%6%' with something more akin to WHERE '6' IN meta_ids but when i try to replace the HAVING statement, with what seems like a reasonable attempt at a functioning WHERE, it ignores the fact that meta_ids is a valid variable/field to use in my clause and errors out.
Any recommendations?
Thanks,
Silver Tiger
UPDATE - Solved unless there is a more elegant way
Based on the recommendations from Thomas below I have updated the query and apparently I can search for as many individual values as I need by adding a line per option using the updated method as follows:
WHERE EXISTS( SELECT * FROM offer_metas AS om1 WHERE om1.meta_option = mo.id AND om1.meta_option = '23')
OR EXISTS( SELECT * FROM offer_metas AS om1 WHERE om1.meta_option = mo.id AND om1.meta_option = '16')
It isn't clear exactly what is being asked here (some test inputs with expected outputs would help) but it doesn't sound like you really need a Having clause. If you want to filter for values where say om.meta_option is "like" 8, then add a Where clause like so:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option = Like '%8%'
)
Of course, this return rows where there exists 8, 80, 18 and 181. If you are looking for a specific value equal to 8, then use:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option = '8'
)
If what you seek is a series of values, then you can do something like:
Where Exists (
Select 1
From meta_options As om1
Where om1.meta_option = mo.id
And om1.meta_option In('8','23')
)
Remember that the purpose of the Having clause to apply filters after the grouping and the Where clause applies filters prior to the grouping. In this case, it sounds like the filtering should happen prior to the grouping and thus the Where clause should be used.
You can change HAVING meta_ids LIKE '%6%' to HAVING meta_ids LIKE '%,6,%' OR '6,%' OR '%,6' OR '6' which should get what you're looking for.
Alternatives include things like subqueries, like the % operator are not very nice performance wise.