Question

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.

No correct solution

OTHER TIPS

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

  1. Always give each table an unique (Auto-Increment) ID column, so add that to the notes table.
  2. 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.
  3. 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.
  4. 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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top