Alternatives to store array with CakePHP+MySQL - mysql

I'm developing a website where users will be able to save in their profile a list (array) of items (which they can find also in the same website and have their own id).
This is the way I've used so far in similar situations:
- Create a 'text' (string) field in the users table in the DB and save the items separated by commas
- To read that field, use the explode method to get the array in CakePHP and then work with it
- To save the array to the DB, use the implode method to convert it to a string and be able to store it in the field
What I dont' like about this method is that it can get really complicated to deal with those lists (add items, remove items...) and you can't really access those items directly, there's always some pre-processing or post-processing to make.
For example, to make it easier to look at a specifc item in one user's list and find other users with the same item in their lists.
Is there any better way to deal with arrays in CakePHP+MySQL? I've read about serialize()/unserialize(), but I don't think that would make a big difference compared to the other method...
Thank you very much in advance for any ideas!

Definately use a table so as with Kristian Antonsen's suggestion, create an items table then create a hasManu relationship in you User model like this:
class User extends AppModel{
$hasMany = array(
'Item'
);
}
Or A HABTM relationship:
class User extends AppModel{
$hasAndBelongsToMany = array(
'Item'
);
}
Use the form helper to create inputs for items.
Hope this helps.

A database table is just a huge spreadsheet. But instead of storing one piece of information in each cell, you clump them all together in one cell. You would never do that in spreadsheet.
You should create an item table like this:
+----+--------+--------+
| ID | UserID | ItemID |
+----+--------+--------+
| 1 | 2 | 3 |
| 2 | 2 | 1 |
| 3 | 3 | 3 |
+----+--------+--------+
And possibly another table:
+----+-------------+
| ID | ItemName |
+----+-------------+
| 1 | Mathematics |
| 2 | Jogging |
| 3 | Movies |
+----+-------------+
That'll let you know that User 2 likes Movies and Mathematics, and User 3 likes Movies.

Related

Best way to handle duplicated rows

I have insurance companies "dictionary" in my database, let's say:
+----+-------------------+----------+
| ID | Name | Data |
+----+-------------------+----------+
| 1 | InsuranceCompany1 | SomeData |
+----+-------------------+----------+
But I'm fetching data from another system, and in result I got duplicates of insurance companies, but without my data:
+----+-------------------+----------+
| ID | Name | Data |
+----+-------------------+----------+
| 1 | InsuranceCompany1 | SomeData |
+----+-------------------+----------+
| 2 | InsuranceCompany1 | |
+----+-------------------+----------+
Both records are related in variety of models but they refer to the same data, and what I want is to pair these records without changing queries or data in other tables, so noone knows there are two records, but both refer to one instance which is
+----+-------------------+----------+
| 1 | InsuranceCompany1 | SomeData |
+----+-------------------+----------+
My question is: Is there some proper way to handle situations like this?
I've came up with solution which is to add parent_id column, and manually set parent_id in duplicated rows, and then override Eloquent methods like find in a model to return parent if there is parent_id set.
Copying SomeData column is not an option because there can be condition if insurance_company_id == id;
You can try creating a view of your dict table something like this:
CREATE VIEW unique_dict AS
SELECT MIN(ID) ID,
Name,
GROUP_CONCAT(Data) Data
FROM dict
GROUP BY Name
That will give you one row per name.
Then, in your queries requiring one row per name, SELECT from the unique_dict view rather than the dict table.
GROUP_CONCAT() yields a list of values from Data, which helps if more than one duplicated row contains a value: you get them all.
Longer term you might be smart to consider these duplicates to be "dirty data", and clean them up as you INSERT new rows. How to do that?
Create a unique index on Name.
CREATE UNIQUE INDEX unique_name ON dict(Name);
Then, when loading new data into dict use Eloquent's updateOrCreate() function. Here's something to read about that. Laravel 5.1 Create or Update on Duplicate

DB structure - form with dynamic number of options

