Pregunta sobre diseño de PK

02/02/2008 - 14:11 por Luis Flores | Informe spam
Me piden para una tabla de Items un codigo alfanumerico pero con la
siguiente caracteristica;

AAA999, donde AAA son tres letras correspondientes a las tres primeras del
nombre del item y 999 es una secuencia.
Ejemplos de codigos: PAP001, PAP002, ALM001, ALM002...

Quieren que el sistema me genere la proxima secuencia. He buscado en los
BOL como hacer eso en SQL pero no encuentro la manera, solo veo lo de las
identity.


Alguna ayuda al respecto? Me conformaria incluso con tener el codigo
separado en dos campos, si no hay mas remedio y tener un el consecutivo
autoincrementado, si se puede.

Gracias

Luis

Preguntas similare

Leer las respuestas

#11 Jesús López
02/02/2008 - 21:59 | Informe spam
Yo tampoco estoy diciendo que la mía sea mejor que la tuya, sólo he
cuestionado la apreciación de que la tuya sea la mejor y he dado argumentos.
Creo que es muy interesante que cuando hay más de una solución a un problema
se discutan las ventajas e inconvenientes de cada una.

Por otra parte la solución que planteas al problema de la posibilidad de que
haya una violación de la clave primaria es válida, ya que tal violación se
evita utilizando el nivel de aislamiento serializable. Sin embargo esto
plantea otro problema: se pueden producir interbloqueos.

Sea la tabla :

USE tempdb
GO

CREATE TABLE Secuencias
(
Prefijo char(3),
Secuencial int,
PRIMARY KEY (Prefijo, Secuencial)
)

Si ejecutamos este código en dos conexiones al mismo tiempo:

USE Tempdb
GO

BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
IF NOT EXISTS (SELECT * FROM Secuencias WHERE Prefijo = 'XXX')
BEGIN
WAITFOR DELAY '00:00:05'
INSERT INTO Secuencias VALUES ('XXX', 0)
END
COMMIT

Recibimos el error de interbloqueo:

Msg 1205, Level 13, State 48, Line 7
Transaction (Process ID 55) was deadlocked on lock resources with another
process and has been chosen as the deadlock victim. Rerun the transaction.


La explicación es que las dos conexiones pueden haber ejecutado SELECT *
FROM Secuencias WHERE Prefijo = 'XXX' antes que la otra haga el INSERT. Al
haber ejecutado la sentencia las dos conexiones han obtenido un bloqueo
compartido de intervalo de claves que impide a la otra otra conexión
insertar un registro con el valor de prefijo 'XXX', produciéndose de esta
manera el interbloqueo.

La secuencia sería esta:

Conexión 1: SELECT * FROM Secuencias WHERE Prefijo = 'XXX' -> obtiene
bloqueo compartido en el intervalo de claves 'XXX' - 'XXX'
Conexión 2: SELECT * FROM Secuencias WHERE Prefijo = 'XXX' -> obtiene
bloqueo compartido en el intervalo de claves 'XXX' - 'XXX'
Conexión 1: Intenta insertar registro con Prefijo = 'XXX', pero no puede ya
que para ello tendría que poner un bloqueo exclusivo sobre la clave 'XXX'
que es incompatible con el bloqueo de intervalo de claves que tiene la
conexión 1. Por tanto queda a la espera de que la conexión 1 libere el
bloqueo
Conexión 2: Intenta insertar registro con Prefijo = 'XXX', pero no puede ya
que para ello tendría que poner un bloqueo exclusivo sobre la clave 'XXX'
que es incompatible con el bloqueo de intervalo de claves que tiene la
conexión 2. Por tanto queda a la espera de que la conexión 2 libere el
bloqueo


La conexión 1 está esperando a que la conexión 2 libere un bloqueo, y la
conexión 2 está esperando a que la conexión 1 libere un bloqueo. Ninguna de
las dos puede continuar. SQL Server detecta esta condición conocida como
interbloqueo, elige una como víctima (la más barata), deshace la transacción
de la víctima y le envía un error.

Saludos:


Jesús López
www.solidq.com




"Alejandro Mesa" escribió en el
mensaje news:
Jesús López,

La solución de Alejandro tiene un problema: presupone que todos los
prefijos
ya están añadidos a la tabla de prefijos. ¿Qué pása si intentas añadir un
item cuyo prefijo no está en la tabla de prefijos? Sencillamente no
funciona. Una primera aproximación a ese problema sería que el
procedimiento
usp_TomarUltimaSeq insertara el prefijo en caso de no existir, pero eso
plantea un nuevo problema ¿Qué pasa si dos conexiones intentan insertar
un
nuevo item cuyo prefijo no está en la tabla de prefijos? pues que las dos
conexiones intentarán insertar el mismo prefijo, violándose la clave
primaria.



El primer problema que plateas tiene sentido y tiene solución. El segundo
problema no es en realidad un problema, pues existen metodos para evitar
esto.

Con lo anterior no quiero decir que la solucion que sugei sea mejor que la
tuya.

USE tempdb
GO

CREATE TABLE dbo.prefijo_item_ultima_seq (
prefijo_item_ultima_seqID INT NOT NULL IDENTITY(1, 1),
prefijo_item CHAR(3) NOT NULL,
ultima_seq INT NOT NULL DEFAULT(0) CHECK(ultima_seq >= 0),
CONSTRAINT PK_prefijo_item_ultima_seq PRIMARY KEY CLUSTERED
(prefijo_item_ultima_seqID),
CONSTRAINT UQ_prefijo_item_ultima_seq_prefijo_item UNIQUE (prefijo_item)
)
go

CREATE PROCEDURE dbo.usp_TomarUltimaSeq
@prefijo_item CHAR(3),
@seq INT OUTPUT
AS
DECLARE @error INT
DECLARE @rc INT

SET NOCOUNT ON

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION

IF NOT EXISTS (
SELECT *
FROM dbo.prefijo_item_ultima_seq
WHERE prefijo_item = @prefijo_item
)
BEGIN
SET @seq = 1

INSERT INTO prefijo_item_ultima_seq (
prefijo_item,
ultima_seq
) VALUES (@prefijo_item, 1)

