PHP and MYSQL DB design: using a column vs another table - php

I'm self-taught programmer/web-guy. And I am completely new to MYSQL. This is not a question asking how-to because I can make it do what I want. Rather I am asking for advice/critiques on how to do it better with regards to the DB design and queries I used.
A little background about me
I am not a professional programmer. Programming has always been more of a hobby interest for me. My job is designing custom jewelry using a CAD program and then have my models milled out via a CNC made especially for carving jewelry models out of wax (and a few days ago we got a 3D printer that prints in wax suitable for jewelry casting...yay!).
Why I'm doing this mysql/php project
For about a year, I've used a physical bulletin board by my workstation to track my 20 to 30 some odd design projects I always seem to have in queue. I have been wanting to make a website whereby I could enter some details about the job ticket and track it that way. This has various benefits, not the least of which is getting rid of the physical board which I always seem to be knocking the tickets off of. But mainly, the boss can view ticket statuses from anywhere in the world. A few months ago I finally got it roughed-in and it is even usable now, but I wanted to add a few more things to the project (namely, notes and special instructions).
So, I've got a main table that's approximately like...
ticket_id | lname | fname | job_desc | state_id |
----------|-------|-------|----------|-----------|
1 | Smith | Bob | dia ring | 1 |
----------|-------|-------|----------|-----------|
2 | Parker| Gil | pendant | 3 |
----------|-------|-------|----------|-----------|
There's other columns in the tickets table, but gives the general idea. The state_id refers to another table of states (statuses), which looks like so...
state_id | state_desc |
---------|----------------|
1 | Not Started |
---------|----------------|
2 | Being Designed |
---------|----------------|
3 | Rendered |
There's more states a ticket can have, but that gives the idea. The state ids end up being column heads which I use on the physical bulletin board.
I would like to have special instructions and notes for each ticket. This was something that has been awkward, though not impossible, on the physical board. It will be much nicer on the website version.
My first thought was to add another column to the tickets table like so....
ticket_id | lname | fname | job_desc | state_id | sp_instr | notes |
----------|-------|-------|----------|----------|----------|-------|
1 | Smith | Bob | dia ring | 1 | | |
----------|-------|-------|----------|----------|----------|-------|
For each ticket, there will be multiple instructions/notes and I want them to end up as unordered lists in the web page. So I would have to do something like...
Finger Size: 6.5|||Use three largest diamonds only|||Use customer's gold
And then explode on the "|||" (or some other arbitrary character combination) to get the array to make the list from. Simple enough. It was my first direction. Not too bad for the instructions. Never gonna have more than 5 or 6 short sentences. But the notes (which could double as an activity log, too) could be more complex and numerous.
So then I considered making a separate table like so...
ticket_id | instr |
----------|----------------------------------|
12 | Finger Size: 6.5 |
----------|----------------------------------|
12 | Use three largest diamonds only |
----------|----------------------------------|
12 | Use customer's gold |
----------|----------------------------------|
18 | Put bird on pendant somehow |
----------|----------------------------------|
18 | Use cust's white gold in pendant |
----------|----------------------------------|
Below is the meat of the php code that uses the database (connection info is not shown and table names have been changed)....
$db = new mysqli("{connection info}");
// put column heads in an array
$result = $db->query("SELECT * FROM states_table");
$col_heads = array();
while($row = $result->fetch_assoc()){
$col_heads[] = $row;
}
// put ticket info in an array
$result = $db->query("SELECT * FROM main_table ORDER BY due_date");
$tickets = array();
while($row = $result->fetch_assoc()){
$tickets[]=$row;
}
// do a bunch of stuff here to make HTML for the columns & tickets
// collect special instructions
$result = $db->query("SELECT main_table.ticket_id, instructions.instr FROM main_table LEFT JOIN instructions ON main_table.ticket_id = instructions.ticket_id");
while($row = $result->fetch_object()) {
var_dump($row);
}
But it's that last query that is giving me a headache. For tickets that have no special instructions, I get...
object(stdClass)#4 (2) {
["ticket_id"]=>
string(1) "6"
["instr"]=>
NULL
}
But when they do, I get a separate object for each record in the special instructions table like this...
object(stdClass)#2 (2) {
["ticket_id"]=>
string(2) "39"
["instr"]=>
string(22) "Use dias marked in red"
}
object(stdClass)#4 (2) {
["ticket_id"]=>
string(2) "39"
["instr"]=>
string(32) "Cust likes side shank of RG ring"
}
object(stdClass)#2 (2) {
["ticket_id"]=>
string(2) "39"
["instr"]=>
string(73) "Cust likes top of WG ring as well as the top of the ring from our website"
}
I am definitely able to (and did) go through that mess with a while loop and collect the instructions for a single ticket id, pack them into an array and associate them with the id so I have key=>value pairs like so...
ticket_id => array of instructions
So I can strong-arm my way through it, but I feel like I am overlooking some power in the MYSQL query statements somehow. Are my database tables and queries laughable? Have I overlooked something common/useful/powerful? Or is that just how linking a single record from one table to another table with multiple records is?
Sorry for the rambling post.