I've been reading similar questions, but I think my case is a bit more complicated.
I have a form that register items. These items may have options with sub-options (checkboxes and radio buttons):
The number of checkboxes and radio buttons may decrease/increase but the real pain to design a good structure is for the checkboxes, as these must have (at least I think so) a fixed name column for each one.
The case for radio buttons is easier as I just assign an id to each one (and save the names in a different table).
My current DB structure is simple (between parenthesis is the table/column name):
The items table (item) have columns of type integer (to save the id of the radio buttons).
Another table for the checkboxes (item_option), with columns of type integer (1 if checked, 0 if unchecked). And 1 PK column (item_id) that points to the PK column (id) of the items table.
And tables (again item_option) for the names of the radio buttons with a PK column (id) that points to the option column (is this understandable? Sorry for my bad english).
I think a different table containing the sub-options is better than put all the columns in the main table, right?
So, the radio buttons are stored in the main table (1 column per option) and the checkboxes in a separeted table (1 table per option):
Items table:
+-----+----------+----------+
| id | Option_1 | Option_2 |
+-----+----------+----------+
| 123 | 3 | 1 |
+-----+----------+----------+
| 456 | 2 | 3 |
+-----+----------+----------+
| 789 | 1 | 2 |
+-----+----------+----------+
item_option_3 table (this would be needed to know which ones are checked):
+--------------+--------------+--------------+---------+
| Sub_Option_1 | Sub_Option_2 | Sub_Option_3 | item_id |
+--------------+--------------+--------------+---------+
| 1 | 0 | 1 | 123 |
+--------------+--------------+--------------+---------+
| 1 | 1 | 0 | 456 |
+--------------+--------------+--------------+---------+
| 0 | 1 | 1 | 789 |
+--------------+--------------+--------------+---------+
item_option_1-2 table (this would be used to print the names):
+-----------+--------------+--------------+
| option_id | name | name_es |
+-----------+--------------+--------------+
| 1 | Sub_Option_1 | Sub_Opción_1 |
+-----------+--------------+--------------+
| 2 | Sub_Option_2 | Sub_Opción_2 |
+-----------+--------------+--------------+
| 3 | Sub_Option_3 | Sub_Opción_3 |
+-----------+--------------+--------------+
What kind of structure do I need to spawn these sub options (checkboxes) dynamically?
What about something like this?
Your model has option keys as columns and values as rows. Why have both keys and values be rows? If you don't need complex type-based validation, it should suffice to have a single options table with a one to optionally many relationship to itself to account for suboptions. To enumerate all options and values, just retrieve all rows from the table. If ParentOptionId is null, then it is a base-level option; otherwise it is a suboption.
UML & ER version below.
EDIT: After reading through your question and comments again, I've come up with a more complicated but more robust design for you to consider:
It works like this:
Every user input is an Option. Every option consists of a display text (OptionText), tooltip/subtext/etc (Description), a default and then user supplied value (Value), a value type (ValueType boolean,text, date, etc). It also has a DisplayOrder so you know where to situate it in relation to other Options in its group. Options can also have a parent/child relationship with other Options. You can do the same for the other entities if you want but I did not model that.
Every Option is contained within an OptionGroup with 0 or more sibling Options. OptionGroups are just a collection of one or more related Options. The GroupType field dictates how your form builder needs to treat that group. The most obvious example would be for your radio button groups; each of those would be an OptionGroup and each radio button would be a boolean Option within the OptionGroup. An OptionGroup could just as easily handle a multiple selection checkbox group or just some related text inputs that need a common header text (like a street address).
For further dynamic design OptionGroups are contained within GroupSections, even if there is just one default GroupSection in a form.
Finally, a Form models your final actual UI form and consists of one or more GroupSections.
This should be flexible enough for you to tweak to your liking. What do you think?
Final note: if you are looking into dynamically building your forms in Javascript, check out a few frameworks like X-editable or formly. They take JSON or configuration objects and build out the entire form with validation/etc from there while giving you some hooks for event handling. Chances are you don't need to completely reinvent the wheel unless you want to keep your implementation as simple and specific as possible.

