I'm currently working on a website for a photographer, every photo is tagged with multiple keywords which are extracted when uploading.
My database looks like this (simplified)
TAGS
-------------------
| | |
| | |
| ID | TAG |
| | |
| | |
-------------------
IMAGES
-------------------
| | |
| | |
| ID | URL |
| | |
| | |
-------------------
TAGS_IMAGES
-------------------
| | |
| TAG | IMAGE |
| ID | ID |
| | |
| | |
-------------------
So all tags are stored in a seperated table to prevent duplicates, the same goes for the images and then the tags and images are linked together in another table.
When searching for a tag, I execute this SQL to find all images based on the given tag
SELECT DISTINCT SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? ORDER BY id DESC LIMIT ?, ?
But the problem is that I'm still getting multiple duplicates because the DISTINCT only seems to work on the tag.id.
As you can see here: http://pastebin.com/MWt5B0Aq, based on the tag "water", some images have keywords like "water", "watervogel", "waterloop".
Is there a way to get the DISTINCT to work on the images.id?
I tried this, but that didn't help
SELECT DISTINCT images.id SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? ORDER BY id DESC LIMIT ?, ?
Would doing a group by help?
SELECT DISTINCT SQL_CALC_FOUND_ROWS tags.tag, tags_image.imageID, images.src FROM tags INNER JOIN tags_image ON tags.tagID = tags_image.tagID INNER JOIN images ON tags_image.imageID = images.id WHERE tag LIKE ? GROUP BY images.id ORDER BY id DESC LIMIT ?, ?
Related
I have two SQL tables. customer and tag joined on the customer.id=tag.attach_id
customer
+------+-------------+--------------+
| id | name | email |
| 9 | Alan | alan#me.com |
+------+-------------+--------------+
tag
+------+-------------+--------------+
| id | attach_id | content |
| 1 | 9 | alan-tag |
| 2 | 9 | second-tag |
+------+-------------+--------------+
I want to output this:
+-------+-----------------+-----------------------+
| name | email | content |
+-------+-----------------+-----------------------+
| alan | alan#me.com | alan-tag, second-tag |
+-------+-----------------+-----------------------+
Here's my best attempt at SQL for this:
SELECT customer.name, customer.email, tag.content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id,tag.content;
Is this even possible without first processing the data in another language like PHP?
Yes you need to use GROUP_CONCAT as suggested by others on comment area, more specifically (exactly) your query is
SELECT `name`, email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM
customer
INNER JOIN tag ON customer.id = tag.attach_id
GROUP BY customer.id
This Query will give you the exact result that you have osted on your post
You can use listagg if you are using sql
select c.name,email,listagg(content,',') within group (order by c.name) "content"
from customer c, tag t
where c.id = t.attach_id
group by c.name,email
Use this:
SELECT customer.name, customer.email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id;
I'm currently designing a website for file hosting. I'm sorting files by tags and I have an issue for building a search request in SQL. If I search for multiple tags at once, i want to be able to get the files that have all these tags.
Below is an example of what i want to do. If my search is "#code #python", my request must return me the files that have both tags (file with id 2 in this example). If the search is just "#code", the request must return file 2 and 3.
Here is how i store all the data, i have a table for files, one for tags and an other one with the links between files and tags (files_tags).
Table Tags
------------------------
| id | name |
------------------------
| 1 | Code |
------------------------
| 2 | Python |
------------------------
| 3 | PHP |
------------------------
Table Files
------------------------
| id | name |
------------------------
| 1 | python.pdf | ( #python )
------------------------
| 2 | main.py | ( #code #python )
------------------------
| 3 | class.php | ( #code #php )
------------------------
Table files_tags
------------------------
| id | tag | file |
------------------------
| 1 | 1 | 2 |
------------------------
| 2 | 1 | 3 |
------------------------
| 3 | 2 | 1 |
------------------------
| 4 | 2 | 2 |
------------------------
| 5 | 3 | 3 |
------------------------
My current request is :
SELECT DISTINCT `files`.* FROM `files`, `files_tags`, `tags` WHERE `files`.`id` = `files_tags`.`file` AND `files_tags`.`tags` = `tags`.`id` AND `tags`.`name` LIKE %"#code #python"%;
But it gets me all the 3 files, not just file 2.
What am i doing wrong ?
Thanks for help
Your syntax looks odd. Try the following one instead:
SELECT `files`.`id`, `files`.`name`
FROM `files`
JOIN `files_tags` ON `files`.`id`=`files_tags`.`file`
JOIN `tags` ON `files_tags`.`tag` = `tags`.`id`
WHERE `tags`.`name` IN ('code','python');
GROUP BY `files`.`id`, `files`.`name`
I would recommend something like this:
SELECT f.*
FROM `files` f JOIN
`files_tags` ft
ON f.id = ft.file JOIN
`tags` t
ON ft.tags = t.id
WHERE FIND_IN_STR(t.name, #SearchStr)
GROUP BY f.id
HAVING COUNT(*) = LENGTH(#SearchStr) - LENGTH(REPLACE(#SearchStr, ',', '')) + 1;
This takes the liberty of changing the format of your search string. Instead of "#", just use commas (with no spaces). Of course, the above can be adapted to support "#", but the code requires more string manipulation.
first of all, you sould take your search text, and use it as an array, that is , not a string. Split values by space " " so you have an array where each element represents a tag.
select * from files where
length_of_tags_array =
(select count(1) from tags inner join files_tags on files_tags.tag = tags.id where files_tags.file = files.id and tags.name in ("+putyourtagshere+")
)
since your tags are strings, add each element enbetween quitations
dont use like in this case for it would retrieve tags that are substring pof other tags such as "C" is a substring of "C#"
remove the hashtag as well since it is not in your stored tag's name
Thank you, it works know.
select * from files where 2 = (select count(1) from tags inner join files_tags on files_tags.tag = tags.id where files_tags.file = files.id and tags.name in ("code","python"))
I have a problem. I have 2 database tables.
table 1 people:
+----------+--------------+
| id | name |
+----------+--------------+
| 1 | johanalj |
| 2 | hjgjhggjh |
+----------+--------------+
table 2 images of people:
+----------+--------------+----------------+
| id | url | people_ID |
+----------+--------------+----------------+
| 1 | 3765345.png | 1 |
| 2 | 87e58974.png | 1 |
+----------+--------------+----------------+
Now I want to select person with id 1 from table 1 and all pictures from table 2 that have people_ID 1.
I tried LEFT JOIN in combination with a WHERE but cant get it to work
$sql = "SELECT * FROM people p LEFT JOIN images i ON i.people_ID = p.id WHERE id = '1'";
But I get a no result massage. What am I doing wrong?
There is an error(ambiguous column id). Both tables have id column. You need to add the table alias with id. try with -
$sql = "SELECT * FROM people p LEFT JOIN images i ON i.people_ID = p.id WHERE p.id = '1'";
I have planned to use three tables for my sites tagging system, they looks like:
|-----------------------|
|==> photo |
| -> id |
| -> custom_id |
| -> title |
| -> slug |
| -> date |
| -> image_url |
|-----------------------|
|==> tags |
| -> id |
| -> slug |
| -> title |
|-----------------------|
|==> tags_relation |
| -> tid | <-- this is the tags.id
| -> pid | <-- this is the photo.custom_id
|-----------------------|
So, to fetch the recent posts of a specific tag i am using INNER JOIN by the following query:
SELECT p.id, p.custom_id, p.title, p.slug, p.date, p.image_url, t.id, t.slug, tr.*
FROM tags t INNER JOIN
tags_relation tr
ON t.id = tr.tid INNER JOIN
photo p
ON p.custom_id = tr.pid
WHERE t.slug = 'people'
ORDER BY p.date DESC
LIMIT 20
Everything works fine except the fact that the 'slug, id, title' column of the photo table is being replaced by the 'slug, id, title' column of the tags table!
I figured out a solution that is changing the tags columns name, but is there any best practices to solve this problem?
Thanks
I think that you should use Aliases.
For example:
SELECT p.id AS Person_Id, p.title AS Person_Title ...
You can learn more about aliases here.
You're going to have to cast some of those fields like this
SELECT t.column, s.column AS column2
Otherwise, MySQL will indeed pick a field to return (typically the last one with that name) and that's all you get!
Our best practice may seem like more work, but we do add unique prefixes to all our column names so you don't get lost in an alias war.
|-----------------------|
|==> photo |
| -> photo_id |
| -> photo_custom_id |
| -> photo_title |
| -> photo_slug |
| -> photo_date |
| -> photo_image_url |
|-----------------------|
|==> tags |
| -> tag_id |
| -> tag_slug |
| -> tag_title |
|-----------------------|
|==> tags_relation |
| -> tagRelation_tid |
| -> tagRelation_pid |
|-----------------------|
Which would change your query to
SELECT photo_id, photo_custom_id, photo_title, photo_slug, photo_date, photo_image_url, tag_id, tag_slug, tags_relation.*
FROM tags INNER JOIN
tags_relation
ON tag_id = tagRelation_tid INNER JOIN
photo
ON photo_custom_id = tagRelation_pid
WHERE tag_slug = 'people'
ORDER BY photo_date DESC
LIMIT 20
More verbose, but also more descriptive and when you have many many tables and really really long joins, it works out great...especially when your intellisense kicks in.
mp3s
ID
Title
Description
tags
ID
Title
artists
ID
Title
artist_relations
mp3ID //call to mp3s.ID
artistID // call to artists.ID
tag_relations
mp3ID //call to mp3s.ID
tagID // call to tags.ID
I need this result in one query:
row1 : mp3ID | mp3Title | mp3Description | tag1,tag2,tag3 | artist1
row2 : mp3ID | mp3Title | mp3Description | tag20,tag22 | artist8,artist5
...
select
mp3.ID as mp3id,
mp3.title as mp3title,
mp3.description as mp3description,
group_concat(distinct artist.title) as artists,
group_concat(distinct tag.title) as tags
from mp3s as mp3
left join artist_relations as ar
on ar.MP3ID = mp3.ID
left join artists artist
on ar.ARTISTID = artist.ID
left join tag_relations as tr
on tr.MP3ID = mp3.ID
left join tags as tag
on tag.ID = tr.TAGID
group by mp3.ID
And I tried to reproduce on expecially created db with structure you described and got this result:
| mp3_id | mp3_title | mp3_description | artists | tags |
| 1 | first | first one | zemfira,splean | a,b |
| 2 | second | second one | leningrad,deep purple,led zeppelin | c,d,e |