I've looked around for info on an efficient 'related videos' algorithm but i'm struggling to get well ordered, accurate results
I get given the 'genre' as a pipe-delimited string. eg: |Action|Sci-Fi|Thriller|
$genre = explode("|", $row['genre']);
if (count($genre) == 3) {
$sql = "SELECT title FROM `movie` WHERE genre LIKE '%$genre[1]%' LIMIT 0,8";
} else {
$sql = "SELECT title FROM `movie` WHERE (genre LIKE '%$genre[1]%' AND genre LIKE '%$genre[2]%') UNION SELECT title FROM `movie` WHERE (genre LIKE '%$genre[1]%' OR genre LIKE '%$genre[2]%') LIMIT 0,10";
}
$related = mysql_query($sql);
Then I basically explode it and do a manual, inefficient search for genre matches depending on genre count. The results are poor and returns anything that is semi related.
This code makes me want to gag! It works but I hate it and I know its uber lame. Any tips to improve the SQL and getting richer results?
Move the mappings of genres to movies into a new table movie_genres with columns movie and genre.
This allows you to do this:
$genres = explode('|', trim($row['genre'], '|'));
$sql = "SELECT `movie`, COUNT(*) AS hits
FROM `movie_genres`
WHERE `genre` IN ('" . join("', '", $genres) . "')
GROUP BY `movie`
ORDER BY `hits` DESC
LIMIT 8";
You have to make sure to prevent SQL injection, though.
The extra table is also a good idea, because your database schema is not normalized. Especially Chris Date's fourth condition of the first normal form is violated:
Every row-and-column intersection contains exactly one value from the applicable domain (and nothing else).
Related
how would i go about ordering by a value that is not in the table where i am selecting from, in this instance the value $count1 is not in the table search.
count has the same identifying id as that of the thing it is being reffered to in the other table, this is where count1 is grabbed
$q = $db->prepare("SELECT COUNT(rating) FROM ratings WHERE id='$id' AND rating = 'd'");
$q->execute();
$count1 = $q->fetchColumn();
$query = "SELECT * FROM search WHERE title LIKE '$each' ORDER BY '$count1'"
$query = $db->prepare($query);
$query->execute();
that is from ratings, how would i go about ordering the entries like that, so that they are based off the number of count1 and are decided, i might have to implement something like
$query = "SELECT * FROM search WHERE title LIKE '$each' AND id = '$id' ORDER BY '$count1'"
$query = $db->prepare($query);
$query->execute();
Possible Duplicate: Mysql order by specific ID values
Same thing here, you'll just output your $count1as a comma separated string and add it in the SQL query as ORDER BY FIELD(COUNT,___comma_sep_string___)
ratings is a table, not a database. You can join tables or use subqueries to get the desired result, without having to make multiple queries.
You haven't described how the FOREIGN_KEY is set up in the ratings table, but assuming you have something ratings.search_id, this should work:
SELECT search.*, (SELECT COUNT(rating)
FROM ratings
WHERE ratings.search_id = search.id
AND rating = 'd'
) AS rating_count
FROM search
WHERE title LIKE '$each'
ORDER BY rating_count
I am retrieving data from a database with php and MySQL as follows
$query = mysql_query("SELECT * FROM pictures WHERE (title LIKE '%$Search%' OR keywords LIKE '%$Search%') AND approved = 'YES' ORDER BY title ASC");
The query is correct and there are no errors and the query works fine for "title LIKE '%$Search%'" but the parameter "OR keywords LIKE '%$Search%'" is not retrieving data. The parameter "AND" also works correctly.
The keywords are stored in the database for example "pizza, restaurants, take away" but I don't see that is a problem.
My question is "What is the correct syntax for applying the "OR" parameter?
Remove the brackets around (title LIKE '%$Search%' OR keywords LIKE '%$Search%')
Those are generally used for subqueries.
$query = mysql_query("
SELECT * FROM pictures
WHERE title LIKE '%$Search%'
OR keywords LIKE '%$Search%'
AND approved = 'YES'
ORDER BY title ASC
");
https://dev.mysql.com/doc/refman/5.0/en/subqueries.html
Here is an example of a subquery, and pulled from the manual on MySQL.com:
SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
Edit:
Or try a different quoting method:
$query = mysql_query("
SELECT * FROM pictures
WHERE title LIKE '".%$Search%."'
OR keywords LIKE '".%$Search%."'
AND approved = 'YES'
ORDER BY title ASC
");
You could also try escaping your data:
$Search = mysql_real_escape_string($Search);
as an example. I don't know how you're assigning that variable.
phpMyAdmin test edit:
This is what I used inside phpMyAdmin:
SELECT * FROM table
WHERE col1 LIKE '%pizza%'
OR col2 LIKE '%pizza%'
AND col3 = 'YES'
ORDER BY col1 ASC
using pizza as the search keyword seeing that $Search will be based on the same keyword for you, where columns contain "large pizza" in one, and "pizza, take away, restaurants" in another.
Remember that, whatever you're using/assigning $Search to, must reside inside all your queried columns.
You may also want to make use of explode().
Here is an example pulled from https://stackoverflow.com/a/15289777/
<?php
$search = 'Gold Chain Shirt';
$bits = explode(' ', $search);
$sql = "SELECT name FROM product WHERE name LIKE '%" . implode("%' OR name LIKE '%", $bits) . "%'";
The above will generate this query:
SELECT name FROM product WHERE name LIKE '%Gold%' OR name LIKE '%Chain%' OR name LIKE '%Shirt%'
Sorry for taking some time but this is my working answer to my own question... not the prettiest syntax but it works without any string functions or explode functions. MySql can handle keywords quite well without any other functions being included:
$query = mysql_query("SELECT * FROM pictures
WHERE
title LIKE '%$Search%' AND featured IS NOT NULL AND streetview IS NOT NULL AND (id_user > '1') AND (status = '1')
OR
keywords LIKE '%$Search%' AND featured IS NOT NULL AND streetview IS NOT NULL AND (id_user > '1') AND (status = '1') ORDER BY title ASC");
Thank you all for your contributions
I think I don't understand how 'sort' works, so please don't judge me. I really searched all day long.
I have a movies table with actors column. A column it's named "actors". The actors are links separated by space " ". The order of the links it's very important.
I explode the links into an array which looks like [0]-> link0, [1]->link1, ...
I have the actors table where every actor also has it's movies links. I don't want to make 20 different sql searches so I made a variable with all the links I want, like this ( WHERE actor_link = link1 OR actor_link = link2 OR .. )
The problem is this. The search will probably find first the link7, and so my sorting it's gone. What can I do to keep that order from the movies table. I want to display the actors by popularity in the movie, not the order of my database.
Can you give me another method to search the actors without making 'x' sql searches and still keeping the order?
$actors[] = explode(" ", $row['actors_link']);
$x=0;
$actors2 = '';
while ($actors[0][$x]) {
$actors2 = $actors2 . "`link_imdb_actor` = " . "'".$actors[0][$x]."' OR ";
$x++;
}
$actors2 = substr($actors2, 0, -3);
$sql = "SELECT * FROM `actors` WHERE $actors2";
$sql_result = mysql_query($sql) or die(" ");
while ($row3 = mysql_fetch_array($sql_result)) {
echo $row3['link_imdb_actor'];
}
So, the movie Hotel Transylvania has Adam Sandler, Andy Samberg and Selena Gomez. My search shows Selena Gomez, Andy Samberg, Adam Sandler because this is the order from my database. How can I sort the sql results by the order of the actors array? Thank you!
To expand on Arjan's comment, if you want to be able to actually use the actor data (e.g. search with it) I would recommend at least two more tables. One called actors with the fields actorID, firstName, and lastName. The other table would be castings with the fields castingID, actorID, movieID, and castingOrder.
Each castingID will then link an actor to a movie - this would make for easy searches of every movie a particular actor has been in or every actor in a particular movie.
The castingOrder field can be used to maintain the order you want.
I need your existing code to really get the gist of what's going on.
I will make one suggestion in your query. Instead of saying WHERE actor_link = a OR actor_link = b OR actor_link = c do this instead:
WHERE actor_link IN (link1, link2, link3)
I'm looking for some general advice on how to go about finding items 'like' the current one.
In my current example I have three tables like so (omitting unrelated data):
games
-game_id
genres
-genre_id
genres_data
-game_id
-genre_id
How can I go about finding games that have genres in common with the current one, from the ones that have all the same genres, descending to ones that only have one in common with it (limited of course to a few rows) with a given row from games?
What's the preferred method of finding like items?
Try this:
SELECT game_id, COUNT(genre_id) AS genres_in_common
FROM genres_data
WHERE
genre_id IN
(
SELECT genre_id
FROM genres_data
WHERE game_id = <game you're searching with>
)
AND
game_id != <game you're searching with>
GROUP BY game_id
ORDER BY genres_in_common DESC
;
The subquery grabs a list of all genre_ids associated with your game, and the main query uses that to search genres_data for any record that matches one of them. In other words, it searches for any game which is associated with any genre your "search game" is associated with.
Because a game can have multiple genres, this query would return the same game_id multiple times, and if you also reported the genre_id on these records they would each show a different genre_id. What we do to find the ones with the most in common is to group the results by each game_id, and we add the COUNT(genre_id) in the main SELECT to show how many different genre_ids there were for each game_id returned in that query.
From there, it's a simple matter of ordering that count of common genres in descending order, so that the games with the most genres in common will be listed first.
I also added a second criterion to the main query to exclude the game you're searching on from the results, otherwise that game would always have the most matches, for obvious reasons.
Hope that helps.
Surely the way to do this for just a single game is to first grab its genres and then loop over them to create a new query:
$query = "SELECT `genre_id` FROM `genres_data` WHERE `game_id` = 'your_game_id_here';"
$genre_id_result = mysql_result($query, $dbconn);
$num = mysql_num_rows($genre_id_result);
if ($num > 0) {
$query = "SELECT `game_id` FROM `games` WHERE ";
for ($i=0;$i<$num;$i++) {
$genre_id = mysql_result($genre_id_result, $i, "genre_id");
if ($WhereSQL == "") {
$WhereSQL = "genre_id = '$genre_id' "
} else {
$WhereSQL .= "AND genre_id = '$genre_id' "
}
}
$GamesInCommonResult = mysql_result($query . $WhereSQL, $dbconn);
}
You could set up a loop to do this for every game in the database and then collate your results. I can't think of how to do this in a single query at the moment.
I'm also a little unsure on your question as either you're looking for the genres that are the most popular (as games with these genres will likely be returned as having the most other games with the same genre in common) or you are looking individually for other game_ids of games in common with another game which might be more useful.
I have a MySQL database called "bookfeather." It contains 56 tables. Each table has the following structure:
id site votes_up votes_down
The value for "site" is a book title. The value for "votes_up" is an integer. Sometimes a unique value for "site" appears in more than one table.
For each unique value "site" in the entire database, I would like to sum "votes_up" from all 56 tables. Then I would like to print the top 25 values for "site" ranked by total "votes_up".
How can I do this in PHP?
Thanks in advance,
John
You can do something like this (warning: Extremely poor SQL ahead)
select site, sum(votes_up) votes_up
from (
select site, votes_up from table_1
UNION
select site, votes_up from table_2
UNION
...
UNION
select site, votes_up from table_56
) group by site order by sum(votes_up) desc limit 25
But, as Dav asked, does your data have to be like this? There are much more efficient ways of storing this kind of data.
Edit: You just mentioned in a comment that you expect there to be more than 56 tables in the future -- I would look into MySQL limits on how many tables you can UNION before going forward with this kind of SQL.
Here's a PHP code snip that should get it done.
I have not tested it so it might have some typos and stuff, make sure you replace DB_NAME
$result = mysql_query("SHOW TABLES");
$tables = array();
while ($row = mysql_fetch_assoc($result)) {
$tables[] = '`'.$row["Tables_in_DB_NAME"].'`';
}
$subQuery = "SELECT site, votes_up FROM ".implode(" UNION ALL SELECT site, votes_up FROM ",$tables);
// Create one query that gets the data you need
$sqlStr = "SELECT site, sum(votes_up) sumVotesUp
FROM (
".$subQuery." ) subQuery
GROUP BY site ORDER BY sum(votes_up) DESC LIMIT 25";
$result = mysql_query($sqlStr);
$arr = array();
while ($row = mysql_fetch_assoc($result)) {
$arr[] = $row["site"]." - ".$row["sumVotesUp"];
}
print_r($arr)
The UNION part of Ian Clelland answer can be generated using a statement like the following. The table INFORMATION_SCHEMA.COLUMNS has a column TABLE_NAME to get all tables.
select * from information_schema.columns
where table_schema not like 'informat%'
and column_name like 'VOTES_UP'
Join all inner SELECT with UNION ALL instead of UNION. UNION is doing an implicit DISTINCT (on oracle).
The basic idea would be to iterate over all your tables (using a SQL SHOW TABLES statement or similar) in PHP, then for every table, iterate over the rows (SELECT site,votes_up FROM $table). Then, for every row, check the site against an array that you're building with sites as keys and votes up as values. If the site is already in the array, increment its votes appropriately; otherwise, add it.
Vaguely PHP-like pseudocode:
// Build an empty array for use later
$votes_array = empty_array();
// Get all the tables and iterate over them
$tables = query("SHOW TABLES");
for($table in $tables) {
$rows = query("SELECT site,votes_up FROM $table");
// Iterate over the rows in each table
for($row in $rows) {
$site = $row['site'];
$votes = $row['votes_up'];
// If the site is already in the array, increment votes; otherwise, add it
if(exists_in_array($site, $votes_array)) {
$votes_array[$site] += $votes;
} else {
insert_into_array($site => $votes);
}
}
}
// Get the sites and votes as lists, and print out the top 25
$sorted_sites = array_keys($votes_array);
$sorted_votes = array_values($votes_array);
for($i = 0; $i < 25; $i++) {
print "Site " . $sorted_sites[$i] . " has " . $sorted_votes[$i] . " votes";
}
"I allow users to add tables to the database." - I hope all your users are benevolent and trustworthy and capable. Do you worry about people dropping or truncating tables, creating incorrect new tables that break your code, or other things like that? What kind of security do you have when users can log right into your database and change the schema?
Here's a tutorial on relational database normalization. Maybe it'll help.
Just in case someone else that comes after you wants to find what this could have looked like, here's a single table that could do what you want:
create database bookfeather;
create user bookfeather identified by 'bookfeather';
grant all on bookfeather.* to 'bookfeather'#'%';
use bookfeather;
create table if not exists book
(
id int not null auto_increment,
title varchar(255) not null default '',
upvotes integer not null default 0,
downvotes integer not null default 0,
primary key(id),
unique(title)
);
You'd vote a title up or down with an UPDATE:
update book set upvotes = upvotes + 1 where id = ?
Adding a new book is as easy as adding another row:
insert into book(title) values('grails in action')
I'd strongly urge that you reconsider.