How to handle multiple value selections in PHP and MySQL - php

I recently took an online PHP / MySQL course and as a way of practicing I make a stock keeping site for a paint shop.
We have a paint table:
paintID: INT, AI
paintType: varchar (e.g. metal, primer, water-based, etc.)
paintPrice: INT (e.g. 10, 15, 20)
paintColor: ???
A colors table:
colorID: INT, AI
colorName: varchar (e.g. blue, red, white, transparent, etc.)
Every paint product comes in one or more colors. So I thought of making a section within the product.php page offering all the colors as checkboxes. While this is simple enough when creating new products,
how can I store the selected colors via PHP in the MySQL database once user presses submit (and what type to give to the paintColor column)
how can I retrieve/show all the possible colors with the chosen ones selected once in 'read-only' or 'update' mode of the product page

The relationship between a Paint and a Color is Many - to - Many.
Conceptually you can have a M <-> M relationship between two entities, but in a concrete implementation, you need a "resolver" table between the two entities. I would probably call this table "paintColor"
It would have something like this in it:
paintColor
----------
paintColorID
paintID (FK to paint)
colorID (FK to color)
As you should be able to discern, this allows you to associate any number of separate color values with a specific paint. I personally don't know if this is actually a feasible design, due to questions regarding whether a specific instance of a paint/color combination doesn't need to have other attributes such that your original design with ColorID as a foreign key in Paint isn't actually what you want.
For example, if a Paint can have different prices for "Gold" vs. "Black" then you have an issue with your current model, unless you attach pricing to paintColor rather than paint. Any data model with a degree of sophistication will have many of these tradeoffs incorporated, and it may not be obvious as to what the reasoning was behind the design.
In your simple model there are probably other foreign key relationships you would want in an actual paint store application, like manufacturer. PaintType should be in a table, and not as a varchar.
MySQL actually has a special datatype that essentially allows you to embed a many to many in one column. It's called an ENUM. If you want to use it for this app, it's not the worse decision you could make, so long as you keep in mind that an ENUM violates the basic rules that all relational databases were supposed to support. For your simple application, it allows you to make a "have your cake and eat it too" which works fine in a simple application. Again, if a paint can have a different price point for 2 different colors that are otherwise the same paint, then you have a problem.
For this reason, often Many to many relationships are more appropriate, and in the way you described your app and the interface you expect, the solution is a Many to many. It's important after you make that choice that you start to learn the implications of that choice for your application.
I usually advise people to think about what the choice entails: If it's a 1-1 relationship then Entity A can have one and only one property B.

If you fill the colors table with preset colors yourself, you can allow the user to select a color from your predefined entries and you store the colorID from the colors table in the the row as foreign key using color in the paint table when you store the user's entry and to retrieve do a left out join on the color table where the ID in the paint row with ID on the row in the color table. The statement would look like...
Junction table
CREATE TABLE juct_paint_color (
paint_key int not null,
color_key int not null,
FOREIGN KEY(paint_key) references paint(paintID),
FOREIGN KEY(color_key) references color(colorID)
)
New insert statement for junction table after you insert the new paint.
INSERT INTO juct_paint_color(paint_key, color_key) VALUES(?, ?)
NEW select statement, you can keep the select the paint however you were doing so before but the two get the color simply used the paintID column from the paint table like so.
SELECT t2.* FROM juct_paint_color t1
LEFT OUTER JOIN color t2
ON t2.colorID = t1.color_key WHERE t1.paint_key = ?;
you could use a transaction or stored procedure in you database to ensure that a new paint row is never inserted without a proper entry to the junction_table, but if you constrict the user input it probably won't be necessary.

Related

Approaches for user extensible entities with a relational database (mySQL)

