MYSQL Match Entries that Meet Multiple Criteria over Multiple Rows - php

I think this will be an easy one for those using MYSQL a lot, but I just can't quite get it...mainly through not knowing the right terminology to search for.
Basically I have a table mapping tag ids to photo ids called tag_map. When I perform this query:
SELECT * FROM tag_map WHERE tag_id='1' OR tag_id='5';
I get the following results:
ph_id tag_id
1 1
2 1
5 1
7 5
8 1
9 5
10 5
11 1
12 1
13 1
But what I really want is to only select ph_id that have a tag_id of BOTH '1' and '5'.
So, as you can probably see, I am trying to filter down selections based on multiple tags. I want to end up with:
ph_id tag_id
7 1
7 5
8 1
8 5
11 1
11 5
So ph_id 7, 8 and 11 reference tag_id 1 AND 5.
Hope that makes sense.
Thanks.
Solution
Due to the dynamic nature of my query (user selecting any number of available tags to 'narrow down selection) I went with a PHP solution, as suggested by #Y U NO WORK
Basically I get the tag_id of all selected tags from table 'tags'
Then I selected all photo ids (ph_id) that are mapped to the selected tag_ids from my table tag_map.
Then I reduce this down to ph_ids that occur the same number of times as the number of selected tags:
$numTags = count($arTagId); //$arTagId is an array of the selected tags
// get only photo ids that match both tags
$arPhId = array();
// in arPhId, find ph_ids that have occurances equal to $numTags
$arPhIdCnt = array_count_values($arPhIdAll); //$arPhIdAll is array of all ph_id that match all selected tag_ids
foreach($arPhIdCnt as $pid => $pidQty) {
if($pidQty == $numTags) {
$arPhId[] = $pid;
}
}
So I end up with an array of only the ph_ids that match both tag_ids.
Thanks for everyone's help.

You will have to join the table with itself, the code could be kinda complicated. PHP would be an easier, but not such a performant solution.

You have to join the table with itself based on ph_id, then check that the tab_id col of table1 instance equals 1 and that tab_id of table2 instance equals 5.
SELECT t1.* FROM tag_map t1, tag_map t2
WHERE t1.ph_id = t2.ph_id
AND t1.tag_id='1'
AND t2.tag_id='5';
with inner join if you prefer
SELECT t1.* FROM tag_map t1
INNER JOIN tag_map t2 on t2.ph_id=t1.ph_id
WHERE t1.tag_id='1'
AND t2.tag_id='5';

Related

select query records if multiple type is matched

I have some mysql tables like below,
1)videos
id name
1 test1
2 test2
2)tags
id name
1 theme1=test1
2 theme1=test2
3 theme2=test1
4 theme2=test2
5 age=senior
6 age=children
3)tags_to_items
vid tagid
1 1
1 5
Here in table tags_to_item you see videos.id=1 has 2 tags
1)theme1=test1 AND 2)age=senior. It means video.id=1 has 2 tags.
Now here is one conflict,
what i am doing now is, i am searching for theme's only. It means if i search where theme1=theme1 then it need to also search theme1 in theme2 and vice versa. like this
WHERE tagid=1 OR tagid=2
This is functioning proper, but now i want to search if video have multiple tag with AND condition like this
WHERE tagid=5 AND tagid IN (1,2)
It should return video.id=1
So, probably it need to search where video 1 contains tag tag age=senior AND theme1=theme1 OR theme2=theme1. But its not working, anyone have idea how can i do like this?
I think i got the answer.
I need to do join like this,
INNER JOIN tags_to_items t2 ON (v.id = t2.tagid AND t2.tagid = 5)
INNER JOIN tags_to_items t3 ON (v.id = t3.tagid AND t3.tagid in ('27' ,'69','84' ,'99' ))

MySQL count datas with row values without new query loop

