MySQL Show recommended items - php

I have a news system where users can recommend the item. But now I want that when someone is viewing an item, display a list of other items that have been recommended by people who recommended the item is viewed. Something like "Users who recommended this item also recommended..."
Recommendations table
+----+------+---------+
| id | user | item |
+----+------+---------+
| 1 | 1 | 1 | User 1 recommended item 1
+----+------+---------+
| 2 | 1 | 2 |
+----+------+---------+
| 3 | 2 | 2 |
+----+------+---------+
| 4 | 3 | 3 |
+----+------+---------+
| 5 | 2 | 3 |
+----+------+---------+
Items table
+----+---------+---------+
| id | title | author |
+----+---------+---------+
| 1 |Hello... | me |
+----+---------+---------+
| 2 | Bye... | you |
+----+---------+---------+
| 3 | Hi... | me |
+----+---------+---------+
As you can see the user with ID 1, recommended item 1 and 2 and User 2 also recommended item 2, also recommended item 3.
So when someone is looking at item 2, should be displayed in the list the item 3.
Also when someone is watching item 3, you should see the list the item 2.
Do not know how to do the SQL query, I guess first I have to get the IDs of all users who recommended the article being viewed, and later, check the ID of items most repeated among the selected user IDs.
And then with those IDs of the items, get the item title or author information.
But I have no idea how to make the SQL query in a more optimized way. I would appreciate your supports.

I splitted your problem into 2 tasks:
1.Select all the users, that recommended this item except the current user:
SELECT
recommendations.user
FROM
recommendations
WHERE
recommendations.item=$item_id
AND
recommendations.user!=$user_id
2.Then we use this query as a subquery to get all the items, recommended by those users, except the currently viewed item:
SELECT
items.id,
items.title
FROM
items
INNER JOIN
recommendations
ON
items.id=recommendations.item
WHERE
recommendations.user IN
(
SELECT
recommendations.user
FROM
recommendations
WHERE
recommendations.item=$item_id
AND
recommendations.user!=$user_id
)
AND
items.id!=$item_id
GROUP BY
items.id
3.If you want to show the most recommended items first, you'll need to sort the results by number of users, who recommended the item:
SELECT
items.id,
items.title,
COUNT(*) AS cnt
FROM
items
INNER JOIN
recommendations
ON
items.id=recommendations.item
WHERE
recommendations.user IN
(
SELECT
recommendations.user
FROM
recommendations
WHERE
recommendations.item=$item_id
AND
recommendations.user!=$user_id
)
AND
items.id!=$item_id
GROUP BY
items.id
ORDER BY
cnt DESC

You could certainly accomplish your goal using Joins, but it might be a better idea to have a third table with a one to one relationship for each item and a corresponding recommended item.
This way your Mysql query for recommended items will be much more efficient querying this third table for the source item and returning a result set of all recommended items.
You can then also do a simple query to the recommendations table to get the ids of the people that recommended the items in question.
With small numbers of items, this may seem to be overkill, but as you approach large numbers, the optimization could be significant in the Mysql processing required.

Related

Fast way to find out if one value of one column exists in the same table with 2 or 3 other values

I have 3 tables. One for products, one for tags and one for products-tags-associations.
The tags have different levels and work like categories and subcategories, but the tags are not associated directly to each other. The only thing that connects tags to each other (or put a subcategory in a category) are the products.
Imagine I have the product with the id 1, called Apple iPhone 13 Pro Max
It has the following tags:
+----+---------------------+-------+
| id | name | level |
+----+---------------------+-------+
| 1 | Telefoon | 1 |
| 2 | Apple | 2 |
| 3 | iPhone 13 Pro Max | 3 |
Which means that the products_tags_association table has these rows in it:
+------------+--------+
| product_id | tag_id |
+------------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
Because of this combination of information, I now know that the tag "Telefoon" has the tag "Apple" inside it, and the tag "Apple" has the tag "iPhone 13 Pro Max" inside it.
This is how I build the menu on my website.
I now have a problem though, as I have a website with 239 536 rows in the products_tags_association table.
I'm trying to create a step-by step selection tool where they first choose a tag from level 1, then level 2, then level 3, but the tags for each level need to only be the tags that have products which have the selected tag from the previous level.
The logic I have right now is this:
$sql='SELECT tags.id,level, name FROM tags WHERE tags.level="'.$lvl.'";';
$tags=getDataFromDb($sql,'id');
if(is_array($tags) && is_array($selectedTagsByLvl) && $lvl>1){
foreach($tags as $t=>$tg){
$conditions='';
if(is_array($selectedTagsByLvl)){
foreach($selectedTagsByLvl as $l=>$tgs){
$conditions.=' AND id IN (SELECT product_id FROM products_tags_association WHERE product_id=products.id AND tag_id IN ('.implode(',',$tgs).'))';
}
}
$sql='SELECT id FROM products WHERE online=1 AND id IN (SELECT product_id FROM products_tags_association WHERE product_id=products.id AND tag_id='.$tg['id'].') '.$conditions.' LIMIT 1;';
if(!is_array(getDataFromDb($sql))){
unset($tags[$t]);
}
}
if(count($tags)==0){
return NULL;
}
}
return $tags;
So first I get all the tags that have the level I'm selecting right now, and then for each of those tags I check if there is one product that has that tag, plus each of the tags that I've selected in the previous steps. I should only be able to select a few tags per level.
This works, the problem is that it's too slow.
How can I make this faster?
All I need to know is if there is a combination of one product_id with each of the selected tag_id's and the tag_id's for the current level. Maybe something with GROUP BY product_id?
I'm using PHP and mysql (MariaDB)
SELECT product_id, COUNT(*) AS ct
FROM table
GROUP BY product_id
Then, ct tells you how many times each product_id shows up in the table. Tacking on HAVING ct > 1 to list just the products that occur more than once.
General rule: When you can do a task in a single SQL statement it is better to do so than shoveling data into the client to have it do the work. (And usually a lot less coding!)

