Retrieving Data from a field with comma delimitation SQL - php

I have a publications database and I need to fetch some information regarding the author. The author field is such that the authors have been lumped together in one field e.g if a book has two authors called Robert Ludlum and John Grisham, in the database it is saved as Ludlum, R.;Grisham,J.;
My application needs to spool information and retrieve data on books authored by a particular author if they click on their name. I am using this statement to retrieve the data
$select = "SELECT tblPublications.Title, tblPublications.Year FROM tblPublications WHERE tblPublications.Authors LIKE '%$sname%'";
$sname is a variable referring to the surname of the author. The problem arises if two authors share the same surname. however a workaround I am trying to implement is to get the applicationtake the surname, insert a comma, take the first name of a user and get the first letter then combine the result to a stringe and match them to each comma delimited value in the author field e.g if it is Grisham's books I am looking for I use *Grisham, J.* in my query.
Any Idea how to do this in PHP,MYSQL?

If it is possible to redesign the database, you should probably have an authors table and a book_authors table that relates books to authors, so that multiple authors can be associated with each book. Where is the Last Name coming from that the user clicks? Is it possible to have the link generated be LastName, First letter of first name? If so then you can probably change the link so it will include the first letter. But it is still possible to have two authors with the same last name and first letter of first name. So I think the best solution is to have an authors table and a Book_authors table and just store the author id as a hidden field and use that to retrieve the books by the selected author.

Your database design is incorrect, you have not normalized the data.
If you use like to search with leading wildcards, you will kill any chance of using an index.
Your only option to fix (if you want to keep the mistaken CSV data) is to convert the table to MyISAM format and put a FULLTEXT index on the authors field.
You can then search for an author using
SELECT fielda, b,c FROM table1 WHERE MATCH(authors) against ('$lastname')
See: http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html
Of course a better option would be to normalize the database and create a separate table for authors with a link table.
TABLE books
------------
id primary key
other book data
TABLE authors
--------------
id primary key
lastname varchar (indexed)
other author data
TABLE author_book_link
----------------------
author_id
book_id
PRIMARY KEY ab (author_id, book_id)
Now you can query using very fast indexes using something like:
SELECT b.name, b.ISBN, a.name
FROM books b
INNER JOIN author_book_link ab ON (ab.book_id = b.id)
INNER JOIN author a ON (a.id = ab.author_id)
WHERE a.lastname = '$lastname'

It would entirely depend on what input you are getting from the user.
If the user just types a name, then there isn't much you can do (as there is no guarantee that they will enter it in a correct format for you to parse).
If you are getting them to type in a firstname and lastname however, something like this could be done:
<?php
$firstname = trim(mysql_real_escape_string($_GET['fname']));
$surname = trim(mysql_real_escape_string($_GET['sname']));
$firstletter = substr($_GET['fname'],0,1);
$sname = $surname.', '.$firstletter;
$select = "SELECT tblPublications.Title,
tblPublications.Year
FROM tblPublications
WHERE tblPublications.Authors LIKE '%$sname%'";

Related

Add second (conditional) result from second table to SQL query

I have two tables in a database. One stores names/details of users with an index ID; the other stores articles they have written, which just keeps the user's ID as a reference (field author). So far so simple. I can easily query a list of articles and include in the query a request for the user's name and status:
SELECT a.name, a.status, s.* FROM articles s, author a WHERE s.author=a.id
The problem comes when I occasionally have a second author credit, referenced in field author2. Up till now I've been doing what I assume is a very inefficient second query when I iterate through the results, just to get the second author's name and status from the table (pseudocode):
while ( fetch a row ) {
if (author2 != 0) {
query("SELECT name, status FROM author WHERE id=author2") }
etc. }
While this worked fine in PHP/MySQL (even if clunky), I'm forced to upgrade to PHP7/PDO and I'd like to get the benefits of unbuffered queries, so this nested query won't work. Obviously one simple solution would be to PDO->fetchALL() the entire results first before iterating all the result rows in a foreach loop and doing these extra queries per row.
But it would be far more efficient to get that second bit of data somehow incorporated into the main query, pulling from the author table using the second ID (author2) as well as the main ID, so that there are name2 and status2 fields added to each row. I just cannot see how to do it...
It should be noted that while the primary author ID field is ALWAYS non-zero, the author2 field will contain zero if there is no second ID, and there is NO author ID 0 in the author table, so any solution would need to handle an author2 ID of 0 by providing null strings or something in those fields, rather than giving an error. (Or far less elegantly, a dummy author ID 0 with null data could be added to the author table, I suppose.)
Can anyone suggest a revised original query that can avoid such secondary queries?
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax.
For your query, use LEFT JOIN:
SELECT s.*, a1.name, a1.status, a2.name, a2.status
FROM articles s LEFT JOIN
author a1
ON s.author = a1.id LEFT JOIN
author a2
ON s.author2 = a2.id
Gordon Linoff's answer looks like what you need.
I would have added this as a comment but it is too long of a message...
I just have a question/comment regarding normalization of the database. Would there ever be an instance when there is an author3? If so then you should probably have an ArticleAuthor table. Since you are rebuilding the code anyway this may be an improvement to consider.
I don't know the names and data types of the information you are storing so this is a primitive example of the structure I would suggest.
Table Article
ArticleID
ArticleData...
Table Author
AuthorID
AuthorName
AuthorStatus
Table ArticleAuthor
ArticleID
AuthorID
If the Status is dependent on the Author Article combination then AuthorStatus would be moved to ArticleAuthor table like this.
Table ArticleAuthor
ArticleID
AuthorID
Status