Because your question is more about MySQL than anything else, i'd say add a query we can comment on. I'll just assume what kind of advise you need here..
As for DB normalisation.. your setup looks good. Some common MySQL advice bulletpoints you might not be aware of are
Always give each table an unique (Auto-Increment) ID column, so add that to the notes table.
Consider adding field prefixes derived from the table name. This makes multi-table queries more readable. Often this is truncated to 3 or 4 characters like noteTicketID inside notes, and tickID inside the tickets table.
Try to make table names the plural of what they contain, but dont make the field names plural unless 1 field actually contains multiple values.
Do not use queries without a ORDER BY clause.
As for DB queries.. a query can be simply:
SELECT * FROM `notes` WHERE `noteTicketID`=3 ORDER BY `noteCreated` DESC
But perhaps you want to integrate several queries into one, and make a huge overview for open tickets. MySQL allows you to do this: (list every tickets of a certain state, including notes)
SELECT * FROM `tickets` WHERE `tickStateID`=0 OR `tickStateID`=1
LEFT JOIN `notes` ON `notes.noteTicketID`=`tickets.tickID`
Its certainly not "good design" to make arrays from MySQL results which you use to run further queries. If you can let MySQL do it for you in one swoop, thats a win-win situation.
As for solving the company problem, consider defining it and looking for existing solutions. Perhaps you need enterprise solutions like ERP, CRM, SCM, or just a simple issuetracker like MantisBT. This might not require much mcGyvering, but you'll likely move towards such a solution anyway eventually. The mcGyvering is certainly worth the effort though, if that helps defining the problem, but it takes a lot of time. None the less, this is how we all learned programming.. + feedback like this SO.
Conclusion:
Not your question, but install MantisBT and see how far this gets you. At the worst, it helps define the problem. But you may see a few workflow tricks that can use in a custom solution, or check out its intricate database design. And best of all, its also written in PHP and uses MySQL.