We have a php/mysql system with about 5 core entities. We now need to add the ability for customers to create custom fields for some of these entities on a per project basis.
They would contain a label, key, type, default value, and possible allowed values.
This is so they could add a custom date field, or a custom dropdown to the UI and save this value against the specific entity.
What is the best approach for storing this kind of data in a mySQL database? I need to store both the config for the field, and then the current value for a specific entity.
I've had a look at various options here.. https://ayende.com/blog/3498/multi-tenancy-extensible-data-model
But this is not really at a tenancy level, more a project level.
I was thinking...
A CustomFields table to hold the configuration of a field against an entity type and project id.
A CustomFieldValues table to hold the value saved against the field - a row per field ( entity_id | field_id | field_value)
Then we create relationships between the entities and these custom values when retrieving the entities.
The issue with this is that there will be as many rows in the Values table as there are custom fields - so saving a entity will result in X extra rows. On top of that, these are versioned, so once a new version is created, there will be another X rows created for that new version.
Also, you can't index the fields on name, joins would become pretty complex i think as you have to join to the configuration and the values to build the key value pair to return against the entity, and how would you select based on a custom field name, when the filed name was actually a value?
I don't want to add dynamic columns to the table, as this will affect ALL the entites in the whole system - not just the ones in the current client / project.
The other option is to store the values in a JSON column.
This could be on the entity row itself customFields or similar. This would prevent the extra rows per field, but also has issues with lack of indexing etc, and still need to join to the config table. However, you could perform queries by the property name if the key=value was stored in the JSON... WHERE entity.customFields->"$.myCustomFieldName" > 1.
Storing the filed name in the json does mean you cannot change it once created, without a lot of pain.
If anyone has any advice on approaches for this, or articles to point me at that would be much appreciated - Im sure this has been solved many times before....
JSON records: No! A thousand times no! If you do that, just wait until somebody actually uses your system for a few tens of millions of records, then asks you to search on one of your extra fields. Your support people will curse your name.
Key-value store. Probably yes. There's a very widely deployed existence proof of this design: WordPress. It has a table called wp_postmeta, containing metadata fields applying to wp_posts (blog pages and posts). It's proven successful.
You will need to do some multiple joining to use this stuff. For example, to search on height and eye-color, you'd need
SELECT p.person_id, p.first, p.last, h.value height, e.value eye_color
FROM person p
LEFT JOIN attrib h ON p.person_id = h.person_id AND h.key='eye_color'
LEFT JOIN attrib e ON p.person_id = e.person_id AND e.key='height'
WHERE e.value='green' and CAST(h.value AS INT) < 160
As the CAST in that WHERE clause shows, you'll have some struggles with data type as well.
You'll need LEFT JOIN operations in this sort of attribute lookup; ordinary inner JOIN operations will suppress rows with missing attributes, and that might not work for you.
But, if you do a good job with indexes, you'll be able to get decent performance from this approach.
The table structure envisioned in my example doesn't have your table describing each additional field, but you know how to add that. It also doesn't have explicit support for multi-project / multitenant data separation. But you can add that as well.

Foreign key vs. SET data type MySQL

I have a MySQL database set up with a list of all my movies, which I imported from a MS Access database. One field contains the possible values for the genre of the movie, movies can have more than one genre, so I need a data type which supports this feature. In access I could link one table 'genre' to the field 'genre' in my table 'movies', so I could choose none, one ore multiple genres per movie. When I switched to MySQL I used the SET data type to define all the possible values. So far everything is running perfectly.
I am now trying to set up a table in html/php to show the mysql table. I want the table to be able to sort on: title, genre, quality, rating, etc. But for the sorting on genre, I would need the possible values from the set data type. I don't know if it is possible to get the values with some php command/code, but after I lurked around on the web for a while, I didn't see many applications where they use the SET data type for obvious negative reasons.
So I started looking into the Foreign Key possibility. The problem I have here is that -for as far as I know- the key can only contain one possible value, which puts me right back at the start of my problem. I do like the idea of a foreign key, because it would make it way easier for me to add a new genre to the list.
Is there a possibility I am overlooking? Is it possible to either get the values from the SET type to php or to use a foreign key with multiple possibilities for one record?
I know I can also put every genre in my php script manually, but I'd like to have it all on one place. So that if I add a movie with a genre I haven't defined yet, I can just update it at one place and everything else adapts to it.
Dagon is absolutely right here - you have an issue with the structure of the tables in your back end. You are wanting to model a many to many relationship when at the moment with your current back end the best you can do is a one to many relationship.
To review:
You have individual films that can have many genres
And you have individual genres that are related to many films
Relational databases actually don't model many to many relationships with one relationship they use recursion of the one to many relationship and create two joins.
To model a many to many relationship you need three tables
A film table (which I think you already have)
A genre table (which I think you already have)
A junction table which as Dagon suggests will consist of two fields film id and genre id.
You then set up two separate one to many relationships. One from the film table to the junction table and one from the genre table to the junction table.
Now if you want to know all the genres a film is in you simply filter the junction table on the relevant film id and if you want to know all the films with a certain genre you filter the junction table on the genre id.
Set up lookups to relate your genre ids to textual descriptions and bang you are free to change the textual description as much as you want and the great thing if you've done it right it will upgrade every single value in your forms.
This is an absolute fundamental concept of the algebra of sets behind the design of SQL and relational database design.

Backend app in OO PHP: Structuring classes/tables efficiently