Related Key/Value table indexing / search

I have table 'elements' that is related with table 'element_parameters'. element_parameters table have key/value structure. Keys and values are dynamicaly, they can be created be user or by some software, so I can't predefine them in 'elements' table.
For example I have elements 'flower' and 'car':
elements
--------
flower
car
element_parameters
------------------
element | key | value
flower | smell | soft
car | manufacturer | fiat
flower | color | red
car | wheels | 4
As you can see, there could be any keys and any values.
But often I need to find elements by their parameters. This is the problem. One element could have 10000+ parameters. So if I have 100 elements, I have to search through 1000000 parameters each time i want to find some element. And i can't do this in one query, be cause first I have to find all parameters thats fits my condition, group them by parameter ID and then search elements table for those ID's.
Is there some search engines, where I could merge and index elements with their parameters to 'one row'? Elasticsearch would be good decision for this situation? Maybe there is other ways to solve it?
Thank you.
I think you should modify your db like this:
elements
-----------
id | name
1 | flower
2 | car
element_parameters
-------------------------------------------------
element_id | parameters
1 | {"color":"red","smell":"soft"}
2 | {"manufacturer": "fiat", "wheels":3}
You can find easily any element's parameter:
$id = 1;
$element = Element::find($id)->load('ElementParameters')->get();
$paremeters = json_decode($element->ElementParameters);

Search part of a string in a record with multiple strings

I have the following record in artists table:
| id | names |
| 1 | eminem rihanna rita ora|
| 2 | taylor swift beyonce |
I want to search for example using inem and I want the id of this record to be found which is id = 1 in this case. I'm using Full Text Search in MySQL.
Is there a better technique to achieve this?
Update:
| id | video_name | title | artists | search_tags |
| 1 | onajr | o'najr | kida | onajr o'najr kida |
I want to search using this strings for ex. onajr, ona, kida, kid.
So in e few words a user can search using al the search tags including part of a tag.
This is my function in php. :
public function tags_search() {
//echo 'ok';
$db = DB::get_instance();
//single word
$query = "
select * from `videos`
where
match(`search_tags`) against (':video_name' IN BOOLEAN MODE) order by rand()
limit 1
";
try {
$run_query = $db->prepare($query);
$run_query->execute(array(
':string' => $this->string
));
return ($run_query->rowCount() == 1) ? $run_query->fetch(PDO::FETCH_OBJ)->video_name : false;
} catch(PDOException $e) {
echo $e->getMessage();
}
}
You should be able to just use LIKE
SELECT id FROM artists WHERE names LIKE '%inem%';
The %'s are wild cards saying that anything can come before or after them.
Also, it's generally not a good idea to store multiple values in a single field. I'd recommend making id and names into a composite PK, and then only have one name per field.
eg)
| id | name |
| 1 | eminem |
| 1 | rihanna |
| 1 | rita ora |
| 2 | taylor swift |
| 2 | beyonce |
If you plan on adding more fields, they should be in another table. Learn about database normalization for the reason why.
Edit after update:
I still think a LIKE statement would suit your purposes, the only thing you would have to do different is add a AND search_tags LIKE '%ona%' etc for each comma delineated tag word.
Just as an example from what you have above:
SELECT id FROM artist
WHERE search_tags LIKE '%onajr%'
AND search_tags LIKE '%ona%'
AND search_tags LIKE '%kida%'
AND search_tags LIKE '%kid%';
I don't know enough about PHP to actually give you some code, but after a short search, explode() appears to be the function you want to look at. Then just append the extra SQL for each search tag. This method would also work if you were to split your tags into another table. If I'm not mistaken, you should just have to group by the songid if you go that route.
Not sure if all this is better than what you have. As I said, I don't know much PHP, and I haven't messed around with match(...) against(...), but I thought I'd throw in my own 2¢.

