Creating a related articles query in PHP - php

I'm trying to make something similar to "related articles". This is what I have.
$query = "SELECT * FROM posts WHERE post_tags LIKE '%$post_tags%'";
I want it to 'select all from post where the post tags are similar to the current post's tags'.
Note: The tags look like this in the database: "tech, news, technology, iphone"
I looked into things like
$tags = explode(",", $post_tags );
But I'm not sure.

Use FullText search -- docs
SELECT * FROM `posts` WHERE MATCH(post_tags) AGAINST('$post_tags' IN BOOLEAN MODE)
Live demo
The query will require you to add a FULLTEXT index to your post_tags table column (unless you have the older MyISAM table). This query will be a lot faster than your current attempt.
query to add the index
ALTER TABLE `posts` ADD FULLTEXT INDEX `tag_search` (`post_tags`)
A better, faster approach
Change how you store the post-to-tag relationship in the DB. Your posts table should not be used to store tags because one post has many tags, but each post has only one record in the posts table. Instead, have a two other tables:
tags table
tag_id | name
1 | technology
2 | news
3 | hobbies
post_tags table
post_id | tag_id
1 | 1
1 | 3
2 | 1
Notice it's easy to tell that post_id #1 has the technology and hobbies tags. This will make your queries easier, and faster.
Even faster!
If you do want to store everything in the posts table but have even faster performance, you will need to store your tags as bit flags. For instance, if the following is true in your PHP application:
$techBit = 0b001; // number 1 in binary form
$newsBit = 0b010; // number 2 in binary form
$hobbiesBit = 0b100; // number 4 in binary form
Then it's easy to store tags in one field. A post that has technology and hobbies tag would have a value:
$tag = $techBit | $hobbiesBit; // 1 + 4 = 5
And if you wanted to search for all records with technology or hobbies, you would do:
// means: records where post_tags has either techBit or hobbiesBit turned ON
SELECT * FROM `posts` WHERE (`post_tags` & ($techBit | $hobbiesBit)) > 0

Well instead of "LIKE" you could use the "IN" clause.
$Results = join("','",$post_tags);
$SQLQuery = "SELECT * FROM galleries WHERE id IN ('$Results')";
Example: Passing an array to a query using a WHERE clause

Related

MYSQL result formatting

Say if we had:
tbl `posts`
id | post_type | post_id | post_key | post_value | Locale
------------------------------------------------------------------------------
1 | news | 1 | title | The climate in Mongolia | pl_pl
2 | news | 1 | content | Mongolia has strange weather... | pl_pl
3 | news | 1 | date | 2015-9-24 | pl_pl
In order to get data for a post I would
SELECT post_key, post_value
FROM posts
WHERE post_id = 1 && post_type = 'news'
AND locale = 'pl_pl'
Then loop through the result set and mysqli_fetch_assoc() each row to give me an associative array with field names as the array keys;
$row['post_key'] == 'title'; $row['post_value'] == 'The climate in Mongolia';
$row['post_key'] == 'content'; $row['post_value'] == 'Mongolia has strange weather...';
What I would like is the actual values of post_key to be the array keys, keeping the value as post_value. So:
$row['title'] == 'The climate in Mongolia';
$row['content'] == 'Mongolia has strange weather...';
Yes I could format it like this with php and another loop, like:
$data = [];
foreach($rows as $r) {
$data[$r['post_key']] = $r['post_value'];
}
But I want to know if there is some mysql magic that could save the additional processing required in PHP?
Further info about this app: As Gal rightly pointed out, it seems strange that I wouldn't just use the field names as per the post_key's / conventional table design. But this is a translations table and I am exploring the possibility of having one table to cater for translations of many other content tables, and that I can use for new content types without too much faffing around with creating multiple additional tables and maintaining new fields across them. Reasons that I am not to worried about performance at this stage: 1. being the only developer on this startup project and where decisions are being made on the fly, I believe this will save time and tick all boxes for now. 2, number of entries will be low compared to a couple of years time, where we would have time then to look a solution to any performance issue that arises.
You can use a select like this to get all the post_key->post_value in a single row:
SELECT
title,
MAX(content) AS content,
MAX(date) AS date
FROM (
SELECT
post_type,
post_id,
CASE
WHEN post_key = 'title' THEN
post_value
END AS title,
CASE
WHEN post_key = 'content' THEN
post_value
END AS content,
CASE
WHEN post_key = 'date' THEN
post_value
END AS date
FROM
posts
WHERE
post_type = 'news' AND
post_id = 1
) AS a
GROUP BY
post_id
http://sqlfiddle.com/#!9/04684/1
UPDATE:
You can do something like i exposed but using the column value as a column name like this questions:
mysql select dynamic row values as column names, another column as value
MySQL select specific row values as column names
Then call a procedure like this:
How to call a MySQL stored procedure from within PHP code?
Hope it helps you!
You could. But as Gal stated, it's probably not the most efficient. If you value efficiency, I'd advice to just make meaningful columns.