END
ELSE
UPDATE dbo.prefijo_item_ultima_seq
SET @seq = ultima_seq = ultima_seq + 1
WHERE prefijo_item = @prefijo_item

SELECT @error = @@ERROR, @rc = @@ROWCOUNT

IF @error != 0 OR @rc = 0 GOTO ErrorHandler

COMMIT TRANSACTION

RETURN @@ERROR

ErrorHandler:
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION

RETURN @error
GO

CREATE TABLE dbo.Item (
ItemID INT NOT NULL IDENTITY(1, 1),
prefijo_item CHAR(3) NOT NULL REFERENCES
dbo.prefijo_item_ultima_seq(prefijo_item),
seq INT NOT NULL CHECK(seq >= 0),
prefijo_seq AS ISNULL(prefijo_item + RIGHT('000' + LTRIM(seq), 3), ''),
Nombre VARCHAR(50) NOT NULL,
CONSTRAINT PK_Item PRIMARY KEY CLUSTERED (ItemID),
CONSTRAINT UQ_Item_prefijo_item_seq UNIQUE (prefijo_item, seq),
CONSTRAINT CK_Item_Nombre CHECK(Nombre LIKE prefijo_item + '%')
)
go

CREATE PROCEDURE dbo.usp_InsertItem
@Nombre VARCHAR(50)
AS
SET NOCOUNT ON

DECLARE @prefijo CHAR(3)
DECLARE @seq INT

DECLARE @rv INT
DECLARE @rc INT
DECLARE @error INT

SET @prefijo = LEFT(@Nombre, 3)

BEGIN TRANSACTION

EXEC @rv = dbo.usp_TomarUltimaSeq @prefijo, @seq OUTPUT

SET @error = COALESCE(NULLIF(@rv, 0), @@ERROR)

IF @error != 0 OR @seq IS NULL GOTO ErrorHandler

INSERT INTO dbo.Item (prefijo_item, seq, Nombre)
VALUES (@prefijo, @seq, @Nombre)

SELECT @error = @@ERROR, @rc = @@ROWCOUNT

IF @error != 0 OR @rc = 0 GOTO ErrorHandler

COMMIT TRANSACTION

RETURN @@ERROR

ErrorHandler:
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION

RETURN COALESCE(NULLIF(@error, 0), -1)
GO

DECLARE @rv INT

EXEC @rv = dbo.usp_InsertItem 'SQL Server 7.0'
EXEC @rv = dbo.usp_InsertItem 'SQL Server 2000'
EXEC @rv = dbo.usp_InsertItem 'SQL Server 2005'
EXEC @rv = dbo.usp_InsertItem 'SQL Server 2008'

EXEC @rv = dbo.usp_InsertItem 'MS Office 2003'
EXEC @rv = dbo.usp_InsertItem 'MS Office 2007'
GO

SELECT * FROM dbo.prefijo_item_ultima_seq
SELECT * FROM dbo.Item
GO

DROP PROCEDURE dbo.usp_InsertItem, dbo.usp_TomarUltimaSeq
GO

DROP TABLE dbo.Item, dbo.prefijo_item_ultima_seq
GO


AMB




"Jesús López" wrote:

¿Por qué dices que es la mejor solución?

La solución de Alejandro tiene un problema: presupone que todos los
prefijos
ya están añadidos a la tabla de prefijos. ¿Qué pása si intentas añadir un
item cuyo prefijo no está en la tabla de prefijos? Sencillamente no
funciona. Una primera aproximación a ese problema sería que el
procedimiento
usp_TomarUltimaSeq insertara el prefijo en caso de no existir, pero eso
plantea un nuevo problema ¿Qué pasa si dos conexiones intentan insertar
un
nuevo item cuyo prefijo no está en la tabla de prefijos? pues que las dos
conexiones intentarán insertar el mismo prefijo, violándose la clave
primaria.

No te creas que cuando yo he propuesto mi solución no he pensado en una
tabla auxiliar de "secuencias" o prefijos como la quieras llamar. He
visto
los problemas que planteaba y he pensado que era mejor solución utilizar
una
variación de la técnica MAX+1 para secuencias. Además la técnica que yo
he
propuesto es más simple, no requiere de tablas auxiliares ni crear
procedimientos auxiliares. También te puedo decir que es eficiente. He
probado de varias formas diferentes de implementarla y he llegado a la
conclusión de que la técnica propuesta es la implementación más eficiente
posible de técnica MAX+1 para secuencias.

Saludos:

Jesús López
www.solidq.com






