إصلاح تصميم قاعدة البيانات السيئة بمجرد دخول البيانات إلى النظام

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

  •  09-06-2019
  •  | 
  •  

سؤال

أعلم أن هذا ليس سؤالاً..على أية حال هنا هو السؤال.

لقد ورثت قاعدة بيانات تحتوي على جدول واحد (واحد) يبدو مثل هذا إلى حد كبير.هدفها هو تسجيل الأنواع الموجودة في مختلف البلدان (200 دولة فردية).

ID 
Species
Afghanistan
Albania
Algeria
American Samoa
Andorra
Angola
....
Western Sahara
Yemen
Zambia
Zimbabwe

عينة من البيانات ستكون مثل هذا

id Species Afghanistan Albania American Samoa
1  SP1         null     null        null
2  SP2          1         1         null
3  SP3         null      null         1

يبدو لي أن هذا موقف نموذجي من كثير إلى كثير وأريد 3 طاولات.الأنواع والبلد والأنواع الموجودة في البلد

سيكون لجدول الارتباط (SpeciesFoundInCountry) مفاتيح خارجية في كل من جداول الأنواع والبلد.

(من الصعب رسم المخطط!)

Species
SpeciesID  SpeciesName

Country
CountryID CountryName

SpeciesFoundInCountry
CountryID SpeciesID

هل هناك طريقة سحرية يمكنني من خلالها إنشاء عبارة إدراج تحصل على معرف البلد من جدول البلد الجديد بناءً على اسم العمود ومعرف النوع حيث يوجد الرقم 1 في الجدول الضخم الأصلي؟

يمكنني القيام بذلك لبلد واحد (هذا اختيار لإظهار ما أريد)

SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.Afghanistan)=1)) AND (((Country.Country)="Afghanistan"));

(الجدول الضخم يسمى الأنواع)

ولكن باستخدام هذه الإستراتيجية سأحتاج إلى إجراء الاستعلام لكل عمود في الجدول الأصلي.

هل هناك طريقة للقيام بذلك في SQL؟

أعتقد أنه يمكنني أو أن أقوم بتحميل عبارات المكان الخاصة بي معًا وكتابة برنامج نصي لإنشاء SQL، يبدو غير أنيق بالرغم من ذلك!

أي أفكار (أو توضيح مطلوب)؟

هل كانت مفيدة؟

المحلول

سأستخدم برنامجًا نصيًا لإنشاء جميع الاستعلامات الفردية، نظرًا لأن هذه عملية استيراد لمرة واحدة.

بعض البرامج مثل Excel جيدة في مزج أبعاد مختلفة من البيانات (مقارنة أسماء الأعمدة بالبيانات الموجودة داخل الصفوف)، لكن قواعد البيانات العلائقية نادرًا ما تكون كذلك.

ومع ذلك، قد تجد أن بعض الأنظمة (مثل Microsoft Access، بشكل مدهش) لديها أدوات ملائمة يمكنك استخدامها لتطبيع البيانات.أنا شخصياً أجد أن كتابة البرنامج النصي أسرع ولكن مهاراتك النسبية في Access والبرمجة النصية قد تكون مختلفة عن مهاراتي.

نصائح أخرى

لماذا تريد أن تفعل ذلك في SQL؟ما عليك سوى كتابة نص صغير يقوم بالتحويل.

عندما أواجه هذه الأشياء، أكتب برنامجًا نصيًا لإجراء التحويل بدلاً من محاولة القيام بذلك في SQL.عادة ما يكون الأمر أسرع وأسهل بكثير بالنسبة لي.اختر أي لغة تناسبك.

إذا كان هذا هو SQL Server، فستستخدم أوامر Unpivot، ولكن بالنظر إلى العلامة التي قمت بتعيينها للوصول - هل أنا على حق؟

على الرغم من وجود أمر التمحور في الوصول, ، لا يوجد بيان عكسي.

يبدو أنه يمكن القيام بذلك من خلال صلة معقدة.افحص هذا مقالة مثيرة للاهتمام للحصول على قائمة منسدلة حول كيفية إلغاء المحور في أمر التحديد.