How do I resolve or avoid need for MySQL with multiple AUTO INCREMENT columns?

I have put a lot of effort into my database design, but I think I am
now realizing I made a major mistake.
Background: (Skip to 'Problem' if you don't need background.)
The DB supports a custom CMS layer for a website template. Users of the
template are limited to turning pages on and off, but not creating
their own 'new' pages. Further, many elements are non editable.
Therefore, if a page has a piece of text I want them to be able to edit,
I would have 'manually' assigned a static ID to it:
<h2><%= CMS.getDataItemByID(123456) %></h2>
Note: The scripting language is not relevant to this question, but the design forces
each table to have unique column names. Hence the convention of 'TableNameSingular_id'
for the primary key etc.
The scripting language would do a lookup on these tables to find the string.
mysql> SELECT * FROM CMSData WHERE CMSData_data_id = 123456;
+------------+-----------------+-----------------------------+
| CMSData_id | CMSData_data_id | CMSData_CMSDataType_type_id |
+------------+-----------------+-----------------------------+
| 1 | 123456 | 1 |
+------------+-----------------+-----------------------------+
mysql> SELECT * FROM CMSDataTypes WHERE CMSDataType_type_id = 1;
+----------------+---------------------+-----------------------+------------------------+
| CMSDataType_id | CMSDataType_type_id | CMSDataType_type_name | CMSDataType_table_name |
+----------------+---------------------+-----------------------+------------------------+
| 1 | 1 | String | CMSStrings |
+----------------+---------------------+-----------------------+------------------------+
mysql> SELECT * FROM CMSStrings WHERE CMSString_CMSData_data_id=123456;
+--------------+---------------------------+----------------------------------+
| CMSString_id | CMSString_CMSData_data_id | CMSString_string |
+--------------+--------------------------------------------------------------+
| 1 | 123456 | The answer to the universe is 42.|
+--------------+---------------------------+----------------------------------+
The rendered text would then be:
<h2>The answer to the universe is 42.</h2>
This works great for 'static' elements, such as the example above. I used the exact same
method for other data types such as file specifications, EMail Addresses, Dates, etc.
However, it fails for when I want to allow the User to dynamically generate content.
For example, there is an 'Events' page and they will be dynamically created by the
User by clicking 'Add Event' or 'Delete Event'.
An Event table will use keys to reference other tables with the following data items:
Data Item: Table:
--------------------------------------------------
Date CMSDates
Title CMSStrings (As show above)
Description CMSTexts (MySQL TEXT data type.)
--------------------------------------------------
Problem:
That means, each time an Event is created, I need to create the
following rows in the CMSData table;
+------------+-----------------+-----------------------------+
| CMSData_id | CMSData_data_id | CMSData_CMSDataType_type_id |
+------------+-----------------+-----------------------------+
| x | y | 6 | (Event)
| x+1 | y+1 | 5 | (Date)
| x+2 | y+2 | 1 | (Title)
| x+3 | y+3 | 3 | (Description)
+------------+-----------------+-----------------------------+
But, there is the problem. In MySQL, you can have only 1 AUTO INCREMENT field.
If I query for the highest value of CMSData_data_id and just add 1 to it, there
is a chance there is a race condition, and someone else grabs it first.
How is this issue typically resolved - or avoided in the first place?
Thanks,
Eric
The id should be meaningless, except to be unique. Your design should work no matter if the block of 4 ids is contiguous or not.
Redesign your implementation to add the parts separately, not as a block of 4. Doing so should simplify things overall, and improve your scalability.
What about locking the table before writing into it? This way, when you are inserting a row in the CMSData table, you can get the last id.
Other suggestion would be to not have an incremented id, but a unique generated one, like a guid or so.
Lock Tables