I've 4 table for a newsletter. Newsletters, Subscribers, Subscriber Groups and Selected Subscriber Groups. I've choose subscriber groups in campaign edit area, and its save selected groups to tbl_newsletter_groups table like;
tbl_newsletters
NID title details
1 text 1 content 1
2 text 2 content 2
tbl_subscriber_groups
GID group_name
5 group 1
6 group 2
tbl_subscribers
SID GID email name
10 5 sub1#mail.com sub1 name
11 6 sub1#mail.com sub1 name
tbl_newsletter_groups
NGID NID GID
15 1 6
16 1 6
17 1 6
I want to show total selected subscriber count when I list newsletters in my page. My soulution works fine, Im looking for simple and clearly statement, there any faster way available like in single newsletter list statement?
Here my own count style (yes I know its too bad and long way);
$subGID = array();
$list = $myconn->query("SELECT * FROM tbl_newsletters");
while($listRs = $list->fetch_assoc()){
$grps = $myconn->query("SELECT * FROM tbl_newsletter_groups WHERE NID=". $listRs['NID'] ."");
while($grpsRs = $grps->fetch_asscoc()){
$subGID[] = $grpsRs['GID'];
} $grps->free();
$subs = implode(" OR GID=",$subGID);
$count = mysqli_num_rows($myconn->query("SELECT ID FROM tbl_subscribers WHERE GID=". $subs));
echo('Total Selected Subscriber: '.$count);
} $list->free();
Thanks.
The search term you want is "set-based logic".
Your thinking is sound: you need everything from tbl_newsletters, then you need to count results from tbl_subscribers, but in order to get those you need information from tbl_newsletter_groups.
In SQL, that's an indication you want a join. You've already discovered the conditions you need, you just don't know the syntax. A reference manual can help there.
Now you'll have a bunch of records, which you need to smash into a smaller number of records. You need aggregation functions and a GROUP BY clause.
So here's the final query:
SELECT n.NID, n.title, n.details, COUNT(s.SID)
FROM tbl_newsletters AS n
JOIN tbl_newsletter_groups AS g ON n.NID = g.NID
JOIN tbl_subscribers AS s ON g.GID = s.GID
GROUP BY n.NID

Selecting rows on Unique Pairs of columns from mysql Table

I have a Table table .
Now this has three columns table_id,content_id,content_type
What i want is to SELECT rows based on unique pairs of columns.
Say For example i have rows like this-->
id|content_id|content_type|
1 1 shirt
2 1 trouser
3 4 skirt
4 4 shirt
5 3 trouser
6 5 jeans
7 1 trouser
8 5 jeans
I want a query which selects Rows with id->1,2,3,4,5,6.
Rows with id->7,8 are not to be selected
Therefore it concludes that i dont want to select complete Duplicates of Rows
You can use a self join to pick a minimum row per group
select t.* from
test t
join (
select min(id) id ,content_id,content_type
from test
group by content_id,content_type
) t1
on(t.id = t1.id
and t.content_id = t1.content_id
and t.content_type = t1.content_type)
Demo
or if there are only these 3 columns in your table then you can simply use min()
select min(id) id ,content_id,content_type
from test
group by content_id,content_type
Demo
This is mysql-specific : If you use the GROUP BY function without using aggregate functions, the group by clause will behave as distinct, and pick up the first distinct row.
select id, content_id, content_type from test group by content_id, content_type order by id
Demo

MYSQL select tag associated id's from same table

