{
DataClasses1DataContext dc = new DataClasses1DataContext();
var q =
from a in dc.GetTable<Order>()
where a.CustomerID == "VINET"
select a;
dataGridView1.DataSource = q;
}
Database interview questions and answers asked in various interviews, faqs,tutorial and articles..
var q =
from a in dc.GetTable<Order>()
where a.CustomerID == "VINET"
select a;
dataGridView1.DataSource = q;
}
var q =
from a in dc.GetTable<Order>()
select a;
dataGridView1.DataSource = q;
}
In an ideal world, databases would be self-monitoring. The database itself would kill a hanging process automatically. The creation of a new database would cause the automatic creation of backup jobs. The database would do what's needed to keep query performance above a certain accepted level. Unfortunately, none of the database providers are even close to providing this functionality. That's why DBAs are still needed.
There are two kinds of monitoring scenarios:
Reactive monitoring deals more with the resolution of existing issues or those that crop up.
Proactive monitoring is the process of looking at a current server state and making estimates and plans for changes to the underlying objects. The objective is to prevent issues, increase scalability, and maintain availability.
One of the real challenges facing administrators is how to manage more and more servers. Operations such as manufacturing and customer support are done around the globe, with local copies of data being synchronized during smaller and smaller windows. Administrators need to be able to remotely install, monitor, troubleshoot, and maintain remote servers. The SQL Server tools team has delivered three categories of interfaces to manage remote environments:
SQLCMD is for folks who are comfortable with command prompt applications. For customers migrating or currently using the command prompt languages, OSQL and ISQL are deprecated and have been replaced with SQLCMD. SQLCMD is a command-line executable. You invoke it at the command prompt by typing SQLCMD.
SQL Server Management Objects (SMO) is for those who build user interface–oriented applications. DBAs who want to build custom management tools will find that SMO has replaced DMO. SMO is a new API architecture that overcomes the limitations of DMO. SMO is scalable, reliable, and flexible. SMO is significantly more robust than DMO, because it is used by SQL Server Management Studio to connect and work with SQL Server instances. Every function found in SQL Server Management Studio is made possible by SMO.
Windows Management Instrumentation (WMI) allows the use of Windows scripting languages, such as VBScript, and it's more complicated than SMO or SQLCMD. WMI is powerful and provides deep hooks into the operating system, which is beyond the reach of SQLCMD and SMO. In an extremely complex infrastructure, the WMI provider may prove to be the most complete solution.
Remote functionality is also enhanced with new capabilities for scripting and working with Replication and Analysis Services (via respective .NET libraries), Replication Management Objects, and Analysis Management Objects.
The basic Web services platform is XML plus HTTP.
This article references Microsoft SQL Server databases in some examples, but can be used generically with other RDBMSs like Oracle, Sybase etc. too. So, here's preferred naming convention for:
Tables: |
Tables represent the instances of an entity. For example, you store all your customer information in a table. Here, 'customer' is an entity and all the rows in the customers table represent the instances of the entity 'customer'. So, why not name your table using the entity it represents, 'Customer'. Since the table is storing 'multiple instances' of customers, make your table name a plural word.
So, name your customer table as 'Customers'.
Name your order storage table as 'Orders'.
Name your error messages table as 'ErrorMessages'.
This is a more natural way of naming tables, when compared to approaches which name tables as tblCustomers, tbl_Orders. Further, when you look at your queries it's very obvious that a particular name refers to a table, as table names are always preceded by FROM clause of the SELECT statement.
If your database deals with different logical functions and you want to group your tables according to the logical group they belong to, it won't hurt prefixing your table name with a two or three character prefix that can identify the group.
For example, your database has tables which store information about Sales and Human resource departments, you could name all your tables related to Sales department as shown below:
SL_NewLeads
SL_Territories
SL_TerritoriesManagers
You could name all your tables related to Human resources department as shown below:
HR_Candidates
HR_PremierInstitutes
HR_InterviewSchedules
This kind of naming convention makes sure, all the related tables are grouped together when you list all your tables in alphabetical order. However, if your database deals with only one logical group of tables, you need not use this naming convention.
Note that, sometimes you end up vertically partitioning tables into two or more tables, though these partitions effectively represent the same entity. In this case, append a word that best identifies the partition, to the entity name.
Views: |
A view is nothing but a table, for any application that is accessing it. So, the same naming convention defined above for tables, applies to views as well, but not always. Here are some exceptions:
1) Views not always represent a single entity. A view can be a combination of two tables based on a join condition, thus, effectively representing two entities. In this case, consider combining the names of both the base tables. Here's an example:
If there is a view combining two tables 'Customers' and 'Addresses', name the view as 'CustomersAddresses'. Same naming convention can be used with junction tables that are used to link two many-to-many related base tables. Most popular example is the 'TitleAuthor' table from 'Pubs' database of SQL Server.
2) Views can summarize data from existing base tables in the form of reports. You can see this type of views in the 'Northwind' database that ships with SQL Server 7.0 and above. Here's the convention that database follows. (I prefer this):
'Product Sales for 1997'
'Summary of Sales by Quarter'
'Summary of Sales by Year'
However, try to stay away from spaces within object names.
Stored procedures: |
Stored procedures always do some work for you, they are action oriented. So, let their name describe the work they do. So, use a verb to describe the work.
This is how I would name a stored procedure that fetches me the customer details given the customer identification number:
'GetCustomerDetails'. Similarly, you could name a procedure that inserts a new customer information as 'InsertCustomerInfo'. Here are some more names based on the same convention: 'WriteAuditRecord', 'ArchiveTransactions', 'AuthorizeUser' etc.
As explained above in the case of tables, you could use a prefix, to group stored procedures also, depending upon the logical group they belong to. For example, all stored procedures that deal with 'Order processing' could be prefixed with ORD_ as shown below:
ORD_InsertOrder
ORD_InsertOrderDetails
ORD_ValidateOrder
If you are using Microsoft SQL Server, never prefix your stored procedures with 'sp_', unless you are storing the procedure in the master database. If you call a stored procedure prefixed with sp_, SQL Server always looks for this procedure in the master database. Only after checking in the master database (if not found) it searches the current database.
I do not agree with the approach of prefixing stored procedures with prefixes like 'sproc_' just to make it obvious that the object is a stored procedure. Any database developer/DBA can identify stored procedures as the procedures are always preceded by EXEC or EXECUTE keyword.
User defined functions: |
In Microsoft SQL Server 2000, User Defined Functions (UDFs) are almost similar to stored procedures, except for the fact that UDFs can be used in SELECT statements. Otherwise, both stored procedures and UDFs are similar. So, the naming conventions discussed above for stored procedures, apply to UDFs as well. You could even use a prefix to logically group your UDFs. For example, you could name all your string manipulation UDFs as shown below:
str_MakeProperCase
str_ParseString
Triggers: |
Though triggers are a special kind of stored procedures, it won't make sense to follow the same naming convention as we do for stored procedures.
While naming triggers we have to extend the stored procedure naming convention in two ways:
So, here's how I would name the insert, update and delete trigger on titles table:
titles_instrg
titles_updtrg
titles_deltrg
Microsoft SQL Server 7.0 started allowing more than one trigger per action per table. So, you could have 2 insert triggers, 3 update triggers and 4 delete triggers, if you want to! In SQL Server 7.0 you can't control the order of firing of these triggers, however you have some control over the order of firing in SQL Server 2000. Coming back to the point, if you have 2 insert triggers on titles table, use the following naming convention to distinguish the triggers:
titles_ValidateData_instrg
titles_MakeAuditEntries_instrg
Same naming convention could be used with update and delete triggers.
If you have a single trigger for more than one action (same trigger for insert and update or update and delete or any such combination), use the words 'ins', 'upd', 'del' together in the name of the trigger. Here's an example. If you have a single trigger for both insert and update on titles table, name the trigger as titles_InsUpdtrg
Indexes: |
Just like triggers, indexes also can't exist on their own and they are dependent on the underlying base tables. So, again it makes sense to include the 'name of the table' and 'column on which it's built' in the index name. Further, indexes can be of two types, clustered and nonclustered. These two types of indexes could be either unique or non-unique. So, the naming convention should take care of the index types too.
My index naming convention is:
Table name + Column name(s) + Unique/Non-uniqueness + Clustered/Non-clustered
For example, I would name the unique, clustered index on the TitleID column of Titles table as shown below:
Titles_TitleID_U_Cidx
I would name the unique, nonclustered index on the PubID column of Publishers table as shown below:
Publishers_PubID_U_Nidx
Here's how I would name a non-unique, non-clustered index on the OrdeID column of OrderDetails table:
OrderDetails_OrderID_NU_Nidx
Indexes can be composite too, meaning, an index can be built on more than one column. In this case, just concatenate the column names together, just the way we did with junction tables and views above. So, here's how I would name a composite, unique, clustered index on OrderID and OrderDetailID columns of OrderDetails table:
OrderDetails_OrderIDOrderDetailID_U_Cidx
Sure, these index names look long and ugly, but who is complaining? You'll never need to reference these index names in code, unless you are creating/dropping/rebuilding the indexes. So, it's not a pain, but it's a very useful naming convention.
Columns: |
Columns are attributes of an entity, that is, columns describe the properties of an entity. So, let the column names be meaningful and natural.
Here's a simplest way of naming the columns of the Customers table:
CustomerID
CustomerFirstName
CustomerAddress
As shown above, it'll be a good idea to prefix the column names with the entity that they are representing.
Here's another idea. Decide on a standard two to four character code for each table in your database and make sure it's unique in the database. For example 'Cust' for Customers table, 'Ord' for Orders tables, 'OrdD' for OrderDetails table, 'Adt' for Audit tables etc. Use this table code to prefix all the column names in that table. Advantage of this convention is that in multi-table queries involving complex joins, you don't have to worry about ambiguous column names, and don't have to use table aliases to prefix the columns. It also makes your queries more readable.
If you have to name the columns in a junction/mapping table, concatenate the table codes of mapped tables, or come up with a new code for that combination of tables.
So, here's how the CustomerID column would appear in Customers table:
Cust_CustomerID
The same CustomerID column appears in the Orders table too, but in Orders table, here's how it's named:
Ord_CustomerID
Some naming conventions even go to the extent of prefixing the column name with it's data type. But I don't like this approach, as I feel, the DBA or the developer dealing with these columns should be familiar with the data types these columns belong to.
User defined data types: |
User defined data types are just a wrapper around the base types provided by the database management system. They are used to maintain consistency of data types across different tables for the same attribute. For example, if the CustomerID column appears half a dozen tables, you must use the same data type for all the occurrences of the CustomerID column. This is where user defined data types come in handy. Just create a user defined data type for CustomerID and use it as the data type for all the occurrences of CustomerID column.
So, the simplest way of naming these user defined data types would be: Column_Name + '_type'. So, I would name the CustoerID type as:
CustomerID_type
Primary keys: |
Primary key is the column(s) that can uniquely identify each row in a table. So, just use the column name prefixed with 'pk_' + 'Table name' for naming primary keys.
Here's how I would name the primary key on the CustomerID column of Customers table:
pk_Customers_CustomerID
Consider concatenating the column names in case of composite primary keys.
Foreign keys: |
Foreign key are used to represent the relationships between tables which are related. So, a foreign key can be considered as a link between the 'column of a referencing table' and the 'primary key column of the referenced table'.
I prefer the following naming convention for foreign keys:
fk_referencing table + referencing column_referenced table + referenced column.
Based on the above convention, I would name the foreign key which references the CustomerID column of the Customers table from the Order's tables CustomerID column as:
fk_OrdersCustomerID_CustomersCustomerID
Foreign key can be composite too, in that case, consider concatenating the column names of referencing and referenced tables while naming the foreign key. This might make the name of the foreign key lengthy, but you shouldn't be worried about it, as you will never reference this name from your code, except while creating/dropping these constraints.
Default and Check constraints: |
Use the column name to which these defaults/check constraints are bound to and prefix it with 'def' and 'chk' prefixes respectively for Default and Check constraints.
I would name the default constraint for OrderDate Column as def_OrderDate and the check constraint for OrderDate column as chk_OrderDate.
Variables: |
Continuing to use OOP terminology, I like to think of this situation as modeling "Table Interfaces". Unlike Table Inheritance, we don't want to create a base entity to be inherited by multiple entities, we just want some entities to "implement" the same interface. So, in our example, both Employees and Offices would implement the "PhoneNumbers" interface. This concept allows us to pick and choose which interfaces an entity has, whereas using inheritance means that all sub-tables always have the same columns/relations. We can add add/remove interfaces from different entities without worrying about affecting other entities that have the same base.
This is not as straight-forward in SQL, and there really is no direct, easy, standard way to handle this situation, unlike with Table Inheritance. Here's just one possible idea for you to consider if you need to implement this concept.
First, let's start with two tables, Employees and Offices:
create table Employees
(
EmpID int primary key,
EmpName varchar(100)
)
create table Offices
(
OfficeID int primary key,
OfficeName varchar(100)
)
We wish for our data model to allow both an Employee and an Office to have multiple phone numbers. Of course, to make our data meaningful and not contain just a random list of phone numbers, we will create a table of Phone Number Types:
create table PhoneNumberTypes
(
PhoneTypeCode varchar(10) primary key,
Description varchar(100),
Sort int not null default 0
)
So far, so good. Now, here's where we have some options and we really need to think a little. Let's start by examining a common attempt to create a re-usable and generic "PhoneNumbers" table that I feel doesn't work so well.
Many database architects will start by creating a table of phone numbers, and then they'd create two more tables: one to relate PhoneNumbers to Offices, and another to relate PhoneNumbers to Employees:
create table PhoneNumbers
(
PhoneID int identity primary key,
AreaCode char(3),
Exchange char(3),
Number char(4),
Extension varchar(10)
)
create table EmployeePhoneNumbers
(
EmpID int references Employees(EmpID) on delete cascade,
PhoneID int refernces PhoneNumbers(PhoneID),
PhoneType varchar(10) references PhoneNumberTypes(PhoneTypeCode),
primary key (EmpID, PhoneType)
)
create table OfficePhoneNumbers
(
OfficeID int references Offices(OfficeID) on delete cascade,
PhoneID int references PhoneNumbers(PhoneID),
PhoneTypeCode varchar(10) references PhoneNumberTypes(PhoneTypeCode)
primary key (OfficeID, PhoneType)
)
However, there are several problems with this situation.
So, I would recommend avoiding this design as it complicates the situation without much benefit. If you're going to do this, you might has well just store the Phone Number data itself in the EmployeePhoneNumbers and OfficePhoneNumbers tables and get rid of the PhoneNumbers table completely.
Instead, consider this approach:
create table PhoneNumbers
(
EmpID int references Employees(EmpID) on delete cascade,
OfficeID int references Offices(OfficeID) on delete cascade,
PhoneTypeCode varchar(10) references PhoneNumberTypes(PhoneTypeCode) not null
AreaCode char(3),
Exchange char(3),
Number char(4),
Extension varchar(10),
check (case when EmpId is null then 0 else 1 end +
case when OfficeID is null then 0 else 1 end = 1),
unique constraint PhoneNumbers_UC (EmpID, OfficeID, PhoneTypeCode)
)
Let's a take minute to dissect that. We have created a table of Phone Numbers, with columns that allow us to relate the phone number to an Employee and/or an Office, but neither is required. We also have a required PhoneTypeCode column, and we have the standard phone number attributes in there as well. We could easily add a notes column, or description, or last updated/modified columns as well.
Notice the check constraint: that expression is simply ensuring that only EmpID or OfficeID is not null, but not both. There is no "exclusive or" boolean operator in T-SQL, so this is one easy way to handle it using CASE expressions that return 0 if a column is null or 1 if it has a non-null value. If we wrote:
check (EmpID is not null OR OfficeID is not null)
Then the table would allow an entry in both the EmpID and OfficeID columns, which we do not want. Each row in this table will relate to only an office or an employee. Plus, we can easily add more columns to this check constraint as necessary (more on that later).
Next, instead of a standard primary key, we have a unique constraint on the columns EmpID/OfficeID/PhoneTypeCode. This ensures that only one row per EmpID/OfficeID/PhoneTypeCode is allowed. A primary key constraint cannot allow NULL values in any of the primary key columns, so we cannot use that type of constraint. If you recall, either EmpID or OfficeID is always null, but never both. Since PhoneTypeCode is never null, this constraint effectly ensures that there are never two entries for a single EmpID/PhoneTypeCode or an OfficeID/PhoneTypeCode.
With that, we are done. We can now write a single stored procedure that allows us add a PhoneNumber to either an Office or an Employee like this:
create procedure PhoneNumberAdd
@OfficeID int = null,
@EmpID int = null,
@PhoneTypeCode varchar(10),
@AreaCode char(3),
@Exchange char(3),
@Number char(4),
@Extension varchar(10)
as
insert into PhoneNumbers (OfficeID, EmpID, PhoneTypeCode, AreaCode, Exchange, Number, Extension)
values (@OfficeID, @EmpID, @PhoneTypeCode, @AreaCode, @Exchange, @Number, @Extension)
This procedure effectively requires that you pass either an EmpID or an OfficeID, since if both are null, or neither is not null, the INSERT will fail.
Writing a procedure that returns the phone number for an entity that "implements" the PhoneNumber interface is a slightly more complicated, but still quite simple. We must write our criteria expression to check for the possibility that either an @EmpID or an @OfficeID is passed in, since both conditions are possible. Thus, that results in something like this:
create procedure PhoneNumberSelect
@OfficeID int = null,
@EmpID int = null
as
select
p.OfficeID, p.EmpID, p.PhoneTypeCode,
pt.Description as PhoneTypeDescription,
p.AreaCode, p.Exchange, p.Number, p.Extension
from
PhoneNumbers p
inner join
PhoneTypes pt on p.PhoneTypeCode = pt.PhoneTypeCode
where
EmpID = @EmpID or OfficeID = @OfficeID
order by
pt.Sort
Again, effectively this requires that you pass a value for either the @OfficeID or the @EmpID parameter, but not both, and the matching data will be returned. We don't have to worry about issues where NULL != NULL because we never want to return a row where both a parameter value and a column value is NULL -- we only want to return rows where both are not null, and therefore the comparison will return TRUE. We could of course also put a @PhoneTypeID parameter in there as well if you only wanted to return a single, specific phone number of the specified type.
Update and Delete will work in much the same way:
create procedure PhoneNumberUpdate
@OfficeID int = null,
@EmpID int = null,
@PhoneTypeCode varchar(10),
@AreaCode char(3),
@Exchange char(3),
@Number char(4),
@Extension varchar(10),
as
update
PhoneNumbers
set
AreaCode=@areacode,
Exchange=@exchange,
Number=@number,
Extension=@extension
where
(PhoneTypeCode = @PhoneTypeCode) and
(EmpID = @EmpID or OfficeID = @OfficeID)
create procedure PhoneNumberDelete
@OfficeID int = null,
@EmpID int = null,
@PhoneTypeCode varchar(10)
as
delete from PhoneNumbers
where
(PhoneTypeCode = @PhoneTypeCode) and
(EmpID = @EmpID or OfficeID = @OfficeID)
Calling our code is very easy. From within T-SQL or from a client, we just set the parameter that corresponds to the entity we are referencing:
exec PhoneNumberDelete @OfficeID=2, @PhoneTypeCode='HOME'
exec PhoneNumberDelete @EmpID=45, @PhoneTypeCode='WORK'
... etc ...
With all this in place, we now can use one set of stored procedures to Add/Select/Update/Delete phone numbers. The only variable is the parameter that we pass in to the stored procedure, which indicates the entity that we are referencing.
That's all fine and dandy, but the question is: how would we set up a new entity to implement the "Phone Number" interface? For example, suppose that we now decide that we need to track "Contacts", and Contacts will have multiple phone numbers. (Please note that we could argue that Employees and Contacts should simply inherit from the same base class, and thus an interface is not necessary, but let's ignore that for the sake of this example.)
Per usual, we would start by creating a table of Contacts:
create table Contacts
(
ContactID int primary key,
ContactName varchar(100)
)
Now, it is time to "implement" the interface. Where do we begin? Well, it is actually done rather backwards. Normally, you alter the class you have created so that it implements a particular interface, but in this case, we need to alter the interface so that it works with the class. So, we actually don't do anything to the Contacts table -- we alter the PhoneNumbers table and the corresponding PhoneNumber stored procedures. This is a bit of a drawback of this approach, and a big reason why implementing an interface in a relational database is not as clean as implementing inheritance.
So, the steps we must take are:
Having done that, we now need to edit each of the stored procedures to include contactID as an optional parameter, and to include contactID throughout the SELECT and WHERE clauses.
It sounds like a lot of work, but it's really not too bad and can be done quite quickly. The "template" we've created for this interface is very easy to maintain. Despite the maintenance required, the overall process is pretty nice because:
Another option is to create one table with foreign key references back to the tables that "implement" your interface, and with an identity primary key on that table, and then put all of the actual data in another table. In other words, you might have one table that just defines "PhoneEntities", with links back to the parents, and then we put the actual address data in a table with a primary key of (PhoneEntityId/PhoneTypeCode).
For example, you might have a PhoneEntities table like this:
create table PhoneEntities
(
PhoneEntityID int identity primary key,
EmpID int references Employees(EmpID) on delete cascade,
OfficeID int references Offices(OfficeID) on delete cascade,
check (case when EmpId is null then 1 else 0 end +
case when OfficeID is null then 0 else 1 end = 0),
unique constraint PhoneEntities_UC (EmpID, OfficeID)
)
Notice that this table just stores a reference back to either an Office or an Employee, and our unique constraint ensures that there is only one value in this table for either. It is a bit simpler because we aren't worry about PhoneTypes here, or actual phone numbers. That data is stored in a more traditional PhoneNumbers table, with a foreign key reference to the PhoneNumberEntity table:
create table PhoneNumbers
(
PhoneEntityID int references PhoneEntities(PhoneEntityID) on delete cascade,
PhoneTypeCode varchar(10) references PhoneTypes(PhoneTypeCode),
AreaCode char(3),
Exchange char(3),
Number char(4),
Extension varchar(10),
primary key (PhoneEntityID, PhoneTypeCode)
)
That allows the PhoneNumbers table to be a more "normal" looking, and a bit simpler as well. This does complicate the stored procedures a little, since first you need to determine if a particular entity already has an entry in the PhoneEntities table, and if not then you need to add one in.
For example, with that schema, here's a PhoneNumberAdd stored procedure:
create procedure PhoneNumberAdd
@OfficeID int = null,
@EmpID int = null,
@PhoneTypeCode varchar(10),
@AreaCode char(3),
@Exchange char(3),
@Number char(4),
@Extension varchar(10)
as
declare @EntityID int
set @EntityID = (select PhoneEntityID from PhoneEntities where Office=@OfficeID or EmpID = @EmpID)
if (@EntityID is null)
begin
insert into PhoneEntities (EmpID, OfficeID)
values (@EmpID, @OfficeID)
set @EntityID = scope_identity()
end
insert into PhoneNumbers (EntityID, PhoneTypeCode, AreaCode, Exchange, Number, Extension)
values (@EntityID, @PhoneTypeCode, @AreaCode, @Exchange, @Number, @Extension)
This approach may or may not work better, depending on your specific situation. As you can see, the logic in the stored procedures can potentially get a little complicated since now we have two tables to deal with. But, this does makes the PhoneNumbers table itself much simpler, so you may get better performance with this approach.
A variety of these ways have been tried for storing objects in a
database. Some products have approached the problem from the
application programming end, by making the objects manipulated by the
program persistent. This also typically requires the addition of some
kind of query language, since conventional programming languages do
not have the ability to find objects based on their information
content. Others have attacked the problem from the database end, by
defining an object-oriented data model for the database, and defining
a database programming language that allows full programming
capabilities as well as traditional query facilities.
Object databases suffered because of a lack of standardization:
although standards were defined by ODMG, they were never implemented
well enough to ensure interoperability between products. Nevertheless,
object databases have been used successfully in many applications:
usually specialized applications such as engineering databases or
molecular biology databases rather than mainstream commercial data
processing. However, object database ideas were picked up by the
relational vendors and influenced extensions made to these products
and indeed to the SQL language.
The network model is a variation on the hierarchical model, to the
extent that it is built on the concept of multiple branches
(lower-level structures) emanating from one or more nodes
(higher-level structures), while the model differs from the hierchical
model in that branches can be connected to multiple nodes. The network
model is able to represent redundancy in data more efficiently than is
the hierarchical model.
The operations of the network model are navigational in style: a
program maintains a current position, and navigates from one record to
another by following the relationships in which the record
participates. Records can also be located by supplying key values.
Although it is not an essential feature of the model, network
databases generally implement the set relationships by means of
pointers that directly address the location of a record on disk. This
gives excellent retrieval performance, at the expense of operations
such as database loading and reorganization.
Most Object databases use the navigational concept to provide fast
navigation across networks of objects, generally using Object
Identifiers as "smart" pointers to related objects. Objectivity/DB,
for instance, implements named 1:1, 1:many, Many:1 and Many:Many named
relationships that can cross databases. Many object databases also
support SQL, combining the strengths of both models.