إنشاء تشغيل خادم SQL للانتقال من مفتاح طبيعي إلى مفتاح بديل
-
30-09-2019 - |
سؤال
خلفية
في العمل حيث نخطط لإهمال عمود مفتاح طبيعي في أحد الجداول الأساسية لدينا. يتكون المشروع من أكثر من 100 تطبيق يرتبط بهذا الجدول/العمود ؛ 400+ الإجراءات المخزنة التي تشير إلى هذا العمود مباشرة ؛ ومجموعة واسعة من الجداول الشائعة بين هذه التطبيقات التي تشير أيضًا إلى هذا العمود.
الانفجار الكبير والبدء من أساليب الصفر خارج الصورة. سنقوم بإيقاف هذا العمود تطبيق واحد في وقت واحد ، ونشهد التغييرات ، والانتقال إلى التالي ... ولدينا هدفًا مستهدفًا طويلًا لجعل هذا الجهد عمليًا.
المشكلة التي أمتلكها هي أن الكثير من هذه التطبيقات قد شاركت الإجراءات والجداول المخزنة. إذا قمت بتحويل جميع الجداول/الإجراءات المخزنة للتطبيق بالكامل ، فسيتم كسر التطبيق B و C حتى يتم تحويلها. هذه بدورها قد تحطم التطبيقات د ، ه ، و ... إلخ. لقد حصلت بالفعل على استراتيجية تم تنفيذها لفئات التعليمات البرمجية والإجراءات المخزنة ، والجزء الذي تمسك به هو حالة الانتقال لقاعدة البيانات.
ها هو أساسي مثال على ما لدينا:
Users
---------------------------
Code varchar(32) natural key
Access
---------------------------
UserCode varchar(32) foreign key
AccessLevel int
ونحن نهدف الآن فقط إلى حالة انتقالية مثل هذا:
Users
---------------------------
Code varchar(32)
Id int surrogate key
Access
---------------------------
UserCode varchar(32)
UserID int foreign key
AccessLevel int
ستظل الفكرة التي تتم ذلك خلال التطبيقات المرحلة الانتقالية غير المهاجرة والإجراءات المخزنة قادرة على الوصول إلى جميع البيانات المناسبة ويمكن أن تبدأ البيانات الجديدة في الضغط على الأعمدة الصحيحة-بمجرد اكتمال الترحيل لجميع الإجراءات والتطبيقات المخزنة في النهاية إسقاط الأعمدة الإضافية.
أردت استخدام مشغلات SQL Server لاعتراض أي إدراج/تحديث جديد تلقائيًا والقيام بشيء مثل ما يلي على كل من الجداول المتأثرة:
CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT(, UPDATE)
AS
BEGIN
DIM @code as Varchar(32)
DIM @id as int
SET @code = (SELECT inserted.code FROM inserted)
SET @id = (SELECT inserted.code FROM inserted)
-- This is a migrated application; find the appropriate legacy key
IF @code IS NULL AND @id IS NOT NULL
SELECT Code FROM Users WHERE Users.id = @id
-- This is a legacy application; find the appropriate surrogate key
IF @id IS NULL AND @code IS NOT NULL
SELECT Code FROM Users WHERE Users.id = @id
-- Impossible code:
UPDATE inserted SET inserted.code=@code, inserted.id=@id
END
سؤال
المشاكل الضخمة التي أواجهها حتى الآن هي:
- لا يمكنني القيام بـ "بعد الإدراج" لأن القيود الفارغة ستجعل الإدراج تفشل.
- "الرمز المستحيل" الذي ذكرته هو كيف أرغب في تنظيف الاستعلام الأصلي ؛ إذا كان الاستعلام الأصلي يحتوي على أعمدة x و y أو z فيه أو فقط x ، فأنا أرغب بشكل مثالي في القيام بذلك. وإذا قمت بإضافة/حذف عمود آخر ، فأود أن يظل الزناد وظيفيًا.
أي شخص لديه مثال رمز حيث يمكن أن يكون ذلك ممكنًا ، أو حتى حلًا بديلًا للحفاظ على هذه الأعمدة مملوءة بشكل صحيح حتى عندما يتم تمرير واحد فقط من القيم إلى SQL؟
المحلول 3
بعد النوم على المشكلة ، يبدو أن هذا هو الحل الأكثر عاما/القابل لإعادة الاستخدام الذي يمكنني التوصل إليه داخل بناء جملة SQL. إنه يعمل بشكل جيد حتى لو كان كلا العمودين لهما ضبط لا خالي ، حتى لو لم تشر إلى العمود "الآخر" على الإطلاق في إدراجك.
CREATE TRIGGER tr_Access_Sync
ON Access
INSTEAD OF INSERT
AS
BEGIN
/*-- Create a temporary table to modify because "inserted" is read-only */
/*-- "temp" is actually "#temp" but it throws off stackoverflow's syntax highlighting */
SELECT * INTO temp FROM inserted
/*-- If for whatever reason the secondary table has it's own identity column */
/*-- we need to get rid of it from our #temp table to do an Insert later with identities on */
ALTER TABLE temp DROP COLUMN oneToManyIdentity
UPDATE temp
SET
UserCode = ISNULL(UserCode, (SELECT UserCode FROM Users U WHERE U.UserID = temp.UserID)),
UserID = ISNULL(UserID, (SELECT UserID FROM Users U WHERE U.UserCode = temp.UserCode))
INSERT INTO Access SELECT * FROM temp
END
نصائح أخرى
الأعمال صعبة...
حسنًا ، أولاً وقبل كل شيء: هذا المشغل سوف ليس العمل في العديد من الظروف:
SET @code = (SELECT inserted.code FROM inserted)
SET @id = (SELECT inserted.code FROM inserted)
يمكن استدعاء الزناد بمجموعة من الصفوف في Inserted
جدول زائف - أي واحد ستختار هنا ؟؟ تحتاج إلى كتابة المشغل الخاص بك بطريقة ستعمل حتى عندما تحصل على 10 صفوف في Inserted
الطاولة. إذا تم إدراج عبارة SQL 10 صفوف ، فسيقوم المشغل الخاص بك ليس يتم إطلاقها عشر مرات - واحدة لكل صف - ولكن فقط ذات مرة للحصول على الدفعة بأكملها - تحتاج إلى أخذ ذلك في الاعتبار!
النقطة الثانية: سأحاول صنع الهوية IDENTITY
الحقول - ثم سيحصلون دائمًا على قيمة - حتى بالنسبة لتطبيقات "Legacy". يجب أن توفر تلك التطبيقات "القديمة" مفتاحًا قديمًا بدلاً من ذلك - لذلك يجب أن تكون بخير هناك. المشكلة الوحيدة التي أراها ولا أعرف كيف تتعامل مع هذه الإدراج من تطبيق تم تحويله بالفعل - هل يوفرون مفتاحًا قديمًا "على الطراز القديم" أيضًا؟ إذا لم يكن الأمر كذلك - ما مدى سرعة حاجة إلى مثل هذا المفتاح؟
ما أفكر فيه سيكون "وظيفة تنظيف" من شأنها أن تعمل على الطاولة وتحصل على جميع الصفوف بمفتاح إرث فارغ ومن ثم توفير بعض القيمة ذات المغزى لذلك. اجعل هذا الإجراء المخزن العادي وقم بتنفيذه كل يوم على سبيل المثال ، أربع ساعات ، 30 دقيقة - أيا كان ما يناسب احتياجاتك. ثم ليس عليك التعامل مع المشغلات وجميع القيود التي لديهم.
ألن يكون من الممكن جعل المخطط يغير "Bigbang" ولكن إنشاء طرق عرض على الجزء العلوي من تلك الجداول التي "تخفي" التغيير؟
أعتقد أنك قد تجد أنك ببساطة تؤجل الكسر إلى نقطة لاحقة: "سنقوم بتهدئة هذا العمود على تطبيق واحد في وقت واحد" - قد يكون ذلك سذاجتي ولكن لا يمكنني رؤية كيف يحدث ذلك على الإطلاق للعمل.
بالتأكيد ، يمكن أن تحدث فوضى أسوأ عندما تقوم التطبيقات المختلفة بأشياء مختلفة؟