"Luis Flores" escribió en el mensaje
news:%
> Gracias.
> Creo que es la mejor solución que puedo implementar y te agradezco los
> detalles y la explicacion.
>
> Saludos
> Luis
>
> "Alejandro Mesa" escribió en
> el
> mensaje news:
>> Luis Flores,
>>
>> Pudieras tener una tabla de ayuda, donde almacenas el par
>> (prefijo_Item,
>> ultima_secuencia) donde el prefijo del item es clave primaria.
>> Entonces
>> cada
>> vez que insertes un item, tomas el valor de la proxima secuencia desde
>> esta
>> tabla.
>>
>> USE tempdb
>> GO
>>
>> CREATE TABLE dbo.prefijo_item_ultima_seq (
>> prefijo_item_ultima_seqID INT NOT NULL IDENTITY(1, 1),
>> prefijo_item CHAR(3) NOT NULL,
>> ultima_seq INT NOT NULL DEFAULT(0) CHECK(ultima_seq >= 0),
>> CONSTRAINT PK_prefijo_item_ultima_seq PRIMARY KEY CLUSTERED
>> (prefijo_item_ultima_seqID),
>> CONSTRAINT UQ_prefijo_item_ultima_seq_prefijo_item UNIQUE
>> (prefijo_item)
>> )
>> go
>>
>> CREATE PROCEDURE dbo.usp_TomarUltimaSeq
>> @prefijo_item CHAR(3),
>> @seq INT OUTPUT
>> AS
>> SET nocount ON
>>
>> UPDATE dbo.prefijo_item_ultima_seq
>> SET @seq = ultima_seq = ultima_seq + 1
>> WHERE prefijo_item = @prefijo_item
>>
>> RETURN @@error
>> GO
>>
>> CREATE TABLE dbo.Item (
>> ItemID INT NOT NULL IDENTITY(1, 1),
>> prefijo_item CHAR(3) NOT NULL REFERENCES
>> dbo.prefijo_item_ultima_seq(prefijo_item),
>> seq INT NOT NULL CHECK(seq >= 0),
>> prefijo_seq AS ISNULL(prefijo_item + RIGHT('000' + LTRIM(seq), 3),
>> ''),
>> Nombre VARCHAR(50) NOT NULL,
>> CONSTRAINT PK_Item PRIMARY KEY CLUSTERED (ItemID),
>> CONSTRAINT UQ_Item_prefijo_item_seq UNIQUE (prefijo_item, seq),
>> CONSTRAINT CK_Item_Nombre CHECK(Nombre LIKE prefijo_item + '%')
>> )
>> go
>>
>> CREATE PROCEDURE dbo.usp_InsertItem
>> @Nombre VARCHAR(50)
>> AS
>> SET NOCOUNT ON
>>
>> DECLARE @prefijo CHAR(3)
>> DECLARE @seq INT
>>
>> DECLARE @rv INT
>> DECLARE @rc INT
>> DECLARE @error INT
>>
>> SET @prefijo = LEFT(@Nombre, 3)
>>
>> BEGIN TRANSACTION
>>
>> EXEC @rv = dbo.usp_TomarUltimaSeq @prefijo, @seq OUTPUT
>>
>> SET @error = COALESCE(NULLIF(@rv, 0), @@ERROR)
>>
>> IF @error != 0 OR @seq IS NULL GOTO ErrorHandler
>>
>> INSERT INTO dbo.Item (prefijo_item, seq, Nombre)
>> VALUES (@prefijo, @seq, @Nombre)
>>
>> SELECT @error = @@ERROR, @rc = @@ROWCOUNT
>>
>> IF @error != 0 OR @rc = 0 GOTO ErrorHandler
>>
>> COMMIT TRANSACTION
>>
>> RETURN @@ERROR
>>
>> ErrorHandler:
>> IF @@TRANCOUNT > 0
>> ROLLBACK TRANSACTION
>>
>> RETURN COALESCE(NULLIF(@error, 0), -1)
>> GO
>>
>> INSERT INTO dbo.prefijo_item_ultima_seq (
>> prefijo_item,
>> ultima_seq
>> ) VALUES ('SQL', 0)
>>
>> INSERT INTO dbo.prefijo_item_ultima_seq (
>> prefijo_item,
>> ultima_seq
>> ) VALUES ('MS ', 0)
>> GO
>>
>> SELECT * FROM dbo.prefijo_item_ultima_seq
>> GO
>>
>> DECLARE @rv INT
>>
>> EXEC @rv = dbo.usp_InsertItem 'SQL Server 7.0'
>> EXEC @rv = dbo.usp_InsertItem 'SQL Server 2000'
>> EXEC @rv = dbo.usp_InsertItem 'SQL Server 2005'
>> EXEC @rv = dbo.usp_InsertItem 'SQL Server 2008'
>>
>> EXEC @rv = dbo.usp_InsertItem 'MS Office 2003'
>> EXEC @rv = dbo.usp_InsertItem 'MS Office 2007'
>> GO
>>
>> SELECT * FROM dbo.prefijo_item_ultima_seq
>> SELECT * FROM dbo.Item
>> GO
>>
>> DROP PROCEDURE dbo.usp_InsertItem, dbo.usp_TomarUltimaSeq
>> GO
>>
>> DROP TABLE dbo.Item, dbo.prefijo_item_ultima_seq
>> GO
>>
>> Ahora te comento porque decidi poner el prefijo del item y la
>> secuencia
>> en
>> columnas separadas. Si deseas hacer este tipo de busquedas:
>>
>> declare @prefijo char(3)
>> declare @seq_inicio int, @seq_fin int
>>
>> set @prefijo = 'SQL'
>> set @seq_inicio = 2
>> set @seq_fin = 3
>>
>> select
>> *
>> from
>> dbo.Item
>> where
>> prefijo_item = @prefijo_Item
>> and seq between @seq_inicio and @seq_fin
>>
>> No tendras ningun problema con el esquema usado, pero si en cambio
>> solo
>> tienes la columna prefijo_item_seq (AAA999), entonces tendras que
>> manipular
>> la columna en la expresion y no estaras ayudando al optimizador de
>> queries en
>> la busqueda del plan de ejecucion adecuado.
>>
>> set @prefijo = 'SQL'
>> set @seq_inicio = 2
>> set @seq_fin = 3
>>
>> select
>> *
>> from
>> dbo.Item
>> where
>> prefijo_item like @prefijo_Item + '%'
>> and right(seq, 3) between @seq_inicio and @seq_fin
>>
>> Fijate que la expresion de filtro cambia, sobre todo fijate en la
>> introduccion de right(seq, 3).
>>
>> No existe una formula magica en esto de dise~nar una base de datos,
>> asi
>> que
>> tambien te comento que usar este esquema tiene el inconveniente de que
>> no
>> podemos llamar al procedimiento que nos devuelve la proxima secuencia
>> a
>> usar
>> para un determinado prefijo, durante una carga masiva (BULK INSERT).
>>
>>
>> AMB
>>
>>
>> "Luis Flores" wrote:
>>
>>> Me piden para una tabla de Items un codigo alfanumerico pero con la
>>> siguiente caracteristica;
>>>
>>> AAA999, donde AAA son tres letras correspondientes a las tres
>>> primeras
>>> del
>>> nombre del item y 999 es una secuencia.
>>> Ejemplos de codigos: PAP001, PAP002, ALM001, ALM002...
>>>
>>> Quieren que el sistema me genere la proxima secuencia. He buscado
>>> en
>>> los
>>> BOL como hacer eso en SQL pero no encuentro la manera, solo veo lo de
>>> las
>>> identity.
>>>
>>>
>>> Alguna ayuda al respecto? Me conformaria incluso con tener el codigo
>>> separado en dos campos, si no hay mas remedio y tener un el
>>> consecutivo
>>> autoincrementado, si se puede.
>>>
>>> Gracias
>>>
>>> Luis
>>>
>>>
>>>
>
>