PHP MySQL is taking 0.8s to 3s to load on search query, how to speed up

my MySQL table is in this structure:
|id|title|duration|thumb|videoid|tags|category|views
|1||Video Name|300|thumb1.jpg|134|tag1|tag2|tag3|category|15
|2||Video Name2|300|thumb2.jpg|1135|tag2|tag3|tag4|category|10
Table contains about 317k rows.
Query is:
SELECT id,title,thumb FROM videos WHERE tags LIKE '%$keyword%' or title LIKE '%$keyword%' order by id desc limit 20
And this is taking 0.8s to 3s to load results.
Im new in php/mysql, how can I speed up these queries, suggestions please, thank you.
The only other suggestion I can throw in is to have a multi-part index of
( tags, title, id )
This way, it can utilize the index to qualify the WHERE clause criteria for both tags and title, and have the ID for the order by clause without having to go back to the raw data pages. Then, when records ARE found, only for those entries does it need to actually retrieve the raw data pages for the other columns associated with the row.
You are using this search construct:
column LIKE '%$keyword%'
The leading % wildcard character definitely defeats the use of indexes to do these searches. How to cure this terrible performance problem? You could use FULLTEXT search, about which you can read. Or, you could try to organize your tables so
column LIKE 'keyword%'
will find what you need, and then index the columns being searched. To do this, you would create a tag table, with a name and id for each distinct tag. This table will have a primary key on the id, and a unique key on the tag. E.g.
tag_id | tag
1 | drama
2 | comedy
3 | horror
4 | historical
The you would create another table, known in the trade as a join table, with two ids in it. The primary key of this table is a composite of the two columns. You also need a non-unique index on the tag_id field.
video_id | tag_id
1 | 1
1 | 4
This sample data gives video with id = 1 the tags "drama" and "historical."
Then to match tags you need
SELECT v.id, v.title, v.thumb
FROM video AS v
JOIN tag_video AS tv ON v.id = tv.video_id
JOIN tag AS t ON tv.tag_id = t.tag_id
WHERE t.tag IN ('drama', 'comedy')
This will look up your tags very fast, and let you look up multiple ones in a single query if you wish.
It won't help with your requirement for full text search on your titles, however.
EDITED:
define indexes on title and keyword fields.
try this:
ALTER TABLE `videos` ADD INDEX (`title`);
ALTER TABLE `videos` ADD INDEX (`keyword`);

searching strings in SQL