If I had to do over again, I would have called this question: MYSQL: Query that Groups Rows from Left Joined Tables???
I knew so little about MYSQL I could hardly think how to ask the question. My main issue was that the left joined table had multiple records per ticket id. I needed a way to group this data.
Enter GROUP_CONCAT.
The query that worked for me?
SELECT c.*,
GROUP_CONCAT(i.instr SEPARATOR '|||') AS instructions,
GROUP_CONCAT(n.note SEPARATOR '|||') AS notes
FROM custom_tickets c
LEFT JOIN ct_instr i ON c.ticket_id = i.ticket_id
LEFT JOIN ct_notes n ON c.ticket_id = n.ticket_id
GROUP BY c.ticket_id
The above query as used in my php code, creates an array with key => value pairs that look something like...
ticket_id => "1"
lname => "Smith"
fname => "Bob"
job_desc => "a cool ring"
instructions => "finger size: 6|||use cust's gold|||don't screw it up"
notes => "Use more diamonds now|||Make it look less stupid"
Some cool things that I (think I) learned
how to reference table names via variables within the query statement
that you can reference a joined table prior to using the JOIN keyword
(apparently) the results of GROUP_CONCAT should be treated as a column
GROUP_CONCAT is a great place to use the AS keyword. It makes for an ugly key in the array if you don't.
Also, by default, GROUP_CONCAT uses the comma as the separator. But as my notes and instructions will commonly include commas, I needed to specify a separator. I randomly picked a group of characters I am unlikely to ever use ("|||").
Thanks for every one's help.

Related

How to Make a More Accurate Live Search in PHP

I've been working on a live search feature for my site. I take a bunch of table values (i.e. bands, genres, albums, etc.) and I put them in their respective tables, as well as a search table for indexing. My main question is how do I make it more accurate.
Here is my mysql query (NOTE: the results are the same with or without Desc):
$search = "
(SELECT * FROM `search`
WHERE `search_name` LIKE '%$search_term%'
ORDER BY '%$search_term%' DESC
LIMIT 0, 8)";
Here is an example of the database:
Search ID | Search Name | Type |
----------------------------------------------------------
8 | Big Deal (What's He Done Lately?) | Album |
12 | Henry's Funeral Shoe | Band |
Problem is, is that when I type say H into the search parameter, I expect Henry's Funeral Shoe to be at the top, but instead I get Big Deal (What's He Done Lately?) before it, because it contains H and it is searched before the more appropriate one is.
So my question: Is there a MySQL function that can sort through the table and find the most relevant results and weigh them against those less relevant?
A basic search is easy. Good search is hard.
When setting up a search, you need to understand the data and what users will be searching for. In your case you want items starting with the terms to be returned first (I assume).
MySQL is not the best platform for search in general, but what you can do is 2 search. The first is:
search_name LIKE '$search_term%'
Note the missing % in front of the search term. These results are the higher ranked ones.
Secondly you should use:
search_name LIKE '%$search_term%'
These are your lower ranked results. Any results that are also in the higher rank list should be removed from the lower ranked list.
Finally you should combine the result sets making sure not to accidental mix up the ranks.
While not perfect nor adjustable, that might help in a basic search field.

Multiply column with same content, make them count as "one"

About
I have this table in my database holding some information saved with a user id and time.
| CONTENT | USER ID | TIME |
| text | 1 | 1405085592 |
| hello | 2 | 1405085683 |
| hey | 1 | 1405086953 |
This example could be a data dump from my database, now as you can count there is "three" rows. However I only need to know how many users there have some information in my database. Therefor the result I'm really looking for is "two", because only two users have information in the database. User ID 1 is owning both "text"(1) & "hey"(3) where user ID 2 haves "hello"(2).
In short
I want to count how many users (regardless how many rows of information they have) there are inside my database.
** What I tried **
I tried to fetch every single row into an array and then using array_unique to count them together, works fine but I do not see this as a clean and best way to do this.
Then what?
I could use the array_unique and just use count to see how many rows there are, but I'm looking for something more clean. I tried to search for this, but I'm not actually sure what I should search for in term to hit something I'm looking for. After being stuck and though I wanted to learn something new, I wanted to post this problem here.
Note
I hope you guys can help me, I have tried to make it clear what I'm looking for and what I tried. If not please let me know. Sorry if some of the above contains misspelled words, incorrect grammar or is badly explained. I do not speak English daily, but I try my best.
You are looking for the DISTINCT keyword. It returns the count of unique values of a column:
SELECT COUNT(DISTINCT user_id)
FROM your_table
See example on SQL Fiddle.
This query:
SELECT DISTINCT user_id FROM table
will return just one row for every user in the table.

