MySQL complicated exclude query - mysql

I have a bunch of articles
+-----------+-------+
| ArticleID | Name |
+-----------+-------+
| 1 | Bla |
| 2 | Blub |
| 3 | Test |
+-----------+-------+
And Prodcutgroups:
+-----------+--------------+
| ProductGroupID | Name |
+-----------+--------------+
| 4 | Group A |
| 5 | Group B |
| 6 | Group C |
+-----------+--------------+
And a link table (M:N relation) that links articles with product-groups. Each article can be in many product-groups at the same time but only once per group:
+-----------+----------------+
| ArticleID | ProductGroupID |
+-----------+----------------+
| 1 | 4 |
| 1 | 5 |
| 2 | 5 |
| 2 | 6 |
| 3 | 4 |
| 3 | 6 |
+-----------+----------------+
My problem is now that I need a query that lets me find out articles that are MISSING in product-group with ID 4.
Usually I would write a PHP Script that loops entire table and checks for values and remembers if product-group-id 4 was not found.
But this seems very sophisticated and annoying as I have this kind of szenario more often here and there.
I cannot use WHERE ProductGroupID NOT IN(4) because when the article is assigned to OTHER product-groups it will find that rows and
the result will NOT tell me wether the article is in this specific group or not.
The result would need to give me only!! Article-ID: 2 as its not in Product-Group with ID 4
I appreciate any helpful advice!

One option, using exists logic:
SELECT a.Name
FROM articles a
WHERE NOT EXISTS (SELECT 1 FROM link l
WHERE l.ArticleID = a.ArticleID AND l.ProductGroupID = 4);
Read in plain English, the above query says to return the name of article for which we cannot find an entry in the link table associated with ProductGroupID = 4.

Related

Finding out only certain Master items from Details table in a Master-Details table relationship

I've been trying to get this going for hours and haven't figured this out yet.
Say I've got 2 tables - master and details.
master/detail has the following data
master table
+------+-------+
| id | name |
+------+-------+
| 1 | jeff |
| 2 | simon |
| 3 | andy |
| 4 | jerry |
+------+-------+
details table
+----+-----------+---------+
| id | master_id | tag |
+----+-----------+---------+
| 1 | 1 | WINDOWS |
| 2 | 1 | MAC |
| 3 | 2 | MAC |
| 4 | 3 | WINDOWS |
| 5 | 3 | MAC |
| 6 | 3 | LINUX |
| 7 | 4 | MAC |
+----+-----------+---------+
how do I select the master records which has both tags 'WINDOWS', 'MAC'.
So it should only return master_id 1 and 3 which is jeff and andy only.
If I do a
select distinct(master_id) from details where tag in ('WINDOWS', 'MAC')
it gives me all of them.
Sorry for the newbie question but if anyone can help, it'll be much appreciated.
You need simple GROUP BY with HAVING clause :
select master_id
from details
where tag in ('WINDOWS', 'MAC')
group by master_id
having count(*) = 2;
If details table has duplicate tags for master_id then you need count(distinct tag).
YOu could use a join with count having 2 value only for tag
select distinct master_id
from detail
inner join (
select master_id from detail
group by master_id
having count(distinct tag) = 2
) t on t.master_id = detail.master_id and detail.tag in ('WINDOWS', 'MAC')

Adding sum from 2 different tables

I have something like this
2 tables:
videos
members
In the members table I have the name of each member:
1 Tom
2 Bob
3 Zack
4 Dan
5 Casey
In the videos table I have a column named members and I have the names in there seperated by commas
1. Tom,Dan
2. Casey,Zack,Bob
3. Tom,Casey,Dan,Zack
4. Zack,Bob,Dan
I'm trying to display how many times each member appears to get these results:
1 Tom = 2
2 Bob = 2
3 Zack = 3
4 Dan = 2
5 Casey = 2
Do I need to do something like SELECT SUM(members) WHERE and use LIKE?
I would strongly suggest to normalize your data as others suggested.
Based on your current design you can use FIND_IN_SET to accomplish the result you want.
SELECT
M.id,
M.name,
COUNT(*) total
FROM members M
INNER JOIN videos V ON FIND_IN_SET(M.name,V.members) > 0
GROUP BY M.name
ORDER BY M.id
See Demo
Running this query on your given data set you will get output like below:
| id | name | total |
|----|-------|-------|
| 1 | Tom | 2 |
| 2 | Bob | 2 |
| 3 | Zack | 3 |
| 4 | Dan | 3 |
| 5 | Casey | 2 |
A must read
Is storing a delimited list in a database column really that bad?
More
This is how your vidoes table would look like if you normalize your data:
videos
id member_id
One way to go is to join the two tables, based on a like expression:
SELECT members.name, count (*) as counter from members inner join videos
ON videos.members like CONCAT('%,',members.name,',%')
GROUP BY members.name;
But I think the better solution will be like #e4c5 said in the comment - you need to normalize the data. the videos table should look like:
+---+-------+
|ID | MEMBER|
+---+-------+
| 1 | Tom |
| 1 | Dan |
| 2 | Casey |
| 2 | Zack |
| 2 | Bob |
| 3 | Tom |
| 3 | Casey |
| 3 | Dan |
| 3 | Zack |
| 4 | Zack |
| 4 | Bob |
| 4 | Dan |
+---+-------+
That way, you can simply count on this table