من المحتمل أنك تريد إنشاء جداول بديلة في مكانها.يعتمد نوع البرنامج النصي على لغة البرمجة النصية المتوفرة لديك، ولكن يجب أن تكون قادرًا على إنشاء جدول معرف البلد ببساطة عن طريق سرد أعمدة الجدول الموجود لديك الآن.بمجرد الانتهاء من ذلك، يمكنك إجراء بعض استبدالات السلسلة لتصفح جميع أسماء البلدان الفريدة وإدراجها في جدول SpecsFoundInCountry حيث لا يكون عمود البلد المحدد فارغًا.

من المحتمل أن تكون ذكيًا وتقوم بالاستعلام عن أسماء الأعمدة في جداول النظام، ثم إنشاء سلسلة استعلام ديناميكية لتنفيذها، ولكن بصراحة قد يكون ذلك أقبح من برنامج نصي سريع لإنشاء عبارات SQL لك.

نأمل ألا يكون لديك الكثير من تعليمات SQL الديناميكية التي تصل إلى الجداول القديمة المدفونة في قاعدة التعليمات البرمجية الخاصة بك.يمكن أن يكون ذلك حقًا جزء صعب.

في SQL Server، سيؤدي هذا إلى إنشاء التحديد المخصص الذي توضحه.يمكنك استقراء إلى إدراج

select 
  'SELECT Species.ID, Country.CountryID FROM Country, Species WHERE (((Species.' + 
 c.name + 
 ')=1)) AND (((Country.Country)="' +
 c.name + 
 '"))'
from syscolumns c
inner join sysobjects o
on o.id = c.id
where o.name = 'old_table_name'

كما هو الحال مع الآخرين، سأفعل ذلك على الأرجح كحل سريع لمرة واحدة وبأي طريقة تناسبك.

مع هذه الأنواع من التحويلات، فهي عبارة عن عناصر لمرة واحدة، وإصلاحات سريعة، وليس من الضروري أن يكون الرمز أنيقًا، بل يجب أن يعمل فقط.بالنسبة لهذه الأنواع من الأشياء، قمت بذلك بعدة طرق.

إذا كان هذا هو SQL Server، فيمكنك استخدام جدول sys.columns للعثور على كافة أعمدة الجدول الأصلي.ثم يمكنك استخدام SQL الديناميكي والأمر المحوري للقيام بما تريد.ابحث عن تلك على الإنترنت للحصول على بناء الجملة.

سأوافق بالتأكيد على اقتراحك بكتابة برنامج نصي صغير لإنتاج SQL الخاص بك مع استعلام لكل عمود.

في الواقع، من الممكن أن يكون النص قد انتهى بالفعل في الوقت الذي أمضيته في التفكير في هذا الاستعلام السحري (الذي ستستخدمه مرة واحدة فقط ثم تتخلص منه، فما الفائدة من جعله كله سحريًا ومثاليًا)

عذرًا، لكن محلل النشر الدموي أزال المسافة البيضاء والتنسيق في مشاركتي.يجعل من الصعب قراءة السجل.

@ستومب:

يوجد فوق المربع الذي تكتب فيه الإجابة عدة أزرار.الرقم 101010 هو نموذج للتعليمات البرمجية.قمت بتحديد كل ما تبذلونه من النص الذي هو رمز، ثم انقر فوق هذا الزر.ثم لا يفسد الأمر كثيرًا.

cout>>"I don't know C"
cout>>"Hello World"

سأستخدم استعلام Union تقريبًا جدًا:

Dim db As Database
Dim tdf As TableDef

Set db = CurrentDb

Set tdf = db.TableDefs("SO")

