« [Tip] Manipulating the z-axis | Main | [News] SQL Anywhere 10 for Intel Macs released »
January 04, 2007
[Best Practices] Relationships
by David Workman
Data Mosaic
Warning: this is a brain dump, altogether too long to absorb easily, not well edited and definitely lacking in examples. However, I am treating this as a work in progress and feel that it is better to get this out in rough form and let the Servoy community post comments to fill in the gaps than to let it sit indefinitely on my to do list to polish off. There are definitely other developers who can explain some of the sections better than me anyway. So feel free to post with your relationship tips and questions and we'll see what the final result is down the line. Here goes....
Index
(1) What a relationship is
(2) Defining a relationship
(3) Relationships across different server names
(4) Relationships with global parameters
(5) Self relationships
(6) Relationships to filter lists
(7) Relationships for value lists
(8) Relationships with lots of parameters
(9) Using relationships in calculations
(10) Using relationships in methods
(11) "Piping" data with relationships
(12) Many-to-many relationships
(13) Performance and showing many related forms at the same time
(14) Date fields as parameters
(15) Going to related record
(1) What a relationship is
A relationship (or relation) is a way of connecting data in one table with data in another table. A relation is defined by one or more matching rules varying from a basic one-to-many relation to complex relations with multiple matches. A match is defined by a field in table A "matching" a field in table B via an operator. The most common operator is an equals sign.
Once defined, you can use a relationship to display related data via a related field, a portal, or a related tab panel. The first is useful for seeing data from a "parent" record, and the others are useful for looking at groups of related "child" records.
A relationship is also a named object which you can access programmatically in the method editor. In this fashion it represents a foundset and can be manipulated in the same way foundsets are. Most functions that use the foundset as a parameter can use the name of a relationship instead of "foundset".
(2) Defining a relationship
A relationship needs at least one column from a table on the right and left sides to be matched via an operator. Valid operators or =, >, >=, <, <=, !=, and LIKE. You can match to the same table or to different tables. You must however match to a stored column on the right side. Which means you can only use global columns on the left side of the relationship. If you are connecting to a calculation on the right side that calculation needs to be stored (create a column with the same name as the calculation).
Most database flavors allow for the ability to create relationships at the database level. If this is the case, Servoy can create relationships in your solution based on your back-end foreign key constraints. If what I just said is greek to you, ignore the "create" button at the top of the relations dialog box! This is a case where you need to have a bit of back-end database familiarity before you venture into this territory.
Note that if there are back-end constraints in the tables you are using for a Servoy solution, these constraints are not bypassed by Servoy even if they are not represented in your solution as relationships. So if one of the constraints has a delete rule this will trigger and delete related records when you delete a parent record in Servoy and it won't be something you can detect from within your solution. Know what you are doing if you have back-end constraints!
(3) Relationships across different server names
You can define relationships that go from a table in one Server connection to a table in another Server connection.
GOTCHA: Servoy does not automatically data broadcast in this case.
What this means is that if you create, modify or delete a record via this relationship, a form that has records showing from the table and server connection on the right side of the relationship will not be updated when changes are made from the left side of the relationship.
You tend to fall into this trap when you have multiple server connections pointing to the same database. As a general rule, do not have multiple server connection names for the same database! There are no advantages, only pitfalls when you do that.
If you must use this kind of relationship, generally only use it for viewing sets of data. Showing a list of transactions from an accounting system database on a client record in a Servoy controlled database is a good example.
(4) Relationships with global parameters
This type of relationship has one or more global fields on the left side of the relationship. These are extremely useful relationships for viewing sets of data based on user choice as you can change the values on the left side of the relationship without changing what record you are on.
Again, this type of relationship is best used for only viewing records. Why is this? Well it's time for the....
GOTCHA: calculations and summaries using integer and number type fields are not reliably updated when records are created or deleted via global relationships.
Just trust me on this one. If you want all your calcs and summaries to show correct values, do not create or delete records through global relationships.
(5) Self relationships
A self relationship is when you match two columns from the same table. Matching a global variable to a column is considered a global relationship even though you can have the same table selected for both sides.
Generally this relationship is used as a view filter like global relationships or to set up value lists. If you are using a lot of self-relationships, it can also be a sign that your data structure is poorly normalized.
There is one unique case where a primary to primary key relationship is sometimes needed. Use it to enforce viewing of the same record in a tab panel. Most times this is not needed as forms based on the same database connection and table share the same foundset. But there are subtle ways to break this connection (or have been in past versions of Servoy) -- none of which are pleasant to track down -- hence this brute force method of getting what you expect at all times.
(6) Relationships to filter lists
As mentioned in an earlier section, it is a common technique to use global relationships to set up dynamically filtered lists. As the value in the global variable changes, the list of records displayed changes based on the relationship match.
This is a correct and fine technique as far as it goes but keep in mind that it is limited. Let's say you have the following value list in your global combobox:
Red
Orange
Green
To show all records in the list the global needs to be blank. Which means you need a button next to the global combobox that clears the global -- you can't add the value "blank" or "show all" to the value list which would be more user friendly.
Better yet, what if you wanted to show the record count next to each value in the value list?
Show All (345)
------------
Red (255)
Orange (79)
Green (11)
Servoy allows you to fire methods when particular events happen. You can also assign values to value lists in a method. So to do the above example, when the form is shown calculate the counts for each category, create an array with each item you want in your value list, and assign the array to the value list.
Then when you change the value of the global field that has the value list, detect what value the user selected and find and display the set of records that corresponds to the selection.
For this example, the displayed list is relationless and you as the programmer manage what records are displayed based on events. If you are coming from a non-event driven development environment, this may be a new concept for you! But as you can see, it is a much more powerful and flexible technique than using a relationship to control filtered lists.
(7) Relationships for value lists
Value lists can be defined many ways in Servoy. The values can be hard coded, pulled from table columns, pulled from related table columns, pulled from related-related table columns, and even set programatically at runtime. This is a lot of options and leads all new developers through an extended phase of experimentation to find the best way to handle value lists. It usually doesn't take long to figure out that hard coding value lists leads to a maintenance nightmare.
A common technique for managing all of your value lists in a solution is to have a value list table where you store all of your value list items. At its most basic, you need a column for the value list item and a column for the value list name. Each value list in the table needs a global field set to auto-fill with the name of the value list. Then create a relationship from the global to your table and create a value list based on this relationship.
You can expand this concept many ways. You can have another column and relationship to define related value lists (if value x1 is in field a, show values 1, 2 & 3 in field y...if value x2 is in field a, show values 4, 5 and 6 in field y...etc). Add a user interface so your users can modify the value lists themselves. Finally, bundle this whole functionality into a module and use in all of your solutions!
(8) Relationships with lots of parameters
I just counted...Servoy allows you to have up to 11 parameter pairs in a relationship. Give me a solution that has relationships with this many parameters and I'll show you three better ways to code that solution. In other words, just because you can do something in Servoy doesn't mean you should!
Let's take a moment to ponder the mechanics of what is going on when Servoy displays a form. Any data fields on that form need to be filled with data. Servoy creates a SQL query for you based on what table the form is based on, what fields you have placed on the form and what relationships these fields are based on. If you are like me you like Servoy because you don't want to have to do all that work yourself. Probably for the exact same reason as me--you aren't a SQL guru wizard!
Ignoring all else SQL related, you should know one thing -- certain SQL queries take longer to figure out at the database end of things than other queries. Generally the longer it is, the more time it takes to figure out and send data back to the client. What makes a SQL query longer? Lots of parameters in the relationship. My rule of thumb is that any relationships with more than three parameter pairs needs to be looked at seriously and justified.
What is your alternative to lengthy relationships? Write the query for the data you want to see, put it in a method and fire it at the specific event you want to return that data. This way, you control exactly when potentially long queries happen. You can even give user feedback to tell them the solution is chugging away at something this way.
(9) Using relationships in calculations
GOTCHA: In calculations you can't assume that there is data on the other side of a relationship. If there isn't, calculations will throw an error which you can see when you dbl-click the status area in Servoy Developer (bottom left border). So always check if a relationship has records in it before doing something:
//check if records in relationship
if (utils.hasRecords(fncl_client_to_office)) {
//relationship has records, now do something
return fncl_client_to_office.name_office
}
UPDATE 2007-01-09: Calculation errors of this type can cause errors when you export a solution.
(10) Using relationships in methods
1- Just like in calculations, be aware that a relationship may not have any records. You don't get an error if there isn't, but a null value is returned if you refer to a related field when there are no records there.
2- The "foundset" object is a special Servoy data structure that represents the current found set of a form. A relationship name is also a foundset object. For example, many functions take the word "foundset" as a parameter. In most of these functions, you can replace the current form's "foundset" with the name of a relationship to use the found set represented through the relationship.
3- You can "chain" relationships together to refer to data multiple tables away. For example, let's assume three tables (clients, orders, order_items), two one-to-many relationships to connect them structurally (clients_to_orders & orders_to_order_items) and two non-structural relationships to view data from the bottom back to the top (order_items_to_orders & orders_to_clients).
To refer to the "order_date" field from the table "orders" in a method at the "order_items" table level you would put the name of the relationship followed by the name of the field (order_items_to_orders.order_date). This is indeed what you get when you dbl-click on the the order_date field under the order_items_to_orders relationship node.
To refer to the "client_name" field in the table "clients" in a method at the "order_items" table level you can do this by specifying both relationships in order: "order_items_to_orders.orders_to_clients.client_name". This relationship object hierarchy is not obvious by looking at the method editor nodes. Notice that you don't need to specify the form names for each relationship either -- you as the programmer just need to know what relationships can chain together to get from one table to another table multiple relationships away.
(11) "Piping" data with relationships
Showing data more than one relationship away on a form is a common challenge. The standard way of doing this is to create a calculation that returns a value from a table one relationship away and then refer to this field from the table two relationships away.
I am not a big fan of this method because it can be a lot of work and just clutters up the calculation list. Considering that it isn't very good practice to make data editable this far away from "home base" the main reason for grabbing data from far away is to display in a non-editable fashion on your form.
What I do instead is create a label object, turn on the displayTags property and then refer to a field using the relationship chain technique referred to in the previous section. To use the same example, place %%order_items_to_orders.orders_to_clients.client_name%%" in the text property of the label and you see the client_name field from the clients table on a form based on the order_items table.
(12) Many-to-many relationships
Refer to Jan Aleman's article here
(13) Performance and showing many related forms at the same time
This is another situation where you need to resist doing something just because you can. With the tab panel object it is easy to go nuts putting many related forms on one form. The more you show, the more SQL queries Servoy has to generate when the parent form is shown. (Note that only the visible tab of a tab panel generates a SQL query so you can safely have many tabs in one tab panel.)
You can also generate a sizable performance hit if you show a related form within a related form within a related form (etc) using nested tab panels. My rule of thumb is to limit tab panel nesting to three deep if they are related tab panels. If they are relationless tab panels, I consider four or five levels deep to be acceptable.
(14) Date fields as parameters
Date fields are perfectly fine as parameters in relationships. Just keep in mind that many date types in SQL databases have a time component stored even if your date field mask doesn't show the time component. The time component can make matching dates problematic as two seemingly equivalent date values will likely not be equivalent once you take the time component into account. If you want to just match on dates, use a date type that doesn't have a time component or create calculation fields that strip out the time component and match on the calculations.
(15) Going to related record
A common task is to go to a detail view of a related record that you are showing as a list. When you click on the record in the related list(onRecordSelection event) run a method that:
- captures the primary key
- loads the record in a detail view form -- forms.form_name.loadRecords(primary key)
- shows the detail form -- forms.form_name.Show()
(You can combine steps 2 & 3 by using showRecords() function.)
There is a side effect to this technique and that is that the foundset of the target form is not preserved. If you want to preserve the foundset of the target form, you need to instead set the record index of the target form to the record index of the record you want to go to. If the source form and the target form share the same found set, you can simply show the target form and you will be on the record you clicked in the source form.
If both source and destination forms do not share the same found sets, the record index for the record will most likely not be the same in both forms. In this case you will need to capture the primary key of the source form and loop through the target form's foundset until you find the record and then set the target form's index to the loop count. Some sample code here:
//arguments[0] is the source form primary key
var userID = arguments[0]
//loop through target form's foundset until you find a primary key match
for ( var i = 0 ; i < foundset.getSize() ; i++ ) {
var record = foundset.getRecord(i + 1)
if (record.id_employee == userID) {
foundset.setSelectedIndex(i + 1)
return
}
}
| Posted by David Workman on January 4, 2007 at 03:45 AM in Best Practices | Permalink
Comments
Hi David
First of all, thanks for the very good idea to write about relationships and their implemented form (possibilities) in Servoy. Following are my comments (or opinion) to your article. Please understand that although I do not agree with you on some aspects (as you will see below .-) I appreciate your effort very much and hope my reply can add in some aspects. Please use what ever you wish to. I am also happy to explain in more detail if you wish. I also would like to say that below is only noted where I do not agree, but I agree on the rest :-)
As an intro, you may say that except in chapter (1), it is an observation of relationships in the context of the Servoy Developer tool.
(1) I would like yo to differentiate between the relational model, presented by Edgar Frank Codd in 1969/70, basis for all relational databases, it's a mathematical model (Codd was a mathematician) and the Entity-Relationship-Model, presented by Peter Cheng in 1974, which was in some way an enhancement to the relational model. Mixing the two seems always a source of misunderstanding (at least to me .-)
An extremly good book (with 4 DVDs covering the coference speeches) comes from a meeting of Software Pioneers, organised by the german company sd&m.
http://www.amazon.de/Software-Pioneers-Contributions-Engineering-Engineering/dp/3540430814/sr=8-4/qid=1168120812/ref=sr_1_4/303-0410037-3430654?ie=UTF8&s=books
Now on to my comments:
(1) 2nd paragraph. May be a hint would be useful about the «mixing» possibilities from let's say the display of a field with a different relationship than the tabless tab panels relationship. What's possible, what's not ( I am not very sure about the possibilities and their effect
(2) 1st paragraph: ... You can match to the same table or to different tables. ... May understanding is it's either a recursive relationship (to the same entity) or to another entity (and only one) so in your writing it should say ... or to a different table (not tables).
(2) 2nd paragraph. This functionality, in my opinion, is useless, because it's wrong implemented. If you build up an ERM with relationships having a semantic statement (a necessity, I assume you know the example of two entities Car and Person and how many relationships may exist between the two, i. e. if you have a single, unnamed or mechanical named as any tool does, including Servoy (and therefore to be given a useful name (semantic) in the context of the design, as the tool still can't know and help in the process of the design as it has not artifical intelligence) in both directions of the relationship, you have no clue what it (should) mean, i. e. what the designer wanted to model (and that is what a design is for: what the designer thought is relevant of the real world to be modelled). So, obviously, mechanically created relationship names don't add anything to a model and leave it up to the the person who looks at it to guess what the original developer meant with it. Happy if you still can ask him :-)
Ok, what's wrong with the implementation:
1) Servoy Developer creates the relationship from the SQL Foreign Key Constraint command, e. g. ALTER TABLE task_waypoints ADD CONSTRAINT task_waypoints_part_of_FK FOREIGN KEY (task_id) REFERENCES tasks (id); Nothing wrong with that, it's the only way to find out about the relationship and it's name. This relationship is the one from the m to the 1 side (therefore it's a Foreign Key Constraint). Up to now everything is ok.
2) Servoy uses this Foreign Key Constraint to create the relationship from the 1 to the m side (can be seen in the Servoy Relations window). And that's a problem now, as the associated relationship name is the wrong one (obviously, as the constraint's direction is from m to the 1 side). For the functioning of the relationhip itself it's not a problem, as a relationship is bidirectional, but if you really press the Create button in the Relation window, every relationship is just named the wrong way and therefore you get a real mess. And, worse, you can not even use this functionality and edit the relation name, as it is locked (padlock symbol). Here it would not be of much help anyway, but as soon as this would be corrected, it would be very nice to be editable.
In my opinion Servoy has to correct that and ideally also generate the oposite relation with a generic (mechanical) name schema to make this functionality useable. And then the designer would only have to edit the relationship name to finish (or do nothing, i. e. leave it generic, if he can live with that).
And, Servoy also restricts a relationship name to be unique for the whole ERM, which is (also) wrong, this restriction should only apply between two entities (tables). Check it in your ERM design tool, Mac users get the very good EOModeler (part of WebObjects) or Data Model within Xcode for free, including SQL code generation and much more!
(2) 3rd paragraph. This is written unclear to me. What happens is that rules applied to the DB are independent of Servoy, as Servoy is for the application development and not for database design, i. e. it's only connected by a JDBC adapter to the otherwise created DB.
(3) BTW, what do you exactly mean by left and right side of the relationship? Is the left side always the 1 side and the right side always the m side? My thanks to Servoy to rename the column Primary key to From key and Foreign key to To key in the Define Relation window!
(4) Agree with all. In my opinion, records should only be created on the basis of data model (ERM) relationships. Never on calc attributes or other «non stable» relationships.
(5) In ERM literature usually called recursive relationship. I only heard the term self relationship in the context of FileMaker, but don't know where it comes from exactly. BTW, this typ of relationship has always to be optional on both ends, otherwise you could not "stop" on either the root and the leave.
(5) 3rd paragraph: In my opinion, a primary to primary key relationship is per definitionem not possible! A relationship is always from a primary to foreign key. It may be that the primary key attribute (as a single attribute or part of a concatenated primary key) is also the foreign key attribute (but then only unidirectional, how would you make such a relationship bidirectional?). But a relationship is per definitionem in a ERM bidirectional. So what I would like to say I don't know exactly what you are saying in this paragraph. May be someone else can explain that in better english :-)
(6) I really don't think using relationships to filter a list is a useful concept (except if there are no other possibilities). Again, I only know it from FileMaker, but where else would anybody use such an idea?
(7) 2nd and 3rd paragraph: In my developing environment, this is not a common technique, in fact it sounds quite strange to me. In Servoy value lists can be built dynamically with a SQL query and application.setValueListItems( ... )
(8) What do you mean by this? Do you refer to a concatenated primary key (a primary key built up of more than one attributes). And yes, I have an application, i. e. an EOModel (Enterprise Object Model) with concatenated keys of ten attributes. If you show me how to do better, I am glad to know :-) Just to give you an example see appendix EOModel part (don't know how to add it here)
(8) 1st paragraph: Obviously, I don't agree, it's very necessary, see above. I am a bit afraid (if I have once more than 11) a concatenated primary key can only consist of 11 attributes and also don't know why this number? Although I agree 11 is surely not very common.
(8) ERM design: May be you would like to look at this book: http://www.amazon.com/Case-Method-Entity-Relationship-Modelling/dp/0201416964/ref=pd_sim_b_2/002-9751320-3847204 from Oracles Richard Barker, still regarded by many devlopers as one of the best books published to ERM design (ERM in Oracle notation)
(8) 3rd paragraph: Any SQL developer knows that, therefore the books about tuning .-), may be useful for people coming from other environments. I don't agree with your rule of thumb. I don't think there is a limit based on any rational criteria. If you know one, I would like to know about it.
(10) I thought that this works unfortunatly only with calculated attributes, but is very nice if it really works in methods as well.
(11) 3rd paragraph: Thanks for this tip. Looks to me much better than what is described in 1st paragraph (as you say should not be used). Why does an ordinary relationship not allow to be piped like your labeled object example. I should think if it works with a labelled object, it should be possible with an ordinary relaitonship, shouldn't it?
(14) Date or DateTime attributes depend on the selected data type for the given attribute for the selected database, i. e. Oracle, SQL Anywhere, ... Tip if you want to use the very same ERM but want to support (generate SQL code for) any database: You can solve the general problem of different data types for different databases extremly elegant with EOPrototypes in EOModeler. Change external data type (data type for the DB) in EOPrototypes - done.
(12) It would be very nice if Servoy supported flattened relationships, i. e. if there exists only a join entity (table to resolve a m:n relationship, i. e. having only primary key attributes).
Best regards, Robert
Posted by: Robert Huber | Jan 6, 2007 5:02:12 PM
Robert initially sent his comments directly to me -- probably because he disagrees with a number of things I mentioned. But his feedback was exactly what I was hoping to get so keep the ideas coming -- if you disagree even better. Believe me, no feelings will be hurt on my end.
Relationships are a fundamental subject and not much has been published about them in the context of Servoy. The more understanding and knowledge we can come up with here the better. Eventually I would love to rewrite it incorporating feedback to bring it up to the level of a Servoy standards document. Anyone who takes an active part in this discussion I will list as a contributor.
Thanks for taking the time to write such an indepth and insightful critique Robert. Lot's of ideas for me to mull over and I will be posting replies to some of the specific things you bring up in the coming days and weeks.
Posted by: David Workman | Jan 9, 2007 1:13:18 AM
15. at the end, why not do something like,
foundset.selectRecord(pk);
instead of the loop, or am I missing something?
Posted by: Swingman | Jan 17, 2007 7:21:59 PM
foundset.selectRecord(pk) function only works if the pk value you pass is already part of the currently loaded records of the found set. It won't go fetch the next 200 records and so on until it finds the pk. The loop in section #15 is the only way I've found to do it that will work in all situations. Clumsy and not something you want to do with larger found sets.
What I don't get about the foundset.selectRecord(pk) is what happens when you pass it more than one pk?
Posted by: David Workman | Jan 17, 2007 10:30:02 PM