mySQL: Selecting data from multiple tables - mysql

I need help building a mysql query to select from multiple tables.
I have three database tables related to images: images, tags, tag_asc. I want to fetch an image data and its tag names by providing image_id.
For example, following is my tables structure:
images:
image_id image_name path date
1 test.jpg dir 1311054433
tags:
tag_id image_id
1 1
2 1
tag_asc:
tag_id tag_name
1 "first"
2 "second"
3 "third"
I want to fetch the data of an image with image_id = 1 from images table and all tag names associated with image_id=1 from tag_asc table.
I'm using CodeIgniter Active records, but I just need idea of joining the tables.
Thanks for any help.

select *
from images i
left join tags t on t.image_id = i.image_id
left join tag_asc ta on ta.tag_id = t.tag_id
where i.image_id = 1;
Using LEFT JOIN means that rows will be returned even if there are no joining rows in the other tables (but you'll get null values in the columns for the missing rows), which is typically desirable.
If you want one row (not stated in question, but in comments), use this:
select i.image_id, group_concat(tag_name) as tag_names
from images i
left join tags t on t.image_id = i.image_id
left join tag_asc ta on ta.tag_id = t.tag_id
where i.image_id = 1
group by 1;

Related

Many-To-Many select only rows with exactly same tags

I have 3 tables: tags, products and relation table between them.
Relation table looks for example like this:
tagId | ProductId
1 | 1
2 | 1
2 | 9
The user can pick two options "All of these" or "One of these".
So if user picks All of these, it's means that the product must have exactly all of tags which the user chose.
So if user pick tags with id 1 and 2, it should select only product with id 1, because this product has exactly the same tags the user chose. (Another way is if the user picks the tag with id 2, it should select only product with id 9.)
So, the product has to have all tags which the user chose (no more, no less).
SQL that I already have for Any/One of these:
SELECT DISTINCT s.SKU
FROM SKUToEAN as s
LEFT JOIN ProductDetails as p ON s.ProductDetailID=p.id
JOIN ProductTagRelation as ptr ON (ptr.productId=p.id and ptr.tagId IN(Ids of selected tags))
Example behavior:
TagId = 1 it should select => None
TagId = 2 it should select => 9
TagId = 1,2 it should select = 1,9
So probably I need two queries. One for any/one of these ( I already have this one ) and the second for all of these.
With PHP I decide which query to use.
You can GROUP BY on the ProductID and use conditional aggregation based filtering inside the Having clause. MySQL automatically casts boolean values to 0/1 when using in numeric context. So, in order to have a specific tagID value available against a ProductID, its SUM(tagId = ..) should be 1.
All of these:
SELECT ptr.productId, s.SKU
FROM SKUToEAN AS s
LEFT JOIN ProductDetails AS p
ON p.id = s.ProductDetailID
JOIN ProductTagRelation AS ptr
ON ptr.productId = p.id
GROUP BY ptr.productId, s.SKU
HAVING SUM(ptr.tagID = 1) AND -- 1 should be there
SUM(ptr.tagID = 2) AND -- 2 should be there
NOT SUM(ptr.tagID NOT IN (1,2)) -- other than 1,2 should not be there
Is this you are looking for (for all condition)?
select product.id
from products
inner join <table> on products.id = <table>.productId
group by product.id
having group_concat(<table>.tagId order by <table>.tagId separator ',') = '1,2';

Selecting rows that are included in a set from another table

I have a table "Products" with a product name and id:
id | title
1 product 1
2 product 2
Each product can have a series of tags. Tags are identified in table "Attributes":
id | name | handle
1 Tag One tag-one
2 Tag Two tag-two
3 Tag Three tag-three
4 Tag Four tag-four
etc
The product to tag relationship is another table "Tags":
id | AttId | OwnerId
1 1 1
2 2 1
3 1 2
4 3 2
etc
Ok, so I am trying to select a set of products that all have at least one specific tag, and a possible selection of other tags. Here is what I am working with now:
SELECT products.id
FROM products
WHERE
EXISTS
(
SELECT 1
FROM Tags
INNER JOIN Attributes ON tags.AttId = Attributes.id
WHERE Attributes.handle = 'tag-one'
AND (
Attributes.handle = 'tag-two'
OR
Attributes.handle = 'tag-four'
)
AND products.id = Tags.OwnerId
)
If I remove the AND (OR) section, the query works. As above, it shows no errors, but also no results; How should I write this so I can get a set of products that have one tag for sure, AND have either/or other specified tags by the tag handle?
I like to approach this type of problem using group by and having -- because I find that this method works very well for expressing many different conditions. For your conditions:
select p.*
from products p join
tags t
on t.OwnerId = p.id join
attributes a
on t.attid = a.id
group by p.id
having sum(a.handle = 'tag-one') > 0 and
sum(a.handle in ('tag-two', 'tag-four')) > 0;
Each condition in the having clause counts the number of rows (for a product) that match a condition. The first says there is at least one row with 'tag-one' handle. The second says that there is at least one row with the other two handles.
I think if you perform two separate queries and take the intersection, that will give you what you want.
-- Get all the owner ids that have 'tag-one'
select OwnerId
from Tags t1
where AttId in
(
select id
from Attributes a1
where a1.handle = 'tag-one'
)
intersect
-- Get all the owner ids that have tag-two and tag-four
select OwnerId
from Tags t2
where AttId in
(
select id
from Attributes a2
where a2.handle in ('tag-two', 'tag-four')
)
;

Joining tables without getting the unmatched records

I have the below two tables (one & two) and need the output as in the third table.
ONE
ID TAG
1 A
2 B
3 c
TWO
ID TAG
1 A
2 Z
OUTPUT
ID TAG
1 A
3 C
Conditions -
1. Need the values for which the 'TAG' matches
2. Need the values from 'ONE' which are not available in the 'TWO' table
3. Do no need the values for which the 'TAG' does not match
Can this be done in a single SQL query?
A LEFT JOIN keeps the unmatched result from the first table:
SELECT one.id AS id, one.tag AS tag
FROM one LEFT JOIN two ON one.id = two.id
WHERE one.tag = two.tag OR two.tag IS NULL;
The first condition one.tag = two.tag gets the matching result; the second two.tag IS NULL gets what are available in table one but not two.
Checkout the demo here. Let me know if it works.
select a.ID, a.TAG
from ONE a LEFT OUTER JOIN TWO b
ON (a.ID = b.ID
and a.TAG = b.TAG)

Simple MySQL joining

Having trouble understanding how to filter an images table by tag information in a second table.
So I have an images table with all the basic fields:
id |
title |
userid |
timestamp |
status
and I have a tags table:
id |
imageid |
text (contains a single tag)
What i'd like to do is simple: take a list of tags and get a list of images that have all of the tags in the list. I just can't wrap my head around it.
Any help is greatly appreciated, thanks!
SELECT
a.*
FROM
images a
INNER JOIN
tags b ON a.id = b.imageid
WHERE
b.text IN ('tag1', 'tag2', 'tag3')
GROUP BY
a.id
HAVING
COUNT(*) = 3
'tag1', 'tag2', 'tag3' is your list of input tags
The 3 in the HAVING clause is the input of the count of tags in your tag list.
Assuming you don't have duplicate tags per imageid, you can join against a query that counts the tags per id, limiting the tags with an IN() clause to the list you want to search.
Then using a HAVING clause, reject the items for which the count doesn't equal the number of tags in your list (those which have fewer than the complete list):
SELECT
images.*
FROM
images
JOIN (
SELECT
imageid,
COUNT(*) AS numtags
/* could also be COUNT(DISTINCT text) */
FROM tags
/* Put only the tags you want into an IN() clause */
WHERE text IN ('tag1,'tag2','tag3')
GROUP BY imageid
/* If an image has all 3 tags, COUNT(*) = 3 */
HAVING numtags = 3
) tlist ON images.id = tlist.imageid

mySQL SELECT FROM table WHERE ... AND ... AND ... AND

I have a table "articles" with columns and data:
article_id title body
1 This is the title This is the body text
2 Another title Another body text
Another table "category" with columns and data:
category_id category
1 localnews
2 visible
3 first10
And a table "categories" with columns and data:
categories_id article_id category_id
1 1 1
2 1 2
3 1 3
4 2 1
5 2 3
I want to SELECT the row(s) WHERE categories.category_id = 1 AND =2 AND =3
I'm using:
SELECT articles.article_id, articles.title, articles.body,
categories.article_id, categories.category_id
FROM articles, categories
WHERE articles.article_id = categories.article_id
AND categories.article_id = 1
AND categories.article_id = 2
AND categories.article_id = 3
but it doesn't work. Obviously mySQL needs another syntax.
Can someone help?
Thanks
SELECT
Articles.article_id,
COUNT( Categories.article_id ) AS total
FROM CategoryArticles
LEFT JOIN Articles USING (article_id)
WHERE
CategoryArticles.category_id IN (1,2,3)
GROUP BY CategoryArticles.article_id
HAVING total = 3
I used a bit different names for table because in your example the distinction between category and categories is hard to notice.
An column of a row cannot be 1, 2 or 3 at the same time, which is what AND stipulates. Use OR in your WHERE condition. Better yet - for readability - you can use IN:
SELECT ...
WHERE `categories`.`article_id` IN(1,2,3)
In addition to the commonly used IN() and using a HAVING count, I would be interested in the performance difference by doing a multiple-join as follows...
SELECT STRAIGHT_JOIN
articles.article_id,
articles.title,
articles.body
FROM
categories c1
JOIN articles
on c1.article_id = articles.article_id
JOIN categories c2
on c1.article_id = c2.article_id
AND c2.category_id = 2
JOIN categories c3
on c1.article_id = c3.article_id
AND c3.category_id = 3
WHERE
c1.Category_ID = 1
Yes, this may look obscure, but lets think about it... by doing a join FIRST on the categories table where ONE of your specific categories -- THIS FIRST FROM instance of categories should be representative of whichever category would have the smallest granularity. Ex: Your categories of Local News, Visible and First 10. Local news would probably have the most entries, while Visible and First 10 would have even less... of those, which would have even the smallest number of records. Use THIS category as the where clause.
So, say you have 100,000 articles, and 90,000 are in local news, 45,000 in Visible, and 12,000 in First 10. By starting your query on only those in the 12,000, you are eliminating most of the data.
By then joining to the articles table, and categories AGAIN as alias C2 and C3 respectively based on the other conditions, if found, done, if not, its excluded.
Again, I'm wondering the performance impact. I would also have a compound index on the categories table on both (article_id, category_id)
The value cannot be all three values simultaneously, so you'd better use an IN clause in your WHERE to define which you want to return. Give you've already got a join condition there, you'd want to move that to an ON clause instead as well; ie:
SELECT articles.article_id, articles.title, articles.body, categories.article_id, categories.category_id
FROM articles
INNER JOIN categories ON articles.article_id = categories.article_id
WHERE categories.article_id IN ( 1, 2, 3 )
Of course, you can go to the next step and do:
SELECT articles.article_id, articles.title, articles.body, category.category
FROM articles
INNER JOIN categories ON articles.article_id = categories.article_id
INNER JOIN category ON categories.category_id = category.category_id
WHERE categories.article_id IN ( 1, 2, 3 )
If instead you wanted to show only articles that appear in all three categories, then you could take an approach like:
SELECT articles.article_id, articles.title, articles.body
FROM articles
INNER JOIN categories AS c1
ON articles.article_id = c1.article_id
AND c1.category_id = 1
INNER JOIN categories AS c2
ON articles.article_id = c2.article_id
AND c2.category_id = 2
INNER JOIN categories AS c3
ON articles.article_id = c3.article_id
AND c3.category_id = 3