I'm currently working on an app backend (business directory). Main "actor" is an "Entry", which will have:
- main category
- subcategory
- tags (instead of unlimited sub-levels of division)
I'm pretty new to OOP but I still want to use it here. The database is MySql and I'll be using PDO.
In an attempt to figure out what database table structure should I use in order to support the above classification of entries, I was thinking about a solution that Wordpress uses - establish relationship between an entry and cats/subcats/tags through several tables (terms, taxonomies, relationships). What keeps me from this solution at the moment is the fact that each relationship of any kind is represented by a row in the relationships table. Given 50,000 entries I would have, attaching to a particular entry: main cat, subcat and up to 15 tags might slow down the app (or I am wrong)?
I then learned a bit about Table Data Gateway which seemed an excellent solution because I liked the idea of having one table per a class but then I read there is virtually no way of successful combating the impedence missmatch between the OOP and relational-mapping.
Are there any other approaches that you may see fit for this situation? I think I will be going with:
tblentry
tblcategory
tblsubcategory
tbltag
structure. Relationships would be based on the parent IDs but I+'m wondering is that enough? Can I be using foreign key and cascade delete options here (that is something I am not too familiar with and it seems to me as a more intuitive way of having relationships between the elements in tables)?
having a table where you store the relationship between your table is a good idea, and through indexes and careful thinking you can achieve very fast results.
since each entry must represent a different kind of link between two entities (subcategory to main entry, tag to subcategory) you need at least (and at the very most) three fields:
id1 (or the unique id of the first entity)
linkid (linking to a fourth table where each link is described)
id2 (or the unique id of the second entity)
those three fields can and should be indexed.
now the fourth table to achieve this kind of many-to-many relationship will describe the nature of the link. since many different type of relationship will exist in the table, you can't keep what the type is (child of, tag of, parent of) in the same table.
that fourth table (reference) could look like this:
id nature table1 table2
1 parent of entry tags
2 tag of tags entry
the table 1 field tells you which table the first id refers to, likewise with table2
the id is the number between the two fields in your relationship table. only the id field should be indexed. the nature field is more for the human reader then for joining tables or organizing data

Advice on database design for portfolio website