So I have a MYSQL table which stores a load of tags that are associated to one another like so:
id level upper_tag value
----------------------------------------------
1 0 0 Electric
2 0 0 Home
3 1 2 Gas Cooker
4 1 1 wire
5 2 4 superconductor_wire
6 0 0 Sexy stuff
7 2 3 cleaner
8 0 0 Sport
9 1 8 trainers
10 1 8 rugby
11 2 10 Nike
Basically I need to find the associated tags so if I call sport I want the tags: Sport, trainers, rugby and nike returned. If I chose the tag rugby I would get back rugby and nike.
I have had a think and am not sure if I am on the right lines as I am a MYSQL noob. here is my attempt but it does not give me the right answer:
SELECT * FROM tag t
WHERE t.`id` = (SELECT `upper_tag` FROM tag) AND t.`id` = 8
;
SQLFIDDLE
What you are looking for is a Nested set model - read through the Wiki article and see the example on how left/right values are assigned. Once you have those two columns added and indexed correctly, your queries will be as simple as
SELECT t.* FROM tag t
WHERE t.`left` >= X and t.`right` <= Y
Where X is the 'left' value and Y is the 'right' value of 'Sport' tag
You can combine the lookup an actual loading into one query
SELECT t.* FROM tag t
JOIN tag t ON t.`left` >= b.`left` AND t.`right` <= b.`right`
WHERE b.id = 8
Howerver you would need extra work to maintain (reindex as necessary) the integrity of these left/right values whenever you add/remove/move any tag.
if you want it across all levels, you need to use joins and unions. add one join per query for each deeper level.
select * from
(
select t1.id as parent, t2.* from tag t1
inner join tag t2 on t1.id=t2.parent_id
union
select t1.id as parent, t3.* from tag t1
inner join tag t2 on t1.id=t2.parent_id
inner join tag t3 on t2.id=t3.parent_id
) tag where tag.parent=1
SELECT * FROM tag t WHERE t.upper_tag=8 OR t.id=8
this should give you all tag with the value upper_tag=8 and id = 8
sport=8, rugby=upper_tag 8, trainer=upper_tag 8
Try like this
SELECT *
from tag AS t
WHERE (t.upper_tag IN(SELECT id from tag WHERE upper_tag=8))
OR (t.id = 8) OR (t.upper_tag = 8)

Count the number of rows in MySQL table with criteria depending on other table

I've got two tables:
content:
id access
1 3
2 5
3 9
viewlevels:
id group
1 [10,12,15]
2 [8,12,11]
3 [9,10,5]
The access field in content is related with the id field in viewlevels.
I select the rows in viewlevels depending on the current user group. So for example, if group is = 10, my query will select rows 1 and 3. If the group is 12, it will select rows 1 and 2, etc. I'm using the following query:
$query="SELECT id FROM #__viewlevels WHERE rules LIKE '%$group%'";
My challenge is to count the number of rows for column id in table content where the access matches with the selected id's from the above query on table viewlevels.
I tried the following code but it is returning the error: undefined variable: nartigos
$query="SELECT count(id) FROM #__content WHERE access IN (SELECT id FROM #__viewlevels WHERE rules LIKE '%$group%')";
if ($stmt = mysqli_prepare($link, $query)) {
/* execute query */
mysqli_stmt_execute($stmt);
/* store result */
mysqli_stmt_store_result($stmt);
$nartigos=mysqli_stmt_num_rows($stmt);
/* close statement */
mysqli_stmt_close($stmt);
};
echo "NÂș de artigos " .$nartigos;
First of all, you really should normalize your data. Consider having a many-to-many join table for viewLevels instead of having all groups in one row. That might look like this:
access_id group_id
1 10
1 12
1 15
2 8
2 11
2 12
3 5
3 9
3 10
That would make your query as simple as
SELECT c.id AS `content_id`, COUNT(v.access_id) AS `content_count`
FROM content AS c INNER JOIN viewLevels AS v
ON c.access_id = v.access_id
WHERE v.group_id = ?
GROUP BY c.id
Here ? is the group id you are querying against.
Without normalization (which again I STRONGLY recommend you do), you would still use a join, but it would look like this:
SELECT c.id AS `content_id`, COUNT(v.access_id) AS `content_count`
FROM content AS c INNER JOIN viewLevels AS v
ON c.access_id = v.access_id
WHERE v.group LIKE '%?%'
GROUP BY c.id
you need to "join" your tables.
the sql command cant query two tables seperately.
when you "join" 2 tables in your sql, think of it as making one virtual/temporary table in the air, of the 2 tables which you can then query.
this is quite a good intro http://www.sitepoint.com/understanding-sql-joins-mysql-database/

Categories