Respuesta Responder a este mensaje
#12 Jesús López
02/02/2008 - 22:11 | Informe spam
"Luis Flores" escribió en el mensaje
news:%
>
Sí da problemas, la transacción no hace que se bloquen las tablas, de
forma predeterminada SQL Server bloquea a nivel de regirstro.




No el select en este caso. El INSERT o el UPDATE sería lo que provocaría
el bloqueo. Al menos eso es lo que yo pensaba que es la forma en que
funciona.


BEGIN TRANSACTION

IF NOT EXISTS( SELECT * FROM Secuencias WHERE Prefijo = @Prefijo)
INSERT INTO Secuencias VALUES (@Prefijo, 0)




Faltó el else, donde habría un UPDATE que como el insert hará que se
bloquee.





Sí ya sé que falta el ELSE, pero no me hace falta para demostrar la
posibilidad de violación de clave


La instrucción BEGIN TRANSACTION por sí misma no bloquea absolutamente
nada.




No, pero el INSERT o el UPDATE sí lo harán. El tiempo del select no sería
tan significativo para una tabla limitada a consecutivos.




Efectivamente el insert y update bloquean el registro de forma exclusiva
hasta la finalización de la transacción. Pero primero se ejecuta el SELECT y
el select no bloquea de forma exlusiva

Luego viene la instrucción IF NOT EXISTS(). Nada impide que dos
conexiones estén ejecutando al mismo tiempo esta instrucción SELECT y
obtengan el mismo resultado. La instrucción select produce bloqueos
compartidos que permiten a otras conexiones leer los datos.

Por lo tanto dos conexiones pueden intentar insertar el mismo prefijo
violando la clave primaria. La situación puede darse perfectamente. Sobre
todo en un sistema con bastante carga y una máquina con varios
procesadores.




Claro que puede darse pero la probabilidad es muy pequeña y si da error de
clave primaria, pues se reintenta y listo.
Ojo: no digo que lo que sugieres no sea lo más preciso.




Así es, la probabilidad es pequeña. Pero ya tienes que andar gestionando esa
posibilidad en el código, lo cual supone más tiempo de desarrollo.



Observa el siguiente código que es el mismo código real, sólo le añadimos
un DELAY para que la situación se produzca más fácilmente.

Prepara este script en dos conexiones de SQL Server:

USE Tempdb
GO

BEGIN TRANSACTION
IF NOT EXISTS (SELECT * FROM Secuencias WHERE Prefijo = 'XXX')
BEGIN
WAITFOR DELAY '00:00:05'
INSERT INTO Secuencias VALUES ('XXX', 0)
END



Antes del insert OK y es lógico pero si lo pones después la historia es
distinta porque "Secuencias" estaría bloqueada hasta el Commit. No?

COMMIT

Y luego ejecuta los dos al mismo tiempo. En el segundo que ejecutas
recibes el error de violación de clave primaria.




Se entiende tu punto pero no es tan grave el asunto. Ojalá me entiendas a
mi :)




Yo no digo que el asunto sea grave, simplemente estoy discutiendo cual de
las soluciones es mejor.



El plan de ejecución que me sale para la ejecución del procedimietno
InsertItem no puede ser mejor, no aparece ningún tipo de agregado,



Dije agregado porque citaste MAX() pero luego chequeé que es un TOP 1 y
Order by lo que haces. Ok. Es que siempre que veo construcciones
similares sobre una valor repetido me luce que es más trabajoso que un
simple seek a la pk de otra tabla.




En este caso es un SEEK sobre un índice, que sólo devuelve un registro.
Rapidísimo.

Pero la aclaración me confirma lo dicho de que tu propuesta es muy buena.
En eso estamos de acuerdo.

De nuevo gracias pues he aprendido y aclarado algunas cosas nuevas con tus
mensajes. Se ve claro que sabes de lo que estas hablando.

Saludos y gracias,

Luis



Respuesta Responder a este mensaje
#13 Jesús López
02/02/2008 - 22:16 | Informe spam
Pero con el nivel SERIALIZABLE tienes interbloqueos en este caso en
concreto. No sé que es peor: si un interbloqueo o la violación de la clave
primaria. Personalmente prefiero la violación de la clave, siempre que puedo
intento evitar el nivel SERIALIZABLE, y además el interbloqueo tarda un
poquito en detectarse.

Saludos:

Jesús López



"Alejandro Mesa" escribió en el
mensaje news:
Jesús López,

La instrucción BEGIN TRANSACTION por sí misma no bloquea absolutamente
nada.

Luego viene la instrucción IF NOT EXISTS(). Nada impide que dos
conexiones estén ejecutando al mismo tiempo esta instrucción SELECT y
obtengan el mismo resultado. La instrucción select produce bloqueos
compartidos que permiten a otras conexiones leer los datos.



Lo que mencionas no se cumple si el nivel de aislamiento es SERIALIZABLE.
Bajo este nivel, se garantiza:

- Otra transaccion no puede leer data que ha sido modificada y no salvada
- Ninguna otra transaccion puede modificar data que se ha leido por la
transaccion corriente hasta que esta se haya completado.
- Ninguna otra transaccion puede insertar nuevas filas con clave que
pueden
caer en el range de las claves leidas por cualquier sentencia en la
transaccion corriente hasta que esta se complete.

Lo importante aqui es que se tiene un indice unico por el prefijo.

Saludos,
Alejandro Mesa


"Jesús López" wrote:

>>pero eso plantea un nuevo problema ¿Qué pasa si dos conexiones intentan
>>insertar un nuevo item cuyo prefijo no está en la tabla de prefijos?
>>pues
>>que las dos conexiones intentarán insertar el mismo prefijo, violándose
>>la
>>clave primaria.
>>
>
> Realmente no da problemas porque hay una transaccion explícita abierta
> que bloqueará ambas tablas actualizadas. De hecho con el begin
> transaction no es necesario hacer bloqueos explicitos.
> Claro, solo hay que hacer la prueba para saber si funciona.
>


Sí da problemas, la transacción no hace que se bloquen las tablas, de
forma
predeterminada SQL Server bloquea a nivel de regirstro.

Dada la tabla de secuencias:

USE tempdb
GO

CREATE TABLE Secuencias
(
Prefijo char(3),
Secuencial int,
PRIMARY KEY (Prefijo, Secuencial)
)

