I am currently developing a Student Information System that is going to be used by educational group to provide students & teachers a portal.
I am using Laravel4 which has a good authentication driver built in but can use only one table for authentication. I am unable to figure out how to authenticate them because I have users in multiple tables. Example :
SchoolOne_students
SchoolOne_teachers
SchoolTwo_students
SchoolTwo_teachers
and so on....
A significantly better way, rather than multiple users tables, would be to link each user to their school and status (teacher or student). You certainly can twist Laravel into doing what you want, but in order to prevent conflicts between the four different user types, you'll probably end up having to rewrite a large part of the entire authentication package.
What I would do is, in your users table, have an ENUM column, with the options student and teacher. Then, have an integer column, school_id (with a separate table for school data, if needed). This will allow much more flexibility, and when your design requirements change (yes, they will change), you'll be able to take them in stride.
Using this method, you should not have to modify any of Laravel's own code.
if all the tables have identical schemas, you could create a view that does a UNION of all the tables and then use that view as your Laravel auth table. I am not saying this is an ideal solution, but if you have restrictions on altering the existing tables and must use them as is, this could work.
The view would not be update-able (I don't believe so, at least) so adding or updating users would require specific add/update code, but for Auth, this may do the trick.
CREATE VIEW users_combined_view
AS
SELECT id, username, password
FROM schoolOne_students
UNION
SELECT id, username, password
FROM schoolOne_teachers
UNION
...
note - this method would be somewhat useless (ok, totally useless) if username was not unique across the tables
A good database design is scalable.
This is particularly a bad idea:
SchoolOne_students
SchoolOne_teachers
SchoolTwo_students
SchoolTwo_teachers and so on...
There should be only one table for all the users, in your case for students.
There should be another table for Schools where all the school information will be stored.
Then you should join the students table with schools table by adding a school_id field to the schools table where the school_id will be stored for the corresponding students row.
===Students Table===
+-----------------------------------------
+ id | name |school_id | ... | ... |
+----+--------+----------+-----------+----
+ 1 |Student1| 5 | ... | ... |
+----+--------+----------+-----------+----
+ 2 |Student1| 3 | ... | ... |
===Schools Table===
+-----------------------------------------
+ id | name | ... | ... | ... |
+----+--------+----------+-----------+----
+ 3 |School 3| ... | ... | ... |
+----+--------+----------+-----------+----
+ 5 |School 5| ... | ... | ... |
+----+--------+----------+-----------+----
This way you can add as many schools as you may want and students for them
I hope this will help
Related
I'm currently developing an application that allows a customer to register for an event through a custom form. That custom form will be built by the event admin for specific input by the customer.
The customer will go to the form, complete the input and pick a venue that will then display the available time-slots. I'm stuck with these two database designs and wondering which one is a better approach.
Pivot table with 3 foreign keys
Table 'Customers' -
| id | name |
Table 'Events' -
| id | name | form_fields (json)
Table 'Venues' -
| id | address | event_id |
Table 'Timeslots' -
| id | datetime | slots | venue_id |
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
Two pivot tables
Table 'Customers' -
| id | name |
Table 'Events' -
| id | name | form_fields (json)
Table 'Venues' -
| id | address | event_id |
Table 'Timeslots' -
| id | datetime | slots | venue_id |
Pivot Table 'Tickets' -
| id | customer_id | timeslot_id |
Pivot Table 'EventCustomers' -
| id | customer id | event_id | form_data (json)
In addition, I will store the HTML markup of the custom form built by admin in 'form_fields' (json) and have the customer complete the form and store the values in 'form_data' (json).
Is it also sensible to have the custom form and data saved in json?
Thank you.
To answer your question(even if it's a bit off topic):
None of the above.
To model data we must ask ourselves what are the constraints. Data is often easier to define by what it cannot do, not what it can do.
For example, can you have a Tickets record that:
Does not have a customer record ( customer_id = null )
Does not have a timeslot ( timeslot_id = null) -timeslot is related to venue or the location and time of the event.
Does not have an event ( event_id = null )
If you answered no to all of these then we have to bring this data all together at one time (but, not necessarily in the same table).
Now in my mind, it's pretty clear you could/should not have a ticket that:
wasn't assigned to a customer
does not have an event
does not have a timeslot
does not have a venue
whose number exceeds the number of slots for the event (this you mostly missed on)
So I will assume these are our "basic" constraints
Problems with your second case:
you could sell a ticket to a customer for a particular timeslot ( at a venue ), but for an unknown event. Record in Tickets, and No record in the EventCustomers table
you could also have a customer registered to an event, with no ticket or timeslot/venue. Record in EventCustomers and No record in the Tickets table
To me that seems somewhat illogical, and indeed it violates the constraints I outlined above.
Problems with your first case:
On the surface the first case looks fine as far as our constraints above look. But as I worked though it some issues popped up. To understand these, as a general rule, we always want a unique index on all the foreign keys in a pivot table ( aka a unique compound key ).
So in the first case we want this(idealy):
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
//for this table you would want this compound, unique index
Unique Key ticket (customer_id,timeslot_id,event_id)
This lead me to the number of "slots" as this would imply that a customer could only have one tickets record per event and timeslot/venue. This relates back to the part I said that you mostly missed on, i.e. you have no way to track how many you have used. At first you might want to allow duplicates in this table. "We can just add some more tickets in right?" - you think, and this is the easy fix, not.
Exhibit A:
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
| 1 | 1 | 1 | 1 | {}
| 2 | 1 | 1 | 1 | {}
While contemplating Exhibit A consider some basic DB design rules:
In a good DB design you always want ( ideally )
a surrogate primary key, a key with no relation to the data, this is id
a natural key, a unique key that is part of the data. An example would be if you had an email field attracted to customer, you could make this unique to prevent adding duplicate customers. It's a piece of the data that is by it's nature unique and part of the data.
The first one (surrogate keys) allow you use the data with no knowledge of the data itself. This is good as it gives us some separation of concerns, some abstractions between our code and the data. When you join two tables on their primary key, foreign key relationship you don't need to know anything else about the data.
The second (natural key) is essential to prevent duplicate data. In the case of a pivot table the foreign keys, which are surrogate keys in their respective tables, become a natural key in the pivot table. These are now part of the data in the context of the pivot table and they uniquely and naturally identify that data.
Why is uniqueness so important?
Once you allow duplicates with the pivot tables you will run into several issues (especially if you have accessory data like the form_data):
How to tell those records apart?
Which of the duplicates is the authoritative copy, which is in charge.
How do you synchronize that accessory data, if you need to change form_data, which record do you change it in. Only one? Which one? Both? how do you maintain synchronizing all the duplicates.
What if an accidental duplicate gets entered, how will you know it was accidental? How do you know it's a real duplicate or true duplicate and not a valid record.
Even if you knew it was an accidental duplicat, how do you decide which one of the duplicates should be removed, this goes back to which is the authoritative record.
In short order, it really becomes a mess to deal with.
Finally (what I would suggest)
Table 'customer' -
| id | name |
Table 'event' -
| id | name | form_fields (json)
Table 'venue' -
| id | address | slots |
Table 'show' -
| id | datetime | venue_id | event_id |
Table 'purchase' -
| id | show_id | customer_id | slots | created |
Table 'ticket' ( customers_shows )
| id | purchase_id | guid |
I changed quite a few things (you can use some or all of these changes):
I changed the plural names to singular. I only use plurals when I do pivot tables that have a no accessory data, such a name would be venues_events. This is because a record from customer is a single entity, I don't need to do any joins to get useful data. A record from our hypothetical venues_events would encompass 2 entities, so I would know right away I need to do a join no matter what as there is no other data besides the foreign keys.
Now in the case of show, you may notice that is essentially a pivot table. So why did I not name it venues_events as I listed above. The reason is we have a datetime column in there, which is what I mean by "accessory" data. So in this case I could pull data just from show if I just wanted the datetime and I would not need a join to do it. So it can be considered a single entity that has some Many to One relationships. ( A Many to Many is a Many to One and a One to Many that's why we need pivot tables ) More on this table later.
Letter Casing and spacing. I would suggest using all lowercase and no spaces. MySql is case sensitive and doesn't play nice with spaces. It's just easier from a standpoint of not having to remember did we name it venuesEvents or VenuesEvents or Venuesevents etc... Consistency in naming convention is paramount in good DB design.
The above is largely Opinion based, it's my answer so it's my opinion. Deal with it.
Table show
I moved the slotscolumn to venue. I am assuming that the venue will determine how many slots are available, in my mind this is a physical requirement or attribute of the venue itself. For example a Movie theater has only X number of seats, no matter what time the movie is at doesn't change how many seats are there. If those assumptions are correct then it saves us a lot of work trying to remember how many seats a venue has every time we enter a show.
The reason I changed timeslot to show is that in both your original cases, there is some disharmony in the data model. Some things that just don't tie together as well as they should. For example your timeslots have no direct relation to the event.
Exhibit B (using your structure):
Table 'event' -
| id | name | form_fields (json) |
| 1 | "Event A" | "{}" |
| 2 | "Event B" | "{}" |
Table 'Venues' -
| id | address | event_id |
| 1 | "123 ABC SE" | 1 |
| 2 | "123 AB SE" | 2 | //address entered wrong as AB instead ABC
Table 'Timeslots' -
| id | datetime | slots | venue_id |
| 1 | "2018-01-27 04:41:23" | 200 | 1 |
| 2 | "2018-01-27 04:41:23" | 200 | 2 |
In the above exhibit, we can see right away we have to duplicate the address to create more then one event at a given venue. So if the address was entered wrong, it could be correct in some venues and incorrect in others. This can be a real issue as programmatically how do you know that AB was supposed to be ABC when the venue ID and event ID are both different for this record. Basically how do you tell those records apart at run time? You will find that it is very difficult to do. The main problem is you have to much data in Veneues, your trying to do to much with it and the relationship doesn't fit the constraints of the data.
That's not even the worst of it as a further problem creeps in, because now that the venue_id is different we can corrupt our Timeslots table and have 2 records in there at the same time for the same venue. Then, because the slots are tied to this table, we can also corrupt things down stream such as selling more tickets then we should for that time and place. Everything just starts to fracture.
Even counting the numbers of shows at a given venue becomes a real challenge, this "flaw" is in both data models you presented.
The same Data in my Model
#with Unique compound Key datetime_venue_id( show.datetime, show.venue_id)
Table 'event' -
| id | name | form_fields (json) |
| 1 | "Event A" | "{}" |
#| 2 | "Event B" | "{}" |
Table 'venue' -
| id | address | slots |
| 1 | "123 ABC SE" | 200 |
Table 'show' -
| id | datetime | venue_id | event_id |
| 1 | "2018-01-27 04:41:23" | 1 | 1 |
#| 2 | "2018-01-27 04:41:23" | 1 | 2 |
As you can see, you no longer have the duplicate address. And while it looks like you could enter in 2 shows for the same venue at the same time, this is only because we don't have a compound unique key that includes the datetime and venue_id a.k.a. Unique Key datetime_venue_id( datetime, venue_id). If you tried inserting that data with that constraint MySql would blowup on you. And if you included both inserts ( event and show ) in the same "Transaction" (which is how I would do it, in innodb engine) the whole thing would fail and get rolled back and neither the event or show would get inserted.
Now you could try to argue that you could have the same Unique constraint on Exhibit B, but as the Venue ID is different there, you would be wrong.
Anyway, show is our new main pivot table with foreign keys from event and venue and then the accessory data datetime.
Besides what I went over above, this setup gives us several advantages over the old structure, in this one table we now have access to:
what and where is the event (by joining on Table event )
when is the event ( timestamp )
how many slots available for the event (by joining on Table venue)
This centers everything around the show record. We can build a "show" independent of a customer or tickets. Because really a customer is not part of the show, and including them to soon (or to late depending how you look at it) in the data model muddies everything up.
Exhibit C
#in your first case
Pivot Table 'Tickets' -
|id | customer_id | timeslot_id | event_id | form_data (json)
#in your second case
Pivot Table 'Tickets' -
| id | customer_id | timeslot_id |
Pivot Table 'EventCustomers' -
| id | customer id | event_id | form_data (json)
AS I said above, you cant put what I am calling a show the what,where and when together without having a customer ID (in either of your data models). As you build your application around this later it will become a huge issue. This may be insurmountable at run time. Basically, you need all that data assembled and waiting on the customer_id. In both of your models that's not the case, and there is data you may not have easy access to. For example for the first case (of the old structure) how would you know that timeslot_id=20 AND event_id=32 plus a customer equals a valid ticket? There is no direct relationship between timeslot and event outside of the pivot table that contains the customer. timeslot_id=20 could be valid for any event and you have no way to know that.
It's so much easier to grab say show=32 and check how many slots are left and then just do the purchase record. Everything is ready and waiting for it.
Table purchase
I also added purchase or an order table, even if the "shows" are free this table provides us with some great utility. This is also a pivot table, but it has some accessory data just like show does. ( slots and created ).
This table
we bind the customer table to the show table here
we have a 'created' field so you will know when this record was created, when the tickets where purchased
we also have a number of slots the customer will use, we can do an aggregate sum of slots grouped on the show_id to see how many slots we have "sold". With one join from show to venue we can find out how many total slots this "show" has with the same integer key (show.id) that we used above to aggregate. Then it would be a simple matter to compare the two, if you wanted to get fancy you may be able to do this all in one query.
Table ticket
Now you may or may not even need this table. It has a many to one relationship to table purchase. So One order can have Many tickets. The records in here would be generated when a purchase is made, the number dependent on what is in slots. The primary use of this table is just to provide a unique record for each individual ticket. For this I have guid column which can just be a unique hash. Basically this would give you some tracking ability on individual tickets, I don't really have enough information to know how this will work in your case. You may even be able to replace this table with JSON data if searching on it is not a concern, and that would make maintenance of it easier in the case that some tickets are refunded. But as I hinted this is very dependent on your particular use case.
Some brief SQL examples
Joining Everything (just to show the relationships):
SELECT
{some fields}
FROM
ticket AS t
JOIN
puchase AS p ON t.purchase_id = p.id
JOIN
customer AS c ON p.customer_id = c.id
JOIN
show AS s ON p.show_id = s.id
JOIN
venue AS v ON s.venue_id = s.id
JOIN
event AS e ON s.event_id = e.id
Counting the used slots for a show:
SELECT
SUM(slots) AS used_slots
FROM
puchase
WHERE
show_id = :show_id
GROUP BY show_id
Get the available slots for a show:
SELECT
v.name,
v.slots
FROM
venue AS v
JOIN
show AS s ON s.venue_id = v.id
WHERE
v.show_id = :show_id
# or you could do s.id = :show_id
It also works out nice that all the tables start with a different letter, which makes aliasing a bit easier.
-note- The table name event may be a reserved word in MySql, I am not sure off the top of my head if it will work as a table name. Some reserved words still work in some parts of the query based on the context it's used in. Even if that is true, I am sure you can come up with a work around for it. Coincidentally this is why I named purchase that instead of order as "order" is a reserved word. (I just happen to think of event)
I hope that helps and makes sense. I probably spent way more time on this then I should have, but I design things like this for a living and I really enjoy the data architecture part of it, so I can get a bit carried away at times.
I have recently installed Doctrine2 on my web server and all works great. I have setup all my entities but now I'm at the point where I want to create a lookup table and I'm a bit confused how to do that. I want a table which looks like this:
tbl_Role tbl_User
+--------+----------+ +--------+----------+--------------+----------+
| id | Name | | id | Name | Password | Role |
+--------+----------+ +--------+----------+--------------+----------+
| 1 | Admin |
| 2 | User |
| 3 | Free |
+--------+----------+
Between the Role and the User table consists a OneToMany relation (one Role has many Users) and this table does not change in future. I do not want to use Enums because of the update or reorder problem with them (if nevertheless something changed)
My question now is how I can represent this lookup table as a Doctrine entity? Normally if I want to create a new User I have to get the fitting Role from the DB and set it as a reference in the User entity. This seems a bit expensive to me. It would be great if I could have only the Role IDs in static PHP fields so I can set them as reference for the User. Is this possible?
Another problem is how I get the lookup values into the database. Can I implement a method into the Role entity itself which is executed after the Role table is created? This way it would be possible to mark the constructor as private and all values are present after a migration.
First of all, it won't be too expensive, because Doctrine2 has a few cache layers and these queries will be efficiently cached since the Role table won't change.
Anyway, if Role table won't change, I'm not sure if it should be kept in database. You could create a "static" factory service for it to create simple value objects as a part of your domain layer.
I decided to work on a notification system with inspiration from another StackOverflow post that suggested I break up the notification from the body of the notification. As such, I have the following schema:
notifications
id | user_id | notification_object_id | is_read
notification_objects
id | message | type
I have it set up this way so I can create a notification object and send this out to multiple users in the case the notification object applies to multiple people. The type is there to condense messages into one notification by type, sort of the way Facebook says, "John Doe, John Smith, and 27 other users commented..." rather than giving you 29 separate notifications.
(The naming schema of the table and column are for CakePHP 3's 'contains')
What I need help on is a way to tie in the different users or objects that would be playing a role in the requests. A user might have commented, so I'll probably need the user_id of the user who messaged and need to store that somewhere.
There are other cases besides users; one might want to know the id of the comment such that he can click the notification and go straight to comment/81. I have many use cases where I want to store the different types of ids that are foreign keys to other tables (users, comments, articles, etc.), some of which might need two foreign keys (say you want to mention "Chris has commented on your article 'How to beg StackOverflow for help'") and have the user_id for Chris and the article id for the article both linked in the notification.
Each different type of notification could have different values and tables it needs to pull information from. What's a good place to start on creating this schema?
I'm going to try and answer this question from an OO perspective. When speaking in classes, not in tables, you'd probably start with something like an AbstractNotificationObject class with the basic properties (date, time, maybe a message) and several specialization classes like NewCommentNotificationObject with additional properties (comment, commenting user) or a NewFriendNotificationObject. Going down to the database schema, you could then apply one of the typical ORM patterns.
Concrete table inheritance (just for completeness)
Using concrete table inhericance would probably not be a good idea, because it would boil down to a separate table for each notification type (i.e. a comment_notification_objects table and maybe many more). This would not scale well, and you would not be able to reference them from the notification table using a foreign key. Also, you would not be able to select a set of notifications of different types with a single query (without something like a UNION).
Single table inheritance
Single table inheritance would use only a single table with a type discriminator column. This single table (let's call it notification_objects) would need to contain a type column that describes of what type of notification the record actually is (you actually already have that in your schema). You'll also need columns for all properties that special notification types may have (for instance, a user_id and a comment_id column).
+--------------+
| notification |
+--------------+
| id |
+--| object_id |
| +--------------+
|
| +-----------------------+
| | notification_objects |
| +-----------------------+
+->| id PK |
| date |
| type |
| user_id FK |--...
| comment_id FK |--...
| friend_id FK |--...
| [more fields to come] |
+-----------------------+
Pros:
Load all notifications with a single query
Keep referential integrity with foreign keys
Cons:
Scales badly (you'll need to add new columns for each notification type)
Table will be sparsely populated (many columns will be NULL)
I would recommend this schema when you have a set of notification types of moderate size that does not regularly change.
Class table inheritance
Class table inheritance is somewhere in between; here you would create a central notification_objects table and separate tables for each notification type (for example an additional comment_notification_object table with an id column (which in turn is a foreign key to the notification_object table and a user_id and column_id:
+--------------+
| notification |
+--------------+
| id |
+--| object_id |
| +--------------+
|
| +----------------------+ +------------------------------+
| | notification_objects | | comment_notification_objects |
| +----------------------+ +------------------------------+
+->| id PK |<--+--| id PK/FK |
| date | | | comment_id FK |--...
| type | | | user_id FK |--...
+----------------------+ | +------------------------------+
|
| +--------------------------------+
| | newfriend_notification_objects |
| +--------------------------------+
+--| id PK/FK |
| friend_id FK |--...
+--------------------------------+
[more TABLES to come...]
Class table inheritance would also allow you to query all notifications with a single select query. Depending on how much of the data you need, you'll need to add JOINs to the respective specialized tables.
Pros:
Scales well/Add new types without modifying existing structure
Keep referential integrity with foreign keys
Cons:
When you need more than the minimal set of fields, you'll need to JOIN the specialized tables (possible performance impact)
I would recommend this schema when you have a lot of different notification types or extend your object model often and need to change/add new notification types on a regular basis.
Outside the relational box
Your question asked explicitly about relational schema, so I've focused on that. When thinking outside this box, alternate solutions might include:
Schemaless NoSQL databases
Messaging systems that follow a publish/subscribe architecture
I have a dynamic form that creates some inputs that will generate values and should be saved into the database. Each set of values should be saved separetely in a single field in the database called "Education":
Should be stored like this:
+--------+---------+----------+--------+--------+--------+--------+-----+
| id | name | Education |
+--------+---------+----------+--------+--------+--------+--------+-----+
| 100 | John | [Harvard, Marketing,2009,2014] [MIT,CS,2005,2009] |
+--------+---------+----------+--------+--------+--------+--------+-----+
| 101 | Daniel | [TEC, Marketing,2009,2014] [Standford,CS,2001,2005]|
+--------+---------+----------+--------+--------+--------+--------+-----+
The Education field can have up to 10 sets of values, I'm just showing 2.
Please look at the JSFIDDLE to see how it actually works: http://jsfiddle.net/YueX2/6/
How can I store into the database when a single set of values is edited and saving it into the database only updating the given set of values?
Also, is this the best way to do it?
There is a practice in designing databases called normalization which would lead you to the best way to go about it. Based on your Question and jsFiddle you would end up with 2 separate database tables.
Ex: tbl_Users: which would contain fields such as
userID
userName
Then you would have another table Ex: tbl_Education which would contain a few fields such as
record_id
userID
schoolName
It is in this table where you would set the particular users id in the userID field which would have to match the userID field from tbl_Users and then a single school they attended in the schoolName field. If they attended multiple schools, they would have multiple entries in the tbl_Education table but only a single entry in the tbl_Users table. If you need to retrieve the data you would perform a SQL query on the two table and join on the User_id field. This would result in multiple records being returned, but with all of the data needed.
Any information which is specific to the particular part of their education would go in the tbl_Education table and anything specific to the user (hair color, eye color, height, etc) would go in the tbl_Users table.
Ex SQL Query:
SELECT tbl_Users.userID, tbl_Users.userName, tbl_Education.schoolName
FROM tbl_Users, tbl_Education
WHERE tbl_Users.userID = tbl_Education.userID;
The WHERE clause is essentially the join between the two tables. There are many ways to write this query, I used the method which seems visually the easiest to see what is going on.
Here is the wikipedia link for normalization to get you started.
http://en.wikipedia.org/wiki/Database_normalization
This is not the best way to solve this problem. You better make 2 tables.
Table users:
+--------+---------+
| id | name |
+--------+---------+
Table education:
+--------+----------+------------+-------+-----+------+
| id | location | discipline | start | end | user |
+--------+----------+------------+-------+-----+------+
As education.user you save a foreign key users.id from the user table. the implementation of the input form is on the one hand side more complex but you have no limitations of entrys per user nor too much overhead in your database.
I have one login form and three groups of users who can login: client, member, admin. All users share some common attributes, like username, password, active.
Most of the other fields are different for the respective group, with the table member having up to 30 fields.
This is the reason I would rather not have one large user table with all the needed fields, but separate the group related fields to different tables.
I would then have following tables:
------------------
| tbl_user |
|----------------|
| id |
| username |
| password |
| active |
------------------
------------------
| tbl_client |
|----------------|
| id |
| pid |
| company |
| address |
| projects |
| ... |
------------------
... same with tbl_member and tbl_admin.
But after the login, how can I select the additional fields from either tbl_client, tbl_member and tbl_admin?
I could use a group field in tbl_user and use the group table name as the value. But that doesn't seem very professional ;)
Any other ideas?
I don't know why you say "it doesn't seem professional". You're separating the user data role from the user authentication information as you should. There's nothing wrong with what you suggested and I would recommend it...
I would suggest adding another column in tbl_user that states what type of account it is. e.g. admin, member, client, that way you can direct them to whatever you need after they have logged in, and you can pull their information.
You could do an outter join on all three tables.
This will return results of all three tables, two of which will have null results (as they don't exist). This is less efficient as you are joining four tables (user, member, client, admin), then adding an additional field to specificy which group the member is a part of, and querying only that table with some logic.
What you suggested is a perfectly acceptable solution - tbl_user needs to identify the user type in some way, somehow. You could swap out the string for an id, if that may be easier to work with. After you determine the user type, you can then simply join with whichever detail table is applicable, or query the detail table directly.