An Investigation on Database Id Generation Strategies (Part I)

The World ID-10092667

In this post, I will Investigate how Id generation strategies chosen for primary keys of business entities at the database level may impact the entire solution. Databases that are the repository of a transactional application do not work in a vaccum: They are a part of a larger solution.

So, design decisions for the database should not be made away from the context of the design decisions of the entire solution. On the contrary, all main design decisions regarding the database of any transactional application should be made considering the impact they may have on the solution as a whole.

So, proper trade-offs may apply regarding all main design decisions in a database that is the repository of a transactional application, including Id Generation Strategies for all business entities that are persisted by the application in the database.

Regarding the architecture of any given solution, we could say that it is all about the design decisions that we make and the consequences of such decisions.

Proper trade-offs are those that could strike the best possible balance of the consequences (both positive and negative) that may impact the solution in the short, mid and long term.

To conduct this investigation, I will choose an Ad-Hoc approach:
I will first navigate the problem, so we all could grasp a better understanding of what is at stake when we make isolated design decisions regarding Id Generation Strategies.

Once we have navigated the problem, so that we have some good understanding of the pros &cons of such a way of making design decisions, we will be in a much better position to explore possible solutions to this problem.

We could not argue that we fully understand any given solution if we do not have a deep understanding of the problem that such a solution is meant to solve.

Since my interest is to investigate this general problem from an architectural perspective, I  will use common design patterns and tools, like Separation of Concerns, Model-View-Mediator (this is a generic way to refer to patterns like Model-View-Controller or MVC, Model-View-Presenter or MVP, or Model-View-ViewModel or MVVM) and Object Relational Mapping (ORM).

When I say “MVC” I mean any tool that implements the MVC form of the general Model-View-Mediator pattern, and not just ASP.NET MVC, as it just as well applies to Spring.NET, or to any other MVC-based tool.

Why should we care to use these patterns and tools?

Mainly, because they are useful to us in a very practical way: they allow us to achieve our development goals with the least amount of effort from our part, if we make proper use of them.

The principle of Separation of Concerns is a very pervasive principle in Software Architecture, since it is applied in just about any architectural tool that we could consider, like for instance, when we use any Model-View-Mediator based tool, or when we use any ORM tool.

The principle of Separation of Concerns (SoC) states that we will organize our code in chunks in such a way that any given chunk of code will have a single and well-defined purpose, and it does not assume any superfluous responsibilities.

It means that if we choose to have an n-tier (or multiple layer) architecture, one of the main reasons behind this decision is the SoC principle.

It also means that if we choose to use some kind of Model-View-Mediator approach (like say, MVC, or MVVM, or MVP), one of the main reasons behind this decision is the SoC principle.

It would also mean that if we choose to use an ORM tool (like say, NHibernate, or EF), one of the main reasons behind this decision is the SoC principle.

With any of these tools and patterns, we use the concept of Model.

The Model is a software representation of a solution to a known problem.

The Model includes all the entities or business objects that are required by the solution to solve such a known problem.

Following the SoC principle, some chunk of code at some layer or tier will use these entities to apply the necessary logic that solves the business problem at hand.

By the same token, some other chunk of code at some other layer or tier will use these entities to persist their changes of state at the proper time and at the proper data repository.

The focus of my investigation will be at the level of this particular responsibility: how the different database id generation strategies affect the CRUD operations of business objects, and I will use an ORM tool as a helper for my analysis.

Speaking of ORM tools: why do we use them? what kind of problem do they solve for us?

As I have already said, the Model is a software representation of a solution to a known problem.

If we use an object-oriented representation of a given solution, such representation is aptly named the Object Model (OM) of said solution.

If we use an entity-relationship representation of a given solution, such representation is aptly named the Data Model (DM) of said solution.

For any given solution, its Object Model is very different from its Data Model.

If your team has to implement a solution with an OOP language like C# and a database like MS-SQL Server, such difference between the two representations of the solution poses a very serious problem to the software development effort of your team.

The formal name for this problem (the wide gap between the OM and the DM of a given solution) is Object-Relational Impedance Mismatch (ORIM).

It has been proven that a certain set of patterns is effective in the solution of the ORIM problem.

ORM tools are practical implementations of these patterns.

All ORM tools use a technique known as Mapping to bridge the gap of the ORIM problem.

ORM tools allow us to use a default set of Mapping rules and conventions, and they also allow us to customize the rules and conventions to be used by our implementation.

The simplest way to use any ORM tool is with the default set of Mapping rules and conventions.

In this post I will use NHibernate as a reference model for an ORM tool.

I will present and use concepts that are relevant for any given mainstream ORM tool, but I will use the names of those concepts as they are referred by NHibernate.

I will start with the simplest of examples, and I will gradually move on to more complex examples.

Since I want to explore how the different database id generation strategies may affect the CRUD operations of business objects, in my first example I will let the ORM tool choose the database id generation strategy by letting it use its default behaviour, then do some basic CRUD operations and use the debugging tools from the ORM engine to obtain useful information to analyze how good (or bad) is the default Id generation strategy from the perspective of the system as a whole.

To do this, I have chosen to use the approach commonly known as “Code First”, and let the ORM tool generate the database schema source code for the Model used in my first example.

I will use some POCO classes as the entities of my Model.

But before I go on, it would be useful to explore a little deeper into the Model and how it is used by the different layers or tiers.

When it comes to solving a given kind of problem, it is at the level of the Business Logic Layer where the “actual” solving of the problem happens.

When it comes to persisting and retrieving the state of business objects, it is at the level of the Data Access Layer where those kinds of operations happen.

At the level of the Business Logic Layer (BLL), all business objects (instances) of all business entities (entity classes) participate.

At the level of the Data Access Layer (DAL), only instances of persistent business entities (persistent entity classes) participate.

For many kinds of businesses, there is a subset of business entities that are non-persistent: that is, instances of such non-persistent entity classes are required and used at the BLL level, but none of such instances of such classes exist at the DAL level, which means that the database schema has no tables to represent the non-persistent entity classes.

At this point, it is very useful to present an example of such kind of scenario.

Let’s consider the following business example: A company has an customer loyalty program as part of their CRM business processes.

Some of the business processes involved in the customer loyalty program apply certain business rules based on algorithms that calculate metrics as a function of the “age” of a given customer in the customer loyalty program.

Let’s suppose that, for any given order, there are 10 different algorithms that use this “age” of the customer to calculate these metrics.

The “age” of a given customer in the customer loyalty program is the number of days, expressed in years (as a real number) between the start date when such a customer joined the program, and today’s date.

We should all realize  that the start date when any given customer joins the customer loyalty program has to be a public property of some business entity that has to be a persistent entity class.

