در اين نوشته مي خواهم روی این نکته تاکید و توصيه کنم که: حداکثر سعی خود را بکنید تا تمام داده هایی را که به دیتابیس مربوط می شود درون جدول ها ذخیره کنید.
می دانم که می گویید اینکه خیلی واضح است! اما اگر متوجه شويد که چه مقدار از دیتای اپلیکیشن تان در عوض آنکه درون جدول های دیتابیس شما _جایی که به آن تعلق دارند_ باشند درون کد قرار گرفته اند، حتما تعجب خواهید کرد!
مثال شماره 1
شرکتی 3 شیفت در روز تعریف می کند: شیفت 1 از 12:00 صبح–8 صبح، شیفت 2 از 8 صبح-4 عصر، شیفت 3 از 4 عصر-12 صبح.
بنابراین اگر بخواهید یک متغیر از نوع DateTime را محاسبه کنید تا ببینید در کدام شیفت قرار گرفته می نویسید:
SELECT ..., CASE
WHEN Hour(SomeDate) < 8 THEN 1
WHEN Hour(SomeDate) < 16 THEN 2
ELSE 3
END as ShiftNo
FROM ...
فوق العاده است، فقط الان دیتا را درون کد ذخیره کرده اید – نه درون یک جدول! بیایید با ساختن جدولی با نام "Shifts" دیتا را جایی ذخیره کنیم که به آنجا تعلق دارد:
Shifts
ShiftNo (PK)
|
StartHour
|
EndHour
|
Description
|
1 |
0 |
7 |
Morning |
2 |
8 |
15 |
Day |
3 |
16 |
23 |
Night |
و حالا به سادگي مي نويسيد:
SELECT ...., Shift.ShiftNo, Shift.Description
FROM ...
INNER JOIN Shift
ON Hour(SomeDate) BETWEEN Shift.StartHour and Shift.EndHour
به این ترتیب می توانید توضیحات، مدیران شیفت ها، تنظیم محاسبات و هر چیز دیگری که به یک شیفت مربوط می شود را هم به جدول اضافه کنید. علاوه بر این می توانید از این جدول Shift برای تهیه گزارش استفاده کنید و حالا دیگر اگر بخواهید تعداد شیفت ها را به 4 عدد تغییر دهید، باید دیتا را تغییر دهید و هیچ لازم نیست کد را دست کاری کنید. حتی از این هم بهتر اینکه می توانید یک فلگ "WeekEnd" (پايان هفته) یا ستون "WeekDay" (روز هفته) به جدول شیفت اضافه کنید تا امکان تعریف شیفت های مختلفی را برای روزهای مختلف هفته فراهم کنید؛ ستون های بیشتری برای موارد ديگر اضافه كنيد. حتي با اضافه كردن ستونهاي StartDate/EndDate به كليد اوليه اين جدول، مي توان تغييرات را از نظر تاريخي ارائه و بصورت مشخص مستند كرد.
(نكته: طبعا" اين يك مثال بسيار ساده است، گو اينكه شيفت ها معمولا تا پاسي بعد از نيمه شب ادامه دارند (!)، اما در اينجا براي آنكه مفهوم اصلي را درك كنيد از آن استفاده كردم.)
مثال شماره 2
شركتي پاداش ها را بر اساس نوع كارمند پرداخت مي كند: مديران 10% حقوق ساليانه، كارمندان عادي 7% و سرپرست ها 5%. بنابراين براي محاسبه پاداش، خيلي ساده مي نويسيد:
SELECT ..., Salary * CASE EmpType WHEN 'Manager' THEN .10
WHEN 'Regular' THEN .07
WHEN 'Admin' THEN .05
ELSE 0
END as Bonus
FROM ...
ضمنا" در گزارش پرسنل هميشه مي خواهيد اول از همه مديران را فهرست كنيد، بعد كارمندان عادي و پس از آن سرپرست ها را؛ نمي توانيد آنها را برحسب حروف الفبا مرتب كنيد، بنابراين مي نويسيد:
SELECT ...
FROM ...
ORDER BY CASE EmpType WHEN 'Manager' THEN 1
WHEN 'Regular' THEN 2
WHEN 'Admin' THEN 3
ELSE 4
END ASC
خيلي خوب كار مي ده! اما شما يك جدول با عنوان EmployeeType (نوع كارمندان) داريد، درسته؟ اگر نداريد الان وقتش است كه ايجادش كنيد. حتي اگر چنين جدولي هم داشتيد، به خاطر داشته باشيد كه داده ها را در جدول قرار بدهيد، نه كد:
EmpType (PK)
|
…
|
BonusPct
|
Sort
|
'Manager'
|
…
|
.10
|
1
|
'Regular'
|
…
|
.07
|
2
|
'Admin'
|
…
|
.05
|
3
|
حالا يك join ساده به اين جدول آنچه كه نياز داريد را به شما تحويل مي دهد. ديگر در SELECT ي كه مي نويسيد نه به CASE هاي بيشتري احتياج است و نه به كدهاي دشوار-دشوار! به علاوه الان تغيير درصدها يا ايجاد انواع جديد كارمندان با ويرايش محتواي جداول ميسر است و به هيچ وجه نيازي به اصلاح كد نيست. خيلي ساده و روشن است، نه؟ البته هميشه هم نه!
مثال شماره 3
در يك شركت محصولات خاصي نبايد در گزارش هاي ساليانه نمايان شوند، زيرا داخلي هستند، تمام شده اند يا براي اهداف آزمايشي استفاده شدند. در گزارشها براي مقادير مشخصي استثناء قائل مي شويم و اين كار را به سادگي تمام انجام مي دهيم، مگر نه؟
SELECT ..
FROM ...
WHERE ProductTypeID Not IN (24,203,1,293)
خيلي ساده است و خوب هم جواب مي ده! اما فكر مي كنم بدانيد كه چه مي شود – چرا اين داده ها را در كد ذخيره كنيم وقتي بايستي در جدولي كه براي نوع محصول داريم نگهداري شوند؟ يك ستون ناقابل يا چند شاخص ديگر به جدول اضافه كنيد تا در بخش ديتا اين اقلام را مشخص كنند تا بتوانيد اين مقادير را از كدتان برداريد. حتي اگر نمي توانيد جدول ProductTypes (انواع محصولات) را ويرايش كنيد، با ساختن يك جدول جديد قادر خواهيد بود اين كار را انجام دهيد:
ProductTypeID (PK, FK)
|
Notes
|
24
|
'Testing'
|
203
|
'Converted to product type #12'
|
1
|
'Internal'
|
293
|
‘Testing’
|
به كمك چنين جدولي، با يك LEFT OUTER JOIN به اين جدول مي توانيد بفهميد چه زماني بايد براي يك مورد خاص استثناء قائل شد:
SELECT ...
FROM ...
LEFT OUTER JOIN ProductTypeExclusions PEX
ON ...
WHERE PEX.ProductTypeID is NULL
حالا در بخش ديتايمان درباره اينكه چه نوع محصولاتي بايد مستثني شوند (و چرا) مستندسازي داريم و به آساني مي توانيم بدون كامپايل دوباره كد، تغييرات انجام دهيم و حتي با استفاده از اين جدول گزارش بنويسيم. مي توانيم به اين جدول دامنه تاريخ يا فاكتورهاي ديگري اضافه كنيم كه مشخص كنند ProductTypes (انواع محصولات) خاصي چه موقع در گزارشهايمان ظاهر شوند و يا حذف گردند.
به عنوان يك اثر جانبي خوب ديگر، عبارت SELECT به روشني به ما مي گويد كه چرا داريم اقلام خاصي را از گزارش مستثني مي كنيم و ديگر لازم نيست ندانيم ليست بزرگي از ID ها چه معني دارند!
مثال شماره 4
و سرانجام اينكه با دقت بيشتري جداول "look-up" را مطالعه كنيد. ما همه از آنها استفاده مي كنيم – اين جدولها روش فوق العاده اي براي ايجاد فهرستي از مقادير معتبر براي ستون هاي خاص هستند؛ و اغلب بيش از اين كاري انجام نمي دهند:
StatusCode (PK)
|
Description
|
A
|
Approved
|
P
|
Preliminary
|
R
|
Rejected
|
D
|
Deleted
|
H
|
Archived
|
اگر جدولي مانند جدول بالا در سيستم تان داريد دستتان را بالا ببريد! اما واقعا" اين جدول به خودي خود چه چيزي به ما مي گويد؟ معني واقعي 'Rejected' چيست؟ براي فهميدن اين مطلب معمولا" بايد به اپليكيشن يا كد T-SQL ي كه نوشتيم نگاه بيندازيم. در آنجا كدهايي مانند اينها نوشتيم:
... WHERE StatusCode IN ('A','H') ... هر چه كه معتبر باشد را بر مي گرداند
... IF StatusCode NOT IN ('P','R','A') THEN ... مواردي كه نمي توان ويرايش كرد
... WHERE StatusCode IN ('P','R') ... تمامي اشيائي را كه مي توان بازنگري كرد نشان مي دهد
... IF StatusCode IN ('P','R') THEN ... اجازه حذف آنرا مي دهد
و الي آخر... (توجه داشته باشيد كه اوضاع از اين هم بدتر مي شود وقتي از شناسه ها (ID) به عنوان PK اين جدول جستجو استفاده مي كنيم، اما شرح اين موضوع خود يك مقاله را در بر مي گيرد!) اين نيز يك مثال بسيار ساده است و ممكن است مفهوم قسمتي از اين كدها را متوجه نشويد، اما منظور اصلي را در مي يابيد.
اما ما درحقيقت اينجا چه مي كنيم؟ مقصود اين جدول Status چيست؟ آيا از آن به حد وافر بهره مي گيريم؟ آيا واقعا بايد ديتا(مقادير كد وضعيت) را با درون كدمان بگنجانيم؟ اگر بخواهيم وضعيت جديد اضافه كنيم چه اتفاقي مي افتد؟
بار ديگر جواب واضح است: سعي كنيد داده ها را تا حد امكان در جدول ها ذخيره كنيد و آنرا از كد بيرون بكشيد. ممكن است اين مثال در نگاه اول به آساني قابل درك نباشد، اما با دقت آنرا ملاحظه كنيد:
StatusCode (PK)
|
Description
|
IsValid
|
IsEditable
|
IsReviewable
|
IsDeletable
|
A
|
Approved
|
1
|
1
|
0
|
0
|
P
|
Preliminary
|
0
|
1
|
1
|
1
|
R
|
Rejected
|
0
|
1
|
1
|
1
|
D
|
Deleted
|
0
|
0
|
0
|
0
|
H
|
Archived
|
1
|
0
|
0
|
0
|
به ناگهان جدول StatusCodes (كد وضعيت ها) تعاريف بسيار روشني از اينكه معني حقيقي اين كدها چيست (خودش خود را به خوبي مستند مي كند!) و نحوه استفاده اپليكيشن و گزارشها از آنها را در خود دارد. اگر به ازاي هر يك از اعمال اصلي مختلفي كه برنامه مي تواند انجام دهد ستوني در اين جدول در نظر بگيريم، تقريبا هرگز لازم نخواهد شد هيچ ديتايي زا به كدمان اضافه كنيم، اما در عوض داده ها در جدول ها زندگي مي كنند! حالا با يك JOIN ساده به جدول Status مي توانيم بنويسيم:
...
WHERE IsValid =1 ...
IF isEditable = 0 THEN ...
... WHERE isReviewable = 1 ...
IF isDeleteable = 1 THEN ...
و غيره. اكنون مي توانيم كدهاي وضعيت را تغيير بدهيم، كدهاي جديد ايجاد كنيم و آنها را مجددا دسته بندي كنيم – همه اين كارها را در بخش ديتا انجام مي دهيم.
نتيجه گيري
تا آنجا كه به نظر مي رسد تمامي موارد بالا يك سري مطالب بديهي و روشنتر از روزند! با اين حال براي همه ما گاهي پيش مي آيد بيش از مقداري كه بايد در كدمان ديتا وارد مي كنيم، بعضي اوقات چون اين كار سريعتر است و معطلي كمتري دارد و برخي اوقات هم چون نمي توانيم نموداري را كه طراحي كرده ايم تغيير بدهيم، اما بيشتر اوقات به اين علت است كه از پيش براي پياده سازي، آن مقدار كه بايد برنامه ريزي نكرده ايم.
اميدوارم اين نوشته توانسته باشد ايده هايي درباره راههايي كه مي توان به وسيله آن ديتا را بيشتر در جايي كه بايد – درون جدول ها -- قرار داد ارائه كند. به اين ترتيب كد مختصرتر و قابل فهم تري خواهيد داشت و خواهيد ديد كه ناخودآگاه داده هايتان در واقع سيستم شما را به خوبي مستندسازي مي كنند و اين امكان را فراهم مي كنند كه هنگام انجام اصلاحات گسترده، تغييرات تنها در جدول ها اعمال شوند.