MySQL Search Over Multiple Tables - php

I'm in the midsts of constructing some database tables, but a possible search issue has just come to mind.
The two tables in question are Genres, a 2 column table holding a list of music genres identified by an ID field, i.e. 1 = Dance, 2 = Rock, and so on. And a Music table, a multi column table with Title, Artist, and Genre_ID fields. And yes you've guest it, Genre_ID refers to the ID of the Genre table.
My question is, if I have a search box on the site powered by PHP, and that search box queries the key fields, so Title, Artist, and Genre to yeld the best result, how can I get that to function correctly in a search, when the Genre name itself is in a separate table, and not in the Music table.
An example search would be, "rock music by ACDC".

To connect multiple tables in a query, you should look at using "join" statements. Rather than reinventing the wheel, the first answer to this post does a good job of explaining them... When to use a left outer join

Create a view where you join both of the tables. Then use SELECT with LIKE in WHERE clause or better use a fulltext search to do the searching job.
The view
create view ViewMusicWithGenre as
select "*"
from Music as m
left join Genre as g on m.genre_id = g.id;
Search option with like
select "*"
from ViewMusicWithGenre
where Title like '%<what_you_search>%'
or Artist like '%<what_you_search>%'
or Genre like '%<what_you_search>%';
I wrote the asterisk in "" because I KNOW that you WILL NOT use an asterisk.
Left join is there because you want the row even without specified genre (very likely).
The fulltext search
This usually depends on the database you use. This is for instance Microsoft SQL Server 2014:
Fulltext search - http://technet.microsoft.com/en-us/library/ms142571.aspx
Fulltext index - http://technet.microsoft.com/en-us/library/ms187317.aspx
Querying fulltext search - http://technet.microsoft.com/en-us/library/ms142583.aspx
EDIT: for MySQL database
MySQL does not support fulltext indeces on views. So you are left with couple of choices:
use the LIKE statement - could be ineffective, also more work later on
create the fulltext index on Music table and omit the genre - not good enough
create a new table that resembles the join and fill it on say daily basis with a job (or something like that) a do the fulltext search on that table - best solution in long terms, but more work to begin with and includes data duplicity
You also have to bear in mind that fulltext indeces only work on MyISAM storage engine.
The create statement for the joint table
create table fulltextSearchTable (
Music_ID int not null primary key,
Music_Title varchar(1024) not null,
Music_Artist varchar(1024) not null,
Genre_ID int not null,
Genre_Title varchar(1024) not null,
fulltext(Music_Title, Music_Artist, Genre_Title)
) engine=MyISAM;
The select with fulltext search
select "*"
from fulltextSearchTable
where match(Music_Title, Music_Artist, Genre_Title) against ('your_keyword');

