Hi,As part of an application I'm currently developing I need to support content localisation, initially only a few languages but potentially far more. As a minimum some form of English will be available for every resource but default to en-GB if available, so for example if German isn't available then fall back to en-GB then en. The Locale code in question is a .Net locale code "en-GB", "de", pt-BR", etc.I have managed to implemented the functionality needed however I'm now looking to optimise it and hopefully remove the cursors.There are three tables in question however I've simplified them slightly for this example. Consider the Resource and Localisation tables to have a few more columns.Locale (Id INT PK, LocaleCode VARCHAR(5))Resource (Id INT PK)Localisation (ResourceId INT FK, LocaleId INT FK, Content NVARCHAR(MAX))InfoThe GetCultureCascade method simply grabs a locale and performs a few string functions to work out the cascade. So for pt-BR it would return 4 rows of "pt-BR", "pt", "en-GB", "en".The idea behind the left outer join to load content is to make sure even if no localisations are available on the first pass the Resources to be loaded are still returned so the row is already in the database for any subsequent passes. Any further passes don't need to find out if it's there first before performing and insert or update.The MissingTrans count aborts the loop when all translations are foundCode -- Create output table DECLARE @Output TABLE ( [Id] INT, [LocaleCode] VARCHAR(5), [Content] NVARCHAR(MAX), ) -- Create language cascade table DECLARE @Cascade TABLE ( Locale varchar(5) ) -- Load in language cascade data INSERT @Cascade EXEC GetCultureCascade @Locale -- Cursor vars DECLARE @CurrentLocale NVARCHAR(5) DECLARE @MissingTrans INT DECLARE @First BIT DECLARE @Id INT DECLARE @LocaleCode VARCHAR(5) DECLARE @Content NVARCHAR(MAX) DECLARE @Count INT -- Set @First = 1 to force the first cycle SET @MissingTrans = 0 SET @First = 1 -- Cursor time DECLARE LocaleCursor CURSOR FOR SELECT [Locale] FROM @Cascade OPEN LocaleCursor -- Fetch locale into the cursor FETCH NEXT FROM LocaleCursor INTO @CurrentLocale -- While fetching is possible and there are (missing translations or first cycle) WHILE @@FETCH_STATUS = 0 AND (@MissingTrans > 0 OR @First = 1) BEGIN -- Load content DECLARE ContentCursor CURSOR FOR SELECT [R].[Id], [RL].[LocaleCode], [RL].[Content] FROM [Resource] AS [R] LEFT JOIN ( SELECT * FROM [Localisation] LEFT OUTER JOIN [Locale] ON [Locale].[Id] = [Localisation].[LocaleId] WHERE [Locale].LocaleCode = @Locale ) AS [RL] ON [R].[Id] = [RL].[ResourceId] -- Open content cursor OPEN ContentCursor -- Get first record FETCH NEXT FROM ContentCursor INTO @Id, @LocaleCode, @Content -- Start looping WHILE @@FETCH_STATUS = 0 BEGIN -- First locale cycle, insert them all IF(@First = 1) BEGIN INSERT INTO @Output ([Id], [LocaleCode], [Content]) VALUES (@Id, @LocaleCode, @Content) END ELSE BEGIN -- It is, update it if the content is empty UPDATE @Output SET [Content] = @Content WHERE [Id] = @Id AND [LocaleCode] IS NULL END -- Continue content loop FETCH NEXT FROM ContentCursor INTO @Id, @LocaleCode, @Content END -- Cleanup CLOSE ContentCursor DEALLOCATE ContentCursor -- Check to see if any content is missing. If not then the loop can be broken SELECT @MissingTrans = COUNT(*) FROM @Output WHERE [Content] IS NULL -- No longer the first SET @First = 0 -- Next FETCH NEXT FROM LocaleCursor INTO @CurrentLocale END -- Tidyup CLOSE LocaleCursor DEALLOCATE LocaleCursor -- Output SELECT * FROM @OUTPUT
CheersPaul