MySql search among array elements in a field

I have the following table
title year authors
--------------------------------------------------------------------------
title book 1 2015-12-01 White McGregor Waine
title book 2 2016-10-14 McGregor Bush Rossi
title book 3 2017-05-22 Bush McGregor Lopes
...... .... .......
Authors field is composed of names separated by a white-space (I might also use a different separation character, if needed).
How do I extrapolate a desc list of authors who published more books? Consider I don't know the names of authors.
In the example the list is:
Author Books published
---------------------------------------
McGregor 3
Bush 2
Whaite 1
Whaine 1
Rossi 1
Lopes 1
The apt way to do this is to normalize.
This falls under many to many relationship.
For storing many-to-many relationships, an intermediate table that mainly stores the primary keys (IDs) of each relationship is required. In your case,
authors_table(author_id, name,...)
books_table(book_id, name,...)
authors_books_table(id, author_id, book_id)
Here is a more elaborate explanation.
This followed by a simple join, will get you the desired result.
First You have to learn Normalization. Database normalization, or simply normalization, is the process of organizing the columns (attributes) and tables (relations) of a relational database to reduce data redundancy and improve data integrity. ... Informally, a relational database relation is often described as "normalized" if it meets third normal form.
You can Also Try With this
<?php
$myarr = "White McGregor Waine";
$myarr = explode(" ",$myarr);
foreach($myarr as $value){
$sql = "SEECT title FROM table where authors = '$value'";
echo $sql;
}
As already pointed out by others, you will need to normalize your database. The advantages of normalization include but do not limit to the following:
you will have an easier time finding the data you are interested in, in your case you will be able to find the authors in a given table and the relations in an intermediary table instead of handling varchars using white space
your database will be consistent, that is, you will be able to easily handle CRUD operations with your authors, for example if you change the name of an author in the authors table, it will be changed for all places the author is referenced at. Also, you will be able to differentiate authors with the same name
you will not have redundant data stored, increasing the size of the data you have
You will therefore have the following tables:
authors(id, name)
author_of_book(id, author_id, book_id)
books(id, title, year)
Make sure the id fields are primary keys and author_id and book_id, respectively are foreign keys.
With this new structure you will be able to do the selection you want like this:
select authors.name, count(*) as `books published`
from authors
join author_of_books
on authors.id = author_of_books.author_id
join books
on author_of_books.book_id = books.id
group by authors.id, authors.name
order by count(*) desc

Use prepared statements in PHP to insert into/display from multiple MySQL tables with foreign key

I have two tables: movies and directors. The directors attribute in the movie entity is multi-valued, which is why I created the directors table with movieId as a foreign key that references the id column of the movies table.
Now, if I want to insert movies into my movies table, how do I add all the information for the movies into the movies table, as well as the name of the directors into the directors table at the same time, possibly using transactions? Also, how do I display the information from the movies table with their corresponding directors from the directors table in a HTML5 table using a while loop? I am using PHP with prepared statements.
This is what I have so far, but it's not complete:
mysqli_autocommit($connect, FALSE);
$stmtMov = $connect->prepare("SELECT id, title, plot, rating, releaseDate, language, duration, country, posterUrl, trailerUrl, imdbUrl FROM movies");
$stmtMov->execute();
$resultMov = $stmtMov->get_result();
$rowMov = $resultMov->fetch_assoc();
$movieId = $rowMov['id'];
$stmtDir = $connect->prepare("SELECT movieId, name FROM directors WHERE movieId = ?")
$stmtDir->bind_param("?", $movieId);
$stmtDir->execute();
$resultDir = $stmtDir->get_result();
$rowDir = $resultDir->fetch_assoc();
Any help would be very much appreciated.
Since you haven't added anything about insert, I'll consider only your select part.
The $rowMov will likely result in a rowset, which is nothing more than an array, which each row will have an ID value. What you should do is iterate with your rowset and generate, for every value, a query for directors entity and get the data you want. Something like:
foreach ($rowMov as $movie) {
$stmt = $connection->prepare("SELECT .... FROM directors WHERE id_movie = ?");
$stmt->bindParam("?", $movie["ID"]);
// $execution, binding results, etc.
}
With that done, you'll have an array with directors and an array with movies. If you want to simplify things on your view (considering you're using a MVC pattern), I would associate both arrays, looking for relations of directors["ID_MOVIE"] and movies["ID"], finally creating an array with both informations like and object.
You've asked two questions,
How do I insert into two tables at the same time, and
How do I display from two tables
But before I go into that, a bit of database review is in order. I would think that each movie would have one director, while each director might have many movies. I suppose the possibility for co-directors exists, too.
So for the first case, each movie must have a director:
movies
------
movie_id
director_id
title
plot, etc...
In this case, you could simply put the director's name in the movie database, but in order to list movies by director you would have to search by the actual name (and mis-spelling would make things complicated). And directors and movies are two different things, so it's better to have separate tables.
In the second case, you need a join table to have a many-to-many relationship.
movies director_movie directors
------- -------------- --------
movie_id movie_id director_id
title director_id name
So to answer your questions,
How do you insert into two tables at the same time?
You don't. First insert into whichever table stands on its own-- in the first case, directors. Then you get the last_insert_id from that table (or if the director already exists, search for the director_id). If last_insert_id is squirrely, you may have to search for what you just inserted to get the id.
Then, you take that id value and insert it into the dependent table movies along with the rest of that table's fields.
For the many-to-many case, you would do it in similar steps: 1) insert into movies 2) get the movie_id 3) insert into direcotors 4) get the director_id 5) insert ids into director_movie
How do I display the results
If there is only one director per movie, it's a simple sql query:
SELECT movies.*, directors.name FROM movies, directors where movies.director_id=directors.director_id AND movies.movie_id=?"
If you have multiple directors per movie, you'll have to loop through results:
SELECT * FROM movies WHERE movie_id=?
then make another query to list the directors
SELECT d.* from directors AS d,director_movie AS dm WHERE dm.director_id=d.director_id AND dm.movie_id=?