I'm building a simple shopping cart. I need to allow for related products. My initial thought was to have a db field in the product table called tags which will have a comma delimited list of tags in it:
tag1,tag2,tag3
When I grab the product from the db I could also grab the tags and explode the string on the comma.
Problem is i'm having trouble thinking of a good way to then call to the db for all other products that have a matching tag. Is there way to search a string in SQL?
can anyone think of a good way to achieve this
You can use FIND_IN_SET() for that purpose:
SELECT * FROM tableName WHERE FIND_IN_SET('tag1', tags) > 0
However, I would strongly suggest to read on database normalization and joins instead.
Pleaso do not use any of the answers mentioning the LIKE syntax. For example WHERE tags LIKE %tag1% would match tag1 but also tag12 which is just wrong.
I would not go down the route of storing comma separated strings in fields, its not very scalable (or normalized) . I would split this up into 3 different tables:
products:
-------------
id | name
-------------
1 | product 1
2 | product 2
tags:
---------------
id | tag
---------------
1 | tag 1
2 | tag 2
product_tags:
----------------------
product_id | tag_id
----------------------
1 | 1
1 | 2
When you want to find products with related tags you would just do
SELECT product_id FROM product_tags WHERE tag_id = TAG_ID
You could then use more advanced joining statements to return the records from the products table (instead of just product_id's from the tags table):
SELECT products.* FROM products
INNER JOIN product_tags ON product_tags.product_id = products.id
WHERE product_tags.tag_id = TAG_ID
Its a bit more work but it will save you headaches in the future.
Consider using MySQL LIKE clause (check out this guide: http://www.tutorialspoint.com/mysql/mysql-like-clause.htm). You may use it on each tag, something like this:
SELECT * FROM `product` WHERE `tags` LIKE '%tag1%' OR `tags` LIKE '%tag2%' OR `tags` lIKE '%tag3'
To search for a string in SQL you can use the LIKE operator. Here is an example:
mysql_query("SELECT * FROM table_name WHERE value LIKE '%str%' ");

count the number of comments (php/mysql)

i am using this code so i can count the number of comments for each article
SELECT *, COUNT(comment_id) as count
FROM article_comments
WHERE article_id =colname
GROUP BY article_id
this is what my comment table look like
http://i54.tinypic.com/2cdu3dk.png
i want to save these number in another table (the articles table.. each number next to it's article ) like this
http://i54.tinypic.com/2dgm82u.png
and when the user enter a comment..the number change automatically
someone help me with the code
or if there is another way to do this
i know it's a long question
but i have been trying to solve this for like..forever
thanx
You could set a TRIGGER that updates the comment count table every time a comment is added. Or you could simply add the UPDATE query right after the INSERT query in your comment page.
You probably do not need a lookup table. 1 article has many comments. Therefore, structure your comments table something like this (add an article field);
id | article | content
-------------------------
1 | 1 | Comment 1 for article 1.
2 | 1 | Comment 2 for article 1.
3 | 2 | Comment 3 for article 2.
When displaying your article, list comments using the following query;
SELECT a.id, a.content FROM articles a WHERE a.article = :myArticleId
When creating a new comment:
INSERT INTO comments (article, content) VALUES (:currentArticleId, :content)
UPDATE article SET commentCount = commentCount + 1 WHERE article = :currentArticleId
The articles table will look something like this;
id | commentCount | content
------------------------------
1 | 0 | Article with 0 comments.
2 | 3 | Article with 3 comments.
This requires some work on your part, but it has more benefits than drawbacks.
Your proposed solution has 2 large drawbacks;
COUNT() in SQL does not scale very well and can be slow, normally it can be avoided.
The lookup table adds unnecessary complexity to your application.
Triggers should also always be avoided. They create "magic" conditions - your database can be changed without you knowing about it. Triggers are often more difficult to change than code too.
$query = mysql_query("SELECT * FROM article_comments WHERE article_id =".$youarticleId);
//the number of comments is :
$number_Of_Comments = mysql_num_rows($query);
//save it to another table
$query2 = mysql_query("UPDATE yourTable set numberOfComments =".$number_Of_Comments);
on saving comments, try to:
update table_where_you_count_the_comments set number_of_comments = number_of_comments +1 where article_id = theID limit 1;
or look for mysql triggers.
you're asking the sql server to select everything and the count id at the same time, use one of them and give it a where close, and Bingo!

To get many tags for a question effectively from Postgres by PHP

How can you get many tags for a question effectively from Postgres database by PHP?
I have tables tags and questions where I have the variables questions_question_id and question_id, respectively, for instance.
One question may have many tags.
I can select question_id, title and a tag from the tables.
However, I do not know how to get many tags for one question out of the database effectively. PHP restricts me in manipulating the data as matrices.
I can read question_id, title and tag to the variable $result.
The data is a matrix for me in the variale $result.
However, I cannot refer to the data like to a matrix.
I have only found the functions pg_fetch_row and pg_fetch_all_columns in PHP. The former gets the data as arrays for rows, while the latter as arrays for columns.
My problem with LEFT JOIN suggests me that it may not be possible to get the data as follows
get the columns for title, question_id and tag
group the data by question_id such that I can put tags to my questions: I have tried to manipulate the data by PHP, but I get only this far.
If you are bold, you can implement an aggregate function in PostgreSQL which concatenates all the relevant tags together for you, and then a single query will Do What You Want (tm).
pg=> create aggregate ARRAY_ACCUM (
sfunc = array_append,
basetype = anyelement,
stype = anyarray,
initcond = '{}'
);
pg=> select QUESTIONS.ID as "QID",
pg-> array_to_string(array_accum(TAG), ',') as "TAGS"
pg-> from QUESTIONS
pg-> left join TAGS on QUESTIONS.ID = TAGS.QID;
QID | TAGS
-----+---------------
1 | foo, bar, baz
2 | foo, bar
3 | foo
4 |
-- Reference, for completeness --
pg=> select * from QUESTIONS;
id
----
1
2
3
4
(4 rows)
pg=> select * from TAGS;
qid | tag
-----+-----
1 | foo
1 | bar
1 | baz
2 | foo
2 | bar
3 | foo
Under MySQL, this functionality is called GROUP_CONCAT. Under pg, it's called "Do It Yourself." :) Now, there are even subtler ways to go about it under pg, too. Check out this clever blog post, and its comments, from which I stole^Wadapted the create aggregate solution proposed above. :)
I think what you want is a second query that gets all the tags for a given question. Trying to join them together is gonna be cumbersome. It can be done but you'll end up with several rows with the same question data but different tag data.
So first do something like this (I don't know the structure of your database so this is just an example):
SELECT * FROM QUESTIONS WHERE ID = <id>;
Then you do a query right after that one, which asks for all the tags:
SELECT * FROM TAGS WHERE QUESTION_ID = <question_id>;

Categories