Trouvez la dernière valeur dans une séquence « enroulée sur » une procédure stockée?

StackOverflow https://stackoverflow.com/questions/710566

  •  22-08-2019
  •  | 
  •  

Question

Supposons que j'eu un ensemble d'identifiants alpha-caractères d'une longueur fixe, par exemple toujours cinq lettres, et ils sont attribués de telle sorte qu'ils sont toujours incrémentant (GGggZ -> GGGHA, etc.). Maintenant, si je reçois à ZZZZZ, puisque la longueur est fixée, je dois « rouler » à AAAAA. Je pourrais avoir un bloc contigu de ZZZAA par AAAam. Je veux écrire un sproc qui me donnera l'identifiant « suivant », dans ce cas AAAAN.

Si je n'ai pas eu ce problème « rouler sur », bien sûr, je voudrais juste ORDER BY et de saisir le DESC meilleur résultat. Mais je suis un peu une perte maintenant - et il ne permet pas à tout ce que SQL n'est pas ma langue la plus forte

.

Si I Vous Je peux me déplacer à mon C # code d'appel, mais un sproc serait un meilleur ajustement.

ETA: Je voudrais éviter de modifier le schéma (nouvelle colonne ou nouvelle table); Je préférerais simplement être en mesure de « le comprendre ». Je pourrais même préférer le faire la force brute (par exemple commencer à la valeur la plus basse et l'incrément jusqu'à ce que je trouve un « trou »), même si cela pourrait coûter cher. Si vous avez une réponse qui ne modifie pas le schéma, ce serait une meilleure solution pour mes besoins.

Était-ce utile?

La solution 6

Je pense que la solution la moins d'impact pour mes besoins est d'ajouter une colonne d'identité. La seule chose que je peux vous garantir que la commande sera telle que les entrées qui devraient « d'abord » seront ajoutés d'abord - je ne serai jamais un avec identifiant ajouter BBBB, puis revenir en arrière et ajouter BBBA plus tard. Si je n'avais pas cette contrainte, de toute évidence cela ne fonctionnerait pas, mais tel qu'il est, je peux commander par la colonne d'identité et d'obtenir le genre que je veux.

Je vais continuer à penser aux autres suggestions - peut-être si elles « clic » dans ma tête, ils ressemblent à une meilleure option

.

Autres conseils

Code est ici que je pense que vous donnera votre valeur suivante. J'ai créé 3 fonctions. Le tableau est juste ma simulation de la table.colonne avec vos alpha ids (je MyTable.AlphaID). Je suppose que c'est que vous avez laissé entendre et il y a un bloc contigu de chaînes alphabétiques majuscules de cinq caractères (AlphaID):

IF OBJECT_ID('dbo.MyTable','U') IS NOT NULL
    DROP TABLE dbo.MyTable
GO
CREATE TABLE dbo.MyTable (AlphaID char(5) PRIMARY KEY)
GO
-- Play with different population scenarios for testing
INSERT dbo.MyTable VALUES ('ZZZZY')
INSERT dbo.MyTable VALUES ('ZZZZZ')
INSERT dbo.MyTable VALUES ('AAAAA')
INSERT dbo.MyTable VALUES ('AAAAB')
GO
IF OBJECT_ID('dbo.ConvertAlphaIDToInt','FN') IS NOT NULL
    DROP FUNCTION dbo.ConvertAlphaIDToInt
GO
CREATE FUNCTION dbo.ConvertAlphaIDToInt (@AlphaID char(5))
RETURNS int
AS
BEGIN
RETURN 1+ ASCII(SUBSTRING(@AlphaID,5,1))-65
              + ((ASCII(SUBSTRING(@AlphaID,4,1))-65) * 26)
              + ((ASCII(SUBSTRING(@AlphaID,3,1))-65) * POWER(26,2))
              + ((ASCII(SUBSTRING(@AlphaID,2,1))-65) * POWER(26,3))
              + ((ASCII(SUBSTRING(@AlphaID,1,1))-65) * POWER(26,4))