You can try INNER JOIN like this:
$result=mysqli_query($YourConnection,"SELECT music.title, music.artist FROM music
INNER JOIN genres ON music.genre_id=genres.genre_id
WHERE music.title LIKE '$searchword'
OR music.artist LIKE '$searchword'
OR genres.genre LIKE '$searchword'");
And then print the results like this:
while($row=mysqli_fetch_array($result)){
echo $row['title']." - ".$row['artist']."<br>";
}

Related

Selecting title, description and keywords from MySQL database using PHP

I am coding a tiny search engine for my practice. I want to add up search functionality in it. I am trying to select all rows of questions table upon matching title, description and keywords.
I created the following 3 tables:
questions(id(PK), title, description)
keywords(id(PK), label);
questions_keywords(id(PK), question_id(FK), keyword_id(FK));
So far my SQL query looks like this:
SELECT q.* FROM question_keywords qk
JOIN keywords k ON qk.keyword_id=k.id
JOIN questions q ON qk.question_id=q.id
WHERE q.description LIKE '%javascript%'
OR
k.keyword_label LIKE '%java%'
In this query, i am selecting all the rows from questions table containing the substring java or javascript
Am I doing it right or there is a better way to do it??
Thanks in advance.
AS others mentioned I would add distinct. I would also reorder the tables. Functionally I don't think it matters it just bugged me... ha ha
SELECT DISTINCT
q.*
FROM
questions AS q
JOIN
question_keywords AS qk ON q.id = qk.question_id
JOIN
keywords AS k ON qk.keyword_id = k.id
WHERE
q.description LIKE '%javascript%'
OR
k.label LIKE '%java%';
As you can see in this DBfiddle
https://www.db-fiddle.com/f/pcVqcMm1yUoU6NdSHitCVr/2
The reason you get duplicates is basically called a Cartesian product
https://en.wikipedia.org/wiki/Cartesian_product
In simple terms is just a consequence of having a "Many to Many" relationship.
If you see in the fiddle I intentionally created this situation by what I added to the Bridge ( or Junction ) table question_keywords in the last 2 Inserts
INSERT INTO question_keywords (question_id,keyword_id)VALUES(4,1);
INSERT INTO question_keywords (question_id,keyword_id)VALUES(4,2);
The duplicate row, is simply because there are 2 entries for this table with the matching value of 4 for question_id. So these are only Duplicates in the sense that we are only selecting the fields form the questions table. If we included fields from the keywords table. Then one row would have a keyword or Java #1 while the other would have Javascript #2 as the keyword.
Hope that helps explain it.
A few other things to note:
You have a syntax error in the query you posted k.keyword_label LIKE '%java%' should be k.label LIKE '%java%' according to your table definition in the question.
Typically the Junction table should be a combination of both tables it joins ( which you almost did ) but the pluralization is wrong question_keywords should be questions_keywords it's a small thing but it could cause confusion when writing queries.
There is really not a need for a separate primary key for the Junction table.
If you notice how I created the table in the fiddle.
CREATE TABLE question_keywords(
question_id INT(10) UNSIGNED NOT NULL,
keyword_id INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(question_id,keyword_id)
);
The primary key is a compound of the 2 foreign keys. This has the added benefit of preventing real duplicate rows from being made. For example if you tried this
INSERT INTO question_keywords (question_id,keyword_id)VALUES(4,1);
INSERT INTO question_keywords (question_id,keyword_id)VALUES(4,1);
With the setup I have it would be impossible to create the duplicate. You can still have a separate primary key (surrogate key), but you should create a compound unique index on those 2 keys in place of it.

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`);

Long string in SQL database alternative?

We are creating a website where users can create a certain profile. At the moment we already have about 662000 profiles (records in our database). The user can link certain keywords (divided into 5 categories) to their profile. They can link up to about 1250 keywords per category (no, this isn't nonsense, for certain profiles this would actually make sense). At the moment we save these keywords into an array and insert the serialized array in the profile's record in the database.
When a different user uses the search function and searches for one of the keywords, an SQL query is executed with 'WHERE keyword LIKE %keyword%'. This means that is has to go to a pretty big number of records and go through the entire serialized array for each record. Adding an index to the keyword columns is pretty tricky, since they don't have a defined max lenght (this could be 22000+ chars!).
Is there any other more sensible and practical way to go about this?
Thanks!
Never, never, never store multiple values in one column!
Use a mapping table
user_keywords TABLE
--------------------
user_id INT
keyword_id INT
users TABLE
---------------------
id INT
name VARCHAR
...
keywords TABLE
---------------------
id INT
name VARCHAR
...
You could then return all users having a specific keyword in their profile like this
select u.*
from users u
inner join user_keywords uk on uk.user_id = u.id
inner join keywords k on uk.keyword_id = k.id
where k.name = 'keyword_name'
Since you are dealing with a large data you should use NoSQL databases such as Hadoop/Hbase, Cassandra etc. You should also take a look at Lucene/Solr...
http://nosql-database.org/

Capturing a row's ID to use as a table's name

Just to save anyone reading this time and trouble, DO NOT use this method to store surveys. As pointed out in the answer, this is incredibly poor programming (not to mention dangerous to kitties)
Forgive me if this question is somewhat convoluted. I'm working on building a program that allows users to create surveys and post them for users to take.
Long story short, I have a table that looks like this:
**survey_info**
id bigint(20) Auto_increment Primary Key
title varchar(255)
category bigint(20)
active tinyint(1)
length int(11)
redirect text
now, when a survey is created, a new table is also created that is custom built to hold hte input for that survey. The naming schema I'm using for these new tables is survey_{survey_id}
What I'm hoping to do is in the list of surveys, put the number of responses to a survey to the right of it.
Alright, now my actual question is this, is there a way to retrieve the number of rows in the collection table (survey_id) within the same query I'm using to gather the list of available surveys? I realize that I can do this easily by just using a second query for each survey and grab it's rowcount, but my fear is that the larger the number of surveys the user has, the more time-consuming this process will become. So is there any way to do something like:
SELECT s.id AS id, s.title AS title, c.title AS ctitle, s.active AS active, s.length AS length, s.redirect AS redirect, n.num FROM survey_info s, survey_category c, (SELECT COUNT(*) AS num FROM survey_s.id) n WHERE s.category = c.id;
I just don't know for sure how to use the s.id as part of the other table's name (or if it can even be done)
Any help, or even a point in the right direction would be appreciated!
You need to use one table for all the surveys.
Add newly created id not as a table name but as a survey id in that table.
You create a relational model that will store all surveys options in one table. This is a sample design:
survey
------
id PK
title
surveyOption
--------------
id PK
survey_id FK
option
surveyResponse
--------------
id PK
surveyOptionId FK
response

SELECT from various tables, with a list of indexes

Here is the scenario.
I'm developing a timeclock system; I have these tables:
-punch (id_punch,date)
-in1 (id_in1,time,id_punch,...)
-in2 (id_in2,time,id_punch,...)
.
-in6 (id_in6,time,id_punch,...)
-out1 (id_out1,time,id_punch,...)
-out2 (id_out2,time,id_punch,...)
.
-out6 (id_out6,time,id_punch,...)
My question is, how can I with only one query in PHP to get all values from in and out table, from a list of id_punch values, for example:
Get all punchs of September, or
Get all punchs of July to December,
I mean... from a list of id_punch between two dates, get all the results from the in, out table.
The only way I think is to do a query with each id_punch variable, but in a month its about 20-25 queries... to much?
To get all the data from the tables you'll need to join them with JOIN MySQL JOIN
But from what I can gather by looking at you tables, you probably should be thinking about making this into one table rather than the multiple tables you have here.
You really need to store all the in/out data in one table that is a child of punch:
CREATE TABLE punch (
id_punch SERIAL PRIMARY KEY,
punch_date DATE NOT NULL,
ip_address INT UNSIGNED NOT NULL
-- plus other attributes
) ENGINE=InnoDB;
CREATE TABLE inout (
id_punch BIGINT UNSIGNED,
in_time TIME NOT NULL,
out_time TIME NULL,
PRIMARY KEY (id_punch, in_time),
FOREIGN KEY (id_punch) REFERENCES punch (id_punch)
) ENGINE=InnoDB;
Now you can query very easily for all punches in September:
SELECT *
FROM punch LEFT OUTER JOIN inout USING (id_punch)
WHERE EXTRACT(YEAR_MONTH FROM punch_date) = '200909';
Your database schema is a little unclear, but if you're asking how to get the results corresponding to a list of ids you already have this should work (assuming your ids are 1,3,5,7,9)
SELECT * FROM table1, table2, table 3
WHERE table1.punch_id = table2.punch_id AND table2.punch_id = table3.punch_id AND table3.punch_id IN (1,3,5,7,9)
you'll probably need to modify it just make sure every table's punch_id is joined to that IN constraint
I can't use one table cause i have some informations in each punch, as ipaddress, and other information.
Neil, the answer was in my nose, i already saw a solution like yours, but my doubt is how to put the list in the query, answer for my own question = use a foreach() in php to "populate" this list...
Something like:
> SELECT * FROM table1, table2, table 3 WHERE table1.punch_id = table2.punch_id AND table2.punch_id = table3.punch_id AND table3.punch_id IN (<? foreach($query->results() as $row) echo $row->id_punch;?>)
im using codeigniter

Categories