The “age” of a customer in the program, as a property, it is a function of the start date and today’s date, so, it is not an independent property, so, it should not be persisted.

Regarding the aforementioned algorithms (we have supposed that there are 10 different calculations for each new order), we could just as well use the persistent start date as parameter with each one of them. But if we did so, it would mean that for each order, we would have to calculate the very same subtraction ten times in a row, which is a clear waste of resources.

So, why not use some non-persistent business entities at the Business Logic Layer when it seems to be useful and it makes a lot of sense from many perspectives?

Now that we have gone through the rationale behind non-persistent business entities, let’s delve into a simple Object Model that could solve the “Tango with Persistent & Non-Persistent classes”:

EntityHierarchy

Now, we can get back to the simplest way to use the “Code First” approach so that our choice of ORM tool, using defaults, generates the source code for the database schema of our Model.As we are using NHibernate as a reference model for any ORM tool, the simplest way to achieve what we need is with Automapping. What Automapping really means is that we will use the default set of rules and conventions with very little customizing.

With Automapping we can tell our ORM tool to generate the source code of the database schema that corresponds to our Model, that is, the object model that represents the business entities of the domain of our solution.

Since the domain of our solution is comprised of two subsets, a subset of persistent business entities, and a subset of non-persistent business entities, we need to tell our ORM tool to generate a database schema that only includes the persistent business entities.

The code for the base classes that we need to solve the “Tango” are these:

namespace SimpleAutoMap.Domain
{

public abstract class EntityBase
{}

}

namespace SimpleAutoMap.Domain
{

public abstract class NonPersistentEntityBase : EntityBase
{}

}

namespace SimpleAutoMap.Domain
{
public abstract class PersistentEntityBase : EntityBase
{
public virtual int Id { get; set; }
}

}

It is interesting to note that, in our model, the base class for all persistent business entity classes already has the Id property included: in this case, we are using implementation inheritance so as to save code!
It is also very important to note that so far, we have only dealt with the “Tango” of Persistent and Non-Persistent classes strictly from the perspective of pure implementation inheritance, and we still need to do some more work so that our ORM tool will work with the business entities as we expect it to do.
Now that we have our base classes in place, we can move on to the main classes of our (rather simple) model:
namespace SimpleAutoMap.Domain
{
public class Product : PersistentEntityBase
{
public virtual string ProductName { get; set; }
}

}

namespace SimpleAutoMap.Domain
{
public class Customer : PersistentEntityBase
{
public virtual string CustomerName { get; set; }
public virtual DateTime InceptionDate { get; set; }
public virtual DateTime ClpStartDate { get; set; }
}

}

namespace SimpleAutoMap.Domain
{
public class LineItem : PersistentEntityBase
{
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public virtual Product Product { get; set; }
}

}

namespace SimpleAutoMap.Domain
{
public class Order : PersistentEntityBase
{
public virtual DateTime OrderDate { get; set; }
public virtual Customer Customer { get; set; }
public virtual IList LineItems { get; set; }
}

}

namespace SimpleAutoMap.Domain
{
class ClpProcessingOptions : NonPersistentEntityBase
{
public double Age { get; set; }
}

}

(NOTE: in the original post, I forgot to include the properties InceptionDate and ClpStartDate to Customer. Now it is fixed!)

Before we go any further, it would be very useful to say a word about why all the properties of the persistent entities have the modifier virtual, while at the same time, the properties of the non-persistent entities do not have the modifier virtual?

At this point I do not want to distract the attention away from the main goal of this post, but nonetheless I will give a short but proper answer to this valid and important question.

From the perspective of the engine of any ORM tool, the model is an atomic unit, in the sense that each and every entity class that is a part of the persistent subset of the model (the part of the model that is relevant to the ORM engine) is “created equal”.

Unless we say otherwise, when we tell the ORM engine to “load”, it will try to load to memory each and every instance of each and every entity class (which happens to be a real waste of resources!).

This funny way to behave (the default behaviour) is apty named eager loading. But if any ORM tool would only support eager loading, it would be useless to us.

So, in order to be useful, all ORM tools also support another behaviour, apty named lazy loading.

With lazy loading, we have complete programmatic control over when and how any give set of instances of any given entity class is loaded to memory by the ORM engine.

To be able to support lazy loading, all entity classes (so as to be able to be handled by the ORM tool in this way), MUST have all of its public properties declared as virtual.

Well, now that we can get back to own main interest, we have to figure out a way to tell the ORM engine to include into the Data Model only the entity classes that inherit from the class PersistentEntityBase.

With NHibernate this goal is very simple to achieve: the default set of rules and conventions is controlled by the class DefaultAutomappingConfiguration.

All we have to do is create a subclass of DefaultAutomappingConfiguration with the proper behaviour and use it in our implementation.

The class DefaultAutomappingConfiguration has a very useful method that will help us in what we want to achieve: the method ShouldMap.

The overload of this method that is interesting to our investigation has the following signature:

public virtual bool ShouldMap(Type type)
This overload in particular is very useful, indeed, for it is virtual (which means that we can override it with our own specialized logic), and it receives as parameter any object of the class Type.
This is simple and wonderful at the same time, as we can figure out how the ORM engine uses this overload: it iterates over the entire set of entity classes of the model, and for each given entity class, it passes it to this method and uses its outcome to determine if said entity class of the model has to be mapped or not.
This is exactly what we need to tell the ORM engine to map only those entity classes that inherit from the base class PersistentEntityBase.
So, our subclass of the base class  DefaultAutomappingConfiguration looks like this:
namespace SimpleAutoMapping
{

public class SimpleAutoMappingConfiguration
: DefaultAutomappingConfiguration
{
public override bool ShouldMap(Type type)
{
return type.IsSubclassOf(typeof(PersistentEntityBase));
}
}

}

Finally, we are ready to tell our ORM tool to follow its default behaviour (with just a very simple customizing), and generate the database schema for the subset of the persistent entity classes of our model.

With a powerful ORM tool (like for instance, NHibernate!), we need a very simple routine to do this:

class Program
{

static void Main(string[] args)
{
string outputFileName = ConfigurationManager.AppSettings[“OutputFileName”];
var cfg = new SimpleAutoMapConfiguration();var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<Customer>(cfg)))
.BuildConfiguration();
var exporter = new SchemaExport(configuration);
exporter.SetOutputFile(outputFileName);exporter.Execute(false, false, false);
Console.WriteLine(“\n\nDB schema source code.”);
Console.ReadLine();
}

}