END
GO 

IF OBJECT_ID('dbo.ConvertIntToAlphaID','FN') IS NOT NULL
    DROP FUNCTION dbo.ConvertIntToAlphaID
GO
CREATE FUNCTION dbo.ConvertIntToAlphaID (@ID int)
RETURNS char(5)
AS
BEGIN
RETURN CHAR((@ID-1) / POWER(26,4) + 65)
      + CHAR ((@ID-1) % POWER(26,4) / POWER(26,3) + 65)
      + CHAR ((@ID-1) % POWER(26,3) / POWER(26,2) + 65)
      + CHAR ((@ID-1) % POWER(26,2) / 26 + 65)
      + CHAR ((@ID-1) % 26 + 65)

END
GO 
IF OBJECT_ID('dbo.GetNextAlphaID','FN') IS NOT NULL
    DROP FUNCTION dbo.GetNextAlphaID
GO
CREATE FUNCTION dbo.GetNextAlphaID ()
RETURNS char(5)
AS
BEGIN
    DECLARE @MaxID char(5), @ReturnVal char(5)
    SELECT @MaxID = MAX(AlphaID) FROM dbo.MyTable
    IF @MaxID < 'ZZZZZ'
        RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(@MaxID)+1)
    IF @MaxID IS NULL
        RETURN 'AAAAA'
    SELECT @MaxID = MAX(AlphaID) 
    FROM dbo.MyTable 
    WHERE AlphaID < dbo.ConvertIntToAlphaID((SELECT COUNT(*) FROM dbo.MyTable))
    IF @MaxID IS NULL
        RETURN 'AAAAA'
    RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(@MaxID)+1)
END
GO

SELECT * FROM dbo.MyTable ORDER BY dbo.ConvertAlphaIDToInt(AlphaID)
GO
SELECT  dbo.GetNextAlphaID () AS 'NextAlphaID'

Par ailleurs, si vous ne voulez pas assumer contiguïté, vous pouvez faire comme vous le suggérez et (s'il y a une rangée de « ZZZZZ ») utiliser le premier écart dans la séquence. Remplacer la dernière fonction avec ceci:

IF OBJECT_ID('dbo.GetNextAlphaID_2','FN') IS NOT NULL
    DROP FUNCTION dbo.GetNextAlphaID_2
GO
CREATE FUNCTION dbo.GetNextAlphaID_2 ()
RETURNS char(5)
AS
BEGIN
    DECLARE @MaxID char(5), @ReturnVal char(5)
    SELECT @MaxID = MAX(AlphaID) FROM dbo.MyTable
    IF @MaxID < 'ZZZZZ'
        RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(@MaxID)+1)
    IF @MaxID IS NULL
        RETURN 'AAAAA'
    SELECT TOP 1 @MaxID=M1.AlphaID
    FROM dbo.Mytable M1
    WHERE NOT EXISTS (SELECT 1 FROM dbo.MyTable M2 
                      WHERE AlphaID = dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(M1.AlphaID) + 1 )
                     )
    ORDER BY M1.AlphaID
    IF @MaxID IS NULL
        RETURN 'AAAAA'
    RETURN dbo.ConvertIntToAlphaID(dbo.ConvertAlphaIDToInt(@MaxID)+1)
END
GO

Il faudrait stocker le dernier identifiant attribué dans la séquence.

Par exemple, le stocker dans une autre table qui a une colonne et une ligne.

CREATE TABLE CurrentMaxId (
    Id CHAR(6) NOT NULL
);

INSERT INTO CurrentMaxId (Id) VALUES ('AAAAAA');

Chaque fois que vous allouez un nouvel identifiant, vous auriez récupérer la valeur dans ce tableau minuscule, incrémenter et stocker cette valeur dans votre table principale, ainsi que la mise à jour la valeur CurrentMaxId.

Les réserves d'usage appliquent à l'égard de la concurrence, de table de verrouillage, etc.