MySQL Search for line in text

in MySQL, I have a row for each user, with a column that contains friends names separated by \n.
eg.
Friend1
Friend2
Friend3
I'd like to be able to quickly search all the users where the Friends field contains Friend2.
I've found FIND_IN_SET but that only works for commas and the data can contains commas and foreign characters.
Obviously searching with regular expressions and the such will be slow. I'm new to the whole cross referencing so I'd love some help on the best way to structure the data so that it can be found quickly.
Thanks in advance.
Edit: Ok, I forgot to mention a point that the data is coming from a
game where friends names are stored locally and there are no links to
another users ID. Thus the strings. Every time they connect I am given
a dump of their friends names which I use in the background to help match games.
The most commonly used structure for this kind of data is usually adding an extra table. I.e.
user
id,
name
email,
e.t.c.
user_friend
user_id
friend_id
Querying this is a matter of querying the tables. I.e.
List all of a users friends names:
SELECT friend_id
FROM user_friend
WHERE user_id = :theUser
Edit: Regarding OPs edit. Just storing the names is possible too. In this case the table structure would become:
user_friend
user_id
friend_name
and the query:
SELECT friend_name
FROM user_friend
WHERE user_id = :theUser
Why are you keeping friend names as text? This will be inefficient to edit uf say a user removes a friend or changes their name. That's another thing, you should store friend names by some auto_increment id key in your database. It's much faster to search for an integer than a string, especially in a very large database. You should set up a friends table which is like
Column 1: connectionid auto_increment key
Column 2: user1id int
Column 3: user2id int
Column 4: date added date
ect...
Then you can search the connection table above for all rows where user is user1id or user2id and get a list of the other users from that.
My database hasn't been filled yet so I can easily change the format and structure in which the data will be stored.
Yes, you need to normalize your database a bit. With current structure, your searches will be quite slow and consume more space.
Check out this wiki for detailed help on normalization.
You can have the friends table and users table separate and link them both by either foreign key constraint or inner joins.
The structure would be:
Users table
id: AUTO_INCRMENT PK
name
other columns
Friends table
id: AUTO_INCREMENT(not required, but good for partitioning)
UserID:
FriendsID
DateAdded
OtherInfo if required.

MySQL query based on 3 tables

I work with PHP. I have a table named books. In table books I have the book_name with the name of the book, book_publisher with the publisher's ID and book_author with the author's ID. Besides the books table I have the books_author table with the authors names and IDs and books_publisher with the books publishers names and IDs.
I give my users 3 input fields that represents author name, book name and publisher name and a search button. They can input an author name, a book name and publisher name in the same search and I have to query my database and return the books names that have author name LIKE (%..%) inputted author name, book name LIKE the inputted book name and publisher name LIKE the inputted publisher name.
The problem is that I have stored only the author's id and publisher's id in my books table and I need to search by all three fields and exclude duplicates (books that were match by name and also by publisher).
Can anybody help me building this query ?
Just join the query:
SELECT *
FROM books b
JOIN book_authors ba ON b.author_id = ba.id
JOIN book_publishers bp ON b.publisher_id = bp.id
WHERE b.book_name LIKE '...'
AND ba.author_name LIKE '...'
AND bp.publisher_name LIKE '...'
Usually in these situations the search boxes are optional so you'll need to dynamically construct the query, meaning the WHERE clause to only filter on, say, publisher name if the user actually entered a publisher name.
Also, searching on LIKE '%blah%' is not scalable beyond thousands or possibly tens of thousands of records. No indexing can cater for that. You may want to look into using something like the MySQL full-text searching instead.
Lastly, make sure you sanitize your input, meaning pass all your input fields through mysql_real_escape_string().

Categories