MySQL Join and newest lot information

I have four tables. The first describing a mix of items. The second is a linking table between the mix, and the items. The third is the item table, and the fourth holds lot information - lot number, and when that lot starts being used.
mix
mixID | mixName
----------------
1 | Foxtrot
2 | Romeo
mixLink
mixID | itemID
----------------
1 | 1
1 | 2
1 | 3
item
itemID| itemName
----------------
1 | square
2 | triangle
3 | hexagon
itemLots
itemID| lotNo | startDate
-------------------------
1 | 22/5/3| 22/07/16
2 | 03/5 | 25/07/16
2 | 04/19 | 12/08/16
3 | 15/0 | 05/08/16
Now, I need to be able to fetch the information from the database, which details all the items from a mix, as well as the most recently used lot number, something like this:
itemName | lotNo
----------------
square | 22/5/3
triangle | 04/19
hexagon | 15/0
I've tried a dozen different mixes of joins, group by's, maxes, subqueries, and havings; all to no avail. Any help would be much appreciated, I've been pulling my hair out for hours, and I feel like my fingernails are just scraping at the solution!
This will give you the result you're after and will perform pretty well if you have your indexes done properly. I'm not sure how you're meaning to reference mix as it's not apparent in your sample output but I've included it in the WHERE clause so hopefully you can understand where you would use it.
SELECT i.itemName
, (SELECT il.lotNo FROM itemLots il
WHERE il.itemID=i.itemID
ORDER BY il.startDate desc
LIMIT 1) as lotNo
FROM item i
JOIN mixLink ml ON ml.itemID=i.itemID
JOIN mix m ON m.mixID=ml.mixID
WHERE m.mixName="Foxtrot";

Mysql Limit rows by field value

I'm working on simple application using PHP and MySQL. Up to this point we needed to display items from database in HTML table. Simple pagination was implemented as well. It looks something like this:
+----+---------------------+
| 1 | Item 1 |
+----+---------------------+
| 2 | Item 2 |
+----+---------------------+
| 3 | Item 3 |
+----+---------------------+
| 4 | Item 4 |
+----+---------------------+
....
+----+---------------------+
| 5 | Item 25 |
+----+---------------------+
Not a rocket science. Now we add new functionality so we can (optionally) group items - We really create a 'lot' of identical items. We decided to add new column in database called groupID - which can be number or NULL for items not contained in any group. On web page we must display it as one element which expands when you click on it.
+----+---------------------+
| 1 | Item 1 |
+----+---------------------+
| 2 | Item 2 |
+----+---------------------+
| 3 | Item 3 |
+----+---------------------+
| 4 | Group 1 (Expanded) |
+----+---------------------+
| Group 1 Item 1 |
+---------------------+
| Group 1 Item 2 |
+---------------------+
| Group 1 Item 3 |
+----+---------------------+
....
+----+---------------------+
| 25| Item 25 |
+----+---------------------+
As you can see Number of items on one page may vary so we must treat items in group as one item, so simple 'limit 25' not working anymore. I wonder if I can make some clever mysql query which will work this way. I rather want to avoid to create new table in database which consists groups and relation to item table, because most of the groups will have only 1 Item. I don't believe this functionality will be used a lot, but You know - client. Also this system works on production for some time so I'd rather avoid such changes. Any Idea how to make it work? Also please keep it simple as possible, because this example is simplified. Real query is already bit complicated.
I also want avoid parsing it via PHP code, because it's just dumb to query all few thousands of rows and then discard all but 25-50 elements.
As you said some group ids can be null, I thought we should fix that.
When null, we use the item id to for the group and we use a prefix to make sure our new group_id is unique.
This is my solution using subqueries (not pretty, but seems to work):
SELECT
i1.id,
i1.itemgroup1
FROM (
SELECT
items.id,
IF(ISNULL(items.group_id),
CONCAT('alone-', items.id),
CONCAT('group-', items.group_id)) as itemgroup1
FROM
items
) as i1
RIGHT JOIN (
SELECT
items.id,
IF(ISNULL(items.group_id),
CONCAT('alone-', items.id),
CONCAT('group-', items.group_id)) as itemgroup2
FROM
items
GROUP BY itemgroup2
LIMIT 2
) as i2 on i2.itemgroup2 = i1.itemgroup1
** UPDATE **
Removed
WHERE
items.group_id IS NOT NULL

