I'm having an issue with finding related database rows that aren't specifically linked. Lets say we have 5 tables.
Products
Id | name
Product_tags
Id | product_id | tag_id
Tags
Id | name
Blog
Id | name
Blog_tags
Id | blog_id | tag_ids
So what I do is I grab a product, with this grab the tags this product has via the Product_tags table. Say I get something like this.
Product 101
Tags 123,124,125,126
Now I have this product page. The thing is, now I want to find a blog if it fits this product, but it doesn't need to have all tags that product has, product just needs to have all that blog has. So if blog has tags 124 and 126 it needs to match. Currently I save all the tags a blog has in one row (comma seperated) but this could be changed to save 1 tag per row if needed. If it was reversed I could do it but I need to check for a partial list in a different table.
Product 101
Tags 123,124,125,126
Should find
Blog 201
Tags 124,126
But not
Blog 202
Tags 120,124
I tried a few ways but I couldn't find a way to make this work, my closest attempt was a reverse like like this.
select * from Blog_tags where "'".implode(",",$tagIds)."'" LIKE concat("%", tag_ids, "%")
This sort-of worked but not for when the product had 1,2,3 and the blog only had 1,3.
This is untested and only from memory, so feel free to comment and tell me if it doesn't work.
First of all, normalize your tag list, do not use comma-separated string. You said this could be changed, so I'm going work to assume it's one row per tag, and the column name is tag_id.
Let's start by finding the blogs having some of the tags of you product 101.
SELECT *
FROM Blog
JOIN Blog_tags ON Blog.Id = Blog_tags.blog_id
WHERE tag_id IN (123,124,125,126)
GROUP BY Blog.Id
Now this query will also include blogs that have tags not in this list. So we need to remove them. I think something like that could work:
SELECT *
FROM (
SELECT Blog.id as blogId
FROM Blog
JOIN Blog_tags ON Blog.Id = Blog_tags.blog_id
WHERE tag_id IN (123,124,125,126)
GROUP BY Blog.Id
) as Blog_filtered
LEFT JOIN Blog_tags ON Blog_filtered.blogId = Blog_tags.blog_id AND Blog_tags.tag_id NOT IN (123,124,125,126)
WHERE Blog_tags.id IS NULL
GROUP BY Blog_filtered.blogId
Related
I have a news list, with three categories. Let's say category1, category2, category3. Each article has a publish date. Now is it possible to get the list as follows:
most recent article in category1
most recent article in category2
most recent article in category3
second most recent article in category1
second most recent article in category2
second most recent article in category3
And if both category1 and category3 have one article left while category2 has run out of articles, the above list will be followed by:
third most recent article in category1
third most recent article in category3
I don't know if this is possible in mysql query. Please help!
If your category Number is limited you can loop it and in every loop, concat the sqls with UNION; the this sql:
"SELECT * FROM news WHERE 1 GROUP BY ".$category." ORDER BY publish_date DESC LIMIT ".$categoryCount." UNION ALL"
Note: Don't forget the substr the last "UNION ALL" string
I see questions like this so often, especially when it comes to web-based app content. I want the most recent X per Whatever. In this case, you want the most recent 3 articles. Instead of trying to structure queries that will be a pain to work with, I would suggest denormalizing the data for the news category table..
Add 3 columns for the most recent 3 articles. Then, add a trigger to the articles table. When an article is added to the table, do an update to the category table with it as the newest position, and shift the other two down so it is always rolling... Not positive on the trigger syntax, but something like the following for the update...
update Category
set ThirdArticleID = SecondArticleID,
SecondArticleID = FirstArticleID,
FirstArticleID = NewArticleIDJustAdded
where CategoryID = NewArticleCategoryID
Then, you can just do left-join per category and always have the "latest 3" something like
select
c.*,
A1.ArticleTitle as FirstArticle,
A2.ArticleTitle as SecondArticle,
A3.ArticleTitle as ThirdArticle
from
Category C
LEFT JOIN Articles A1
on C.FirstArticleID = A1.ArticleID
LEFT JOIN Articles A2
on C.SecondArticleID = A2.ArticleID
LEFT JOIN Articles A3
on C.ThirdArticleID = A3.ArticleID
Then you dont have any min(), max(), group by, order by desc, etc to worry about. You may want to pre-prime these category article entries, but after the entries get new articles, it will be self-synchronizing.
I have a table called Products that contains Titles of products. Some of these Titles have words I would like to replace. The table contains over 6,000 records. The text strings are found in the Title Column
ID | Title | Description | Price
I have another table called ReplaceText that contains a list of strings I am searching for and new text string. (e.g. small | tiny) This currently has 102 records.
ID | OldString | NewString
I would like to create a PHP/MySQL script to search the Title field in the Products table for matching words from the 'OldStringfield in theReplaceText` table.
I have tried various methods but all can't crack the code. Please help :)
Thank you in advance.
I suggest the following approach, using only SQL queries.
First, create a temporary table ProductsTMP containing only the rows Products with a new Title.
CREATE TEMPORARY TABLE ProductsTMP AS
SELECT p.ID, r.NewString AS Title, p.Description, p.Price
FROM Products p INNER JOIN ReplaceText r ON p.Title = r.OldString;
Then, remove the old rows from the original Products table:
DELETE FROM Products
WHERE ID IN (SELECT ID FROM ProductsTMP);
Finally, add the updated rows from ProductsTMP to the original table Products, and remove the temporary table:
INSERT INTO Products (ID, Title, Description, Price)
SELECT ID, Title, Description, Price FROM ProductsTMP;
DROP TABLE ProductsTMP;
I currently have a tag system for my blog.
Each blog is inserted in the blog table, and the tags are inserted in the tag table.
The tag table has a column blog_id to link each tag to a blog item.
So let's say we have:
Blog table:
id - name
20 - a nice blog post about product x
Tag table:
id - blog_id - tag
12 - 20 - nice
13 - 20 - product x
I have a search function that searches through the tags based on a search string and that works fine.
But I would like to expand the query to search for multiple tags, and order it by the best match. Searching for multiple tags will not be a problem, because I could just explode the search string and loop through it, but ordering it by the best match is something I can not figure out.
So let's say I have 3 blog posts, with each the following tags:
1. sunny, in-ear, earphones, review
2. pear, out-ear, earphones, review
3. pear, in-ear, earphones, review
And a user searches for "pear in-ear earphones", I would like the order of the result to be:
3. (because 3 tags match)
1. (because 2 tags match)
2. (because 1 tags match)
This is what the query looks like:
SELECT `b`.*
FROM (`blog` b)
WHERE (
b.name LIKE '%pear in-ear earphones%'
OR
b.id IN (
SELECT bt.blog_id
FROM blog_tags bt
WHERE bt.tag LIKE '%pear in-ear earphones%'
AND bt.blog_id = b.id
)
)
ORDER BY `b`.`date` desc
Who could help me out?
I've looked at "Full Text Search" but that is not an option, because my table is InnoDB.
Thanks in advance!
I personally love using Solr for text matching. You can create these complex formulas that will weight matches in the name higher than matches in the tags or vice versa. It also matches pluralizations too. So if I search butterflies it will find the butterfly matches.
Here's another query that may help you with order blog tags by frequency. This query will get all of the blog items with at least one match in the tags. It will order by the number of tags that matches
SELECT *
FROM blog b
JOIN (
SELECT blog_id, COUNT(*) as matches
FROM tags
WHERE tag in ('pear', 'in-ear', 'earphones')
GROUP BY blog_id
) t
ON t.blog_id = b.blog_id
ORDER BY matches desc
You can add the number of matches for a particular string like this:
SELECT *,
t.matches +
COALESCE((LENGTH(b.`title`)-LENGTH(REPLACE(b.`title`,'pear','')))/LENGTH('pear'),0) +
COALESCE((LENGTH(b.`title`)-LENGTH(REPLACE(b.`title`,'in-ear','')))/LENGTH('in-ear'),0) +
COALESCE((LENGTH(b.`title`)-LENGTH(REPLACE(b.`title`,'earphones','')))/LENGTH('earphones'),0) AS total_matches,
FROM blog b
LEFT JOIN (
SELECT blog_id, COUNT(*) as matches
FROM tags
WHERE tag in ('pear', 'in-ear', 'earphones')
GROUP BY blog_id
) t
ON t.blog_id = b.blog_id
ORDER BY total_matches desc
ORDER BY
Just a note this query will probably be pretty slow with all of these matching and things. I still recommend using an indexing software like Solr
The following query counts the number of tags that match a particular list and orders by the number of matches:
select b.*
from blog b join
blog_tags bt
on b.id = bt.blog_id
where bt.tag in ('pear', 'in-ear', 'earphones')
group by blog_id
order by COUNT(*) desc;
Note that the use of like in your original query is incorrect. None of the tags contain the string 'pear in-ear earphones'.
I have a few linked tables in my custom forum: categories, sub_categories and posts
basically, I have people able to add up to three categories and five sub-categories when they make a new post.
I also enable people to 'listen' to certain categories and sub-categories and have them in an easy to access bar at the side of the page.
My tables are set up thus (only showing relavent fields for ease):
posts:
id INT
category_id VARCHAR(12)
sub_category_id VARCHAR(35)
categories:
id INT
name VARCHAR(20)
sub_categories:
id INT
name VARCHAR(20)
in my posts table, I store the set categories and sub-categories by their ID in the following format:
category_id [2][4][8]
sub_category_id [1][2][3][4][5]
thus enabling me to execute the following query in PHP and get the post based on category and sub-category:
SELECT * FROM posts WHERE category_id LIKE '%[{$id}]%' AND sub_category_id LIKE '%[{$id2}]%'
the problem I have is selecting the sub_categories for the access bar that people 'listen' to...
$sql = "SELECT title, id FROM categories";
$query = mysql_query($sql);
$list = array();
while ($row = mysql_fetch_assoc($query)){
$list[$row['title']] = array();
$sql = "SELECT sub_categories.title FROM sub_categories, posts WHERE (category_id LIKE '%[{$row['id']}]%') AND (????) LIMIT 0,100";
$query = mysql_query($sql);
while($result = mysql_fetch_assoc($query)){
$list[$row['title']][] = $result['title'];
}
}
print_r($list);
Obviously you can see where I am stuck (????), but before I explain what I am trying to do, I'll explain what the output I am looking for is.
when I print the $list array, I want it to print a multi-dimensional array featuring the categories as the first key, with their values being an array of sub-categories that have been tagged in the main category.
The problem I have is that in my sub_category_id field on the post table, remember the values are stored in the format [1][2][3] and I need to check the value against the subcategory field id.
I have tried the following:
"SELECT sub_categories.title FROM sub_categories, posts WHERE (category_id LIKE '%[{$row['id']}]%') AND (sub_category_id LIKE '%[sub_categories.id]%') LIMIT 0,100"
But it didn't work. I don't know whether there is an error in my query or whether it even SHOULD work, but I would be grateful if anyone could tell me how to do it or where I am going wrong in my code!
NB. I am trying to find which sub_categories appear in which categories based on people tagging them in a post together.
You're facing problems because you're not aware about few concepts in database...
In your case, you want to create associations called "many-to-many".
It means that a category can be used in many post and a post can be represented by many category.
To translate that into a "SQL" world, you have to create an intermediate table.
this table will store both identifier of the two tables.
for exemple:
-------------------
| categorisation |
-------------------
| post_id | => is the link to posts
| category_id | => is the link to categories
-------------------
When you create a new post, you create a new object in the table post. But you also create N records in the table categorisation.
When you want to retrieve which categories applied to this post, you can do a query like that:
SELECT post.id, post.name
FROM post
INNER JOIN categorisation on (post.id = categorisation.post_id)
INNER JOIN category ON (categorisation.category_id = category.id)
I think you need to read some articles on the web about database before progressing in you project ;)
I have a table called "posts" with the following struture:
post_id | post_body | post_posterid
And I have another table called "tags":
tag_id | tag_name
A third table stores the relation between the two tables (will have multiple tags_id for each post_id as separate records):
post_id | tag_id
This is similar to stackoverflow's tagging that i am trying to create.
But I do not know how to join the tables and to concatenate tags for each post, in a single record. Like this:
post_id | post_body | post_postername | tag_id1 | tag_id2 | tag_id3 ..... tag_name1 | tag_name2 | tag_name3 ....
So while fetching from mysql using PDO, I can avoid further queries to get tag names.
EDIT
More details:
First table("posts") contains post id(a unique id), posted message and poster's id(unique poster id which is stored in table "poster").
Second table "tags" contains possible tags(like "php", "vb.net", etc. like in stackoverflow) with unique tag_id.
Third table contains the relation between posted message and tags. So it contains post_id and its corresponding tag_id. It is possible to have more than 1 tag assigned to same post.
What i want: when queried, i can join "posts" with "posters" table to get name. But i want to get tags for each posts also.
GROUP_CONCAT() is answer for my question. But how to obtain it as tag_id - tag_name pair, instead of all tag_id in a field and all tag_name in another.
Eg: 1-VB , 5-PHP , 19-SQL
I will separate this pairs in PHP
What i am trying is same as stackoverflow's tagging system.
keyword is GROUP_CONCAT
and query is like
SELECT
posts.*,
GROUP_CONCAT(tags.tag_name) tags
FROM
posts
LEFT JOIN relation ON relation.post_id = posts.id
LEFT JOIN tags ON tags.tag_id = relation.tag_id
GROUP BY posts.id
hope this help