Restructured database, using SQL in phpmyadmin to move data around

I've recently been working on normalizing and restructuring my database to make it more effective in the long run. Currently I have around 500 records, and obviously I don't want to lose the users data.
I assume SQL through phpmyadmin is the easiest way to do this?
So let me give you guys an example
In my old table I would have something like this
records //this table has misc fields, but they are unimportant right now
id | unit |
1 | g |
With my new one, I have it split apart 3 different tables.
records
id
1
units
id | unit
1 | g
record_units
id | record_id | unit_id
1 | 1 | 1
Just to be clear, I am not adding anything into the units table. The table is there as a reference for which id to store in the record_units table
As you can see it's pretty simple. What moved in the second table is that I started using an index table to hold my units, since they would be repeated quite often. I then store that unit id, and the pairing record id in the record_units table, so I can later retrieve the fields.
I am not incredibly experienced with SQL, though i'd say I know average. I know this operation would be quite simple to do with my cakephp setup, because all my associations are already setup, but I can't do that.
If I understand correctly you want to copy related records from your old table to the new tables, in which case you can use something like this
UPDATE units u
INNER JOIN records r
ON u.id=r.id
SET u.unit = r.unit
This will copy the unit type from your old table to the matching id in the new units table and then you can do something similare on your 3rd table

Want help: how to design my webpage that sorts data from a database?