Supongamos que el código que estás ejecutando es el siguiente:

BEGIN TRANSACTION

IF NOT EXISTS( SELECT * FROM Secuencias WHERE Prefijo = @Prefijo)
INSERT INTO Secuencias VALUES (@Prefijo, 0)

etc etc.

COMMIT

La instrucción BEGIN TRANSACTION por sí misma no bloquea absolutamente
nada.

Luego viene la instrucción IF NOT EXISTS(). Nada impide que dos
conexiones estén ejecutando al mismo tiempo esta instrucción SELECT y
obtengan el mismo resultado. La instrucción select produce bloqueos
compartidos que permiten a otras conexiones leer los datos.

Por lo tanto dos conexiones pueden intentar insertar el mismo prefijo
violando la clave primaria. La situación puede darse perfectamente. Sobre
todo en un sistema con bastante carga y una máquina con varios
procesadores.

Observa el siguiente código que es el mismo código real, sólo le añadimos
un
DELAY para que la situación se produzca más fácilmente.

Prepara este script en dos conexiones de SQL Server:

USE Tempdb
GO

BEGIN TRANSACTION
IF NOT EXISTS (SELECT * FROM Secuencias WHERE Prefijo = 'XXX')
BEGIN
WAITFOR DELAY '00:00:05'
INSERT INTO Secuencias VALUES ('XXX', 0)
END
COMMIT

Y luego ejecuta los dos al mismo tiempo. En el segundo que ejecutas
recibes
el error de violación de clave primaria.

En el código real el delay no existe, pero nada impide que la secuencia
de
ejecución sea la siguiente:

Conexión 1: SELECT * FROM Secuencias WHERE Prefijo = 'XXX'
Conexión 2: SELECT * FROM Secuencias WHERE Prefijo = 'XXX'
Conexión 1: INSERT INTO ... COMMIT
Conexión 2: INSERT INTO (Violación de clave primaria)


> Tu solución también es buena, de hecho muy buena. Solo que pensé en el
> crecimiento de la tabla, lo cual puede ser mas eficiente con una tabla
> de
> consecutivos que hacer cada vez un select y un agregado

El plan de ejecución que me sale para la ejecución del procedimietno
InsertItem no puede ser mejor, no aparece ningún tipo de agregado,
simplemente se busca un registro en un índice, no hay cosa más rápida en
SQL
Server.


(14% del batch)
SELECT < Top <- Compute Scalar < Index Seek


(86% del batch)
INSERT < Index Insert < Compute Scalar < Clustered Index Insert




Aquí lo tienes completo