So I'm a visual designer type guy who has learned a respectable amount of PHP and a little SQL.
I am putting together a personal multimedia portfolio site. I'm using CI and loving it. The problem is I don't know squat about DB design and I keep rewriting (and breaking) my tables. Here is what I need.
I have a table to store the projects:
I want to do fulltext searcheson titles and descriptions so I think this needs to be MyISAM
PROJECTS
id
name (admin-only human readable)
title (headline for visitors to read)
description
date (the date the project was finished)
posted (timestamp when the project was posted)
Then I need tags:
I think I've figured this out. from researching.
TAGS
tag_id
tag_name
PROJECT_TAGS
project_id (foreign key PROJECTS TABLE)
tag_id (foreign key TAGS TABLE)
Here is the problem I have FOUR media types; Photo Albums, Flash Apps, Print Pieces, and Website Designs. no project can be of two types because (with one exception) they all require different logic to be displayed in the view. I am not sure whether to put the media type in the project table and join directly to the types table or use an intermediate table to define the relationships like the tags. I also thinking about parent-types/sub-types i.e.; Blogs, Projects - Flash, Projects - Web. I would really appreciate some direction.
Also maybe some help on how to efficiently query for the projects with the given solution.
The first think to address is your database engine, MyISAM. The database engine is how MySQL stores the data. For more information regarding MyISAM you can view: http://dev.mysql.com/doc/refman/5.0/en/myisam-storage-engine.html. If you want to have referential integrity (which is recommended), you want your database engine to be InnoDB (http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html). InnoDB allows you to create foreign keys and enforce that foreign key relationship (I found out the hard way the MyISAM does not). MyISAM is the default engine for MySQL databases. If you are using phpMyAdmin (which is a highly recommended tool for MySQL and PHP development), you can easily change the engine type of the database (See: http://www.electrictoolbox.com/mysql-change-table-storage-engine/).
With that said, searches or queries can be done in both MyISAM and InnoDB database engines. You can also index the columns to make search queries (SELECT statements) faster, but the trade off will be that INSERT statements will take longer. If you database is not huge (i.e. millions of records), you shouldn't see a noticeable difference though.
In terms of your design, there are several things to address. The first thing to understand is an entity relationship diagram or an ERD. This is a diagram of your tables and their corresponding relationships.
There are several types of relationships that can exist: a one-to-one relationship, a one-to-many relationship, a many-to-many relationship, and a hierarchical or recursive relationship . A many-to-many relationship is the most complicated and cannot be produced directly within the database and must be resolved with an intermittent table (I will explain further with an example).
A one-to-one relationship is straightforward. An example of this is if you have an employee table with a list of all employees and a salary table with a list of all salaries. One employee can only have one salary and one salary can only belong to one employee.
With that being said, another element to add to the mix is cardinality. Cardinality refers to whether or not the relationship may exist or must exist. In the previous example of an employee, there has to be a relationship between the salary and the employee (or else the employee may not be paid). This the relationship is read as, an employee must have one and only one salary and a salary may or may not have one and only one employee (as a salary can exist without belonging to an employee).
The phrases "one and only one" refers to it being a one-to-one relationship. The phrases "must" and "may or may not" referring to a relationship requiring to exist or not being required. This translates into the design as my foreign key of salary id in the employee table cannot be null and in the salary table there is no foreign key referencing the employee.
EMPLOYEE
id PRIMARY KEY
name VARCHAR(100)
salary_id NOT NULL UNIQUE
SALARY
id PRIMARY KEY
amount INTEGER NOT NULL
The one-to-many relationship is defined as the potential of having more than one. For example, relating to your portfolio, a client may have one or more projects. Thus the foreign key field in the projects table client_id cannot be unique as it may be repeated.
The many-to-many relationship is defined where more than one can both ways. For example, as you have correctly shown, projects may have one or more tags and tags may assigned to one or more projects. Thus, you need the PROJECT_TAGS table to resolve that many-to-many.
In regards to addressing your question directly, you will want to create a separate media type table and if any potential exists whatsoever where a project is can be associated to multiple types, you would want to have an intermittent table and could add a field to the project_media_type table called primary_type which would allow you to distinguish the project type as primarily that media type although it could fall under other categories if you were to filter by category.
This brings me to recursive relationships. Because you have the potential to have a recursive relationship or media_types you will want to add a field called parent_id. You would add a foreign key index to parent_id referencing the id of the media_type table. It must allow nulls as all of your top level parent media_types will have a null value for parent_id. Thus to select all parent media_types you could use:
SELECT * FROM media_type WHERE parent_id IS NULL
Then, to get the children you loop through each of the parents and could use the following query:
SELECT * FROM media_type WHERE parent_id = {$media_type_row->id}
This would need to be in a recursive function so you loop until there are no more children. An example of this using PHP related to hierarchical categories can be viewed at recursive function category database.
I hope this helps and know it's a lot but essentially, I tried to highlight a whole semester of database design and modeling. If you need any more information, I can attach an example ERD as well.
Another posibble idea is to add columns to projects table that would satisfy all media types needs and then while editting data you will use only certain columns needed for given media type.
That would be more database efficient (less joins).
If your media types are not very different in columns you need I would choose that aproach.
If they differ a lot, I would choose #cosmicsafari recommendation.
Why don't you take whats common to all and put that in a table & have the specific stuff in tables themelves, that way you can search through all the titles & descriptions in one.
Basic Table
- ID int
- Name varchar()
- Title varchar()
etc
Blogs
-ID int (just an auto_increment key)
-basicID int (this matches the id of the item in the basic table)
etc
Have one for each media type. That way you can do a search on all the descriptions & titles at the one time and load the appropriate data when the person clicked through the link from a search page. (I assume thats the sort of functionality you mean when you say you want to be able to let people search.)

PHP: Categorizing images into database

I'm building a website in PHP to share my comics. I'd like to implement categorization to allow people to filter which comics they'd like to see.
I've asked this question before, but at that time my site's architecture was not using a database.I've since implemented a database (which is amazing, btw) so I need to change things up.
I'm thinking the way to do this is:
1) Make 2 tables: 1 for categories, 1 for images
2) Insert images into their respective tables based on which filesystem folder they're in and assign that table id
3) Insert all images into All_Images table with their newly assigned category id
4) Take in user input to decide which images to show. So, if user input = cat 1, then show images with category 1 id.
So, basically I need a way to initially assign categories to the images when they come in from the filesystem. Is there an easier way to do this? Do I have to create multiple tables?
Any thoughts on this?
Thanks!!
The normal way would be to have an images table (presumably with filenames rather than the actual images?) and then a one-to-many relationship to categories so that each comic can have more than one:
Table:Image
-----------
rowid: integer identity
displayname: varchar
filename: varchar
Table:Category
--------------
rowid: integer identity
displayname: varchar
Table:ImageCategoryLink
-----------------------
imageid: integer foreign key references Image:rowid
categoryid: integer foreign key references Category:rowid
Clear?
One table category with id and name etc, one table for image with id src name etc.
Two choices after that :
If an image has one and only one category, put a field id_category in table image
If an image can has several categories make another table image_category with id_image and id_category
Since you are a beginner (no offence) and I don't think there will be millions or cartoons in the table. Use a SET field (or ENUM if your cartoon can only have one category) for you categories.
There are people who will vote this down since there are some negative side effects of using this (mainly when you want to change the categories). But with a relative small table this will not have any effect.
This will be the easiest solution. If you expect the site to grow big, use a second table for your categories and join the tables.

Categories