This routine generates a database schema that looks like this:

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKDDD0206ACBEF7F6]’) AND parent_object_id = OBJECT_ID(‘[LineItem]’))
alter table [LineItem] drop constraint FKDDD0206ACBEF7F6

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKDDD0206A75BA3E60]’) AND parent_object_id = OBJECT_ID(‘[LineItem]’))
alter table [LineItem] drop constraint FKDDD0206A75BA3E60

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK3117099B4095694A]’) AND parent_object_id = OBJECT_ID(‘[Order]’))
alter table [Order] drop constraint FK3117099B4095694A

if exists (select * from dbo.sysobjects where id = object_id(N'[Customer]’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table [Customer]

if exists (select * from dbo.sysobjects where id = object_id(N'[LineItem]’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table [LineItem]

if exists (select * from dbo.sysobjects where id = object_id(N'[Order]’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table [Order]

if exists (select * from dbo.sysobjects where id = object_id(N'[Product]’) and OBJECTPROPERTY(id, N’IsUserTable’) = 1) drop table [Product]

create table [Customer] (
Id INT IDENTITY NOT NULL,
CustomerName NVARCHAR(255) null,
InceptionDate DATETIME null,
ClpStartDate DATETIME null,
primary key (Id)
)

create table [LineItem] (
Id INT IDENTITY NOT NULL,
Quantity INT null,
UnitPrice DECIMAL(19,5) null,
Product_id INT null,
Order_id INT null,
primary key (Id)
)

create table [Order] (
Id INT IDENTITY NOT NULL,
OrderDate DATETIME null,
Customer_id INT null,
primary key (Id)
)

create table [Product] (
Id INT IDENTITY NOT NULL,
ProductName NVARCHAR(255) null,
primary key (Id)
)

alter table [LineItem]
add constraint FKDDD0206ACBEF7F6
foreign key (Product_id)
references [Product]

alter table [LineItem]
add constraint FKDDD0206A75BA3E60
foreign key (Order_id)
references [Order]

alter table [Order]
add constraint FK3117099B4095694A
foreign key (Customer_id)
references [Customer]

We can check that the ORM tool, with the small set of constraints that we have given it and its own default behaviour, has generated a database schema that uses IDENTITY-based primary keys on all entities.

How good (or bad) is this decision from the perspective of the entire solution (and not just from the perspective of the database itself)?
We will explore this in my next blog post (Part II of this investigation).

To download the code sample, click here

Kind regards, GEN

How should we estimate software sizing in a software development project when using Agile?

Caliper

In this post, I will focus on how should we do estimates in an Agile project.

Just to set the proper context, it is important to keep in mind the main tenets of Agile:

In any Agile project, the primary measure of progress is working software, which means that any other kind of deliverable is basically of low value.

Another main tenet is simplification: Maximize the amount of work not done!

During development, we need to be able to control the process, and we all know that we can’t control what we do not measure, so we do need some estimates.

But we should choose any software sizing estimation technique that is good enough for us to deliver working software: the focus should be on good enough!

When it comes to Agile estimation techniques, there’s a lot written about it.

On most books and articles on Agile Estimation techniques, the lion’s share goes to a technique popularly known as Planning Poker.

I agree with the idea that Planning Poker is fun to do, but I also agree with the well established fact that any team using Planning Poker will require a lot of time to complete this activity for a set of user stories. A lot of time spent on anything different from delivering working software is the wrong thing to do when you are using Agile.

Spending a lot of time on estimates is not consistent with the idea of maximizing the amount of work not done (more on this at the end of this post!).

There are alternative techniques, some of them are better versions of Planning Poker. Among the few techniques that have succeeded where Planning Poker has not is a technique known as Silent Grouping.

Even though Silent Grouping shares some elements with Planning Poker, it has avoided to include the elements that in PP contribute the most to the long duration of the sessions.

What is the basic “operation” of Silent Grouping?

The “game plan” for Silent Grouping has a Setup stage, two rounds of estimations and a Reflection stage.

The main elements are a card for each user story to estimate, a PP Board and a Parking Lot.

The most important aspect in the entire “game plan” of Silent Grouping is that the participants do not talk to each other during the two rounds of estimations (thus the “Silent” reference).

It is meant to eliminate, as much as it is possible, any kind of discussions among participants during the rounds.

At the Setup stage, the moderator will lay the ground rules of the session to the participants, and set expectations.

During the first round, the team members that participate in the session stand in a queue, so that each member has to wait for her/his turn. When it is the turn of a given team member, she/he will pick up the next card from the stack of cards to estimate, and put the card at the board within the “swim lane” that belongs to the number of points that the member considers to be the closest estimate for that given user story.

During the first round, each participant will give feedback on just one single card for each given turn at the board.

If the list of participants is small and the set of user stories is large, participants will have to iterate as many times as necessary so that each user story card is assigned an initial estimate.

Even though any given participant may give feedback on more than one card during the first round, any given card with have feedback from one and only one participant during the first round.

The board with “swim lanes” shows at the top the number of user story points for that group (thus the “Grouping” reference). Typically, the board will have 8 “swim lanes” or groups.

The first “swim lane” at the left side of the board has the number 1 at the top, and the number increases for each following “swim lane” to the right according to the sequence of Fibonacci numbers, typically until the number 34 (that 8th number in the Fibonacci sequence):

1, 2, 3, 5, 8, 13, 21, 34

(Did you ever wonder WHY is it that the Planning Poker Board NEVER has a number bigger than 34?)

During the first round, participants give feedback on card estimates in complete silence.

During the second round, the participants stand in a queue.

When it is the turn of a given team member, the participant is free to change the estimate of all the cards, by moving each card and placing it at the board within the “swim lane” that belongs to the number of points that the member considers
to be the closest estimate for that given user story.

It is possible that during the second round, some of the cards suffer a lot of moves. It is the responsibility of the moderator of the session to take note of these “controversial” cards (the cards that get shuffled at lot).

This second round is also done in complete silence: Participants give their feedback just by moving cards, one participant at a time.

During the second round, each and every participant is free to give feedback on the estimates of each and every card, but this time, each participant will have one and only one turn at the board.

Once the second round finishes, the moderator will place all the “controversial” cards in the Parking Lot (any kind of place holder for these “outlier” cards: a box, a basket, etc.).

At the reflection stage, if there are any “controversial” or “outlier” cards, participants will resolve any disputes of estimates for these cards only, but this part should we run without any kind of discussions or arguments. The goal is to either agree on a fast estimate for each card or else.

The Product Owners should not participate in the rounds of estimations, but could give feedback to participants during the reflection stage, if and only if any participant has questions regarding any given “outlier” user story: just short answers to simple questions.

Regarding this “outlier” cards, two possible outcomes could happen during the Reflection stage: the participants are able to easily resolve the dispute on the estimate of a given “outlier” card, or they don’t.

If they easily resolve the estimation dispute for a given “outlier” user story, everything’s fine with that user story.

If they are not able to resolve the estimation dispute for a certain user story, I can tell you that the estimate is not the ONLY hard question that the participants are not able to answer regarding that user story!.

In a sense, Agile is an adventure of exploration and discovery around a given set of user stories.

When the team members cannot agree on the estimation for a given user story, it could be a clear indicator that the team has not explored that user story enough at that point in time, so, the user story is not ripe enough for development.

If your team faces this kind of problem with any given “stubborn” user story at the end of the Reflection stage of a Silent Grouping session, the moderator should set aside each and every “stubborn” user story for further exploration and discovery.

As any Agile team is free to organize itself to solve any issue, the team should decide on how to deal with this situation: which of the team members should work on the exploration and discovery of the “missing” details of the “stubborn” user stories?

After the necessary stage of exploration and discovery is done for all user stories, they should be all ripe for rapid estimation and development.

On any given project, a team may need to run a few estimation sessions to cover all user stories, mainly because on any given project not necessarily all user stories are known from the get-go.

As a parting thought, we have come to the conclusion that any estimation session in Agile should always be a fast affair.

We should use techniques like Silent Grouping instead of Planning Poker, and most importantly, the team should never waste time feuding on estimates.

The proper way to deal with “stubborn” user stories is to set aside a group of team members to do some further exploration and discovery of the details of such user stories.

Kind regards, GEN

What is software architecture? What does a software architect do? How could we tell the good software architects from the bad software architects?

LightSpeed

In this post I will deal with an interesting (and thorny) set of questions regarding architecture.

Without much further ado, let’s get into the game.

What is software architecture?

In a broad sense, software architecture is the complete set of design decisions that defines and determines the structure of a given software solution, but let’s bear in mind that when we talk about “software architecture“, we really mean the main design decisions that define the main structural elements of a given software solution, and not each and every design decision, that may include a large amount of rather simple, obvious and very uninteresting design decisions.

What does a software architect do?

In plain and simple terms, the software architect is the person that makes those design decisions, so, that is mainly what the software architect does:
making the design decisions regarding a given software solution.

How could we tell the good software architects from the bad software architects?

To be able to answer this question, we should start with a simpler question:

How could we tell a good design decision from a bad design decision?

Well, design decisions are either good or bad just because of their consequences: a design decision is good if and only if it produces good consequences, just as much as it will be bad if and only if it produces bad consequences.

So, getting back to the answer for the original question, the good software architects are the ones that make design decisions that have good consequences for the project, the team and the product, and the bad software architects are the ones that make design decisions that have bad consequences for the project, the team and the product.

I am aware of the fact that any Product Owner may argue that my answer is not useful for them since, by the time they realize that the software architect is not any good, it is already too late!

Well, allow to say in my defense that it is not so, since you can detect the tell-tale signs of either good or bad consequences from design decisions being made by the architect early on the duration of any software development project.

As a parting idea to this post, I will give you another tip on good software architects. All of them are really good at these two things:

1) Good software architects, when they have to make a design decision, they never forget to ask the following question:

How could I make sure that the design decision that I’m about to make will neither compromise nor limit our ability to keep making the design decision that we need, for the forseeable future?

(Since you have allowed me a few things, allow me to say that this is a quote of my own batch)

2) Good software architects, when they ask themselves this question, they always figure out a successful answer to it for the solution at hand.

Most people may argue that the question in 1) is an impossible question since we cannot predict the future, so, there is no way that today we could guess what might be the design decisions that we will need to make in the future.

Again, allow me to say in my defense that most people are not software architects (let alone good software architects), so, most people will not pay attention to the key to this question: “neither compromise nor limit our ability to keep making the design decision that we need

Good software architects do not need to predict the future to be able to figure out the answer to the key to the question.

Kind regards, GEN

Ese asunto de los cursores

Introducción

La idea básica de este post es avanzar un poco más en el análisis de las razones  por las cuales es recomendable evitar el uso de cursores.

Razones principales del impacto de los cursores server-side en los recursos del Database Engine

El Database Engine esta optimizado para procesar conjuntos de registros, de la misma forma que el lenguaje T-SQL es un lenguaje de tipo imperativo orientado a conjuntos de registros, donde los comandos indican el resultado a obtener en vez de detallar el camino para obtenerlo. En suma, tanto el Engine como T-SQL están diseñados de acuerdo con un esquema set-oriented.

Los cursores son un recurso programático que permite recuperar en memoria un conjunto de registros y procesarlos un registro por vez, es decir, operan con un esquema de tipo record-oriented.

En el caso de cursores implementados en T-SQL, para que el Database Engine pueda lograr que este tipo de recursos opere en forma adecuada y lo haga en armonía con el resto de sí mismo, necesita contener a los cursores en un entorno de ejecución propio, adecuado para el esquema record-oriented, que permita conservar la consistencia y armonía entre el cursor y el resto de las cosas que el Engine controla, que operan con el modelo set-oriented.

Dicho entorno de ejecución consume en forma significativa recursos compartidos y escasos, y esta es la razón principal por lo cual es tan generalizada la recomendación de evitar el uso de cursores, en particular, cursores server-side, tal como es el caso de los cursores implementados en T-SQL.

Por su parte, en el caso de los cursores server-side, la metadata del cursor, necesaria para su ejecución, es almacenada en memoria del mismo server, lo que agrava aún más la situación en relación al consumo de recursos compartidos y escasos de la instancia.

Otros problemas asociados al uso de cursores

Además de los problemas ya mencionados en relación al uso de cursores en T-SQL, es común que surjan algunos otros problemas que afectan al uso de recursos en una instancia durante la ejecución de una rutina basada en cursores T-SQL.

Estos problemas frecuentes son consecuencia del hecho que no se configura el cursor en forma adecuada según los requerimientos de la rutina que lo contiene.

Para ilustrar este tipo de problemas, usaremos como base de nuestro análisis un ejemplo típico de uso de cursores que podemos obtener haciendo una búsqueda en Internet.

Un ejemplo “clásico” podria ser algo así:

DECLARE @name VARCHAR(50) — database name

DECLARE @path VARCHAR(256) — path for backup files

DECLARE @fileName VARCHAR(256) — filename for backup

DECLARE @fileDate VARCHAR(20) — used for file name

SET @path = ‘C:\Backup\’

SELECT @fileDate = CONVERT(VARCHAR(20),GETDATE(),112)

DECLARE db_cursor CURSOR FOR

SELECT name

FROM MASTER.dbo.sysdatabases

WHERE name NOT IN (‘master’,’model’,’msdb’,’tempdb’)

OPEN db_cursor

FETCH NEXT FROM db_cursor INTO @name

WHILE @@FETCH_STATUS = 0

BEGIN

SET @fileName = @path + @name + ‘_’ + @fileDate + ‘.BAK’

BACKUP DATABASE @name TO DISK = @fileName

FETCH NEXT FROM db_cursor INTO @name

END

CLOSE db_cursor

DEALLOCATE db_cursor

Vamos a usar para el análisis un ejemplo de cursores similar, con el mismo tipo de configuración de cursor no adecuada.

Vamos a usar la base de datos AventureWorks para este ejemplo (en realidad, una copia de la misma cuyo nombre es AW).

A fin de mostrar como afecta el uso de una configuración no adecuada del cursor, el ejemplo de código no cierra el cursor, es decir, lo deja abierto para ver que pasa con la base de datos afectada por un cursor con este tipo de configuración durante la ejecución del mismo.

El código de la rutina de ejemplo es el siguiente:

DECLARE @OrderQty int

DECLARE @QtyIncrease decimal(8, 3)

USE AW

DECLARE db_cursor CURSOR FOR

SELECT OrderQty from Sales_SalesOrderDetail order by SalesOrderDetailId

OPEN db_cursor

FETCH NEXT FROM db_cursor INTO @OrderQty

BEGIN

SET @QtyIncrease = (@OrderQty * 1.25)

PRINT @QtyIncrease

FETCH NEXT FROM db_cursor INTO @OrderQty

END

Para analizar como un cursor con esta configuración afecta a la base de datos y a la instancia involucradas en su ejecución, usaremos el siguiente query con algunas DMVs adecuadas para el análisis de ejecución de cursores:

SELECT c.creation_time, c.cursor_id, c.name, c.session_id

,tl.resource_type

,tl.resource_description

,tl.request_session_id

,tl.request_mode

,tl.request_type

,tl.request_status

,tl.request_reference_count

,tl.request_lifetime

,tl.request_owner_type

,s.transaction_isolation_level

,s.login_time

,s.last_request_start_time

,s.last_request_end_time

,s.status

,s.program_name

,s.nt_user_name

FROM sys.dm_exec_cursors(0) AS c

inner JOIN sys.dm_exec_sessions AS s ON c.session_id = s.session_id

inner join sys.dm_tran_locks tl on

tl.request_session_id = s.session_id

GO

El resultado de este query es algo así:

 Result

Como se puede apreciar, durante la ejecución de un cursor con este tipo de configuración, el cursor aplica un lock a la base de datos que contiene a las tablas involucradas.

Dado que a propósito no hemos cerrado el cursor, podemos verificar el lock de la base de datos completa intentando hacer algún cambio a la misma que ponga de manifiesto dicho lock, como por ejemplo, intentar renombrarla.

Al intentar renombrar a la base de datos AW, despues de unos segundos SSMS retorna el siguiente mensaje de error:

Error_Message

La falta de la correcta configuración de los cursores server-side es un error muy común y generalizado.

Sin embargo, en este post no le dedicaremos tiempo a revisar los detalles de una correcta configuración de cursores server-side, dado que el objetivo principal del post es exponer las razones por las cuales no deberíamos usar cursores, y si hemos puesto el foco en exponer con ejemplos concretos algunas de las consecuencias negativas que explican porque no deberíamos usar cursores.

Saludos, GEN

XAML y el desarrollo cross-platform de la UI de smartphone apps

Blue Wave

En este post revisaremos brevemente algunas de las razones por las cuales el uso de XAML en el desarrollo cross-platform de la UI de smartphone apps es una decisión de diseño muy importante, lo que habla muy bien de aquellas empresas, como Xamarin, que han tomado tal decisión.

Contexto de hardware gráfico previo al lanzamiento de Windows Vista

Previo al lanzamiento de Windows Vista, un porcentaje de las PCs de ese entonces contaban con hardware acelerador de gráficos, tales como tarjetas aceleradoras dedicadas, en el caso de usuarios asiduos de los video games de PCs, o chipsets con capacidades básicas de aceleración integradas en el motherboard.

Previo al lanzamiento de Vista, para poder trabajar con dicho hardware gráfico especializado, el software debía hacer uso de APIs complejos como Direct3D, mientras que la funcionalidad gráfica básica de Windows se basa en el uso de otros APIs más sencillos pero con capacidades más básicas, como es el caso de GDI+.

En base a este panorama, la gente de Microsoft vió propicio incorporar a las tools de desarrollo de .NET, a través de nuevos componentes del framework, un nuevo API integrador de manejo gráfico que evitase la complejidad para los desarrolladores de tener que lidiar con diversos APIs, de acuerdo con el hardware con el que disponga la PC en cuestión.

Este nuevo API integrador es Windows Presentation Foundation, que detecta que hardware gráfico está disponible en la PC y lo aprovecha para obtener el mejor resultado visual posible, como por ejemplo, aplicaciones cuya interfase de usuario presente ventanas con algún porcentaje de transparencia.

Esto puede parecer algo superfluo, pero la presión competitiva de las interfases de usuario con efectos visuales atractivos que ofrecían las aplicaciones en Mac era muy fuerte y llevó a Microsoft a pensar como incorporar al desarrollo .NET un API con una curva de aprendizaje relativamente baja (en comparación con la necesaria para aprender a usar Direct3D) que permita que muchos desarrolladores puedan implementar rapidamente aplicaciones visualmente muy atractivas.

Breve revisión de la arquitectura de Windows Presentation Foundation

Para poder comprender el aporte crucial que representa usar XAML en el desarrollo cross-platform de la Interfase de Usuario (UI) de smartphone apps, es conveniente revisar brevemente la arquitectura de Windows Presentation Foundation, que es el área del framework de .NET donde se usa XAML vinculado con el desarrollo de la interfase de usuario de una aplicación.

WPF Arch

WPF Architecture

Como se puede apreciar, un único y mismo API que permite describir los elementos de interfase de usuario en WPF puede usar el hardware gráfico disponible, a fin de aplicar los efectos visuales solicitados con la mejor performance que el equipo permita.

Es importante destacar el punto mas relevante del diagrama de arquitectura: el mismo API de elementos de interfase de usuario puede ser ejecutado por hardware diverso, en base a lo que este disponible en el equipo en cuestión, y es el mismo framework el que aisla al programador de la decisión de que clases dentro del árbol de herencia es necesario invocar para tener acceso al hardware a usar.

Ventajas de XAML/WPF para el desarrollo Cross-Platform de Smartphone Apps

Dadas las características de WPF mencionadas previamente, empresas como Xamarin vieron las ventajas implícitas en la arquitectura original de WPF para extenderla, de tal forma de ser aprovechada para el desarrollo cross-platform de smartphone apps de diversos vendors, que han de ejecutar en forma nativa en diferentes sistemas operativos y con diferente hardware.

La amplia experiencia previa de la gente de Mono (nucleo de desarrollo del que se derivó el equipo de desarrollo de Xamarin) en la extensión del framework de .NET y de la implementación del CLR para otros sistemas operativos y plataformas de hardware les ha permitido realizar este tipo de adaptaciones en forma exitosa y ofrecer estas ventajas a los desarrolladores.

Saludos, GEN

Escalabilidad Infinita y tablas Memory-Optimized

Clock

 

 

En este post voy a analizar algunos escenarios de escalabilidad de aplicaciones, en particular, aquellos escenarios que pueden verse beneficiados con el uso de tablas memory-optimized.

Los puntos principales del post son los siguientes:

  • Rangos de escalabilidad de aplicaciones
  • Algunas restricciones a la escalabilidad infinita impuestas por la arquitectura tradicional de base de datos relacionales
  • Ventajas de las tablas memory-optimized para sistemas con escalabilidad infinita

Rangos de escalabilidad de aplicaciones

Cuando hablamos de escalabilidad de aplicaciones, nos referimos a la capacidad que tiene una aplicación dada de mantener el mismo nivel de performance ante cantidades crecientes de usuarios concurrentes que la utilizan.

La escalabilidad de aplicaciones puede agruparse en diversos rangos, según el punto de vista del análisis, pero una forma típica es la siguiente:

  • Escalabilidad básica
  • Escalabilidad intermedia
  • Escalabilidad ilimitada o infinita

Escalabilidad básica es el rango mínimo de escalabilidad, que abarca entre uno y unos pocos miles de usuarios concurrentes.

En este rango se ubican aplicaciones de PyMEs y aplicaciones departamentales de grandes empresas.

La edición Express de SQL Server puede atender este rango de escalabilidad.

Este rango no es el foco de este post.

Escalabilidad Intermedia es un rango que abarca entre unos pocos miles y varias decenas de miles a varios cientos de miles (para muchos escenarios) de usuarios concurrentes. Este es el rango de escalabilidad que las ediciones de SQL Server a partir de la Standard pueden proveer (con los recursos apropiados de hardware del server).

En este rango se ubican aplicaciones corporativas del estilo de ERP, portales corporativos, aplicaciones de Helpdesk y otras similares.

En este rango de escalabilidad, las ediciones de SQL Server mencionadas (con los recursos de hardware apropiados), son capaces de atender con alta performance a todo tipo de escenarios. También es importante destacar que a partir de este rango de escalabilidad, es imprescindible usar versiones 64 bits de SQL Server con múltiples procesadores de múltiples núcleos, y con un dimensionamiento adecuado de memoria.

Escalabilidad ilimitada o infinita es el rango que se corresponde con aplicaciones web de uso masivo, a partir de varias decenas de miles de usuarios concurrentes en adelante, sin cota superior definida.

Este rango puede ser atendido por las mismas ediciones ya mencionadas de SQL Server, con los adecuados recursos de hardware en el server, sin embargo, es importante destacar que con cantidades muy grandes de usuarios concurrentes, existen diversos escenarios donde puede aumentar la latencia en las transacciones hasta niveles no deseables: este es el tipo de escenarios que se puede beneficiar con las ventajas de las tablas memory-optimized.

Para poder analizar las ventajas que las tablas memory-optimized ofrecen para este tipo de requerimiento (escalabilidad infinita), es necesario analizar con más detalle algunas de las restricciones a la escalabilidad infinita que imponen algunos elementos de la arquitectura tradicional de los engines de bases de datos relacionales.

Algunas restricciones a la escalabilidad infinita impuestas por la arquitectura tradicional de base de datos relacionales

Vamos a repasar brevemente la secuencia de pasos que ocurren en un server de base de datos SQL Server desde que recibe una solicitud de ejecutar un proceso hasta que el resultado del proceso es retornado al proceso cliente en un contexto de escalabilidad infinita.

Para realizar este breve análisis, me focalizaré en un concepto que es útil para este propósito: el concepto de recurso limitante.

En un contexto de alta escalabilidad, el server de base de datos ha de recibir una cantidad muy alta de Remote Procedure Calls concurrentes, provenientes de procesos cliente.

Para poder atender a todos estas invocaciones concurrentes sin tener que encolarlas, es necesario que el server tenga multiples procesadores con múltiples núcleos.

Desde este punto de vista, el procesamiento paralelo es favorable para cubrir el requerimiento de escalabilidad infinita.

Para poder ejecutar el proceso solicitado, el server ha de verificar si los datos necesarios para dicho proceso están disponibles en el Data Caché.

Este es el primer recurso limitante relevante que se presenta en los diversos pasos a seguir para ejecutar el proceso solicitado.

Si en el Buffer Pool no están todos los datos necesarios, el server debe leer los datos faltantes en los files que los contienen y ubicarlos en el Data Caché para poder ejecutar el proceso solicitado.

Al querer leer los datos faltantes en los files que los contienen, aparece el segundo recurso limitante relevante: que el disco que contiene los files donde se alojan los datos requeridos esté ocupado realizando otra tarea previa.

Esto hace que se debe encolar la tarea de lectura y esperar su turno hasta que pueda ser realizada: esta espera es la causante de los wait types “infames” del tipo PageIOLatch*.

Es importante destacar que tener múltiples procesadores no ayuda para reducir ambos recursos limitantes: es más, cuantos más procesadores tenga el server, es más probable que múltiples invocaciones concurrentes estén “peleando” entre sí por los recursos limitantes mencionados.

También es importante destacar que el aumento de memoria permite tener un Data Caché más grande, y por lo tanto, bajar un poco la probabilidad que el server no encuentre los datos necesarios para ejecutar el proceso solicitado y por lo tanto, tenga que leer dichos datos de los files que los contienen.

En cuanto al recurso limitante que el disco esté ocupado con otra tarea previa, el elemento crucial es la velocidad del disco, y tener más procesadores o más memoria en el server no resuelve  en forma significativa este problema (toda vez que es necesario ir a leer los datos faltantes).

En resumen, las restricciones a la escalabilidad infinita surgen por el tiempo total necesario para ejecutar el proceso solicitado, y dicho tiempo se ve afectado por la espera para leer los datos faltantes, y esto ocurre porque dichos datos no están disponibles en el Data Caché al momento de ejecutar el proceso solicitado.

Por lo tanto, si se puede garantizar que los datos necesarios para un proceso en particular estén “siempre” en memoria, se eliminan las restricciones mencionadas a la escalabilidad infinita.

Justamente, esto es lo que aporta el uso de tablas memory-optimized a los escenarios de escalabilidad infinita.

Ventajas de las tablas memory-optimized para sistemas con escalabilidad infinita

Al eliminar la necesidad de tener que ir a leer al disco los datos faltantes para ejecutar un proceso en particular, el uso de tablas memory-optimized puede mejorar en forma significativa la performance de los procesos que operan con dichas tablas, donde el factor de mejora típicamente es por lo menos un orden de magnitud, y en muchos casos, dos órdenes de magnitud: justamente esto justificó que el codename de esta tecnología sea Hekaton, que literalmente es “cien” en griego.

Este nivel de mejora de performance es crucial para aplicaciones tanto Internet Facing como Cloud.

Para aplicaciones Web Internet Facing, se tiene disponible las tablas memory-optimized en SQL Server 2014 Enterprise (On Premises), mientras que para Cloud-based web applications, se tiene disponible tablas memory-optimized en SQL Azure.

Saludos, GEN

 

¿ Qué es un objeto Sequence y en que escenarios se usa ?

En este post voy a tratar brevemente sobre que es un objeto de base de datos Sequence, y en que escenarios se usa.

Focalizaremos nuestra atención en este post en analizar el uso básico de Sequence, las comparaciones principales entre Identity y Sequence, y las principales razones de requerimientos funcionales que justifican el uso de objetos Sequence, pero tratar en otro post el análisis de razones técnicas para optar por el uso de Identity o de Sequence en determinados tipos de escenarios.

Los puntos principales de este post son los siguientes:

  • ¿ Qué es un objeto de base de datos Sequence ?
  • ¿ Qué similitudes y diferencias existen entre Sequence y Identity ?
  • Escenarios típicos donde se usa Sequence

¿ Qué es un objeto de base de datos Sequence ?

Un Sequence es un objeto de base de datos que permite recuperar el próximo valor numérico entero de una secuencia de números, configurada con un valor mínimo, un valor máximo, un valor inicial y un valor de paso o incremento.

Un objeto Sequence posee algunos elementos parecidos a un Identity, pero a diferencia de este último, el Sequence es un objeto de base de datos independiente de una tabla, mientras que un Identity es una propiedad de un campo de una tabla.

Es importante no confundir Sequence con Identity, por lo tanto, es conveniente que revisemos la sintaxis de la sentencia de creación de un Sequence a través de algunos ejemplos.

Antes de entrar de lleno a los ejemplos, es necesario destacar que la secuencia de valores de un objeto Sequence es un conjunto de números que define el dominio de dicho objeto, donde el dominio es el conjunto de valores válidos.

Dado que la operación básica con un objeto Sequence es la de recuperación del próximo valor en la secuencia, debemos tomar aquellas decisiones de diseño que nos aseguren que la lógica de la solución que opera con un objeto Sequence (en base a solicitar el próximo valor de la secuencia), lo haga a sabiendas que el próximo valor a recuperar pertenece al dominio del objeto Sequence a invocar, porque de lo contrario, el objeto Sequence arrojará una exception por haber solicitado un próximo valor cuando el dominio del mismo ya está agotado.

La sintaxis mínima de creación de un objeto Sequence es así:

CREATE SEQUENCE UnTurno;

GO

Esta sentencia no indica ningún valor numérico (ni un valor inicial, ni un valor mínimo, ni un valor máximo, ni un valor de paso o incremento), por lo tanto, el engine aplica los defaults de la sentencia para ejecutar el comando.

En este caso, los defaults aplicados son los siguientes:

El tipo de dato asignado al Sequence creado (dado que el comando usado no define un tipo de dato en particular para el objeto Sequence) es bigint.

El paso o incremento asignado al Sequence creado (dado que el comando no define paso o incremento) es +1, es decir, por defecto, es un Sequence ascendente de incremento unitario.

En este caso, tanto el valor mínimo como el valor inicial asignado al Sequence creado (dado que el comando no define ninguno de los dos) se corresponden con el valor mínimo del data type para un Sequence ascendente, o el valor máximo del data type para un Sequence descendente. En este caso, como el data type es bigint y el Sequence es ascendente por defecto, el valor mínimo y el valor inicial de este Sequence es el valor mínimo del data type bigint, es decir, el valor inicial es el número entero -9.223.372.036.854.775.808.

El valor máximo asignado al Sequence creado (dado que el comando no define un valor inicial) es el valor máximo del data type para un Sequence ascendente, o el valor mínimo del data type para un Sequence descendente. En este caso, como el data type es bigint y el Sequence es ascendente por defecto, el valor máximo de este Sequence es el valor máximo del data type bigint, es decir, el valor máximo es el número entero 9.223.372.036.854.775.807.

En este caso de comando mínimo que opera con todos los defaults, Sequence no usa Caché de números de la secuencia, ni recicla cuando llega al final de la secuencia: en aquellos casos como este, donde los límites de la secuencia (valores inicial y final) coinciden con los límites del data type asociado a dicha sequencia, el Sequence no puede reciclar.

Ya mencionamos al inicio del post que nos focalizaremos en las razones de uso del Sequences basadas en requerimientos funcionales y dejaremos para otro post el análisis de razones técnicas, por lo tanto, en estos ejercicios veremos el uso de Sequence con reciclado de dominio, pero no veremos el uso de Sequence con Caché, dado que los pros y contras de usar Sequence con Caché son principalmente técnicas y de Quality Attributes.

Tal como es esperable, contamos con una System View, sys.sequences, para consultar sobre la metadata y el estado actual de cada objeto Sequence de una base de datos dada:

SELECT * FROM sys.sequences WHERE name = N’UnTurno’;

Al revisar los detalles del Sequence creado en esta System View, podemos verificar que indica que tanto el valor inicial del mismo es el número -9.223.372.036.854.775.808, y el valor mínimo es también dicho número. De igual forma, podemos revisar el resto de los datos de metadata (valor máximo, incremento, valor actual del Sequence) del Sequence recién creado.

Para que un Sequence sea realmente útil, tenemos que ver que comando usar para recuperar el próximo valor de dicha secuencia:

SELECT NEXT VALUE FOR UnTurno;

Vemos que el valor que retorna este comando para el Sequence recién creado se corresponde con el valor inicial, es decir, en este caso retorna el valor -9.223.372.036.854.775.808.

Queda claro que si al crear un objeto Sequence no le definimos en forma explícita que pertenece a un Schema existente dado, por defecto pertenece al Schema dbo.

Veamos ahora un ejemplo algo más completo, donde definimos en forma explícita el data type, valor inicial, y paso o incremento del Sequence (usamos el data model OLTP de AdventureWorks para SQL Server 2012 en los ejemplos restantes):

USE AdventureWorks2012

GO

IF EXISTS (SELECT * FROM sys.sequences WHERE name = N’UnTurno’)

     DROP SEQUENCE UnTurno;

GO

CREATE SEQUENCE UnTurno AS tinyint

     START WITH 0

     INCREMENT BY 100;

GO

— Cuarto Select ha de fallar

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno; — <= Falla!

Era predecible que la cuarta invocación del “NEXT VALUE FOR” debe arrojar una exception, dado que queda fuera del rango válido de valores (fuera del dominio) que pertenecen a la secuencia configurada en el Sequence creado en este ejercicio.

En relación con los data types que se pueden usar para definir en un Sequence, podemos usar todos los data types de tipo entero built-in que soporta SQL Server, así como todos aquellos alias data types que hemos creado que referencian a cualquiera de los data types enteros built-in (un ejemplo con el uso de Sequence con alias data type es un caso de razón principalmente técnica, y no lo veremos en este post).

A continuación veremos cómo crear un objeto Sequence capaz de retornar valores de una secuencia periódica, y en este caso, definimos en forma explícita tanto el data type, como el valor inicial, el valor mínimo, el valor máximo y el paso o incremento. Para lograr esto, vamos a ver un caso con reciclado.

USE AdventureWorks2012

GO

IF EXISTS (SELECT * FROM sys.sequences WHERE name = N’UnTurno’)

     DROP SEQUENCE UnTurno;

GO

CREATE SEQUENCE UnTurno AS tinyint

     START WITH 0

     INCREMENT BY 1

     MINVALUE 0

     MAXVALUE 3

     CYCLE;

GO

— Estos selects no presentan falla

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

Al ejecutar todos estos “NEXT VALUE FOR”, se aprecia claramente el comportamiento periódico o cíclico de la secuencia configurada en el Sequence creado en este ejercicio, y como los parámetros de configuración del mismo determinan dicho comportamiento y el rango de valores del dominio de dicho período.

¿ Qué similitudes y diferencias existen entre Sequence y Identity?

Tal como hemos visto al comienzo de este post, la principal diferencia entre un objeto Sequence y Identity, es que el objeto Sequence es un objeto independiente, mientras que un Identity es una propiedad de una columna de una tabla.

Otras diferencias importantes entre Sequence y Identity es que un objeto Sequence solamente puede retornar valores numéricos de data types enteros, mientras que una columna con propiedad Identity puede estar asociada tanto a data types enteros como a data types con decimales: las columnas Identity solo pueden tener valores enteros, pero el uso de data types como el decimal(x,y) permite tener un rango de valores válidos más amplio que con la mayoría de los data types enteros (el soporte de decimal(18,0) en columas Identity tenía sentido antes de la inclusión del data type bigint).

Una diferencia a destacar es que Sequence permite crear secuencias periódicas de valores numéricos, mientras que Identity solamente puede crear secuencias no periódicas.

Otra diferencia relevante es que Sequence se puede usar en muchos contextos, como por ejemplo, para asignar en forma directa un valor a una variable, formando parte de una expresión en SELECTs, INSERTs o UPDATEs, mientras que un Identity solamente puede asignarle valor a la columna asociada en INSERTs.

En cuanto a las similitudes, ambos permiten crear secuencias de números con un valor inicial, un valor máximo, un valor de paso o incremento, tanto secuencias ascendentes como descendentes.

Un ejemplo sencillo de uso de Sequence con INSERTs y UPDATEs es el siguiente:

USE AdventureWorks2012

GO

IF EXISTS (SELECT * FROM sys.sequences WHERE name = N’UnTurno’)

     DROP SEQUENCE UnTurno;

GO

CREATE SEQUENCE UnTurno AS tinyint

     START WITH 0

     INCREMENT BY 1

     MINVALUE 0

     MAXVALUE 3

     CYCLE;

GO

CREATE TABLE data1

(col1 int not null);

GO

INSERT data1

VALUES (NEXT VALUE FOR UnTurno);

GO 10

UPDATE data1

SET col1 = NEXT VALUE FOR UnTurno;

SELECT col1 from data1;

Escenarios típicos donde se usa Sequence

Vamos a concentrar nuestra atención en algunos ejemplos de requerimientos funcionales que son adecuadamente implementados a través de objetos Sequence.

Por ejemplo, el tipo de requerimiento funcional que claramente se implementa en forma adecuada mediante objetos Sequence es el manejo de turnos por orden de llegada.

En diversos lugares de atención al público o a clientes, como en oficinas públicas o en bancos, es común que el esquema de atención al público es por estricto orden de llegada de los clientes. En forma tradicional, para administrar este manejo y evitar problemas con los clientes, se usan talonarios con tickets pre-impresos con una secuencia de números.

Más recientemente, las organizaciones han mostrado interés en implementar sistemas que emitan los tickets en forma automática, por lo tanto, es conveniente contar con mecanismos robustos que permitan generar secuencias configurables de números similares a las existentes en los talonarios de números.

Es conveniente presentar un caso concreto para apreciar cuan conveniente es usar un Sequence como base de la implementación de un requerimiento funcional de este tipo. Supongamos que tenemos que implementar una solución que sea capaz de generar turnos por orden de llegada en base a tickets numerados secuenciales cíclicos, con una secuencia de números entre 1 y 100, periódica o cíclica, que después del 100 recicle al número 1 y repita la secuencia.

A nivel de la capa de acceso a datos (SQL Server), la implementación tendrá un aspecto similar al siguiente:

USE AdventureWorks2012

GO

IF EXISTS (SELECT * FROM sys.sequences WHERE name = N’UnTurno’)

     DROP SEQUENCE UnTurno;

GO

CREATE SEQUENCE UnTurno AS tinyint

     START WITH 1

     INCREMENT BY 1

     MINVALUE 1

     MAXVALUE 100

     CYCLE;

GO

— Estos selects permiten probar la implementación

SELECT NEXT VALUE FOR UnTurno;

GO 98;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

SELECT NEXT VALUE FOR UnTurno;

Los SELECTs permiten ver como el Sequence ejecuta el reciclado y vuelve a repetir la secuencia de números del dominio en forma periódica, de tal forma que permite operar de igual forma que con un talonario de tickets pre-impreso que cuando se termina un talonario (con el Nro 100), se empieza a usar un nuevo talonario desde el comienzo, es decir, a partir del primer ticket que aparece, con el Nro 1.

 Con esto concluimos esta presentación introductoria del objeto de base de datos Sequence, donde se han expuesto ejemplos de escenarios funcionales que justifican su uso.

Saludos, GEN