1 1 SELECT TOP(1) @Secuential = Secuential + 1 FROM Items
WHERE ItemPrefix = @ItemPrefix ORDER BY Secuential DESC 1 1 0
NULL
NULL NULL NULL 1 NULL NULL NULL 0,0032833 NULL NULL SELECT 0 NULL
1 1 |--Top(TOP EXPRESSION:((1))) 1 2 1 Top Top TOP EXPRESSION:((1))
NULL 1
0 1E-07 11 0,0032833 [Expr1003] NULL PLAN_ROW 0 1
0 0 |--Compute
Scalar(DEFINE:([Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
[Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1))) 1 3 2 Compute
Scalar
Compute Scalar
DEFINE:([Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
[Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1))
[Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
[Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1) 1 0 1E-07 15
0,0032832
[Pruebas].[dbo].[Items].[Secuential],
[Pruebas].[dbo].[Items].[Secuential],
[Expr1003] NULL PLAN_ROW 0 1
1 1 |--Index
Seek(OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
SEEK:([Pruebas].[dbo].[Items].[ItemPrefix]=[@ItemPrefix]) ORDERED
FORWARD) 1
4 3 Index Seek Index Seek
OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
SEEK:([Pruebas].[dbo].[Items].[ItemPrefix]=[@ItemPrefix]) ORDERED FORWARD
[Pruebas].[dbo].[Items].[Secuential] 1 0,003125 0,0001581 11 0,0032831
[Pruebas].[dbo].[Items].[Secuential] NULL PLAN_ROW 0 1


1 1 INSERT INTO Items VALUES( @CodItem, @ItemName) 2 1 0 NULL NULL NULL
NULL
1 NULL NULL NULL 0,02000326 NULL NULL INSERT 0 NULL
1 1 |--Index
Insert(OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
SET:([CodItem1006] = [Pruebas].[dbo].[Items].[CodItem],[ItemPrefix1007] >> [Pruebas].[dbo].[Items].[ItemPrefix],[Secuential1008] >> [Pruebas].[dbo].[Items].[Secuential])) 2 2 1 Index Insert Insert
OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
SET:([CodItem1006] = [Pruebas].[dbo].[Items].[CodItem],[ItemPrefix1007] >> [Pruebas].[dbo].[Items].[ItemPrefix],[Secuential1008] >> [Pruebas].[dbo].[Items].[Secuential]) NULL 1 0,01 1E-06 9 0,02000326 NULL
NULL PLAN_ROW 0 1
0 0 |--Compute
Scalar(DEFINE:([Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
[Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0)))
2 3 2 Compute Scalar Compute Scalar
DEFINE:([Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
[Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0))
[Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
[Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0)
1 0 1E-07 22 0,01000226 [Pruebas].[dbo].[Items].[CodItem],
[Pruebas].[dbo].[Items].[ItemPrefix],
[Pruebas].[dbo].[Items].[Secuential]
NULL PLAN_ROW 0 1
1 1 |--Clustered Index
Insert(OBJECT:([Pruebas].[dbo].[Items].[PK__Items__412EB0B6]),
SET:([Pruebas].[dbo].[Items].[CodItem] >> RaiseIfNull([@CodItem]),[Pruebas].[dbo].[Items].[ItemName] >> RaiseIfNull([@ItemName]))) 2 4 3 Clustered Index Insert Insert
OBJECT:([Pruebas].[dbo].[Items].[PK__Items__412EB0B6]),
SET:([Pruebas].[dbo].[Items].[CodItem] >> RaiseIfNull([@CodItem]),[Pruebas].[dbo].[Items].[ItemName] >> RaiseIfNull([@ItemName])) NULL 1 0,01 1E-06 13 0,01000216
[Pruebas].[dbo].[Items].[CodItem] NULL PLAN_ROW 0 1



"Luis Flores" escribió en el mensaje
news:%
>> ya están añadidos a la tabla de prefijos. ¿Qué pása si intentas añadir
>> un
>> item cuyo prefijo no está en la tabla de prefijos? Sencillamente no
>> funciona. Una primera aproximación a ese problema sería que el
>> procedimiento usp_TomarUltimaSeq insertara el prefijo en caso de no
>> existir,
>
> Claro. Yo noté ese detalle y lo completé justamente haciendo un if not
> exists() y un insert. Lo importante es tomar la idea de la solución
> que
> el me dio que, como tu propuesta, me ayudó a dar solucion terminada a
> mi
> problema. Obviamente no hice solo copiar y pegar.
>
>>
>
>>
>> No te creas que cuando yo he propuesto mi solución no he pensado en
>> una
>> tabla auxiliar de "secuencias" o prefijos como la quieras llamar. He
>> visto los problemas que planteaba y he pensado que era mejor solución
>> utilizar una variación de la técnica MAX+1 para secuencias. Además la
>> técnica que yo he propuesto es más simple, no requiere de tablas
>> auxiliares ni crear procedimientos auxiliares. También te puedo decir
>> que
>> es eficiente. He probado de varias formas diferentes de implementarla
>> y
>> he llegado a la conclusión de que la técnica propuesta es la
>> implementación más eficiente posible de técnica MAX+1 para secuencias.
>>
>
> Tu solución también es buena, de hecho muy buena. Solo que pensé en el
> crecimiento de la tabla, lo cual puede ser mas eficiente con una tabla
> de
> consecutivos que hacer cada vez un select y un agregado. Además
> pensando
> quizás en generalización del generador del consecutivo, bastaría con
> agregar el parámetro NombreTabla a la tabla de consecutivos.
>
> De todos modos gracias por tu aportación que ha sido muy buena y
> tambien
> por la explicación.
>
>
> Saludos
> Luis
>
>
>



Respuesta Responder a este mensaje
#14 Josias
02/02/2008 - 22:48 | Informe spam
Hola Jesús:

dos preguntas:
-Yo tengo una tabla de documentos donde la clave es tipo char(8) para
guardar un numero secuencial que lleva ceros a la izquierda. Por razones que
no voy a explicar aqui debe ser char(8) y no un identity. Podría adaptar
ese metodo que usas para llevar de forma eficiente el consecutivo sin tener
que poner una tabla aparte ?
Nota: la tabla tiende a crecer mucho (imaginate son 8 posiciones de la
clave).

-Que ocurre si el sp_getapplock no prospera?



"Jesús López" escribió en el
mensaje news:
Bueno pues una solución podría ser la siguiente:


CREATE TABLE Items
(
CodItem char(6) PRIMARY KEY,
ItemPrefix AS LEFT(CodItem, 3),
Secuential AS CONVERT(int, RIGHT(CodItem, 3)),
ItemName varchar(50) NOT NULL
)
GO

CREATE INDEX Items_Prefix_Secuential ON Items(ItemPrefix, Secuential DESC)


ALTER PROCEDURE InsertItem
@ItemName varchar(50),
@CodItem char(6) OUT
AS
BEGIN
SET NOCOUNT ON
DECLARE @ItemPrefix char(3)
SET @ItemPrefix = LEFT(@ItemName, 3)
DECLARE @Secuential int
BEGIN TRANSACTION
EXEC sp_getapplock @ItemPrefix, 'Exclusive'

SELECT TOP(1) @Secuential = Secuential + 1
FROM Items
WHERE ItemPrefix = @ItemPrefix
ORDER BY Secuential DESC

IF @Secuential IS NULL SET @Secuential = 1

SET @CodItem = @ItemPrefix + RIGHT( '00' + CONVERT(varchar(3),
@Secuential), 3)
SET NOCOUNT OFF
INSERT INTO Items VALUES( @CodItem, @ItemName)
SET NOCOUNT ON
EXEC sp_releaseapplock @ItemPrefix
COMMIT
END


Pruebas:

DECLARE @CodItem char(6)
EXEC InsertItem 'AAA uno', @CodItem OUT
PRINT @CodItem
GO

DECLARE @CodItem char(6)
EXEC InsertItem 'AAA dos', @CodItem OUT
PRINT @CodItem
GO

DECLARE @CodItem char(6)
EXEC InsertItem 'AAA tres', @CodItem OUT
PRINT @CodItem
GO

DECLARE @CodItem char(6)
EXEC InsertItem BBB uno', @CodItem OUT
PRINT @CodItem
GO

DECLARE @CodItem char(6)
EXEC InsertItem BBB dos', @CodItem OUT
PRINT @CodItem
GO

DECLARE @CodItem char(6)
EXEC InsertItem BBB tres', @CodItem OUT
PRINT @CodItem
GO

Saludos:

Jesús López
www.solidq.com



"Luis Flores" escribió en el mensaje
news:
Me piden para una tabla de Items un codigo alfanumerico pero con la
siguiente caracteristica;

AAA999, donde AAA son tres letras correspondientes a las tres primeras
del nombre del item y 999 es una secuencia.
Ejemplos de codigos: PAP001, PAP002, ALM001, ALM002...

Quieren que el sistema me genere la proxima secuencia. He buscado en
los BOL como hacer eso en SQL pero no encuentro la manera, solo veo lo de
las identity.


Alguna ayuda al respecto? Me conformaria incluso con tener el codigo
separado en dos campos, si no hay mas remedio y tener un el consecutivo
autoincrementado, si se puede.

Gracias

Luis





Respuesta Responder a este mensaje
#15 Alejandro Mesa
02/02/2008 - 23:03 | Informe spam
Jesús López,

Interbloqueos por que razón?

Pudieras probar o corroborar lo que planteas?

Sinplemente una transaccion tendra que esperar a que la otra termine. Si
tedas cuenta esta no es una transaccion larga para la cantidad de items que
estaran siendo insetados concurrentemente.


Alejandro Mesa


"Jesús López" wrote:

Pero con el nivel SERIALIZABLE tienes interbloqueos en este caso en
concreto. No sé que es peor: si un interbloqueo o la violación de la clave
primaria. Personalmente prefiero la violación de la clave, siempre que puedo
intento evitar el nivel SERIALIZABLE, y además el interbloqueo tarda un
poquito en detectarse.

Saludos:

Jesús López



"Alejandro Mesa" escribió en el
mensaje news:
> Jesús López,
>
>> La instrucción BEGIN TRANSACTION por sí misma no bloquea absolutamente
>> nada.
>>
>> Luego viene la instrucción IF NOT EXISTS(). Nada impide que dos
>> conexiones estén ejecutando al mismo tiempo esta instrucción SELECT y
>> obtengan el mismo resultado. La instrucción select produce bloqueos
>> compartidos que permiten a otras conexiones leer los datos.
>
> Lo que mencionas no se cumple si el nivel de aislamiento es SERIALIZABLE.
> Bajo este nivel, se garantiza:
>
> - Otra transaccion no puede leer data que ha sido modificada y no salvada
> - Ninguna otra transaccion puede modificar data que se ha leido por la
> transaccion corriente hasta que esta se haya completado.
> - Ninguna otra transaccion puede insertar nuevas filas con clave que
> pueden
> caer en el range de las claves leidas por cualquier sentencia en la
> transaccion corriente hasta que esta se complete.
>
> Lo importante aqui es que se tiene un indice unico por el prefijo.
>
> Saludos,
> Alejandro Mesa
>
>
> "Jesús López" wrote:
>
>> >>pero eso plantea un nuevo problema ¿Qué pasa si dos conexiones intentan
>> >>insertar un nuevo item cuyo prefijo no está en la tabla de prefijos?
>> >>pues
>> >>que las dos conexiones intentarán insertar el mismo prefijo, violándose
>> >>la
>> >>clave primaria.
>> >>
>> >
>> > Realmente no da problemas porque hay una transaccion explícita abierta
>> > que bloqueará ambas tablas actualizadas. De hecho con el begin
>> > transaction no es necesario hacer bloqueos explicitos.
>> > Claro, solo hay que hacer la prueba para saber si funciona.
>> >
>>
>>
>> Sí da problemas, la transacción no hace que se bloquen las tablas, de
>> forma
>> predeterminada SQL Server bloquea a nivel de regirstro.
>>
>> Dada la tabla de secuencias:
>>
>> USE tempdb
>> GO
>>
>> CREATE TABLE Secuencias
>> (
>> Prefijo char(3),
>> Secuencial int,
>> PRIMARY KEY (Prefijo, Secuencial)
>> )
>>
>> Supongamos que el código que estás ejecutando es el siguiente:
>>
>> BEGIN TRANSACTION
>>
>> IF NOT EXISTS( SELECT * FROM Secuencias WHERE Prefijo = @Prefijo)
>> INSERT INTO Secuencias VALUES (@Prefijo, 0)
>>
>> etc etc.
>>
>> COMMIT
>>
>> La instrucción BEGIN TRANSACTION por sí misma no bloquea absolutamente
>> nada.
>>
>> Luego viene la instrucción IF NOT EXISTS(). Nada impide que dos
>> conexiones estén ejecutando al mismo tiempo esta instrucción SELECT y
>> obtengan el mismo resultado. La instrucción select produce bloqueos
>> compartidos que permiten a otras conexiones leer los datos.
>>
>> Por lo tanto dos conexiones pueden intentar insertar el mismo prefijo
>> violando la clave primaria. La situación puede darse perfectamente. Sobre
>> todo en un sistema con bastante carga y una máquina con varios
>> procesadores.
>>
>> Observa el siguiente código que es el mismo código real, sólo le añadimos
>> un
>> DELAY para que la situación se produzca más fácilmente.
>>
>> Prepara este script en dos conexiones de SQL Server:
>>
>> USE Tempdb
>> GO
>>
>> BEGIN TRANSACTION
>> IF NOT EXISTS (SELECT * FROM Secuencias WHERE Prefijo = 'XXX')
>> BEGIN
>> WAITFOR DELAY '00:00:05'
>> INSERT INTO Secuencias VALUES ('XXX', 0)
>> END
>> COMMIT
>>
>> Y luego ejecuta los dos al mismo tiempo. En el segundo que ejecutas
>> recibes
>> el error de violación de clave primaria.
>>
>> En el código real el delay no existe, pero nada impide que la secuencia
>> de
>> ejecución sea la siguiente:
>>
>> Conexión 1: SELECT * FROM Secuencias WHERE Prefijo = 'XXX'
>> Conexión 2: SELECT * FROM Secuencias WHERE Prefijo = 'XXX'
>> Conexión 1: INSERT INTO ... COMMIT
>> Conexión 2: INSERT INTO (Violación de clave primaria)
>>
>>
>> > Tu solución también es buena, de hecho muy buena. Solo que pensé en el
>> > crecimiento de la tabla, lo cual puede ser mas eficiente con una tabla
>> > de
>> > consecutivos que hacer cada vez un select y un agregado
>>
>> El plan de ejecución que me sale para la ejecución del procedimietno
>> InsertItem no puede ser mejor, no aparece ningún tipo de agregado,
>> simplemente se busca un registro en un índice, no hay cosa más rápida en
>> SQL
>> Server.
>>
>>
>> (14% del batch)
>> SELECT < Top <- Compute Scalar < Index Seek
>>
>>
>> (86% del batch)
>> INSERT < Index Insert < Compute Scalar < Clustered Index Insert
>>
>>
>>
>>
>> Aquí lo tienes completo
>>
>>
>> 1 1 SELECT TOP(1) @Secuential = Secuential + 1 FROM Items
>> WHERE ItemPrefix = @ItemPrefix ORDER BY Secuential DESC 1 1 0
>> NULL
>> NULL NULL NULL 1 NULL NULL NULL 0,0032833 NULL NULL SELECT 0 NULL
>> 1 1 |--Top(TOP EXPRESSION:((1))) 1 2 1 Top Top TOP EXPRESSION:((1))
>> NULL 1
>> 0 1E-07 11 0,0032833 [Expr1003] NULL PLAN_ROW 0 1
>> 0 0 |--Compute
>> Scalar(DEFINE:([Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
>> [Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1))) 1 3 2 Compute
>> Scalar
>> Compute Scalar
>> DEFINE:([Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
>> [Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1))
>> [Pruebas].[dbo].[Items].[Secuential]=[Pruebas].[dbo].[Items].[Secuential],
>> [Expr1003]=[Pruebas].[dbo].[Items].[Secuential]+(1) 1 0 1E-07 15
>> 0,0032832
>> [Pruebas].[dbo].[Items].[Secuential],
>> [Pruebas].[dbo].[Items].[Secuential],
>> [Expr1003] NULL PLAN_ROW 0 1
>> 1 1 |--Index
>> Seek(OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
>> SEEK:([Pruebas].[dbo].[Items].[ItemPrefix]=[@ItemPrefix]) ORDERED
>> FORWARD) 1
>> 4 3 Index Seek Index Seek
>> OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
>> SEEK:([Pruebas].[dbo].[Items].[ItemPrefix]=[@ItemPrefix]) ORDERED FORWARD
>> [Pruebas].[dbo].[Items].[Secuential] 1 0,003125 0,0001581 11 0,0032831
>> [Pruebas].[dbo].[Items].[Secuential] NULL PLAN_ROW 0 1
>>
>>
>> 1 1 INSERT INTO Items VALUES( @CodItem, @ItemName) 2 1 0 NULL NULL NULL
>> NULL
>> 1 NULL NULL NULL 0,02000326 NULL NULL INSERT 0 NULL
>> 1 1 |--Index
>> Insert(OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
>> SET:([CodItem1006] = [Pruebas].[dbo].[Items].[CodItem],[ItemPrefix1007] > >> [Pruebas].[dbo].[Items].[ItemPrefix],[Secuential1008] > >> [Pruebas].[dbo].[Items].[Secuential])) 2 2 1 Index Insert Insert
>> OBJECT:([Pruebas].[dbo].[Items].[Items_Prefix_Secuential]),
>> SET:([CodItem1006] = [Pruebas].[dbo].[Items].[CodItem],[ItemPrefix1007] > >> [Pruebas].[dbo].[Items].[ItemPrefix],[Secuential1008] > >> [Pruebas].[dbo].[Items].[Secuential]) NULL 1 0,01 1E-06 9 0,02000326 NULL
>> NULL PLAN_ROW 0 1
>> 0 0 |--Compute
>> Scalar(DEFINE:([Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
>> [Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0)))
>> 2 3 2 Compute Scalar Compute Scalar
>> DEFINE:([Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
>> [Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0))
>> [Pruebas].[dbo].[Items].[ItemPrefix]=substring([Pruebas].[dbo].[Items].[CodItem],(1),(3)),
>> [Pruebas].[dbo].[Items].[Secuential]=CONVERT(int,right([Pruebas].[dbo].[Items].[CodItem],(3)),0)
>> 1 0 1E-07 22 0,01000226 [Pruebas].[dbo].[Items].[CodItem],
>> [Pruebas].[dbo].[Items].[ItemPrefix],
>> [Pruebas].[dbo].[Items].[Secuential]
>> NULL PLAN_ROW 0 1
>> 1 1 |--Clustered Index
>> Insert(OBJECT:([Pruebas].[dbo].[Items].[PK__Items__412EB0B6]),
>> SET:([Pruebas].[dbo].[Items].[CodItem] > >> RaiseIfNull([@CodItem]),[Pruebas].[dbo].[Items].[ItemName] > >> RaiseIfNull([@ItemName]))) 2 4 3 Clustered Index Insert Insert
>> OBJECT:([Pruebas].[dbo].[Items].[PK__Items__412EB0B6]),
>> SET:([Pruebas].[dbo].[Items].[CodItem] > >> RaiseIfNull([@CodItem]),[Pruebas].[dbo].[Items].[ItemName] > >> RaiseIfNull([@ItemName])) NULL 1 0,01 1E-06 13 0,01000216
>> [Pruebas].[dbo].[Items].[CodItem] NULL PLAN_ROW 0 1
>>
>>
>>
>> "Luis Flores" escribió en el mensaje
>> news:%
>> >> ya están añadidos a la tabla de prefijos. ¿Qué pása si intentas añadir
>> >> un
>> >> item cuyo prefijo no está en la tabla de prefijos? Sencillamente no
>> >> funciona. Una primera aproximación a ese problema sería que el
>> >> procedimiento usp_TomarUltimaSeq insertara el prefijo en caso de no
>> >> existir,
>> >
>> > Claro. Yo noté ese detalle y lo completé justamente haciendo un if not
>> > exists() y un insert. Lo importante es tomar la idea de la solución
>> > que
>> > el me dio que, como tu propuesta, me ayudó a dar solucion terminada a
>> > mi
>> > problema. Obviamente no hice solo copiar y pegar.
>> >
>> >>
>> >
>> >>
>> >> No te creas que cuando yo he propuesto mi solución no he pensado en
>> >> una
>> >> tabla auxiliar de "secuencias" o prefijos como la quieras llamar. He
>> >> visto los problemas que planteaba y he pensado que era mejor solución
>> >> utilizar una variación de la técnica MAX+1 para secuencias. Además la
>> >> técnica que yo he propuesto es más simple, no requiere de tablas
>> >> auxiliares ni crear procedimientos auxiliares. También te puedo decir
>> >> que
>> >> es eficiente. He probado de varias formas diferentes de implementarla
>> >> y
>> >> he llegado a la conclusión de que la técnica propuesta es la
>> >> implementación más eficiente posible de técnica MAX+1 para secuencias.
>> >>
>> >
>> > Tu solución también es buena, de hecho muy buena. Solo que pensé en el
>> > crecimiento de la tabla, lo cual puede ser mas eficiente con una tabla
>> > de
>> > consecutivos que hacer cada vez un select y un agregado. Además
>> > pensando
>> > quizás en generalización del generador del consecutivo, bastaría con
>> > agregar el parámetro NombreTabla a la tabla de consecutivos.
>> >
>> > De todos modos gracias por tu aportación que ha sido muy buena y
>> > tambien
>> > por la explicación.
>> >
>> >
>> > Saludos
>> > Luis
>> >
>> >
>> >
>>
>>
>>



Respuesta Responder a este mensaje
Ads by Google
Help Hacer una preguntaSiguiente AnteriorRespuesta Tengo una respuesta
Search Busqueda sugerida