strSQL = "SELECT ID, Species, """ & tdf.Fields(2).Name _
    & """ AS Country, [" & tdf.Fields(2).Name & "] AS CountryValue FROM SO "

For i = 3 To tdf.Fields.Count - 1
    strSQL = strSQL & vbCrLf & "UNION SELECT ID, Species, """ & tdf.Fields(i).Name _
    & """ AS Country, [" & tdf.Fields(i).Name & "] AS CountryValue FROM SO "
Next

db.CreateQueryDef "UnionSO", strSQL

سيكون لديك بعد ذلك طريقة عرض يمكن إلحاقها بتصميمك الجديد.

عندما قرأت العنوان "تصميم قاعدة بيانات سيئة سيئة"، كان لدي فضول لمعرفة مدى سوء ذلك.أنت لم تخيب ظني :)

كما ذكر آخرون، فإن البرنامج النصي سيكون أسهل طريقة.يمكن تحقيق ذلك عن طريق كتابة حوالي 15 سطرًا من التعليمات البرمجية بلغة PHP.

SELECT * FROM ugly_table;
while(row)
foreach(row as field => value)
if(value == 1)
SELECT country_id from country_table WHERE country_name = field;

if(field == 'Species')
SELECT species_id from species_table WHERE species_name = value;

INSERT INTO better_table (...)

من الواضح أن هذا رمز زائف ولن يعمل كما هو.يمكنك أيضًا ملء جدول البلدان والأنواع سريعًا عن طريق إضافة بيانات الإدراج هنا.

عذرًا، لقد قمت بالقليل جدًا من برمجة Access ولكن يمكنني تقديم بعض الإرشادات التي من شأنها أن تساعد.

أولاً دعنا نتعرف على المشكلة.من المفترض أنك ستحتاج عادةً إلى إنشاء صفوف متعددة في SpeciesFoundInCountry لكل صف في الجدول الأصلي.وبعبارة أخرى، تميل الأنواع إلى التواجد في أكثر من بلد واحد.من السهل بالفعل القيام بذلك باستخدام منتج ديكارتي، وهو صلة بدون معايير صلة.

للقيام بمنتج ديكارتي، ستحتاج إلى إنشاء جدول الدولة.يجب أن يحتوي الجدول على معرف البلد من 1 إلى N (يمثل N عدد البلدان الفريدة، 200 أو نحو ذلك) واسم البلد.لجعل الحياة سهلة، فقط استخدم الأرقام من 1 إلى N بترتيب الأعمدة.وهذا سيجعل أفغانستان 1 وألبانيا 2...زيمبابوي ن.يجب أن تكون قادرًا على استخدام جداول النظام للقيام بذلك.

قم بعد ذلك بإنشاء جدول أو طريقة عرض من الجدول الأصلي الذي يحتوي على الأنواع وقيمة 0 أو 1 لكل بلد.سوف تحتاج إلى تحويل القيمة الخالية، وليس القيمة الخالية إلى نص 0 أو 1 وربط كافة القيم في سلسلة واحدة.من شأن وصف الجدول ومحرر النصوص الذي يحتوي على تعبيرات عادية أن يجعل هذا الأمر سهلاً.قم بالتجربة أولاً باستخدام عمود واحد، وبمجرد أن يعمل ذلك، قم بتحرير طريقة عرض/إدراج الإنشاء مع جميع الأعمدة.

قم بعد ذلك بربط الجدولين معاً بدون معايير ربط.سيعطيك هذا سجلاً لكل الأنواع في كل بلد، لقد أوشكت على الانتهاء.

الآن كل ما عليك فعله هو تصفية السجلات غير الصالحة، وسيكون لها صفر في الموقع المقابل في السلسلة.نظرًا لأن عمود رمز البلد في جدول البلد يحتوي على موقع السلسلة الفرعية، فكل ما عليك فعله هو تصفية السجلات حيث يكون الرقم 0.

where substring(new_column,country_code) = '1'

ستظل بحاجة إلى إنشاء جدول الأنواع والانضمام إليه

where a.species_name = b.species_name

a وb عبارة عن أسماء مستعارة للجدول.

نأمل أن تكون هذه المساعدة

أوبتو،

إذا كانت لديك استعلامات يتم تشغيلها بالفعل على الجدول القديم، فستحتاج إلى إنشاء طريقة عرض تكرر الجداول القديمة باستخدام الجداول الجديدة.سوف تحتاج إلى القيام بمجموعة من أجل إلغاء تسوية الجداول.

أخبر المستخدمين بأن الجدول/طريقة العرض القديمة لن تكون مدعومة في المستقبل وأن كافة الاستعلامات الجديدة أو التحديثات للاستعلامات القديمة يجب أن تستخدم الجداول الجديدة.

إذا اضطررت إلى إنشاء مجموعة كبيرة من عبارات SQL المماثلة وتنفيذها جميعًا، فغالبًا ما أجد أن برنامج Excel مفيد جدًا.خذ الاستعلام الأصلي الخاص بك.إذا كان لديك قائمة بلدان في العمود A وبيان SQL الخاص بك في العمود B، تم تنسيقهما كنص (بين علامتي اقتباس) مع إدراج مراجع الخلايا حيث يظهر البلد في SQL

على سبيل المثال="إدراج في new_table حدد ...(الأنواع." & A1 & ")= ...))؛"

ثم قم فقط بنسخ الصيغة لأسفل لإنشاء 200 عبارة SQL مختلفة، ثم انسخ/الصق العمود في المحرر الخاص بك واضغط على F5.يمكنك بالطبع القيام بذلك مع أي عدد تريده من المتغيرات.

عندما واجهت مشكلات مماثلة، وجدت أنه من المناسب إنشاء برنامج نصي يقوم بإنشاء نصوص SQL النصية.هذه هي العينة التي قدمتها، تم تلخيصها لاستخدام %PAR1% بدلاً من أفغانستان.

SELECT Species.ID, Country.CountryID
FROM Country, Species
WHERE (((Species.%PAR1%)=1)) AND (((Country.Country)="%PAR1%"))
UNION

كما تمت إضافة اتحاد الكلمة الرئيسية كوسيلة للجمع بين كافة التحديدات.

بعد ذلك، تحتاج إلى قائمة البلدان، التي تم إنشاؤها من بياناتك الحالية:

أفغانستان ألبانيا.،.

بعد ذلك ، تحتاج إلى برنامج نصي يمكنه التكرار من خلال قائمة البلد ، ولكل تكرار ، ينتج عن إخراج يحل محل أفغانستان بنسبة ٪ Par1 ٪ على التكرار الأول ، وألبانيا للتكرار الثاني وما إلى ذلك.تشبه الخوارزمية عملية دمج البريد في معالج النصوص.إنه عمل قليل لكتابة هذا السيناريو.ولكن، بمجرد حصولك عليه، يمكنك استخدامه في العشرات من المشاريع لمرة واحدة مثل هذا المشروع.

أخيرًا، تحتاج إلى تغيير "UNION" الأخير يدويًا إلى فاصلة منقوطة.

إذا تمكنت من جعل Access ينفذ هذا الاتحاد العملاق، فيمكنك الحصول على البيانات التي تريدها بالشكل الذي تريده، وإدراجها في جدولك الجديد.

أود أن أجعلها عملية مكونة من ثلاث خطوات مع تعديل مؤقت بسيط لجدول SpeciesFoundInCountry الخاص بك.أود إضافة عمود إلى هذا الجدول لتخزين اسم البلد.ثم ستكون الخطوات على النحو التالي.

1) إنشاء/تشغيل برنامج نصي يتنقل بين الأعمدة في الجدول المصدر ويقوم بإنشاء سجل في SpeciesFoundInCountry لكل عمود له قيمة حقيقية.سيحتوي هذا السجل على اسم البلد.2) قم بتشغيل عبارة SQL التي تقوم بتحديث حقل SpeciesFoundInCountry.CountryID من خلال الانضمام إلى جدول البلد في اسم البلد.3) قم بتنظيف جدول SpeciesFoundInCountry عن طريق إزالة عمود CountryName.

إليك رمزًا زائفًا صغيرًا لـ MS Access VB/VBA لإعطائك الجوهر

Public Sub CreateRelationshipRecords()

  Dim rstSource as DAO.Recordset
  Dim rstDestination as DAO.Recordset
  Dim fld as DAO.Field
  dim strSQL as String
  Dim lngSpeciesID as Long

  strSQL = "SELECT * FROM [ORIGINALTABLE]"
  Set rstSource = CurrentDB.OpenRecordset(strSQL)
  set rstDestination = CurrentDB.OpenRecordset("SpeciesFoundInCountry")

  rstSource.MoveFirst

  ' Step through each record in the original table
  Do Until rstSource.EOF
    lngSpeciesID = rstSource.ID
    ' Now step through the fields(columns). If the field
    ' value is one (1), then create a relationship record
    ' using the field name as the Country Name
    For Each fld in rstSource.Fields
      If fld.Value = 1 then
        with rstDestination
          .AddNew
          .Fields("CountryID").Value = Null
          .Fields("CountryName").Value = fld.Name
          .Fields("SpeciesID").Value = lngSpeciesID
          .Update
        End With
      End IF
    Next fld  
    rstSource.MoveNext
  Loop

  ' Clean up
  rstSource.Close
  Set rstSource = nothing
  ....

End Sub

بعد ذلك يمكنك تشغيل عبارة SQL بسيطة لتحديث قيم CountryID في جدول SpeciesFoundInCountry.

تحديث SpeciesFoundInCountry INNER JOIN Country ON SpeciesFoundInCountry.CountryName = Country.CountryName SET SpeciesFoundInCountry.CountryID = Country.CountryID;

وأخيرًا، كل ما عليك فعله هو تنظيف جدول SpeciesFoundInCountry عن طريق إزالة عمود CountryName.

****ملاحظة جانبية:لقد وجدت أنه من المفيد أن يكون لديك جداول بلدان تتضمن أيضًا اختصارات ISO (رموز البلدان).في بعض الأحيان يتم استخدامها كمفاتيح خارجية في جداول أخرى بحيث لا يلزم تضمين أي صلة بجدول البلد في الاستعلامات.

لمزيد من المعلومات: http://en.wikipedia.org/wiki/Iso_country_codes

(نأمل) أن يكون هذا تمرينًا لمرة واحدة، لذا فإن الحل غير الأنيق قد لا يكون سيئًا كما يبدو.

المشكلة (أنا متأكد من أنك على دراية تامة!) هي أنه في مرحلة ما من الاستعلام الخاص بك، يتعين عليك إدراج كل هذه الأعمدة.:( السؤال هو ما هي الطريقة الأكثر أناقة للقيام بذلك؟وفيما يلي محاولتي.يبدو الأمر غير عملي نظرًا لوجود العديد من الأعمدة، ولكنه قد يكون ما تبحث عنه، أو على الأقل قد يوجهك في الاتجاه الصحيح.

حل SQL المحتمل:

/* if you have N countries */
CREATE TABLE Country
(id    int, 
 name  varchar(50)) 

INSERT Country
      SELECT 1, 'Afghanistan'
UNION SELECT 2, 'Albania', 
UNION SELECT 3, 'Algeria' ,
UNION SELECT 4, 'American Samoa' ,
UNION SELECT 5, 'Andorra' ,
UNION SELECT 6, 'Angola' ,
...
UNION SELECT N-3, 'Western Sahara', 
UNION SELECT N-2, 'Yemen', 
UNION SELECT N-1, 'Zambia', 
UNION SELECT N, 'Zimbabwe', 



CREATE TABLE #tmp
(key        varchar(N),  
 country_id int) 
/* "key" field needs to be as long as N */  


INSERT #tmp 
SELECT '1________ ... _', 'Afghanistan' 
/* '1' followed by underscores to make the length = N */

UNION SELECT '_1_______ ... ___', 'Albania'
UNION SELECT '__1______ ... ___', 'Algeria'
...
UNION SELECT '________ ... _1_', 'Zambia'
UNION SELECT '________ ... __1', 'Zimbabwe'

CREATE TABLE new_table
(country_id int, 
species_id int) 

INSERT new_table
SELECT species.id, country_id
FROM   species s , 
       #tmp    t
WHERE  isnull( s.Afghanistan, ' ' ) +  
       isnull( s.Albania, ' ' ) +  
       ... +  
       isnull( s.Zambia, ' ' ) +  
       isnull( s.Zimbabwe, ' ' ) like t.key 

اقتراحي

شخصيا، لن أفعل هذا.سأقوم بحل سريع وقذر مثل الحل الذي تلمح إليه، باستثناء أنني سأقوم بترميز معرفات البلد (لأنك ستفعل ذلك مرة واحدة فقط، أليس كذلك؟ويمكنك القيام بذلك مباشرة بعد إنشاء جدول البلد، حتى تعرف ما هي جميع المعرفات):

INSERT new_table SELECT Species.ID, 1 FROM Species WHERE Species.Afghanistan = 1 
INSERT new_table SELECT Species.ID, 2 FROM Species WHERE Species.Albania= 1 
...
INSERT new_table SELECT Species.ID, 999 FROM Species WHERE Species.Zambia= 1 
INSERT new_table SELECT Species.ID, 1000 FROM Species WHERE Species.Zimbabwe= 1 
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top