Kohana/PHP - Retrieving items based on user applied filter

My website is Q&A site like stackoverflow.com. When a user creates a question he have the ability to attach some tags to it. Later, when he need to find all questions which belongs to a category, he can use the filter box which accepts tag names. based on user entries i will just refresh the job list.
My table design is like below
Table: Questions
id | QuestionTitle |Other details.....|
1 | Why is earth round? |.............|
2 |How much is moon's diameter?|......|
Table: Tags
id | tagname
1 | planets
2 | earth
3 | moon
Table: AttachedTags
id | question_id | tag_id
1 | 1 |2
2 | 1 |1
3 | 2 |3
In the PHP/Controller i will get tag id's as user input's in the filter box.
What is the best method to fetch all those questions under a particular tag.
I am using PHP and Kohana 3.3
try joining your tables as shown, join both tables based on question id
select q.id,q.title
from questions q inner join attachedtags at on (q.id = at.question_id) where at.tag_id = ur-passed-tag-id

Counting rows by category-filter during fetching of categories from database

I have two tables in my database
table: products table: companies
+-----------+------------+ +------+------------+
| name | company_id | | id | name |
+-----------+------------+ +------+------------+
|Product 1 | 1 | | 1 | Company 1 |
|Product 2 | 1 | | 2 | Company 2 |
|Product 3 | 3 | | 3 | Company 3 |
|Product 4 | 1 | | 4 | Company 4 |
|Product 5 | 1 | | ... |
|Product 6 | 3 | +------+------------+
|Product 7 | 2 |
|... |
+-----------+------------+
Now I have to make company-selector (filtering products by company) using HTML SELECT element with names of all companies from table companies and COUNT of products after company name in the list.
So, my goal is to get SELECT options like this:
Company 1 (4)
Company 2 (1)
Company 3 (2)
Company 4 (0)
(note: counts inside the brackets are from example above)
What have I tried so far?
I was using mysql_* functions earlier and later it was mysqli procedural model. I can do this manually with one query for companies and another one inside while block to get COUNT of elements (filtered by current company's id in the loop). Now I'm trying to work with PDO object which is something new for me and I'm not very familiar with it.
Question
Is it somehow possible to get COUNT with one query (using JOINs or something)? If not, how I can do it with query inside the loop (old way) using my $dbPDO object? Or any other way?
I've looked some examples here but nothing could adapt to fit my requirements. Maybe I missed something but working with PDO is still painful for me (and I must learn it ASAP).
Looking at my own question, I don't think it's something hard, but the worst thing, I can't find solution by myself.
At the end, I have solution in mysqli, just I don't like it so much and think there's easier way of making this task done!
I thought this question is something I need but still don't understand that query in answer.
So far I have this code and have no idea how to make it counts products:
$dbPDO = new PDO('mysql:dbname=comixdb;host=localhost', 'root', '');
$sel_cat = ''; # selector HTML
$sql = "SELECT * FROM categories ORDER BY name";
foreach ($dbPDO->query($sql) as $row) {
$sel_cat .= "<option value=\"{$row['id']}\">{$row['name']}</option>";
}
$sel_cat = "<select><option value=\"*\">All categories</option>$sel_cat</select>";
echo $sel_cat;
Hope I've clarified question enough. Any help would be appreciated.
What you need can be done in SQL:
SELECT companies.name, SUM(IF(company_id IS NULL, 0, 1)) AS products
FROM companies
LEFT JOIN products ON (companies.id = products.company_id)
GROUP BY companies.id;
The LEFT JOIN ensures that all companies get selected, and the SUM ensures that the count is correct (a simple COUNT would return 1 for companies with no products).

Categories