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
Related
I need to summary columns together on each row, like a leaderboard. How it looks:
Name | country | track 1 | track 2 | track 3 | Total
John ENG 32 56 24
Peter POL 45 43 35
Two issues here, I could use the
update 'table' set Total = track 1 + track 2 + track 3
BUT it's not always 3 tracks, anywhere from 3 to 20.
Secound if I don't SUM it in mysql I can not sort it when I present data in HTML/php.
Or is there some other smart way to build leaderboards?
You need to redesign your table to have colums for name, country, track number and data Then instead if having a wide table with just 3 track numbers you have a tall, thin table with each row being the data for a given name, country and track.
Then you can summarise using something like
SELECT
country,
name,
sum(data) as total
FROM trackdata
GROUP BY
name,
country
ORDER BY
sum(data) desc
Take a look here where I have made a SQL fiddle showing this working the way you want it
Depending upon your expected data however you might really be better having a separate table for Country, where each country name only appears once (and also for name maybe). For example, if John is always associated with ENG then you have a repeating group and its better to remove that association from the table above which is really about scores on a track not who is in what country and put that into its own table which is then joined to the track data.
A full solution might have the following tables
**Athlete**
athlete_id
athlete_name
(other data about athletes)
**Country**
country_id
country_name
(other data about countries)
**Track**
Track_id
Track_number
(other data about tracks)
**country_athlete** (this joining table allows for the one to many of one country having many athletes
country_athlete_id
country_id
athlete_id
**Times**
country_athlete_id <--- this identifies a given combination of athlete and country
track_id <--- this identifies the track
data <--- this is where you store the actual time
It can get more complex depending on your data, eg can the same track number appear in different countries? if so then you need another joining table to join one track number to many countries.
Alternatively, even with the poor design of my SQL fiddle example, it might be good to make name,country and track a primary key so that you can only ever have one 'data' value for a given combination of name, country and track. However, this decision, and that of normalising your table into multiple joined tables would be based upon the data you expect to get.
But either way as soon as you say 'I don't know how many tracks there will be' then you should start thinking 'each track's data appears in one ROW and not one COLUMN'.
Like others mentioned, you need to redesign your database. You need an One-To-Many relationship between your Leaderboard table and a new Tracks table. This means that one User can have many Tracks, with each track being represented by a record in the Tracks table.
These two databases should be connected by a foreign key, in this case it could be a user_id field.
The total field in the leaderboard table could be updated every time a new track is inserted or updated, or you could have a query similar to the one you wanted. Here is how such a query could look like:
UPDATE leaderboard SET total = (
SELECT SUM(track) FROM tracks WHERE user_id = leaderboard.user_id
)
I recommend you read about database relationships, here is a link:
https://code.tutsplus.com/articles/sql-for-beginners-part-3-database-relationships--net-8561
I still get a lot of issues with this... I don't think that the issue is the database though, I think it's more they way I pressent the date on the web.
I'm able to get all the data etc. The only thing is my is not filling up the right way.
What I do now is like: "SELECT * FROM `times` NATURAL JOIN `players`
Then <?php foreach... ?>
<tr>
<td> <?php echo $row[playerID];?> </td>
<td> <?php echo $row[Time];?> </td>
....
The thing is it's hard to get sorting, order and SUM all in ones with this static table solution.
I searched around for leaderboards and I really don't understand how they build theres with active order etc. like. https://www.pgatour.com/leaderboard.html
How do they build leaderboards like that? With sorting and everything.
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
I am trying to SELECT id, description, title FROM table1, table2, table100
Say I get this working, is it better for me to just combine all my tables in phpmyadmin?
The problem is I have around 100 tables all of different categories of books so I want to keep them seperated in their individual tables.
I am trying to make a search engine that searches all the books in the entire database. All tables have the same column names.
So really all I really am trying to do is search the entire database's tables for an id, description, title. My search works, just I can only search 1 table and every solution online I have found only really works efficiantly with 2 or 3 tables.
Thanks in advance.
The best is to redesign your database, everything into a single table with an additional "category" column.
in the meantime, you can create a view which union the tables with an additional column for the category.
I recommend redesign the model and unifique this 100 tables to 1, and add a new column with category but integer value, not string value. In this way, you can index the category column with the other fields (id, description, title) for speed up the query.
This resolution is more easy for avoid pain later.
I recommend keeping one table A with id, description, title, category and create another table B with categories. Table A has to have a foreign key with table categories. Then create a query to retrieve the books with a specific category.
Example:
SELECT id, description, title, category FROM books WHERE category = "drama"
I think it speaks to the database design itself as mentioned by most here. You've a few options depending on how much time you have on your hands:
(Short Term / Quick Fix) Central table with all your current fields plus category as a flag to differentiate between the current tables you have. So your insert will be something like "INSERT INTO newtable (ID,AssetID,ServiceID,Category) SELECT id, description, title, 'Fiction' FROM table1 ;"
If you tables are incrementally named like table1, table2 upto table100, you could then maybe write a quick php script that will iterate through the insert loop while incrementing on table on each iteration until the last table.
In the long run, you could invest in a json field that will house all your other data excluding keys that pertaining to a single entry
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=?
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%'";