Je pense que je l'aurais essayé de stocker la séquence comme un entier, puis le traduire en chaîne. Ou stocker autre colonne de nombre entier qui est incrémenté en parallèle en même temps que la valeur alpha. De toute façon, vous pouvez trier la colonne entière.

Un problème ici est que vous ne pouvez pas vraiment dire à partir des données où la « dernière » entrée est moins qu'il n'y ait plus de détails sur la façon dont les anciennes entrées sont supprimées.

Si je comprends bien, vous êtes enroulant autour à la fin de la séquence, ce qui signifie que vous devez être certains de vos supprimez les anciennes données pour faire de la place. Toutefois, si les données ne sont pas supprimées de manière parfaitement uniforme, vous vous retrouverez avec des fragments, comme ci-dessous:

ABCD   HIJKL NOPQRS   WXYZ

Vous remarquerez qu'il n'y a pas évidente valeur suivante ... D pourrait être la dernière valeur créée, mais il pourrait aussi être L ou S.

Au mieux, vous pouvez chercher le premier ou le dernier élément manquant (utiliser une procédure stockée pour effectuer un x + 1 chèque comme vous le feriez pour trouver un élément manquant dans une séquence entière), mais il ne va pas fournir tout dommage spécial résultat pour listes enroulés sur.

Depuis que je ne me sens pas comme écrire du code pour incrémenter des lettres, je crée une table de toutes pièces d'identité valides (AAAAAA par ZZZZZZ) avec un nombre entier de 1 à X pour ces ID. Ensuite, vous pouvez utiliser les éléments suivants:

SELECT @max_id = MAX(id) FROM Possible_Silly_IDs

SELECT
    COALESCE(MAX(PSI2.silly_id), 'AAAAAA')
FROM
    My_Table T1
INNER JOIN Possible_Silly_IDs PSI1 ON
    PSI1.silly_id = T1.silly_id
INNER JOIN Possible_Silly_IDs PSI2 ON
    PSI2.id = CASE WHEN PSI1.id = @max_id THEN 1 ELSE PSI1.id + 1 END
LEFT OUTER JOIN My_Table T2 ON
    T2.silly_id = PSI2.silly_id
WHERE
    T2.silly_id IS NULL

Le COALESCE est là au cas où la table est vide. Pour être vraiment robuste, vous devez calculer le « aaaaaa » (SELECT @min_silly_id = id = silly_id OU 1) dans le cas où vos changements d'algorithme « de numérotation ».

Si vous voulez vraiment faire les choses, vous voulez refaire la conception de base de données comme cela a été suggéré.

Pour revenir la prochaine ID pour un ID donné (avec retournement), on utilise:

SELECT  COALESCE
        (
        (
        SELECT  TOP 1 id
        FROM    mytable
        WHERE   id > @id
        ORDER BY
                id
        ),
        (
        SELECT  TOP 1 id
        FROM    mytable
        ORDER BY
                id
        )
        ) AS nextid

Cette requête recherche pour le ID à côté de la donnée. S'il n'y a pas ID, elle retourne la première ID.

Voici les résultats:

WITH mytable AS
        (
        SELECT  'AAA' AS id
        UNION ALL
        SELECT  'BBB' AS id
        UNION ALL
        SELECT  'CCC' AS id
        UNION ALL
        SELECT  'DDD' AS id
        UNION ALL
        SELECT  'EEE' AS id
        )
SELECT  mo.id,
        COALESCE
        (
        (
        SELECT  TOP 1 id
        FROM    mytable mi
        WHERE   mi.id > mo.id
        ORDER BY
                id
        ),
        (
        SELECT  TOP 1 id
        FROM    mytable mi
        ORDER BY
                id
        )
        ) AS nextid
FROM    mytable mo

id      nextid
-----   ------
AAA     BBB
BBB     CCC
CCC     DDD
DDD     EEE
EEE     AAA

, i. e. il retourne BBB pour AAA, CCC pour BBB, etc., et, enfin, pour AAA EEE qui est le dernier dans le tableau.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top