I apologize in advance for the long question. I am designing a webpage for a DNA research lab and I am stuck on one particular point. The webpage accesses a single MySQL database, but the database contains dozens of tables. Each table corresponds to one experiment. The tables each follow the same general format: one column lists DNA gene names and the next column displays the amount of the DNA gene present. However, each table contains a different set of genes (the genes in one experiment aren't always the same as from another experiment).
At this point, I want the user to input which gene he is interested in and then the webpage will display which experiments have data for that gene. Basically, I need to figure out which MySQL tables in the database have the data that I want.
The way I see it, I need to cycle through each table in the MySQL database and do a SELECT WHERE query on each table. If it returns something, it is a table that I want and I will add the table name to an array. If not, I just move on to the next table.
What is the best way to do this and what languages do I need? I will use HTML and PHP for the webpage and MySQL for the database queries. However, what can I use to cycle through the tables? I was thinking javascript or ASP?
Let's assume that you can't change your database structure. You can get a list of all of the tables in your database using the query:
SHOW TABLES
Next, you need to know which tables are for experiments you care about. You'll probably have to do some kind of string matching -- hopefully they have names that start with "experiment_" or something.
Then you just run a SELECT statement looking for that gene in the table. Finally, you somehow map the experiment names to the table names, and display those experiment names. The code would be something like:
$result = mysql_query("show tables");
$tables = array();
while ($row = mysql_fetch_array($result)) {
// Determine whether this is an experiment table.
if (preg_match("/^experiment_/", $row[0])) {
$tables[] = $row[0];
}
}
$tables_with_gene = array();
// As you can see, every search runs bunches of queries.
foreach ($tables as $table_name) {
$result = mysql_query("select gene_name from $table where gene_name = '$gene_name'");
if (mysql_num_rows($result)) {
$tables_with_gene[] = $table_name;
}
}
// Now you look up the experiment names
$experiment_names = array();
foreach ($tables_with_gene as $table_name) {
$result = mysql_query("select experiment_name from experiments where table_name = '$table_name'");
while ($row = mysql_fetch_array($result)) {
$experiment_names[] = $row[0];
}
}
At the end of all this, $experiment_names has a list of the experiments that include the gene in question.
Note that if the gene name is user input you'll want to sanitize it first to avoid SQL injection.
But yeah, you probably want one table that looks like:
experiment_id
gene_name
gene_frequency
Then you could do it all with one query:
SELECT e.experiment_name FROM experiment_data d JOIN experiments e
ON d.experiment_id = e.id
WHERE d.gene_name = 'your gene name'
It sounds like you may need to redesign your database? I think you only need one table, and the "gene set" that is currently distinguishing tables should be a non-unique key on that table.
Then, you should be able to query that single table WHERE the gene set equals the set you are looking for...
Since you are planning to use PHP then that is a good choice for performing the logic that you need.
Do you have control over the structure of the database? If you do, it may be easier to restructure the database itself to support the types of queries that you need. For instance, you can have a single table listing the experiments, another table listing the genes, and a third table connecting the experiment to the gene and the other data that goes with it. This would avoid all the searching through tables for data that you have to do. The advantage would then be that as more experiments are added the application would continue to work without modifying the PHP code.
You should really consider that redesign people have mentioned if at all possible. Your data format has some real problems. If it were not done this way you wouldn't have this problem. 28000 records is quite small in database terms and it doesn't matter if the gene is involved in more than one experiment. That's really the whole point of multiple fields in databases. They are meant to work with data of exactly that type. You just need another field denoting which experiment the data in the amount column refers to.
So rather than....
-----------------
| Gene | Amount |
-----------------
| abc | 123 |
| xyz | 789 |
-----------------
You have:
------------------------------
| Experiment | Gene | Amount |
------------------------------
| ex1 | abc | 123 |
| ex2 | abc | 456 |
| ex2 | xyz | 789 |
| ex1 | xyz | 058 |
------------------------------
etc, etc, etc
Then if you need to see just the data from ex1 it's:
SELECT *
FROM tblGeneData
WHERE Experiment = "ex1"
That query will give you the same results as:
SELECT *
FROM tblExperiment1
This is how relational databases are meant to work. They are not generally meant to keep the same type of data in two different tables just because there is a differentiating property.
EDIT:
I feel the need to also point out that you would generally also want an additional field to use as a unique key for the table. I would add an additional field called "Id" to the table and make it autonumber. You could use a compound key made up of your data but the generally accepted "best practice" is to have a separate unique key field that is meaningless outside the context of the inner workings of the database. This field would be used as the primary key for the table.

How do I add "friends" function in PHP?

Hello stack overflow I need help with this problem.
Ok, I have a flat file database in php it records users , hobbies, fav movies and all.
Now i want to add a buddy system so they can have friends and send messages to each other in php without SQL.
Consider having another table (er, flat file?) that maintains links. "Mark" and "John" are buddies if there exists a row in this table (ff?) that links "Mark" and "John". I'd recommend using some sort of index (you know, like a primary key).
Suppose you have a users table (or flat file, whatever, it doesn't matter that much) that contains users and some data, it looks like this:
UID | Username | Hobbies
------------------------
1 | Mark | Swimming, Sailing, Skiing
2 | John | Biking, Paragliding
3 | Suzie | Flying, Skiing
And you have this other friends table (again, flat file, whatever...):
Pair ID | A | B
----------------
1 | 1 | 2
2 | 2 | 3
We've encoded in this friends table that Mark and John are friends, and that John and Suzie are friends, but with the absence of relation 1 and 3, we know that Mark and Suzie are not friends (at least according to our records).
Note that if you want to get all of John's friends, you have to find all rows in your table (or file) that have John's UID (here = 2) in either column A or column B.
Well I am afraid there's no magical answer or a magical PHP function you can call to enable this behavior.
If we are to help you at all we really need some more to work with.
If you really, for mysterious reasons, decide to stick without a SQL database - then I would probably still "tilt" towards a SQL like way of storing it. Assuming you're currently storing each user as a row in a file, with each "field" separated by some character - simply add another "field" in the file and have this containing each "id" of every user that they're friends with (id, being whatever you use for that, could be a name as long as it is unique).
As for the messages, another flat file describing the message itself, sender and recipient would probably be the way to go.
Now the real question is, why so eager to avoid using a SQL database? If it is because of having to install a database, try SQLite

Categories