How to write a join query for many to many relationship? for dictionary project

I have working on database for dictionary project. I have to store a word and meaning with -- many to many -- relationship.
Below I have mentioned the sample with my table structure. I hope the table structure is right, but I don't know how to select all meanings for single word while user searching.
And also I have to write a query to select all word linked to a single meaning.
And also I have to write a query to select all meaning linked to a single word.
word_table
+----+------+
| id | word |
+----+------+
| 1 | A |
| 2 | B |
| 3 | C |
+----+------+
meaning_table
+----+--------+
| id | meaning|
+----+--------+
| 1 | X |
| 2 | Y |
| 3 | Z |
+----+--------+
word_meaning_table
+---------+-----------+
| word_id | meaning_id|
+---------+-----------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
| 3 | 3 |
+---------+-----------+
Expected output should be like this.
If user searching for a word "A" in word table the result should be
Result for word "A"
+----+----------+
| word| meaning |
+----+----------+
| A | X |
| A | Y |
| A | Z |
+----+----------+
I don't know how to write a join query for this scenario.
SELECT wt.word, mt.meaning
FROM word_table wt
INNER JOIN word_meaning_table wmt
ON wt.id = wmt.word_id
INNER JOIN meaning_table mt
ON wmt.meaning_id = mt.id
WHERE wt.word = 'A'
Follow the link below for a working demo:
SQLFiddle
Try
select word, meaning
from word_table
join meaning_table on word_table.id=meaning_table.id;

Query Peformance for Tags

I’m creating a system, backed by a MySQL database, and a question appeared about what would be the best practice.
I have a “group“ entity, which have zero or more "tags".
The tables could be represented by:
Groups
+-------+--------+
| id | name |
+-------+--------+
| 1 | Group1 |
| 2 | Hey Guy|
| 3 | Chacko |
| 4 | Dropo |
+-------+--------+
GroupsTags
+-------+--------+
|idGroup| idTag |
+-------+--------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
+-------+--------+
Tags
+-------+--------+
| id | name |
+-------+--------+
| 1 | Health |
| 2 | Happy |
+-------+--------+
The system, during the “group” registration, I want that to autocomplete every key press, showing the most popular tags, similar to StackOverflow. In other words, I have to do a query at database for each letter type, order by popularity.
What solution is the best?
What is the best?
select count(*) as qt,Tags.nome from GroupsTags
inner join Tags
on Tags.id = GroupsTags.idTag
where Tags.name like "phrase%"
group by GroupsTags.idTag
order by qt desc
or, save in the Tags table, the amount of times it was used. Like this
Tags
+-------+--------+--------+
| id | name | qtUsed |
+-------+--------+--------+
| 1 | Health | 2 |
| 2 | Happy | 1 |
+-------+--------+--------+
select * from Tags where name like "phrase%" order by qtUsed desc
I wonder if it is really needed to put the number of times that the tag was used to get a better performance. Would it be a bad practice ?

Select Distinct Set Common to Subset From Join Table

Given a join table for m-2-m relationship between booth and user
+-----------+------------------+
| booth_id | user_id |
+-----------+------------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 5 |
| 1 | 9 |
| 2 | 1 |
| 2 | 2 |
| 2 | 5 |
| 2 | 10 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
| 3 | 4 |
| 3 | 6 |
| 3 | 11 |
+-----------+------------------+
How can I get a distinct set of booth records that are common between a subset of user ids? For example, if I am given user_id values of 1,2,3, I expect the result set to include only booth with id 3 since it is the only common booth in the join table above between all user_id's provided.
I'm hoping I'm missing a keyword in MySQL to accompish this. The furthest I've come so far is using ... user_id = all (1,2,3) but this is always returning an empty result set (I believe I understand why it is though).
The SQL query for this will be:
select booth_id from table1 where [user_id]
in (1,2,3) group by booth_id having count(booth_id) =
(select count(distinct([user_id])) from table1 where [user_id] in (1,2,3))
If this could help you creating the MySQL query.