اذهب إلى المحتوى

محمد الخضور

الأعضاء
  • المساهمات

    21
  • تاريخ الانضمام

  • تاريخ آخر زيارة

كل منشورات العضو محمد الخضور

  1. سنتناول في هذا المقال العبارات الشرطية، التي تعد واحدة من العناصر الأساسية في مختلف لغات البرمجة. حيث سنستعرض كيفية استخدام العبارات الشرطية بشكل فعّال في لغة الاستعلام البنيوية SQL ونتعلم كيفية تطبيقها في مختلف سيناريوهات البرمجة. تتضمّن لغات البرمجة عادةً عباراتٍ شرطية، وهي عبارة عن أمر أو عدة أوامر تُنفّذ فعل مُحدّد لدى تحقق شرط معيّن. ولعلّ من أشهر العبارات الشرطية عبارة if, then, else والتي تتبع عادةً المنطق التالي: if condition=true then action A else action B إذ يُترجم منطق هذه العبارة لغويًا على النحو التالي: "إذا كان الشرط محققًا، نفّذ الأمر أو مجموعة الأوامر A. وإلّا في حال كون الشرط غير محققًا، نفّذ الأمر أو مجموعة الأوامر B." تُعدّ تعابير CASE ميزة في لغة الاستعلام البنيوية SQL، فهي تتيح لنا إمكانية تطبيق منطق مماثل للعبارات الشرطية على استعلامات قواعد البيانات، وتعيين شروط لكيفية إرجاع أو عرض القيم في مجموعة النتائج الخاصة بنا. ولمعرفة كيفية استخدام تعبير CASE لتعيين شروط على البيانات باستخدام كل من الكلمات المفتاحية WHEN وTHEN وELSE وEND تابع الفقرات التالية من المقال. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول من نوع sudo مختلف عن المستخدم الجذر، وجدار حماية مُفعّل، كما هو موضح في المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. نظام إدارة قواعد بيانات MySQL مثبت ومؤمن على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على استخدام تعابير CASE في هذا المقال. لذا ننصحك بمتابعة الفقرة التالية بعنوان الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إنشاء قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم caseDB: mysql> CREATE DATABASE caseDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرج كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات caseDB، نفّذ تعليمة USE التالية: mysql> USE caseDB; الخرج Database changed الآن وبعد اختيار قاعدة البيانات caseDB، لننشئ جدولًا ضمنها باستخدام الأمر CREATE TABLE. كمثال في مقالنا هذا، سنُنشئ جدولًا يحتفظ ببيانات حول الألبومات الموسيقية العشر الأكثر مبيعًا على مر الزمان. إذ سيحتوي هذا الجدول على الأعمدة الستة التالية: music_id: يُمثّل قيمًا من نمط بيانات الأعداد الصحيحة int وسيكون المفتاح الأساسي للجدول، ما يعني أن كل قيمة في هذا العمود ستلعب دور المعرّف الفريد لسجلها. artist_name: مُخصص لتخزين أسماء الفنان أو مجموعة الفنانين المشاركين في الألبوم باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. album_name: يستخدم نمط البيانات varchar، بحد أقصى 30 محرفًا أيضًا لتخزين أسم كل ألبوم. release_date: يتتبع تاريخ إصدار كل ألبوم باستخدام نمط البيانات DATE، الذي يستخدم تنسيق التاريخ YYYY-MM-DD (اليوم بخانتين-الشهر بخانتين-السنة بأربع خانات). genre_type: يعرض تصنيف النوع الموسيقي لكل ألبوم باستخدام نمط البيانات varchar بحد أقصى 25 محرفًا. copies_sold: يستخدم نمط البيانات decimal لتخزين العدد الإجمالي لنسخ الألبوم المباعة بالملايين. إذ سنحدّد لدى تعريف هذا العمود الدقة Precision لتساوي أربعة أرقام، بواقع رقم واحد إلى يمين الفاصلة العشرية. ما يعني أن القيم في هذا العمود يمكن أن تتضمّن أربعة أرقام، واحد منها على يمين الفاصلة العشرية. لننشئ الآن جدولاً باسم top_albums يتضمّن هذه الأعمدة من خلال تنفيذ أمر CREATE TABLE التالي: mysql> CREATE TABLE top_albums ( mysql> music_id int, mysql> artist_name varchar(30), mysql> album_name varchar(30), mysql> release_date DATE, mysql> genre_type varchar(25), mysql> copies_sold decimal(4,1), mysql> PRIMARY KEY (music_id) mysql> ); والآن لنملأ هذا الجدول الفارغ ببعض البيانات التجريبية النموذجية: mysql> INSERT INTO top_albums mysql> (music_id, artist_name, album_name, release_date, genre_type, copies_sold) mysql> VALUES mysql> (1, 'Michael Jackson', 'Thriller', '1982-11-30', 'Pop', 49.2), mysql> (2, 'Eagles', 'Hotel California', '1976-12-08', 'Soft Rock', 31.5), mysql> (3, 'Pink Floyd', 'The Dark Side of the Moon', '1973-03-01', 'Progressive Rock', 21.7), mysql> (4, 'Shania Twain', 'Come On Over', '1997-11-04', 'Country', 29.6), mysql> (5, 'AC/DC', 'Back in Black', '1980-07-25', 'Hard Rock', 29.5), mysql> (6, 'Whitney Houston', 'The Bodyguard', '1992-11-25', 'R&B', 32.4), mysql> (7, 'Fleetwood Mac', 'Rumours', '1977-02-04', 'Soft Rock', 27.9), mysql> (8, 'Meat Loaf', 'Bat Out of Hell', '1977-10-11', 'Hard Rock', 21.7), mysql> (9, 'Eagles', 'Their Greatest Hits 1971-1975', '1976-02-17', 'Country Rock', 41.2), mysql> (10, 'Bee Gees', 'Saturday Night Fever', '1977-11-15', 'Disco', 21.6); الخرج Query OK, 10 rows affected (0.01 sec) Records: 10 Duplicates: 0 Warnings: 0 وبمجرّد إدخالك للبيانات تغدو مستعدًا لبدء تعلّم كيفية استخدام تعابير CASE في SQL. فهم صياغة تعابير CASE تتيح لنا تعابير CASE تحديد شروط لبياناتنا واستخدام منطق مشابه لجمل if-then للبحث ضمنها ومقارنة القيم وتقييم ما إذا كانت تحقق الشروط التي حددناها، بمعنى أنها تجعل هذه الشروط تُقيّم كمحققة "True". وفيما يلي مثال على الصياغة العامة لتعبير CASE: الصيغة العامّة لتعبير CASE . . . CASE WHEN condition_1 THEN outcome_1 WHEN condition_2 THEN outcome_2 WHEN condition_3 THEN outcome_3 ELSE else_outcome END . . . كما سنستخدم الكلمات المفتاحية التالية ضمن تعبير CASE وذلك اعتمادًا على عدد الشروط التي نريد تحديدها لبياناتنا: WHEN: تقيّم هذه الكلمة المفتاحية البيانات في الجدول وتقارنها بالشروط أو المعايير المُحدّدة، وهي مُشابهة بالمبدأ للتعبير if في بنية الجمل الشرطية النموذجية (if-then-else). THEN: تُستخدم لفحص كل شرط لتحديد ما إذا كانت قيمة معينة لا تستوفي المعايير المطلوبة. ELSE: إذا لم تحقق قيمة البيانات أيًا من الشروط المحددة بعد التحقق من جمل WHEN وTHEN، فعندها تُستخدم هذه الكلمة المفتاحية لتحديد الشرط النهائي الذي ستُصنّف القيمة تحته. END: لإنهاء وتنفيذ تعبير CASE بنجاح ولتحديد شروطك، لابدّ من اختتام التعبير بالكلمة المفتاحية END. استخدام تعابير CASE تخيل أنك مُنسّق موسيقي تُعد قائمة أغاني من أجل حفلة لأحد أقاربك من أصحاب الذوق الموسيقي الصعب. لذا قررت أن تبحث عن أفضل عشر ألبومات موسيقية مبيعًا على مر الزمان للاستئناس بها وتوجيه قراراتك الموسيقية. لنراجع بدايةً القائمة التي جمعناها في جدول أفضل الألبومات top_albums بتنفيذ استعلام SELECT واستخدام رمز * لعرض كافّة البيانات من كل عمود: mysql> SELECT * FROM top_albums; الخرج +----------+-----------------+-------------------------------+--------------+------------------+-------------+ | music_id | artist_name | album_name | release_date | genre_type | copies_sold | +----------+-----------------+-------------------------------+--------------+------------------+-------------+ | 1 | Michael Jackson | Thriller | 1982-11-30 | Pop | 49.2 | | 2 | Eagles | Hotel California | 1976-12-08 | Soft Rock | 31.5 | | 3 | Pink Floyd | The Dark Side of the Moon | 1973-03-01 | Progressive Rock | 21.7 | | 4 | Shania Twain | Come On Over | 1997-11-04 | Country | 29.6 | | 5 | AC/DC | Back in Black | 1980-07-25 | Hard Rock | 29.5 | | 6 | Whitney Houston | The Bodyguard | 1992-11-25 | R&B | 32.4 | | 7 | Fleetwood Mac | Rumours | 1977-02-04 | Soft Rock | 27.9 | | 8 | Meat Loaf | Bat Out of Hell | 1977-10-11 | Hard Rock | 21.7 | | 9 | Eagles | Their Greatest Hits 1971-1975 | 1976-02-17 | Country Rock | 41.2 | | 10 | Bee Gees | Saturday Night Fever | 1977-11-15 | Disco | 21.6 | +----------+-----------------+-------------------------------+--------------+------------------+-------------+ 10 rows in set (0.00 sec) وبما أنّ قريبك ولد في عام 1957، فلا بدّ وأنّه قد استمتع بالكثير من الموسيقى الناجحة في شبابه زمن السبعينيات والثمانينيات. وأنت تعلم أنّه من محبي أنماط موسيقى pop وsoft rock وdisco، لذا سنعطي هذه الأنماط الأولوية في قائمة الأغاني. الأمر الذي يمكننا تنفيذه باستخدام تعبير CASE لتعيين شرط يحدد "أولوية عالية (High Priority)" لهذه الأنماط الموسيقية، من خلال الاستعلام عن قيم البيانات هذه من عمود النمط الموسيقي genre_type. يُنفّذ الاستعلام التالي ذلك، ويُنشئ اسمًا بديلاً للعمود الناتج من تعبير CASE، ليكون priority (الأولوية). كما يتضمّن هذا الاستعلام كل من اسم الفنان artist_name واسم الألبوم album_name وتاريخ الإصدار release_date لتوفير المزيد من المعلومات، ولم ننس استخدام الكلمة المفتاحية END لإكمال تعبير CASE: mysql> SELECT artist_name, album_name, release_date, mysql> CASE WHEN genre_type = 'Pop' THEN 'High Priority' mysql> WHEN genre_type = 'Soft Rock' THEN 'High Priority' mysql> WHEN genre_type = 'Disco' THEN 'High Priority' mysql> END AS priority mysql> FROM top_albums; الخرج +-----------------+-------------------------------+--------------+---------------+ | artist_name | album_name | release_date | priority | +-----------------+-------------------------------+--------------+---------------+ | Michael Jackson | Thriller | 1982-11-30 | High Priority | | Eagles | Hotel California | 1976-12-08 | High Priority | | Pink Floyd | The Dark Side of the Moon | 1973-03-01 | NULL | | Shania Twain | Come On Over | 1997-11-04 | NULL | | AC/DC | Back in Black | 1980-07-25 | NULL | | Whitney Houston | The Bodyguard | 1992-11-25 | NULL | | Fleetwood Mac | Rumours | 1977-02-04 | High Priority | | Meat Loaf | Bat Out of Hell | 1977-10-11 | NULL | | Eagles | Their Greatest Hits 1971-1975 | 1976-02-17 | NULL | | Bee Gees | Saturday Night Fever | 1977-11-15 | High Priority | +-----------------+-------------------------------+--------------+---------------+ 10 rows in set (0.00 sec) على الرغم من أنّ هذا الخرج يعكس الشروط المُحددة للأنماط الموسيقية ذات الأولوية العالية High Priority، ولكن ونظرًا لعدم استخدامنا للكلمة المفتاحية ELSE فقد ظهرت قيم بيانات غير معروفة أو مفقودة، والتي تُعرف بالقيم الخالية NULL. ففي حين قد لا يكون استخدام الكلمة المفتاحية ELSE ضروريًا في حال كانت قيم البيانات تلبي جميع الشروط المُحددّة في التعبير CASE، إلّا أنّها مفيدة لأي بيانات متبقية (لا تلبي الشروط المُحددة)، إذ باستخدامها يمكن تصنيفها تحت شرطٍ آخر واحد. للاستعلام التالي، سنكتب نفس تعبير CASE السابق، ولكن سنحدد هذه المرة شرطًا باستخدام الكلمة المفتاحية ELSE. إذ تُصنّف تعليمة ELSE في المثال التالي أي قيم بيانات أنماط موسيقية غير مُصنّفة ضمن الأولوية العالية High Priority على أنّها "Maybe أي قد تؤخذ بالحسبان": mysql> SELECT artist_name, album_name, release_date, mysql> CASE WHEN genre_type = 'Pop' THEN 'High Priority' mysql> WHEN genre_type = 'Soft Rock' THEN 'High Priority' mysql> WHEN genre_type = 'Disco' THEN 'High Priority' mysql> ELSE 'Maybe' mysql> END AS priority mysql> FROM top_albums; [sceondary_label Output] +-----------------+-------------------------------+--------------+---------------+ | artist_name | album_name | release_date | priority | +-----------------+-------------------------------+--------------+---------------+ | Michael Jackson | Thriller | 1982-11-30 | High Priority | | Eagles | Hotel California | 1976-12-08 | High Priority | | Pink Floyd | The Dark Side of the Moon | 1973-03-01 | Maybe | | Shania Twain | Come On Over | 1997-11-04 | Maybe | | AC/DC | Back in Black | 1980-07-25 | Maybe | | Whitney Houston | The Bodyguard | 1992-11-25 | Maybe | | Fleetwood Mac | Rumours | 1977-02-04 | High Priority | | Meat Loaf | Bat Out of Hell | 1977-10-11 | Maybe | | Eagles | Their Greatest Hits 1971-1975 | 1976-02-17 | Maybe | | Bee Gees | Saturday Night Fever | 1977-11-15 | High Priority | +-----------------+-------------------------------+--------------+---------------+ 10 rows in set (0.00 sec) وبذلك يُعبّر هذا الخرج على نحوٍ أفضل عن الشروط التي وضعناها لتحديد لألبومات ذات الأولوية الأعلى وتلك بدون أولوية. وعلى الرغم من أنّ ذلك يساعد في إعطاء الأولوية لأفضل أربع ألبومات — وهي Thriller وHotel California وRumours وSaturday Night Fever، ومع ذلك أنت مقتنع بأنّه ينبغي تنويع قائمة الأغاني أكثر، ولكن في هذه الحالة سيتوجب عليك إقناع قريبك بالأمر أيضًا. لذا تقرّر إجراء تجربة بسيطة، طالبًا من قريبك توسيع ذائقته الموسيقية مُستمعًا إلى الألبومات المتبقية. فتقدّم هذه الألبومات له دون أن تعطيه أي تصوّر مُسبق عنها، وتطلب منه تقييمها بأمانة كهادئة Mellow أو ممتعة Fun أو مملة Boring. بعد انتهائه، يُعطيك قائمة مكتوبة بخط اليد تحتوي على تقييماته. وبذلك أصبح لديك المعلومات اللازمة لتحديد الشروط لاستعلامك كما يلي: mysql> SELECT artist_name, album_name, release_date, mysql> CASE WHEN genre_type = 'Hard Rock' THEN 'Boring' mysql> WHEN genre_type = 'Country Rock' THEN 'Mellow' mysql> WHEN genre_type = 'Progressive Rock' THEN 'Fun' mysql> WHEN genre_type = 'Country' THEN 'Fun' mysql> WHEN genre_type = 'R&B' THEN 'Boring' mysql> ELSE 'High Priority' mysql> END AS score mysql> FROM top_albums; الخرج +-----------------+-------------------------------+--------------+---------------+ | artist_name | album_name | release_date | score | +-----------------+-------------------------------+--------------+---------------+ | Michael Jackson | Thriller | 1982-11-30 | High Priority | | Eagles | Hotel California | 1976-12-08 | High Priority | | Pink Floyd | The Dark Side of the Moon | 1973-03-01 | Fun | | Shania Twain | Come On Over | 1997-11-04 | Fun | | AC/DC | Back in Black | 1980-07-25 | Boring | | Whitney Houston | The Bodyguard | 1992-11-25 | Boring | | Fleetwood Mac | Rumours | 1977-02-04 | High Priority | | Meat Loaf | Bat Out of Hell | 1977-10-11 | Boring | | Eagles | Their Greatest Hits 1971-1975 | 1976-02-17 | Mellow | | Bee Gees | Saturday Night Fever | 1977-11-15 | High Priority | +-----------------+-------------------------------+--------------+---------------+ 10 rows in set (0.00 sec) بناءً على هذا الخرج، يبدو أنّ قريبك مستعد لتجربة موسيقا جديدة، وقد سرّك على وجه الخصوص تقييمه الجيد لفرقة Pink Floyd. لكنكَ شعرتَ بخيبة أمل لعدم إظهاره الاهتمام الكافي بأغاني AC/DC وMeat Loaf وWhitney Houston الرائعة. ولربما سيكون قريبك أكثر تقبّلاً للتغيير إذا أظهرتَ له أنّ بعض الألبومات تحظى بشعبية أكبر من غيرها على نحوٍ موضوعيّ، لذا تقرّر أن تعرض بعض الأرقام لدعم وجهة نظرك. فهذه الألبومات في الواقع هي الأكثر مبيعًا، إذ حققت مبيعات تُقدّر بملايين النسخ على مرّ العقود. لذا، في استعلامنا القادم، سنُنشئ تعبير CASE جديد يُحدّد تقييمًا استنادًا إلى البيانات الرقمية من عمود عدد النسخ المُباعة copies_sold للألبومات التي تم بيعها حتى الآن. سنستخدم تعبير CASE لتحديد الشروط بحيث نُعيّن الألبومات التي حققت ما لا يقل عن 35 مليون نسخة على أنّها "الأفضل best"، وتلك التي بيع منها 25 مليون نسخة على أنّها "ممتازة great"، وتلك التي بيع منها 20 مليون على أنّها "جيدة good"، وأي شيء أقل من ذلك على أنًه "متوسط mediocre"، كما في المثال التالي: mysql> SELECT artist_name, album_name, release_date, CASE WHEN copies_sold >35.0 THEN 'best' mysql> WHEN copies_sold >25.0 THEN 'great' mysql> WHEN copies_sold >20.0 THEN 'good' mysql> ELSE 'mediocre' END AS score FROM top_albums; الخرج +-----------------+-------------------------------+--------------+-------+ | artist_name | album_name | release_date | score | +-----------------+-------------------------------+--------------+-------+ | Michael Jackson | Thriller | 1982-11-30 | best | | Eagles | Hotel California | 1976-12-08 | great | | Pink Floyd | The Dark Side of the Moon | 1973-03-01 | good | | Shania Twain | Come On Over | 1997-11-04 | great | | AC/DC | Back in Black | 1980-07-25 | great | | Whitney Houston | The Bodyguard | 1992-11-25 | great | | Fleetwood Mac | Rumours | 1977-02-04 | great | | Meat Loaf | Bat Out of Hell | 1977-10-11 | good | | Eagles | Their Greatest Hits 1971-1975 | 1976-02-17 | best | | Bee Gees | Saturday Night Fever | 1977-11-15 | good | +-----------------+-------------------------------+--------------+-------+ 10 rows in set (0.00 sec) وفق الخرج السابق لم يُصنّف أي من الألبومات على أنّه "متوسط mediocre"، نظرًا لأنّ كل منها قد حقق مبيعات تزيد عن 20 مليون نسخة. ومع ذلك وبناءً على هذه التقييمات، تبرز بعض الألبومات مقارنةً بالبقية. وبذلك يمكنك الآن أن تقدّم لقريبك دليلًا قويًا يبرّر تشغيل أغاني الفنانين AC/DC أو Whitney Houston، إذ أنّ ألبوماتهم قد حققت مبيعات تزيد عن 25 مليون نسخة، مما يجعلها من أهم الأعمال الموسيقية الموجودة. وبذلك تشكّل لديك فهم لكيفية استخدام تعبير CASE لتحديد شروط تعود لأغراض متنوعة، بحيث تتعامل مع قيم بيانات محرفية ورقمية. كما غدوت على معرفة بكيفية استخدام التعبير CASE لنفس منطق عبارة if-then لمقارنة تلك القيم وإنشاء الردود المناسبة بناءً على الشروط التي ترغب بها. الخلاصة لعلّ فهم كيفية استخدام تعبير CASE قد يساعد على تصفية بياناتك وفقًا للشروط التي تضعها. فسواء كنت تريد تحديد أولويات مختلفة لقيمٍ معينة أو تقييمها بناءً على معايير تعكس الرأي العام أو أرقام مُحدّدة، فإنّها توفّر مرونة تلبي احتياجاتك. وإذا كنت ترغب في معرفة طرق أخرى يمكنك من خلالها معالجة قيم البيانات في مجموعات النتائج الخاصة بك، اطّلع على مقال حول كيفية معالجة البيانات باستخدام دوال CAST وتعابير الضم في SQL. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use CASE Expressions in SQL لصاحبه Jeanelle Horcasitas. اقرأ أيضًا المقال السابق: كيفية معالجة البيانات باستخدام دوال CAST وتعابير الضم في SQL تنفيذ تعليمات شرطية عبر CASE في SQL التجميع والترتيب في SQL دوال التعامل مع البيانات في SQL
  2. لدى إنشاء جدول في قاعدة بيانات SQL للمرة الأولى، يجب تحديد بنيته العامّة، وذلك من خلال سرد جميع الأعمدة التي نريد لهذا الجدول أن يحتويها ونمط البيانات التي ستخزنها كل منها. بعدها وعند إضافة بيانات إلى الجدول، يجب أن تتطابق القيم التي نُدخلها مع أنماط البيانات المُحددّة لكل عمود على حدة. إذ يمكن لقاعدة بيانات SQL ضمان عدم إدخال أي قيم على نحو خاطئ من خلال فرض إدخال قيم تتماشى مع البنية المُعرّفة مُسبقًا للجدول. ورغم ذلك، قد تجعل هذه البنية الصارمة الأمور أصعب لدى مقارنة قيمتين بأنماط بيانات مختلفة أو لدى محاولة تجميع قيم من أعمدة متعددة لتظهر كقيمة ناتجة واحدة. نشرح في هذا المقال كيفية معالجة البيانات باستخدام دوال CAST التي تفيد في تحويل نمط بيانات قيمة معينة -أو مجموعة قيم- إلى نمط بيانات آخر، واستخدام تعبير ضم concatenation expression لربط قيم بيانات محرفية ورقمية معًا كسلسلة. كما سنتدرب على تنفيذ دالة CAST وتعبير الضم في نفس الاستعلام للحصول على تعليمة كاملة. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول من نوع sudo مختلف عن المستخدم الجذر، وجدار حماية مُفعّل، كما هو موضح في المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على استخدام دوال CAST وتعابير الضم في هذا المقال. لذا ننصحك بمتابعة الفقرة التالية الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إنشاء قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم castconDB: mysql> CREATE DATABASE castconDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرج كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات castconDB، نفّذ تعليمة USE التالية: mysql> USE castconDB; الخرج Database changed الآن وبعد اختيار قاعدة البيانات castconDB، لننشئ جدولًا ضمنها باستخدام الأمر CREATE TABLE. كمثال في مقالنا هذا، سننشئ جدولًا يُسجّل الدرجات خلال فترة فصل الخريف الدراسي لصف الأستاذ فريد لطلاب الصف السادس. سيضم الجدول الأعمدة الخمسة عشر التالية: student_id: يُمثّل قيمًا من نمط بيانات الأعداد الصحيحة int وسيكون المفتاح الأساسي للجدول، ما يعني أن كل قيمة في هذا العمود ستلعب دور المعرّف الفريد لسجلها. first_name: مُخصص لأسماء الطلاب الأولى وذلك باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. last_name: مُخصص لتخزين أسماء الطلاب الأخيرة وذلك باستخدام نمط البيانات varchar بحد أقصى أيضًا 20 محرفًا. email_address: لتخزين البريد الإلكتروني لكل طالب باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. participation_grade: يعرض درجة النشاط الصفّي الإجمالية لكل طالب باستخدام نمط البيانات int. attendance_grade: يستخدم نمط بيانات int لعرض درجات حضور كل طالب. midterm_deadline: يستخدم نمط بيانات TIMESTAMP من أجل عرض الموعد النهائي الذي يجب على كل طالب تقديم الامتحان النصفي بحلوله. يجمع هذا النمط بين التاريخ والوقت في سلسلة واحدة ويستخدم الصيغة التالية: (YYYY-MM-DD HH:MM:SS). midterm_submitted: يسجّل اليوم والوقت الدقيق اللذين قدّم فيهما الطلاب الامتحان النصفي باستخدام نمط البيانات TIMESTAMP. midterm_grade: يستخدم نمط بيانات decimal لتخزين درجة كل طالب في الامتحان النصفي. إذ سنحدد لدى تعرف هذا العمود الدقة (Precision) لتساوي أربعة أرقام، بواقع رقم واحد إلى يمين الفاصلة العشرية (Scale). ما يعني أن القيم في هذا العمود يمكن أن تتضمن أربعة أرقام، واحد منها على يمين الفاصلة العشرية. essay_deadline: يعرض الوقت والتاريخ اللذين يجب على الطلاب تقديم مقالاتهم بحلولهما، باستخدام نمط البيانات TIMESTAMP. essay_submitted: يستخدم نمط بيانات TIMESTAMP لتتبع وقت وتاريخ تقديم الطلاب لواجب كتابة المقال. essay_grade: يحتوي على درجات المقال لكل طالب باستخدام نمط البيانات decimal، بدقة أربعة أرقام واحد منها إلى يمين الفاصلة العشرية. finalexam_deadline: يخزن معلومات موعد الامتحان النهائي باستخدام نمط البيانات TIMESTAMP. finalexam_submitted: يستخدم نمط بيانات TIMESTAMP لتسجيل الوقت والتاريخ الفعليين لتقديم الطلاب لامتحانهم النهائي. finalexam_grade: يحتوي على درجة الامتحان النهائي لكل طالب باستخدام نمط البيانات decimal بدقة أربعة أرقام، واحد منها إلى يمين الفاصلة العشرية. لننشئ الآن جدولاً باسم fall_grades يتضمّن هذه الأعمدة من خلال تنفيذ أمر CREATE TABLE التالي: mysql> CREATE TABLE fall_grades ( mysql> student_id int, mysql> first_name varchar(20), mysql> last_name varchar(20), mysql> email_address varchar(30), mysql> participation_grade int, mysql> attendance_grade int, mysql> midterm_deadline TIMESTAMP, mysql> midterm_submitted TIMESTAMP, mysql> midterm_grade decimal(4,1), mysql> essay_deadline TIMESTAMP, mysql> essay_submitted TIMESTAMP, mysql> essay_grade decimal(4,1), mysql> finalexam_deadline TIMESTAMP, mysql> finalexam_submitted TIMESTAMP, mysql> finalexam_grade decimal(4,1), mysql> PRIMARY KEY (student_id) mysql> ); والآن لنملأ هذا الجدول الفارغ ببعض البيانات التجريبية النموذجية: mysql> INSERT INTO fall_grades mysql> (student_id, first_name, last_name, email_address, participation_grade, attendance_grade, midterm_deadline, midterm_submitted, midterm_grade, essay_deadline, essay_submitted, essay_grade, finalexam_deadline, finalexam_submitted, finalexam_grade) mysql> VALUES mysql> (1, 'Arnold', 'Shortman', 'ashortman@ps118.com', 98, 90, '2022-10-16 12:00:00', '2022-10-16 06:30:00', 85.8, '2022-11-20 12:00:00', '2022-11-20 03:00:00', 90.1, '2022-12-11 12:00:00', '2022-12-11 03:00:00', 82.5), mysql> (2, 'Helga', 'Pataki', 'hpataki@ps118.com', 85, 100, '2022-10-16 12:00:00', '2022-10-16 10:00:00', 88.4, '2022-11-20 12:00:00', '2022-11-21 03:15:00', 72.5, '2022-12-11 12:00:00', '2022-12-11 05:00:00', 90.0), mysql> (3, 'Gerald', 'Johanssen', 'gjohanssen@ps118.com', 100, 95, '2022-10-16 12:00:00', '2022-10-16 02:00:00', 94.2, '2022-11-20 12:00:00', '2022-11-20 02:45:00', 95.8, '2022-12-11 12:00:00', '2022-12-11 11:00:00', 88.1), mysql> (4, 'Phoebe', 'Heyerdahl', 'pheyerdahl@ps118.com', 100, 100, '2022-10-16 12:00:00', '2022-10-16 11:00:00', 98.8, '2022-11-20 12:00:00', '2022-11-20 11:15:00', 90.4, '2022-12-11 12:00:00', '2022-12-11 11:40:00', 100.0), mysql> (5, 'Harold', 'Berman', 'hberman@ps118.com', 100, 75, '2022-10-16 12:00:00', '2022-10-16 08:00:00', 75.7, '2022-11-20 12:00:00', '2022-11-22 09:15:00', 67.5, '2022-12-11 12:00:00', '2022-12-11 09:15:00', 90.9), mysql> (6, 'Eugene', 'Horowitz', 'ehorowitz@ps118.com', 100, 100, '2022-10-16 12:00:00', '2022-10-16 01:00:00', 100.0, '2022-11-20 12:00:00', '2022-11-20 01:22:00', 89.9, '2022-12-11 12:00:00', '2022-12-11 07:55:00', 98.2), mysql> (7, 'Rhonda', 'Lloyd', 'rlloyd@ps118.com', 100, 80, '2022-10-16 12:00:00', '2022-10-16 06:00:00', 90.4, '2022-11-20 12:00:00', '2022-11-20 06:09:00',81.3, '2022-12-11 12:00:00', '2022-12-11 06:45:00', 95.5), mysql> (8, 'Stinky', 'Peterson', 'speterson@ps118.com', 100, 85, '2022-10-16 12:00:00', '2022-10-16 03:00:00', 70.6, '2022-11-20 12:00:00', '2022-11-20 05:55:00', 93.1, '2022-12-11 12:00:00', '2022-12-11 10:11:00', 73.2); الخرج Query OK, 8 rows affected (0.01 sec) Records: 8 Duplicates: 0 Warnings: 0 وبمجرّد إدخالك للبيانات تغدو مستعدًا لبدء تعلّم كيفية استخدام الدوال CAST وتعابير الضم في SQL. استخدام دوال CAST تتيح لك الدالة CAST إمكانية تحويل قيمة حرفية أو القيم المُخزنة ضمن عمودٍ ما إلى نمط بياناتٍ محدد. ويُعدّ استخدامها مفيدًا لضمان توافق أنماط بيانات القيم ضمن تعبيرٍ مُعيّن. ولاستخدام الأمر CAST، لابدّ من تحديد العمود أو الأعمدة المُتضمّنة لقيم البيانات الحالية المرغوب بتحويلها، ومن ثم كتابة قيم البيانات المُفضلة و/أو طولها ضمن التعبير. وفيما يلي مثال على الصيغة العامّة: CAST function syntax . . .CAST(قيم البيانات الحالية AS قيم البيانات المرغوبة) . . . وتجدر الملاحظة إلى أنّ دوال CAST تخضع لقواعد معيّنة كي تعمل على نحو صحيح. فعلى سبيل المثال، من المهم التأكد من كون نمط البيانات الذي نرغب في تحويله متوافق مع النمط الذي سنحوّل إليه. فبالعودة إلى البيانات في مثالنا، لن نتمكن باستخدام دالة CAST من تحويل القيم في عمود درجة الامتحان النهائي finalexam_grade من قيم بيانات رقمية (وهو نمط البيانات decimal في حالتنا) مباشرةً إلى قيم سلاسل محرفية، كأن نعبّر عن الدرجات بأحرف. وبالمثل، لا يمكننا تحويل أنماط بيانات مُحدّدة بطول أقصى مثل الأعمدة التي تحمل قيم varchar(30)‎ في مثالنا إلى مقدار أطول مثل ‎varchar(35). كما تجدر الملاحظة إلى أنّ تقديمات SQL المختلفة ستتصرف على نحوٍ مختلف لدى تنفيذ استعلامات باستخدام دوال CAST لتحويل أنماط البيانات. فقد ينتج مثلًا عن تنفيذ استعلام باستخدام دالة CAST في MySQL نتائج مختلفة عن تشغيل نفس الاستعلام في PostgreSQL. الآن ولتحقيق فهم أعمق حول كيفية استخدام دوال CAST، لنتخيل السيناريو التالي بناءً على البيانات في المثال السابق والتي أدخلناها في الخطوة السابقة. إذ يستعد الأستاذ فريد، معلم الصف السادس في مدرسة PS 118، لتقديم درجاته للفصل الدراسي الخريفي. إذ كان يتابع عن كثب تقدم كل طالب، وهو مهتم على وجه الخصوص بدرجاتهم في كل من الامتحان النصفي وواجب كتابة المقال والامتحان النهائي. وبفرض أنّك تعمل مع الأستاذ فريد كمدرّس مساعد، وقد طلب منك تزويده بالمعلومات المُتعلّقة بالمهام الدراسية آنفة الذكر. ستستخرج في هذه الحالة البيانات المطلوبة من خلال إنشاء استعلام عن جدول fall_grades لاسترجاع المعلومات اللازمة، مثل أسماء الطلاب الأولى first_name وأسماءهم الأخيرة last_name، بالإضافة إلى درجاتهم لكل مهمة دراسية، على النحو التالي: mysql> SELECT first_name, last_name, midterm_grade, essay_grade, finalexam_grade FROM fall_grades; الخرج +------------+-----------+---------------+-------------+-----------------+ | first_name | last_name | midterm_grade | essay_grade | finalexam_grade | +------------+-----------+---------------+-------------+-----------------+ | Arnold | Shortman | 85.8 | 90.1 | 82.5 | | Helga | Pataki | 88.4 | 72.5 | 90.0 | | Gerald | Johanssen | 94.2 | 95.8 | 88.1 | | Phoebe | Heyerdahl | 98.8 | 90.4 | 100.0 | | Harold | Berman | 75.7 | 67.5 | 90.9 | | Eugene | Horowitz | 100.0 | 89.9 | 98.2 | | Rhonda | Lloyd | 90.4 | 81.3 | 95.5 | | Stinky | Peterson | 70.6 | 93.1 | 73.2 | +------------+-----------+---------------+-------------+-----------------+ 8 rows in set (0.00 sec) وبعد تقديم هذه النتائج إلى الأستاذ، بيّن لك أنّ النظام الذي يستخدمه يسمح له بإدخال الدرجات كأعداد صحيحة حصرًا، وبالتالي لابدّ من تحويل هذه القيم العشرية إلى أعدادٍ صحيحة. فتقرر استخدام دالة CAST لتحويل عدد القيم المحرفية المحدد (وهو في هذه الحالة أربعة محارف من نمط البيانات decimal) إلى قيمتين محرفيتين. وسنستخدم لهذا الاستعلام نفس صيغة الاستعلام من المثال أعلاه ولكن مع تضمين دالة CAST لتحويل نمط البيانات decimal إلى قيمتين محرفيتين فقط لكل من المهام الدراسية. إذ ستُطبّق الدالة CAST على ثلاث تعابير مختلفة (لكل من midterm_grade و essay_grade و final_exam_grade) وذلك لتحويلها إلى قيم بيانات مكونة من قيمتين محرفيتين فقط: mysql> SELECT first_name, last_name, mysql> CAST(midterm_grade AS char(2)) AS midterm, mysql> CAST(essay_grade AS char(2)) AS essay, mysql> CAST(finalexam_grade AS char(2)) AS finalexam mysql> FROM fall_grades; الخرج +------------+-----------+---------+-------+-----------+ | first_name | last_name | midterm | essay | finalexam | +------------+-----------+---------+-------+-----------+ | Arnold | Shortman | 85 | 90 | 82 | | Helga | Pataki | 88 | 72 | 90 | | Gerald | Johanssen | 94 | 95 | 88 | | Phoebe | Heyerdahl | 98 | 90 | 10 | | Harold | Berman | 75 | 67 | 90 | | Eugene | Horowitz | 10 | 89 | 98 | | Rhonda | Lloyd | 90 | 81 | 95 | | Stinky | Peterson | 70 | 93 | 73 | +------------+-----------+---------+-------+-----------+ 8 rows in set, 24 warnings (0.00 sec) الآن وبعد مراجعة درجات كل طالب، يسأل السيد فريد إذا كان بإمكانك جلب معلومات حول التواريخ والأوقات الدقيقة التي قدم فيها كل طالب مهامه الدراسية، ولجلب هذه البيانات، سننفّذ تعليمة SELECT التالية: mysql> SELECT first_name, last_name, midterm_deadline, essay_deadline, finalexam_deadline FROM fall_grades; الخرج +------------+-----------+---------------------+---------------------+---------------------+ | first_name | last_name | midterm_deadline | essay_deadline | finalexam_deadline | +------------+-----------+---------------------+---------------------+---------------------+ | Arnold | Shortman | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Helga | Pataki | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Gerald | Johanssen | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Phoebe | Heyerdahl | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Harold | Berman | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Eugene | Horowitz | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Rhonda | Lloyd | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | | Stinky | Peterson | 2022-10-16 12:00:00 | 2022-11-20 12:00:00 | 2022-12-11 12:00:00 | +------------+-----------+---------------------+---------------------+---------------------+ 8 rows in set (0.00 sec) يتنهد الأستاذ فريد بعد مراجعة هذا الخرج مُخبرًا إياك بأنّ هذه المعلومات صعبة التحليل للغاية. إذ تم تعيين كل هذه الأعمدة لتخزين قيم من نمط البيانات TIMESTAMP، وهو سبب طولها البالغ. فتقرر استخدام دالة CAST لتحويل الخرج إلى شكل أسهل للقراءة والفهم، وأيضًا لتقسيم الاستعلام إلى قسمين واحد للتواريخ وآخر للأوقات. للاستعلام فقط عن أوقات تقديم الطلاب لواجباتهم، لننفّذ دالة CAST محولين قيم البيانات من تلك الأعمدة المحددة إلى قيم وقت من النمط time: mysql> SELECT first_name, last_name, mysql> CAST(midterm_submitted AS time) AS midterm, mysql> CAST(essay_submitted AS time) AS essay, mysql> CAST(finalexam_submitted AS time) AS finalexam mysql> FROM fall_grades; الخرج +------------+-----------+----------+----------+-----------+ | first_name | last_name | midterm | essay | finalexam | +------------+-----------+----------+----------+-----------+ | Arnold | Shortman | 06:30:00 | 03:00:00 | 03:00:00 | | Helga | Pataki | 10:00:00 | 03:15:00 | 05:00:00 | | Gerald | Johanssen | 02:00:00 | 02:45:00 | 11:00:00 | | Phoebe | Heyerdahl | 11:00:00 | 11:15:00 | 11:40:00 | | Harold | Berman | 08:00:00 | 09:15:00 | 09:15:00 | | Eugene | Horowitz | 01:00:00 | 01:22:00 | 07:55:00 | | Rhonda | Lloyd | 06:00:00 | 06:09:00 | 06:45:00 | | Stinky | Peterson | 03:00:00 | 05:55:00 | 10:11:00 | +------------+-----------+----------+----------+-----------+ 8 rows in set (0.00 sec) يُقدّم هذا الخرج لمحة عامة حول الإطار الزمني الذي أنهى فيه كل طالب واجباته. ومع ملاحظة أنّ موعد تسليم كل من الواجبات يحين عند منتصف ليل يوم الأحد، نجد أنّ العديد من الطلاب كانوا متسقين مع توقيتهم، في حين أنهى آخرون واجباتهم مبكرًا، أو قبل الموعد المحدد عند منتصف الليل بقليل. ولكن تُمثّل هذه المعلومات نصف ما طلبه الأستاذ فريد فقط، لذا دعونا نعمل على الاستعلام التالي الذي سيستخدم دالة CAST لتحويل تلك القيم ذاتها من نمط البيانات TIMESTAMP إلى قيم تواريخ. سننفّذ نفس الاستعلام كما في المرة السابقة، ولكن مستبدلين نمط بيانات التاريخ time بنمط بيانات الوقت time هذه المرة: mysql> SELECT first_name, last_name, mysql> CAST(midterm_submitted AS date) AS midterm, mysql> CAST(essay_submitted AS date) AS essay, mysql> CAST(finalexam_submitted AS date) AS finalexam mysql> FROM fall_grades; الخرج +------------+-----------+------------+------------+------------+ | first_name | last_name | midterm | essay | finalexam | +------------+-----------+------------+------------+------------+ | Arnold | Shortman | 2022-10-16 | 2022-11-20 | 2022-12-11 | | Helga | Pataki | 2022-10-16 | 2022-11-21 | 2022-12-11 | | Gerald | Johanssen | 2022-10-16 | 2022-11-20 | 2022-12-11 | | Phoebe | Heyerdahl | 2022-10-16 | 2022-11-20 | 2022-12-11 | | Harold | Berman | 2022-10-16 | 2022-11-22 | 2022-12-11 | | Eugene | Horowitz | 2022-10-16 | 2022-11-20 | 2022-12-11 | | Rhonda | Lloyd | 2022-10-16 | 2022-11-20 | 2022-12-11 | | Stinky | Peterson | 2022-10-16 | 2022-11-20 | 2022-12-11 | +------------+-----------+------------+------------+------------+ 8 rows in set (0.00 sec) يُمكننا استنادًا إلى هذا الخرج تحديد الطلاب الذين قدموا واجباتهم بعد الموعد النهائي، ما يوضّح سبب تأثر درجاتهم الناتج عن خصم نقاط بسبب التأخر. فعلى سبيل المثال، قدّمت الطالبة هيلجا واجبها بعد يوم واحد من الموعد (2022-11-21)، وقدّم الطالب هارولد واجبه بعد يومين (2022-11-22) من الموعد النهائي للمقال والمُحدد في (2022-11-20). على الرغم من رضا الأستاذ فريد عن هذه النتائج المُنقحة، إلا أنه يحتاج إلى مزيد من المساعدة في صياغة البيانات على نحوٍ أوضح لتقديم تقرير درجاته. ستتدرب في القسم التالي من المقال على كيفية استخدام تعابير الضم القادرة على دمج قيم حرفية متعددة أو القيم من أعمدة مختلفة ضمن قيمة سلسلة نصية واحدة، مما يساعد في جعل المعلومات أوضح للتفسير كعبارة أو جملة كاملة. استخدام تعابير الضم يُمكنك مع استخدام تعبير الضم CONCAT معالجة البيانات بجمع قيم محرفية أو رقمية من أعمدة مختلفة معًا لتظهر كنتيجة واحدة. إذ تعيد قواعد بيانات SQL بوجهٍ عام قيم البيانات ضمن مجموعات نتائج منفصلة في أعمدتها المختلفة. فعلى سبيل المثال، لو استعلمنا عن الاسم الأول first_name والأخير last_name لطلاب PS 118، سيظهر الخرج كما يلي: mysql> SELECT first_name, last_name FROM fall_grades; الخرج +------------+-----------+ | first_name | last_name | +------------+-----------+ | Arnold | Shortman | | Helga | Pataki | | Gerald | Johanssen | | Phoebe | Heyerdahl | | Harold | Berman | | Eugene | Horowitz | | Rhonda | Lloyd | | Stinky | Peterson | +------------+-----------+ 8 rows in set (0.00 sec) لكن هذه المعلومات غير مُقدّمة بالشكل الذي يفضله الأستاذ فريد لتقريره. لذا، لننفّذ استعلامًا آخر باستخدام الضم لدمج الأسماء الأولى والأخيرة للطلاب ضمن سلسلة نصية واحدة. يؤدي الاستعلام التالي هذه المهمة باستخدام الكلمة المفتاحية CONCAT موفرًا للعمود الناتج الاسم بديل full_names، على النحو التالي: mysql> SELECT CONCAT(first_name, last_name) AS full_names FROM fall_grades; الخرج +-----------------+ | full_names | +-----------------+ | ArnoldShortman | | HelgaPataki | | GeraldJohanssen | | PhoebeHeyerdahl | | HaroldBerman | | EugeneHorowitz | | RhondaLloyd | | StinkyPeterson | +-----------------+ 8 rows in set (0.00 sec) تعمل تعابير الضم مع كافة أنماط البيانات إجمالًا، ولكن في حال عدم تحديد تفاصيل من قبيل التباعد بين قيم البيانات، سيظهر الخرج على هيئة سلسلة نصية متصلة دون فواصل كما يتضح من الخرج أعلاه. ولتصحيح ذلك، يمكن إضافة زوج من علامات الاقتباس الفردية مع فراغ بينهما (' ') بين عمودي first_name وlast_name بحيث تظهر القيم على هيئة سلسلة واحدة، ولكن هذه المرة مع فراغ بينهما لجعلها أسهل للقراءة: mysql> SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM fall_grades; الخرج +------------------+ | full_name | +------------------+ | Arnold Shortman | | Helga Pataki | | Gerald Johanssen | | Phoebe Heyerdahl | | Harold Berman | | Eugene Horowitz | | Rhonda Lloyd | | Stinky Peterson | +------------------+ 8 rows in set (0.00 sec) بإدراج فراغ بين علامات الاقتباس الفردية في الاستعلام، يُظهر الخرج الآن أسماء الطلاب بوضوح ككلمتين منفصلتين، بدلًا من كلمة مركبة واحدة. ملاحظة: معظم أنظمة إدارة قواعد البيانات العلاقية الحديثة تستخدم الصياغة الموضحة في هذا القسم لضم القيم. إلّا لأنّ هذه الصياغة (استخدام كلمة CONCAT المفتاحية) ليست الصياغة التقليدية المحددة بمعيار SQL. الطريقة التقليدية لضم القيم في SQL تتمثّل في وضع زوج من الخطوط العمودية بين قيم البيانات التي نريد ضمها. تجدر الملاحظة بأنّ MySQL لا تسمح باستخدام هذه الصياغة على الإطلاق، في حين تسمح بعض أنظمة إدارة قواعد البيانات مثل PostgreSQL باستخدام إحدى الطريقتين. الاستعلام التالي (المُنفذ على قاعدة بيانات PostgreSQL) يُعطي نفس الخرج كالاستعلام السابق، ولكن هذه المرة باستخدام الخطوط العمودية: SELECT first_name || ' ' || last_name AS full_name FROM fall_grades; الخرج full_name ------------------ Arnold Shortman Helga Pataki Gerald Johanssen Phoebe Heyerdahl Harold Berman Eugene Horowitz Rhonda Lloyd Stinky Peterson (8 rows) الآن، لنجرب مثالاً آخر حيث سنسترجع مزيدًا من المعلومات حول كل طالب. إذ نريد في هذه المرة ضم قيم البيانات من أعمدة الاسم الأول first_name والاسم الأخير last_name وعنوان البريد الإلكتروني email_address ودرجة الامتحان النهائي finalexam_grade ووقت وتاريخ تقديم الامتحان النهائي finalexam_submitted ضمن عمود واحد باستخدام CONCAT. ومن المهم هنا عدم نسيان إضافة علامات اقتباس فردية بين كل عمود وآخر نرغب بإضافة فراغ بينهما كما في المثال التالي: mysql> SELECT CONCAT(first_name, ' ', last_name, ' ', mysql> email_address, ' ', finalexam_grade, ' ', finalexam_submitted) mysql> AS student_info FROM fall_grades; الخرج +-----------------------------------------------------------------+ | student_info | +-----------------------------------------------------------------+ | Arnold Shortman ashortman@ps118.com 82.5 2022-12-11 03:00:00 | | Helga Pataki hpataki@ps118.com 90.0 2022-12-11 05:00:00 | | Gerald Johanssen gjohanssen@ps118.com 88.1 2022-12-11 11:00:00 | | Phoebe Heyerdahl pheyerdahl@ps118.com 100.0 2022-12-11 11:40:00 | | Harold Berman hberman@ps118.com 90.9 2022-12-11 09:15:00 | | Eugene Horowitz ehorowitz@ps118.com 98.2 2022-12-11 07:55:00 | | Rhonda Lloyd rlloyd@ps118.com 95.5 2022-12-11 06:45:00 | | Stinky Peterson speterson@ps118.com 73.2 2022-12-11 10:11:00 | +-----------------------------------------------------------------+ 8 rows in set (0.00 sec) الأستاذ فريد راضٍ عن هذه النتائج ولكنه يودّ جعلها أكثر إيجازًا ضمن تقرير درجاته وذلك من خلال تحويل بعض قيم البيانات. سنستخدم في هذا السيناريو دالة CAST لتحويل نمط بيانات عمود finalexam_grade إلى رقم صحيح، ونمط بيانات عمود finalexam_submitted من TIMESTAMP إلى نمط بيانات التاريخ date كما في المثال التالي: mysql> SELECT CONCAT(first_name, ' ', last_name, ' ', email_address, ' ', mysql> CAST(finalexam_grade AS char(2)), ' ', mysql> CAST(finalexam_submitted AS date)) mysql> AS student_info FROM fall_grades; الخرج +-----------------------------------------------------+ | student_info | +-----------------------------------------------------+ | Arnold Shortman ashortman@ps118.com 82 2022-12-11 | | Helga Pataki hpataki@ps118.com 90 2022-12-11 | | Gerald Johanssen gjohanssen@ps118.com 88 2022-12-11 | | Phoebe Heyerdahl pheyerdahl@ps118.com 10 2022-12-11 | | Harold Berman hberman@ps118.com 90 2022-12-11 | | Eugene Horowitz ehorowitz@ps118.com 98 2022-12-11 | | Rhonda Lloyd rlloyd@ps118.com 95 2022-12-11 | | Stinky Peterson speterson@ps118.com 73 2022-12-11 | +-----------------------------------------------------+ 8 rows in set, 8 warnings (0.00 sec) لنحسّن الخرج أكثر، ونكتب استعلامًا يستخدم دالة CAST وتعبير الضم للحصول على جملة كاملة في الخرج. يمكننا القيام بذلك بكتابة عبارة قصيرة بين علامات الاقتباس الفردية. ولا بدّ من الحفاظ على مسافة بين كل عمود وآخر عن طريق إضافة مسافة واحدة قبل وبعد نهاية العبارة أو العبارات المكتوبة: mysql> SELECT CONCAT(first_name, ' ', last_name, ' can be contacted at ', email_address, mysql> ' and received a grade of ', mysql> CAST(finalexam_grade AS char(2)), mysql> ' after submitting the final exam on ', mysql> CAST(finalexam_submitted AS date)) mysql> AS student_info FROM fall_grades; الخرج +------------------------------------------------------------------------------------------------------------------------------------+ | student_info | +------------------------------------------------------------------------------------------------------------------------------------+ | Arnold Shortman can be contacted at ashortman@ps118.com and received a grade of 82 after submitting the final exam on 2022-12-11 | | Helga Pataki can be contacted at hpataki@ps118.com and received a grade of 90 after submitting the final exam on 2022-12-11 | | Gerald Johanssen can be contacted at gjohanssen@ps118.com and received a grade of 88 after submitting the final exam on 2022-12-11 | | Phoebe Heyerdahl can be contacted at pheyerdahl@ps118.com and received a grade of 10 after submitting the final exam on 2022-12-11 | | Harold Berman can be contacted at hberman@ps118.com and received a grade of 90 after submitting the final exam on 2022-12-11 | | Eugene Horowitz can be contacted at ehorowitz@ps118.com and received a grade of 98 after submitting the final exam on 2022-12-11 | | Rhonda Lloyd can be contacted at rlloyd@ps118.com and received a grade of 95 after submitting the final exam on 2022-12-11 | | Stinky Peterson can be contacted at speterson@ps118.com and received a grade of 73 after submitting the final exam on 2022-12-11 | +------------------------------------------------------------------------------------------------------------------------------------+ 8 rows in set, 8 warnings (0.00 sec) يُنتج هذا الخرج جملًا كاملة عن كل طالب في فصل الأستاذ فريد. إذ أحدثت هذه الإضافات الطفيفة بين علامات الاقتباس الفردية فارقًا كبيرًا في جعل المعلومات واضحة من حيث هوية صاحب كل معلومة والبيانات المتعلقة به. وذلك بفضل الأعمدة المحددة التي استخرجنا منها البيانات في استعلامنا. الأستاذ فريد ممتن جدًا لعملك الرائع، وهو راضٍ تمامًا لأنك وفرت عليه الوقت أيضًا بتحويلك البيانات إلى جمل مفهومة يمكنه إضافتها بسهولة إلى تقريره. الخلاصة قدّمنا في هذا المقال شرحًا حول استخدامات متنوعة لمعالجة البيانات باستخدام دالة CAST وتعابير الضم. إذ تدربت على كيفية تحويل قيم عمود من نمط بيانات إلى آخر بفضل الدالة CAST. كما تعلمت كيفية استخدام تعابير الضم لجمع قيم بيانات مختلفة سواء كانت محرفية أو رقمية في سلسلة نصية واحدة. كما نفذت استعلامًا يتضمّن كل من دالة CAST وتعبير الضم معًا، مما أتاح لك إنشاء خرج بجمل كاملة توفر تفسيرًا أشمل للبيانات، الأمر الذي يجعل من الأسهل كتابة الجمل على نحوٍ مستقل عن بعضها، إذ يمكنك تنظيم المعلومات بكفاءة ونسخها ولصقها بالصيغة التي هي عليها مباشرةً. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Manipulate Data with CAST Functions and Concatenation Expressions in SQL لصاحبه Jeanelle Horcasitas. اقرأ أيضًا المقال السابق: كيفية التعامل مع التواريخ والأوقات في SQL مدخل إلى SQL دوال التعامل مع البيانات في SQL دوال التعامل مع النصوص في SQL التعابير الجدولية الشائعة Common Table Expressions في SQL
  3. قد تضطر في بعض الأحيان إلى التعامل مع قيم تمثل تواريخ أو أوقات محددة لدى العمل مع قواعد البيانات العلاقية ولغة الاستعلام البنيوية SQL. فعلى سبيل المثال، قد ترغب في حساب إجمالي الساعات المُستغرقة في أداء نشاطٍ ما، أو قد تحتاج إلى مُعالجة قيم التواريخ أو الأوقات باستخدام المعاملات الرياضية ودوال التجميع لحساب مجموعها أو متوسطها. ستتعلم في هذا المقال كيفية استخدام التواريخ والأوقات في SQL. إذ ستبدأ بإجراء العمليات الحسابية واستخدام دوال متنوعة مع التواريخ والأوقات باستخدام تعليمة SELECT وحدها. لتتدرب بعد ذلك على تنفيذ استعلامات على بياناتٍ نموذجية تجريبية، وستتعلم كيفية استخدام دالة CAST لجعل النتائج أيسر للقراءة. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول من نوع sudo مختلف عن المستخدم الجذر، وجدار حماية مُفعّل، كما هو موضح المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على الأمثلة حول كيفية استخدام التاريخ والوقت في هذا المقال. لذا ننصحك بمتابعة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم datetimeDB: mysql> CREATE DATABASE datetimeDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات datetimeDB، نفّذ تعليمة USE التالية: mysql> USE datetimeDB; الخرج Database changed الآن وبعد اختيار قاعدة البيانات datetimeDB، لننشئ جدولًا ضمنها. كمثال في مقالنا هذا، سننشئ جدولًا يُسجّل نتائج اثنين من العدائين في مختلف السباقات التي شاركا بها على مدار عام. سيضم الجدول الأعمدة السبعة التالية: race_id: يُمثّل قيمًا من نمط بيانات الأعداد الصحيحة int وسيكون المفتاح الأساسي للجدول، ما يعني أن كل قيمة في هذا العمود ستلعب دور المعرّف الفريد لسجلها. runner_name: مُخصص لأسماء هذين العدّائين وهما في مثالنا أحمد ومحمد وذلك باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. race_name: يُخزّن أسماء السباقات باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. start_day: لتسجيل تاريخ السباق باليوم والشهر والسنة باستخدام نمط البيانات DATE. إذ يتبع نمط البيانات هذا الصيغة القياسية التالية: أربعة أرقام للسنة، وحد أقصى من رقمين لكل من الشهر واليوم (YYYY-MM-DD). start_time: يُمثل وقت بداية السباق باستخدام نمط بيانات TIME بالساعات والدقائق والثواني (HH:MM:SS). ويستخدم نمط البيانات هذا الوقت بصيغة 24 ساعة، أي 15:00 على سبيل المثال للتعبير عن الساعة 3:00 مساءً. total_miles: يعرض المسافة الإجمالية لكل سباق باستخدام نمط بيانات decimal، وذلك نظرًا لأن العديد من المسافات الإجمالية للسباقات ليست بأرقامٍ صحيحة. ويُمكّننا نمط البيانات decimal من تمثيل المسافات بدقة تصل إلى ثلاثة أرقام ككل، منها رقم واحد إلى يمين الفاصلة العشرية، أي بدقة تصل إلى عُشر الميل. end_time: يُسجّل وقت انتهاء كل عدّاء من السباق باستخدام نمط بيانات TIMESTAMP، الذي يدمج التاريخ والوقت ضمن تنسيق واحد يجمع بين صيغتي DATE وTIME مُشتملًا على السنة والشهر واليوم متبوعًا بالساعة والدقيقة والثانية، وهو يعتمد في ذلك على الصيغة القياسية (YYYY-MM-DD HH:MM:SS) لتقديم توقيت دقيق للحظة انتهاء السباق. ولإنشاء هذا الجدول، لننفّذ الأمر CREATE TABLE التالي: mysql> CREATE TABLE race_results ( mysql> race_id int, mysql> runner_name varchar(30), mysql> race_name varchar(20), mysql> start_day DATE, mysql> start_time TIME, mysql> total_miles decimal(3, 1), mysql> end_time TIMESTAMP, mysql> PRIMARY KEY (race_id) mysql> ); ثم املأ هذا الجدول الفارغ ببعض البيانات التجريبية النموذجية: mysql> INSERT INTO race_results mysql> (race_id, runner_name, race_name, start_day, start_time, total_miles, end_time) mysql> VALUES mysql> (1, 'Ahmad', '1600_meters', '2022-09-18', '7:00:00', 1.0, '2022-09-18 7:06:30'), mysql> (2, 'Ahmad', '5K', '2022-10-19', '11:00:00', 3.1, '2022-10-19 11:22:31'), mysql> (3, 'Ahmad', '10K', '2022-11-20', '10:00:00', 6.2, '2022-11-20 10:38:05'), mysql> (4, 'Ahmad', 'half_marathon', '2022-12-21', '6:00:00', 13.1, '2022-12-21 07:39:04'), mysql> (5, 'Ahmad', 'full_marathon', '2023-01-22', '8:00:00', 26.2, '2023-01-22 11:23:10'), mysql> (6, 'Mohammad', '1600_meters', '2022-09-18', '7:00:00', 1.0, '2022-09-18 7:07:15'), mysql> (7, 'Mohammad', '5K', '2022-10-19', '11:00:00', 3.1, '2022-10-19 11:30:50'), mysql> (8, 'Mohammad', '10K', '2022-11-20', '10:00:00', 6.2, '2022-11-20 11:10:17'), mysql> (9, 'Mohammad', 'half_marathon', '2022-12-21', '6:00:00', 13.1, '2022-12-21 08:11:57'), mysql> (10, 'Mohammad', 'full_marathon', '2023-01-22', '8:00:00', 26.2, '2023-01-22 12:02:10'); الخرج Query OK, 10 rows affected (0.00 sec) Records: 10 Duplicates: 0 Warnings: 0 وبمجرّد إدخالك للبيانات تغدو مستعدًا لبدء التدرّب على استخدام بعض العمليات الحسابية والدوال مع التواريخ في SQL. استخدام العمليات الحسابية مع التواريخ والأوقات من الممكن معالجة قيم التواريخ والأوقات في SQL باستخدام التعابير الرياضية، ولن تحتاج سوى إلى العامل الرياضي والقيم المطلوب حسابها. كمثال، لو أردنا تحديد تاريخ يأتي بعد عدد محدد من الأيام من تاريخٍ آخر. يأخذ الاستعلام التالي قيمة تاريخ معينة (2022-10-05) ويضيف إليها 17، لإعادة قيمة التاريخ الذي يأتي بعد سبعة عشر يومًا من التاريخ المحدد في الاستعلام. وتجدر الملاحظة إلى أننا حددنا 2022-10-05 هنا كقيمة من نوع DATE لضمان عدم تفسير نظام إدارة قاعدة البيانات لها كنص أو أي نوع بيانات آخر: mysql> SELECT DATE '2022-10-05' + 17 AS new_date; الخرج +----------+ | new_date | +----------+ | 20221022 | +----------+ 1 row in set (0.01 sec) نلاحظ من هذا الخرج أنّ اليوم الذي يأتي بعد سبعة عشر يومًا من تاريخ 2022-10-05 هو 2022-10-22، أو 22 أكتوبر 2022. كمثال آخر، بفرض أننا نريد حساب إجمالي الساعات بين وقتين مختلفين من خلال طرحهما من بعضهما البعض. افترضنا في الاستعلام التالي أنّ 11:00 هو الوقت الأول و3:00 هو الوقت الثاني. ولا بدّ في هذه الحالة من تحديد أن كلاهما من نمط البيانات TIME للحصول على الفرق بالساعات: mysql> SELECT TIME '11:00' - TIME '3:00' AS time_diff; الخرج +-----------+ | time_diff | +-----------+ | 80000 | +-----------+ 1 row in set (0.00 sec) يُظهر هذا الخرج أن الفارق بين الساعة 11:00 والساعة 3:00 هو 80000، أو 8 ساعات. الآن سنطبّق استعلامات على بياناتنا النموذجية بهدف التدرّب على كيفية استخدام العمليات الحسابية على معلومات التاريخ والوقت. بالنسبة للاستعلام الأول، سنحسب الزمن الكلي الذي استغرقه العداءان لإنهاء كل سباق من خلال طرح زمن الانتهاء end_time من زمن البدء start_time، على النحو التالي: mysql> SELECT runner_name, race_name, end_time - start_time mysql> AS total_time mysql> FROM race_results; الخرج +-------------+---------------+----------------+ | runner_name | race_name | total_time | +-------------+---------------+----------------+ | Ahmad | 1600_meters | 20220918000630 | | Ahmad | 5K | 20221019002231 | | Ahmad | 10K | 20221120003805 | | Ahmad | half_marathon | 20221221013904 | | Ahmad | full_marathon | 20230122032310 | | Mohammad | 1600_meters | 20220918000715 | | Mohammad | 5K | 20221019003050 | | Mohammad | 10K | 20221120011017 | | Mohammad | half_marathon | 20221221021157 | | Mohammad | full_marathon | 20230122040210 | +-------------+---------------+----------------+ 10 rows in set (0.00 sec) ستلاحظ أن القيم المُعادة في عمود total_time تظهر على نحوٍ مطوّل وقد تصعب قراءتها. لذا سنشرح في قسمٍ لاحق ضمن هذا المقال كيفية استخدام دالة CAST لتنسيق هذه القيم بطريقة تجعلها أوضح وأسهل للقراءة. بفرض أنّ تركيزنا منصب على أداء العدّائين في السباقات ذات المسافات الأطول، من قبيل سباقات نصف الماراثون والماراثون الكامل، سنستخدم استعلامًا لاسترجاع هذه البيانات على وجه الخصوص. إذ سنطرح زمن الانتهاء end_time من زمن البدء start_time مُستخدمين بنية WHERE للتحديد الدقيق للنتائج، لتشمل فقط السباقات التي تزيد مسافتها في العمود total_miles عن 12 ميل: mysql> SELECT runner_name, race_name, end_time - start_time AS half_full_results mysql> FROM race_results mysql> WHERE total_miles > 12; الخرج +-------------+---------------+-------------------+ | runner_name | race_name | half_full_results | +-------------+---------------+-------------------+ | Ahmad | half_marathon | 20221221013904 | | Ahmad | full_marathon | 20230122032310 | | Mohammad | half_marathon | 20221221021157 | | Mohammad | full_marathon | 20230122040210 | +-------------+---------------+-------------------+ 4 rows in set (0.00 sec) أجرينا في هذا القسم بعض العمليات الحسابية على التواريخ والأوقات باستخدام تعليمة SELECT وذلك لأغراض عملية على البيانات النموذجية التجريبية. فيما يلي، ستتدرب على استخدام استعلامات تشمل دوال متنوعة للتواريخ والأوقات. استخدام دوال التاريخ والوقت وتعابير الفترات الزمنية هناك العديد من الدوال التي يُمكن استخدامها لإيجاد ومعالجة قيم التواريخ والأوقات في SQL. إذ تُستخدم الدوال SQL على نحوٍ أساسي لمعالجة البيانات أو التعامل معها والتحكم بها، وتختلف الدوال المتوفرة باختلاف تقديم SQL المُستخدم. بيد أنّ معظم تقديمات SQL تتيح استرجاع القيم الحالية للتاريخ والوقت من خلال الاستعلام عن قيم دالتيّ current_date وcurrent_time. على سبيل المثال، لمعرفة تاريخ اليوم، فالصياغة بسيطة وتتألف فقط من تعليمة SELECT ودالة current_date كما في المثال التالي: mysql> SELECT current_date; الخرج +--------------+ | current_date | +--------------+ | 2022-02-15 | +--------------+ 1 row in set (0.00 sec) كما يمكننا إيجاد الوقت الحالي باستخدام نفس الصياغة اعتمادًا على الدالة current_time، على النحو التالي: mysql> SELECT current_time; الخرج +--------------+ | current_time | +--------------+ | 17:10:20 | +--------------+ 1 row in set (0.00 sec) أمّا إذا كنت تفضل الاستعلام عن كل من التاريخ والوقت معًا في خرجٍ واحد، فاستخدم الدالة current_timestamp: mysql> SELECT current_timestamp; الخرج +---------------------+ | current_timestamp | +---------------------+ | 2022-02-15 19:09:58 | +---------------------+ 1 row in set (0.00 sec) كما يمكن استخدام دوال التاريخ والوقت كتلك المُستخدمة أعلاه ضمن دوال حسابية على نحوٍ مشابه للقسم السابق من مقالنا. فعلى سبيل المثال، بفرض أنّنا نريد معرفة التاريخ قبل 11 يومًا من تاريخ اليوم الحالي، فيمكننا استخدام نفس الصيغة المُستخدمة سابقًا للاستعلام عن قيمة الدالة current_date مطروحًا منها العدد 11 لإيجاد التاريخ قبل أحد عشر يومًا: mysql> SELECT current_date - 11; الخرج +-------------------+ | current_date - 11 | +-------------------+ | 20220206 | +-------------------+ 1 row in set (0.01 sec) يُشير هذا الخرج لكون التاريخ قبل 11 يومًا من current_date (وقت كتابة هذا النص) هو 2022-02-06، أو 6 فبراير 2022. لنحاول الآن تنفيذ نفس العملية مستبدلين الدالة current_date بالدالة current_time: mysql> SELECT current_time - 11; الخرج +-------------------+ | current_time - 11 | +-------------------+ | 233639 | +-------------------+ 1 row in set (0.00 sec) يُظهر هذا الخرج أنه عند طرح 11 من قيمة current_time، يُطرح فعليًا مقدار 11 ثانية. في حين تُفسّر العملية التي نفذناها سابقًا باستخدام الدالة current_date العدد 11 على أنه أيام وليس ثواني. هذا التباين في تفسير الأرقام لدى التعامل مع دوال التاريخ والوقت قد يكون مربكًا. فبدلاً من اضطرارك لمعالجة قيم التاريخ والوقت باستخدام الحسابات الرياضية بهذا الشكل، توفّر العديد من أنظمة إدارة قواعد البيانات وضوحًا أكبر من خلال استخدام تعابير الفترات الزمنية INTERVAL. تسمح تعابير الفترات الزمنية INTERVAL بإيجاد ما سيكون عليه التاريخ أو الوقت قبل أو بعد فترة محددة من تعبير تاريخ أو وقت معين. وتأخذ هذه التعابير الصيغة التالية: INTERVAL `value` `unit` على سبيل المثال، لإيجاد التاريخ بعد خمسة أيام من الآن، يُمكننا تشغيل الاستعلام التالي: mysql> SELECT current_date + INTERVAL '5' DAY AS "5_days_from_today"; أوجدنا في هذا المثال قيمة current_date، ثم أضفنا إليها تعبير الفترة INTERVAL '5' DAY. ما سيُعيد التاريخ بعد خمسة أيام من الآن: الخرج +-------------------+ | 5_days_from_today | +-------------------+ | 2022-03-06 | +-------------------+ 1 row in set (0.00 sec) وهذا أقل غموضًا بكثير من الاستعلام التالي، الذي ينتج عنه خرج مشابه، وحتى إن لم يكن مطابقًا تمامًا: mysql> SELECT current_date + 5 AS "5_days_from_today"; الخرج +-------------------+ | 5_days_from_today | +-------------------+ | 20220306 | +-------------------+ 1 row in set (0.00 sec) يُلاحظ أنه يُمكن أيضًا طرح فترات زمنية من التواريخ أو الأوقات لإيجاد قيم قبل التاريخ المحدد: mysql> SELECT current_date - INTERVAL '7' MONTH AS "7_months_ago"; الخرج +--------------+ | 7_months_ago | +--------------+ | 2021-08-01 | +--------------+ 1 row in set (0.00 sec) تعتمد الوحدات المتاحة لك لاستخدامها في تعابير INTERVAL على نظام إدارة قواعد البيانات DBMS الذي اخترته، ولكن تتوفّر في معظم الأنظمة خيارات من قبيل HOUR وMINUTE وSECOND: mysql> SELECT current_time + INTERVAL '6' HOUR AS "6_hours_from_now", mysql> current_time - INTERVAL '5' MINUTE AS "5_minutes_ago", mysql> current_time + INTERVAL '20' SECOND AS "20_seconds_from_now"; الخرج +------------------+---------------+---------------------+ | 6_hours_from_now | 5_minutes_ago | 20_seconds_from_now | +------------------+---------------+---------------------+ | 07:51:43 | 01:46:43 | 01:52:03.000000 | +------------------+---------------+---------------------+ 1 row in set (0.00 sec) الآن وبعد أن اطلعت على تعابير الفترات الزمنية وتعرفت على بعض دوال التاريخ والوقت، الفرصة أمامك لتتدرّب مستخدمًا البيانات التجريبية النموذجية التي أدرجتها في الخطوة الأولى. استخدام الدالة CAST ودوال التجميع مع التاريخ والوقت بالعودة إلى المثال الثالث من قسم استخدام العمليات الحسابية مع التاريخ والوقت، حين نفّذنا الاستعلام التالي لطرح زمن الانتهاء end_time من زمن البدء start_time لحساب إجمالي الساعات التي أكملها كل عداء لكل سباق. حصلنا على العمود total_time والذي يتضمّن ناتج طويل جدًا يتبع نمط البيانات TIMESTAMP المُحدد لهذا العمود من الجدول: mysql> SELECT runner_name, race_name, end_time - start_time mysql> AS total_time mysql> FROM race_results; الخرج +-------------+---------------+----------------+ | runner_name | race_name | total_time | +-------------+---------------+----------------+ | Ahmad | 1600_meters | 20220918000630 | | Ahmad | 5K | 20221019002231 | | Ahmad | 10K | 20221120003805 | | Ahmad | half_marathon | 20221221013904 | | Ahmad | full_marathon | 20230122032310 | | Mohammad | 1600_meters | 20220918000715 | | Mohammad | 5K | 20221019003050 | | Mohammad | 10K | 20221120011017 | | Mohammad | half_marathon | 20221221021157 | | Mohammad | full_marathon | 20230122040210 | +-------------+---------------+----------------+ 10 rows in set (0.00 sec) ونظرًا لأنّنا نُجري عملية على عمودين بأنماط بيانات مختلفة (عمود end_time يحمل قيم من نمط TIMESTAMP وعمود start_time يحمل قيم من نمط TIME)، فإنّ قاعدة البيانات لا تعرف أي نمط بيانات يجب أن تستخدم لدى طباعة نتيجة العملية. لذا تحوّل كلا القيمتين إلى أعداد صحيحة لتتمكن من تنفيذ العملية، مما ينتج عنه الأرقام الطويلة في عمود total_time. وكحل لجعل هذه البيانات أوضح للقراءة والتفسير، يمكنك استخدام دالة CAST لتحويل هذه القيم الطويلة من الأعداد الصحيحة إلى نمط بيانات TIME. وللقيام بذلك، ابدأ بالدالة CAST ثم اتبعها مباشرةً بقوس فتح ثم القيم التي تريد تحويلها ثم الكلمة المفتاحية AS متبوعة باسم نمط البيانات الذي تريد التحويل إليه. الاستعلام التالي مطابق للمثال السابق، ولكنه يستخدم دالة CAST لتحويل عمود total_time إلى نمط بيانات TIME: mysql> SELECT runner_name, race_name, CAST(end_time - start_time AS time) mysql> AS total_time mysql> FROM race_results; الخرج +-------------+---------------+------------+ | runner_name | race_name | total_time | +-------------+---------------+------------+ | Ahmad | 1600_meters | 00:06:30 | | Ahmad | 5K | 00:22:31 | | Ahmad | 10K | 00:38:05 | | Ahmad | half_marathon | 01:39:04 | | Ahmad | full_marathon | 03:23:10 | | Mohammad | 1600_meters | 00:07:15 | | Mohammad | 5K | 00:30:50 | | Mohammad | 10K | 01:10:17 | | Mohammad | half_marathon | 02:11:57 | | Mohammad | full_marathon | 04:02:10 | +-------------+---------------+------------+ 10 rows in set (0.00 sec) حوّلت الدالة CAST قيم البيانات في الخرج أعلاه إلى نمط البيانات TIME، ما جعلها أسهل للقراءة والفهم. لنستخدم الآن بعضًا من دوال التجميع مع دالة CAST بهدف إيجاد أقصر وأطول توقيت وإجمالي الوقت لكل عدّاء. لنستعلم بدايةً عن أصغر (أو أقصر) مدة زمنية مستغرقة باستخدام دالة التجميع MIN. ومجددًا لا بدّ من استخدام الدالة CAST هنا لتحويل قيم البيانات من النمط TIMESTAMP إلى النمط TIME مما يجعلها أوضح. وتجدر الملاحظة إلى أنّه عند استخدام دالتين كما في هذا المثال، تتطلب العملية استخدام زوجين من الأقواس الهلالية، إذ يجب أن تكون عملية حساب إجمالي الساعات المُتمثلة في طرح زمن البدء من زمن الانتهاء (end_time - start_time) متداخلةً ضمن إحداها. وأخيرًا، سنضيف بنية GROUP BY لتنظيم هذه القيم بناءً على عمود runner_name بحيث يعرض الخرج نتائج سباقات كلا العدّائين: mysql> SELECT runner_name, MIN(CAST(end_time - start_time AS time)) AS min_time mysql> FROM race_results GROUP BY runner_name; الخرج +-------------+----------+ | runner_name | min_time | +-------------+----------+ | Ahmad | 00:06:30 | | Mohammad | 00:07:15 | +-------------+----------+ 2 rows in set (0.00 sec) يُظهر هذا الخرج أقصر زمن لكل عدّاء، في هذه الحالة الحد الأدنى هو ست دقائق وثلاثون ثانية لأحمد، وسبع دقائق وخمس عشرة ثانية لمحمد. الآن ولإيجاد أطول زمن لكل عدّاء، يمكننا استخدام نفس الصيغة كما في الاستعلام السابق مستبدلين الدالة MIN بالدالة MAX: mysql> SELECT runner_name, MAX(CAST(end_time - start_time AS time)) AS max_time mysql> FROM race_results GROUP BY runner_name; الخرج +-------------+----------+ | runner_name | max_time | +-------------+----------+ | Ahmad | 03:23:10 | | Mohammad | 04:02:10 | +-------------+----------+ 2 rows in set (0.00 sec) يُشير هذا الخرج أن أطول زمن جري لأحمد بلغ كإجمالي ثلاث ساعات وثلاث وعشرين دقيقة وعشر ثوانٍ؛ ولمحمد كان أربع ساعات ودقيقتين وعشر ثوانٍ. أمّا الآن، لنستعلم عن بعض المعلومات العامة (الخلاصة) حول إجمالي الساعات التي قضاها كل عدّاء في الجري. سندمج في هذا الاستعلام دالة التجميع SUM لإيجاد المجموع الإجمالي للساعات بناءً على الفرق بين end_time وstart_time، كما سنستخدم الدالة CAST لتحويل هذه قيم البيانات هذه إلى نمط البيانات TIME. كما لم ننسَ تضمين GROUP BY لتنظيم قيم نتائج كلا العدّاءين: mysql> SELECT runner_name, SUM(CAST(end_time - start_time AS time)) mysql> AS total_hours FROM race_results GROUP BY runner_name; الخرج +-------------+-------------+ | runner_name | total_hours | +-------------+-------------+ | Ahmad | 52880 | | Mohammad | 76149 | +-------------+-------------+ 2 rows in set (0.00 sec) يُبيّن هذا الخرج على نحوٍ مثير للاهتمام كيفية تفسير MySQL للنتائج، والتي في الواقع تحسب الزمن الإجمالي في هيئة أعداد صحيحة. فلو قرأنا هذه النتائج على أنها أزمنة، فإنّ الرقم المُعبّر عن إجمالي زمن أحمد يُفصّل إلى خمس ساعات وثمان وعشرين دقيقة وثمانين ثانية؛ وزمن محمد يُفصّل إلى سبع ساعات وواحد وستين دقيقة وتسع وأربعين ثانية. ومن الواضح أنّ هذا التفصيل للأزمنة لا يبدو منطقيًا وفقًا للقيم الفعلية، مما يشير إلى أنّ MySQL تعاملت مع الأزمنة كأعداد صحيحة. أمّا إذا جربنا الأمر عينه في نظام إدارة قواعد بيانات مختلف، من قبيل PostgreSQL على سبيل المثال، فسيبدو الاستعلام مختلفًا قليلًا: postgres=# SELECT runner_name, SUM(CAST(end_time - start_time AS time)) postgres=# AS total_hours FROM race_results GROUP BY runner_name; الخرج runner_name | total_hours -------------+------------- Mohammad | 10:01:44 Ahmad | 06:09:20 (2 rows) يُفسّر الاستعلام في PostgreSQL القيم كأزمنة في هذه الحالة، وتُحسب على هذا الأساس، إذ يمكن تفصيل نتائج محمد إلى إجمالي عشر ساعات ودقيقة واحدة وأربع وأربعين ثانية؛ وزمن أحمد إلى ست ساعات وتسع دقائق وعشرين ثانية. وما هذا سوى مثال حول كيفية تفسير تنفيذات أنظمة إدارة قواعد البيانات المختلفة للقيم البيانات على نحوٍ مختلف حتى ولو كانت تستخدم نفس الاستعلام ونفس مجموعة البيانات. الخلاصة لعلّ فهم كيفية استخدام التاريخ والوقت في SQL مفيد لدى الاستعلام عن نتائج محددة مثل الدقائق أو الثواني أو الساعات أو الأيام أو الأشهر أو السنوات، أو مزيج من كل هذه الوحدات. ناهيك عن وجود العديد من الدوال المتاحة للتواريخ والأوقات والتي تسهّل العثور على قيم معينة، من قبيل التاريخ أو الوقت الحالي. ورغم استخدامنا في هذا المقال لعمليات حسابية بسيطة مثل الجمع والطرح على التواريخ والأوقات في SQL، ولكن يمكنك استخدام قيم التاريخ والوقت مع أي تعبير رياضي. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Work with Dates and Times in SQL لصاحبه Jeanelle Horcasitas. اقرأ أيضًا المقال السابق: كيفية استخدام التعابير الرياضية والدوال التجميعية في SQL أنواع البيانات في SQL دوال التعامل مع البيانات في SQL نظرة سريعة على لغة الاستعلامات الهيكلية SQL التعامل مع الوقت والتاريخ في PHP معالجة الأخطاء والتعديل على قواعد البيانات في SQL
  4. تُستخدم لغة الاستعلام البنيوية SQL لتخزين وإدارة وتنظيم المعلومات في نظام إدارة قواعد البيانات العلاقية RDBMS. كما يمكن لـلغة SQL إجراء الحسابات ومعالجة البيانات باستخدام التعابير Expressions فالتعابير تجمع ما بين معاملات SQL المختلفة مع الدوال والقيم لحساب قيمةٍ ما. وتُستخدم التعابير الرياضية عادةً لجمع وطرح وقسمة وضرب القيم العددية. في حين تُستخدم الدوال التجميعية aggregate functions لتقييم وتجميع القيم ضمن مجموعات بهدف إنشاء ملخص إحصائي حولها، من قبيل المتوسط الحسابي لها أو مجموعها وإظهاره في عمود معين. وبالتالي يمكن أن توفّر التعابير الرياضية والتجميعية رؤًى قيّمة من خلال تحليل البيانات مما يُسهم في اتخاذ قرارات مستقبلية مستنيرة. ستتدرب من خلال هذا المقال على كيفية استخدام التعابير الرياضية. إذ ستتعامل بدايةً مع العمليات الحسابية كما لو كنت تستخدم آلةً حاسبة، لتستخدم بعدها هذه المعاملات مع بيانات نموذجية لتنفيذ استعلامات باستخدام الدوال التجميعية، انتهاءً بسيناريو عملي يتضمّن الاستعلام عن بيانات نموذجية للحصول على معلوماتٍ وتحليلاتٍ أعقد. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول من نوع sudo مختلف عن المستخدم الجذر، وجدار حماية مُفعّل، كما هو موضح في دليل الإعداد الأولي للخادم مع الإصدار 20.04 من أوبنتو، كما يمكنك الاطلاع على المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على الأمثلة العديدة حول التعابير الرياضية في هذا المقال. لذا ننصحك بمتابعة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم mathDB: mysql> CREATE DATABASE mathDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات mathDB، نفّذ تعليمة USE التالية: mysql> USE mathDB; الخرج Database changed الآن وبعد اختيار قاعدة البيانات mathDB، لننشئ جدولًا ضمنها باستخدام الأمر CREATE TABLE. بعد تحديد قاعدة البيانات، سننشئ جدولًا ضمنها باستخدام تعليمة CREATE TABLE. وكمثال في مقالنا هذا، سننشئ جدولاً باسم product_information لتسجيل معلومات المخزون والمبيعات لمحل شاي صغير. سيشتمل هذا الجدول على الأعمدة الثمانية التالية: product_id: يُمثّل قيم من نمط بيانات الأعداد الصحيحة int وسيكون المفتاح الأساسي للجدول، ما يعني أن كل قيمة في هذا العمود ستلعب دور المعرّف الفريد لسجلها. product_name: يُوضّح أسماء المنتجات باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. product_type: يُخزّن أنواع المنتجات باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. total_inventory: يُمثّل عدد الوحدات المتبقية في المخزن من كل منتج، باستخدام نمط البيانات int بحد أقصى 200 وحدة. product_cost: يُظهر سعر شراء كل منتج بالتكلفة الأصلية باستخدام نمط البيانات decimal بحد أقصى 3 أرقام إلى يسار الفاصلة العشرية و2 رقم إلى يمينها. product_retail: يُسجّل أسعار كل منتج يُباع بالتجزئة، باستخدام نمط البيانات decimal بحد أقصى 3 أرقام إلى يسار الفاصلة العشرية و2 رقم إلى يمينها. store_units: يعرض عدد وحدات المنتج المحدد المتاحة في المخزون للمبيعات في المتجر الفعلي باستخدام قيم من نمط البيانات int. online_units: يُمثل عدد وحدات المنتج المحدد المتاحة في المخزون للمبيعات عبر الإنترنت، باستخدام قيم من نمط البيانات int. ولإنشاء هذا الجدول النموذجي، نفّذ الأمر التالي: mysql> CREATE TABLE product_information ( mysql> product_id int, mysql> product_name varchar(30), mysql> product_type varchar(30), mysql> total_inventory int(200), mysql> product_cost decimal(3, 2), mysql> product_retail decimal(3, 2), mysql> store_units int(100), mysql> online_units int(100), mysql> PRIMARY KEY (product_id) mysql> ); الخرج Query OK, 0 rows affected, 0 warnings (0.01 sec) ثم املأ هذا الجدول الفارغ ببعض البيانات التجريبية النموذجية: mysql> INSERT INTO product_information mysql> (product_id, product_name, product_type, total_inventory, product_cost, product_retail, store_units, online_units) mysql> VALUES mysql> (1, 'chamomile', 'tea', 200, 5.12, 7.50, 38, 52), mysql> (2, 'chai', 'tea', 100, 7.40, 9.00, 17, 27), mysql> (3, 'lavender', 'tea', 200, 5.12, 7.50, 50, 112), mysql> (4, 'english_breakfast', 'tea', 150, 5.12, 7.50, 22, 74), mysql> (5, 'jasmine', 'tea', 150, 6.17, 7.50, 33, 92), mysql> (6, 'matcha', 'tea', 100, 6.17, 7.50, 12, 41), mysql> (7, 'oolong', 'tea', 75, 7.40, 9.00, 10, 29), mysql> (8, 'tea sampler', 'tea', 50, 6.00, 8.50, 18, 25), mysql> (9, 'ceramic teapot', 'tea item', 30, 7.00, 9.75, 8, 15), mysql> (10, 'golden teaspoon', 'tea item', 100, 2.00, 5.00, 18, 67); الخرج Query OK, 10 rows affected (0.01 sec) Records: 10 Duplicates: 0 Warnings: 0 وبمجرّد إدخالك للبيانات تغدو مستعدًا لبدء استخدام التعابير الرياضية. إجراء العمليات الحسابية باستخدام التعابير الرياضية في SQL تُجرى الاستعلامات في SQL عادةً باستخدام الكلمة المفتاحية SELECT لاسترجاع البيانات المطلوبة من قاعدة البيانات، كما يمكن استخدامها لتنفيذ العديد من العمليات الرياضية. من المهم التذكير بأن SQL تُستخدم على نحوٍ رئيسي في الواقع العملي بهدف تنفيذ الاستعلامات وإجراء الحسابات على القيم الموجودة ضمن قاعدة البيانات الفعلية. ولكننا سنستخدم SELECT في هذا القسم للتعامل مع قيم عددية مباشرة (وليس مع قيم مخزنة في قاعدة البيانات) بهدف التعرف على صيغة التعابير والعمليات الرياضية. ولكن وقبل الشروع، دعونا نلقي نظرة عامّة على المعاملات الرياضية المُستخدمة لإجراء ست عمليات حسابية في SQL. مع الانتباه إلى أنّ هذه القائمة ليست شاملة بالكامل، وأنّ لكل نظام من أنظمة إدارة قواعد البيانات العلاقية مجموعته الخاصة من المعاملات الرياضية: عملية الجمع تستخدم الرمز + عملية الطرح تستخدم الرمز - عملية الضرب تستخدم الرمز * عملية القسمة تستخدم الرمز / عملية باقي القسمة تستخدم الرمز % عملية الرفع إلى قوة تستخدم الدالةPOW(x,y‎)‎ كما يُمكنك التدرب على إجراء أنماط متنوعة من العمليات الحسابية مُستخدمًا تراكيب قيم من اختيارك. أمّا عنا، فسنُقدم الشرح من خلال الأمثلة التالية، بدءًا بمعادلة للجمع: mysql> SELECT 893 + 579; الخرج +-----------+ | 893 + 579 | +-----------+ | 1472 | +-----------+ 1 row in set (0.00 sec) ومن الجدير بالملاحظة أنه ما من حاجة لتضمين بنية FROM في الاستعلام ضمن مثالنا هذا أو في الأمثلة التالية، وذلك نظرًا لكوننا لا نسترجع أي بيانات من قاعدة البيانات، وإنما نُجري فقط عملياتٍ حسابية على أرقامٍ مباشرة. أمّا الآن، لنجري عملية حسابية باستخدام معامل الطرح. ونلاحظ أنّه من الممكن إجراء العمليات الحسابية على القيم ذات الخانات العشرية كالتالي: mysql> SELECT 437.82 - 66.34; الخرج +----------------+ | 437.82 - 66.34 | +----------------+ | 371.48 | +----------------+ 1 row in set (0.00 sec) كما من الممكن تضمين قيم ومعاملات متعددة ضمن تعبير حسابي واحد في SQL. فمثلًا نستخدم في التعبير الحسابي التالي ثلاث معاملات ضرب لإيجاد حاصل ضرب أربعة أرقام: mysql> SELECT 60 * 1234 * 2 * 117; الخرج +---------------------+ | 60 * 1234 * 2 * 117 | +---------------------+ | 17325360 | +---------------------+ 1 row in set (0.00 sec) لنُجري الآن عملية قسمة تتضمن قيمة عشرية وأخرى من نمط الأعداد الصحيحة، كالتالي: mysql> SELECT 2604.56 / 41; الخرج +--------------+ | 2604.56 / 41 | +--------------+ | 63.525854 | +--------------+ 1 row in set (0.00 sec) كما يعدّ % كمعامل آخر لعملية القسمة والمُسمّى بمعامل باقي القسمة، إذ يحسب قيمة الباقي من قسمة المقسوم على المقسوم عليه: mysql> SELECT 38 % 5; الخرج +--------+ | 38 % 5 | +--------+ | 3 | +--------+ 1 row in set (0.00 sec) كما يعدّ المعامل POW(x,y)‎ من المعاملات المفيدة بدوره، إذ يحسب قيمة القوة لأساس x وأس y، على النحو التالي: mysql> SELECT POW(99,9); الخرج +---------------------+ | POW(99,9) | +---------------------+ | 9.13517247483641e17 | +---------------------+ 1 row in set (0.01 sec) والآن وبعدما تدربت على إجراء العمليات الحسابية مستخدمًا كل عملية على حدة، أصبح بإمكانك تجربة دمج معاملات رياضية مختلفة للتدرب على التعامل مع معادلات رياضية أعقد. فهم ترتيب العمليات في SQL ربما سمعت مسبقًا عن مصطلح PEMDAS، والذي يُمثّل اختصارًا يدل على أولوية ترتيب تنفيذ العمليات الحسابية بدءًا بالأقواس، ثم الرفع إلى قوة، ثم الضرب والقسمة، ثم الجمع والطرح. وهو المصطلح المُستخدم في الولايات المتحدة، وقد تستخدم دول أخرى اختصارات مختلفة لتمثيل قاعدة ترتيب العمليات. ولدى دمج عمليات رياضية مختلفة متداخلة ضمن أقواس، تقرأ SQL العمليات من اليسار إلى اليمين، ومن ثم تقرأ القيم بدءًا من الأقواس الأكثر تداخلًا. لذا، من المهم التأكد بأن القيم داخل الأقواس تعكس بوضوح المعادلة التي نسعى إلى حلها. لنجرّب إجراء العملية الحسابية التالية باستخدام أقواس وبعض المعاملات المختلفة: mysql> SELECT (2 + 4 ) * 8; الخرج +-----------+ | (2+4) * 8 | +-----------+ | 48 | +-----------+ 1 row in set (0.00 sec) تذكّر أنّ مكان وضع الأقواس مهم جدًا، فإذا لم تكن حذرًا قد يتغير الناتج كليًا. على سبيل المثال، سنستخدم فيما يلي نفس القيم الثلاث ونفس المعاملات ولكن بتغيير مكان الأقواس، ما سينتج عنه نتائج مختلفة: mysql> SELECT 2 + (4 * 8); الخرج +-------------+ | 2 + (4 * 8) | +-------------+ | 34 | +-------------+ 1 row in set (0.00 sec) وإذا كنت ممن يفضلون إجراء العمليات الحسابية دون استخدام أقواس، فالأمر ممكن أيضًا، مع ملاحظة أنّ قاعدة ترتيب العمليات تبقى سارية في هذه الحالة؛ وبالتالي وكما في حالة استخدام الأقواس، تأكد من أن المعادلة تعكس بدقة النتيجة المرجوة استنادًا إلى ترتيب العمليات التي ستُقيّم بناءً عليها. في المثال التالي، نلاحظ أنّ عملية القسمة تأخذ الأولوية على معامل الطرح لتنتج قيمة سالبة: mysql> SELECT 100 / 5 - 300; الخرج +---------------+ | 100 / 5 - 300 | +---------------+ | -280.0000 | +---------------+ 1 row in set (0.00 sec) لقد نجحت حتى الآن في استخدام التعابير الرياضية لأداء العمليات الحسابية الأساسية منها والمركبة مستخدمًا مجموعة متنوعة من المعاملات. في الخطوة التالية، ستستخدم البيانات النموذجية لتنفيذ عمليات حسابية باستخدام دوال التجميع مُستخلصًا معلومات جديدة من بياناتك. تحليل البيانات باستخدام دوال التجميع لنفترض بأنك تملك محلًّا صغير لبيع الشاي، وأنّك ترغب في إجراء حسابات تتعلق بالمعلومات المخزنة في قاعدة البيانات لديك. فيمكن لـ SQL استخدام التعابير الرياضية للاستعلام عن البيانات ومعالجتها من خلال استرجاعها من الجداول في قاعدة البيانات بأعمدتها المختلفة، ما يساعد في توليد معلومات جديدة حول البيانات التي تهتم بتحليلها. ستتدرب في هذا القسم على كيفية الاستعلام عن البيانات ومعالجتها باستخدام دوال التجميع وصولًا إلى معلومات ذات طابع تجاري تخص أعمال محل الشاي. تشمل الدوال التجميعية الرئيسية في SQL كل من الدوال SUM و MAX و MIN و AVG و COUNT. تحسب دالة SUM حاصل جمع كافّة القيم في عمودٍ ما. فعلى سبيل المثال، لنستخدم الدالة SUM لحساب حاصل جمع إجمالي الكميات في عمود total_inventory من مجموعة بياناتنا النموذجية: mysql> SELECT SUM(total_inventory) FROM product_information; الخرج +----------------------+ | SUM(total_inventory) | +----------------------+ | 1155 | +----------------------+ 1 row in set (0.00 sec) في حين تحسب الدالة MAX القيمة العظمى في العمود المحدد. لنستخدم الآن هذه الدالة للاستعلام عن القيمة العظمى للتكاليف الأصلية المدفوعة للمنتجات والمُدرجة في عمود product_cost، مع استخدام تعليمة AS لإعادة تسمية ترويسة العمود لتغدو cost_max ما يجعلها أوضح: mysql> SELECT MAX(product_cost) AS cost_max mysql> FROM product_information; الخرج +----------+ | cost_max | +----------+ | 7.40 | +----------+ 1 row in set (0.00 sec) تُعد الدالة MIN النقيض للدالة MAX، إذ تحسب القيمة الدنيا للقيم الموجودة في عمود واحد. لنستخدمها الآن للاستعلام عن القيمة الدنيا المدفوعة للمنتجات بسعر التجزئة في عمود product_retail، على النحو التالي: mysql> SELECT MIN(product_retail) AS retail_min mysql> FROM product_information; الخرج +------------+ | retail_min | +------------+ | 5.00 | +------------+ 1 row in set (0.00 sec) أمّا الدالة AVG فتحسب المتوسط الحسابي لجميع القيم في العمود المحدد. وتجدر الملاحظة إلى إمكانية تشغيل أكثر من دالة تجميعية واحدة في نفس الاستعلام. لنجرّب الآن دمج دالة لإيجاد متوسط سعر المنتجات المباعة بالتجزئة product_retail وأخرى لسعر المنتجات المشتراة بالتكلفة product_cost الأصلية في استعلامٍ واحد: mysql> SELECT AVG(product_retail) AS retail_average, mysql> AVG(product_cost) AS cost_average mysql> FROM product_information; الخرج +----------------+--------------+ | retail_average | cost_average | +----------------+--------------+ | 7.875000 | 5.750000 | +----------------+--------------+ 1 row in set (0.00 sec) في حين تعمل الدالة COUNT على نحوٍ مختلف عن الدوال الأخرى، لأنها تحسب قيمة من الجدول نفسه بعد عد عدد السجلات التي يُعيدها الاستعلام. كمثال، لنستخدم الدالة COUNT بالتزامن مع تعليمة WHERE للاستعلام عن عدد المنتجات التي يزيد سعر بيعها بالتجزئة عن 8 دولارات: mysql> SELECT COUNT(product_retail) mysql> FROM product_information mysql> WHERE product_retail > 8.00; الخرج +-----------------------+ | COUNT(product_retail) | +-----------------------+ | 4 | +-----------------------+ 1 row in set (0.00 sec) الآن لنستعلم عن عدد المنتجات من عمود product_cost المُشتراة من المتجر بسعر يزيد عن 8 دولارات: mysql> SELECT COUNT(product_cost) mysql> FROM product_information mysql> WHERE product_cost > 8.00; الخرج +---------------------+ | COUNT(product_cost) | +---------------------+ | 0 | +---------------------+ 1 row in set (0.00 sec) وبذلك تكون قد استخدمت دوال التجميع بنجاح لتوفير ملخص إحصائي للقيم من قبيل القيمة العظمى والقيمة الدنيا والمتوسط الحسابي والعدد، إذ استرجعت هذه المعلومات من بياناتنا النموذجية لمحاكاة سيناريو واقعي. أمّا في القسم الأخير من هذا المقال، فستطبق كل ما تعلمته حول التعابير الرياضية ودوال التجميع لإجراء استعلامات وتحليلات أكثر تفصيلاً على البيانات النموذجية لمحل الشاي الصغير. تطبيق التعابير الرياضية في سيناريو عملي لأغراض تجارية سنعرض في هذا القسم عدة أمثلة لسيناريوهات مختلفة حول تحليل البيانات لمساعدة مالكي محل الشاي في اتخاذ القرارات المتعلقة بأعمالهم. كسيناريو أول، لنحسب العدد الإجمالي المتاح حاليًا في المخزون من الوحدات بغية فهم كمية المنتجات المتبقية والمتاحة للبيع سواءً في المتجر الفعلي أو عبر الإنترنت. كما سيتضمن هذا الاستعلام تعليمة DESC المُستخدمة لفرز أو ترتيب البيانات من الأكبر إلى الأصغر. فعادةً ما تستخدم قواعد إدارة قواعد البيانات العلاقية الترتيب التصاعدي افتراضيًا، ولكننا في هذا المثال ضمّنا خيار DESC الذي يسمح لنا بعرض البيانات بترتيبٍ تنازلي: mysql> SELECT product_name, mysql> total_inventory - (store_units + online_units) mysql> AS remaining_inventory mysql> FROM product_information mysql> ORDER BY(remaining_inventory) DESC; الخرج +-------------------+---------------------+ | product_name | remaining_inventory | +-------------------+---------------------+ | chamomile | 110 | | chai | 56 | | english_breakfast | 54 | | matcha | 47 | | lavender | 38 | | oolong | 36 | | jasmine | 25 | | golden teaspoon | 15 | | tea sampler | 7 | | ceramic teapot | 7 | +-------------------+---------------------+ 10 rows in set (0.00 sec) هذا الاستعلام مفيد عمليًا لأنه يحسب المخزون المتبقي، الأمر الذي يمكن أن يساعد مالكي محل الشاي في التخطيط لشراء طلبيات جديدة في حال وجود نقص في منتج ما. أمّا للسيناريو التالي، فسنحلل ونقارن مقدار الإيرادات من المبيعات في كل من المتجر الفعلي وعبر الإنترنت: mysql> SELECT product_name, mysql> (online_units * product_retail) AS o, mysql> (store_units * product_retail) AS s mysql> FROM product_information; الخرج +-------------------+--------+--------+ | product_name | o | s | +-------------------+--------+--------+ | chamomile | 390.00 | 285.00 | | chai | 243.00 | 153.00 | | lavender | 840.00 | 375.00 | | english_breakfast | 555.00 | 165.00 | | jasmine | 690.00 | 247.50 | | matcha | 307.50 | 90.00 | | oolong | 261.00 | 90.00 | | tea sampler | 212.50 | 153.00 | | ceramic teapot | 146.25 | 78.00 | | golden teaspoon | 335.00 | 90.00 | +-------------------+--------+--------+ 10 rows in set (0.00 sec) الآن، لنحسب الإيرادات الإجمالية من المبيعات في كل من المتجر الفعلي وعبر الإنترنت باستخدام الدالة SUM مع عدّة معاملات رياضية، على النحو التالي: mysql> SELECT SUM(online_units * product_retail) + mysql> SUM(store_units * product_retail) mysql> AS total_sales mysql> FROM product_information; الخرج +-------------+ | total_sales | +-------------+ | 5706.75 | +-------------+ 1 row in set (0.00 sec) ولعلّ إجراء هذه الاستعلامات مهم لسببين. الأول هو أنه يسمح لمالكي محل الشاي بتقييم أي المنتجات الأكثر مبيعًا وإعطاء الأولوية لتلك المنتجات عند شراء المزيد في المستقبل. والثاني، أنّه يمكّنهم من تحليل مدى نجاح محل الشاي إجمالًا من خلال مبيعات المنتجات في كل من المتجر الفعلي وعبر الإنترنت. سنحسب الآن هامش الربح لكل منتج. وهامش الربح لمنتج ما هو مقدار الإيراد الذي يحققه العمل التجاري من كل وحدة مباعة من هذا المنتج. وبالتالي لفهم مقدار الإيراد الإجمالي الذي حققته، يمكنك ضرب عدد الوحدات المُباعة بهامش الربح. لحساب هامش الربح للمنتجات الفردية في مثالنا، سنطرح سعر التكلفة product_cost من سعر المبيع بالتجزئة product_retail لكل سجل. ثم سنقسّم هذه القيمة على سعر المبيع بالتجزئة للمنتج لحساب نسبة هامش الربح، على النحو التالي: mysql> SELECT product_name, mysql> (product_retail - product_cost) / product_retail mysql> AS profit_margin mysql> FROM product_information; الخرج +-------------------+-------------+ | product_name | profit_margin | +-------------------+-------------+ | chamomile | 0.317333 | | chai | 0.177778 | | lavender | 0.317333 | | english_breakfast | 0.317333 | | jasmine | 0.177333 | | matcha | 0.177333 | | oolong | 0.177778 | | tea sampler | 0.294118 | | ceramic teapot | 0.282051 | | golden teaspoon | 0.600000 | +-------------------+-------------+ 10 rows in set (0.00 sec) نلاحظ استنادًا إلى هذا الخرج أنّ المنتج الذي يتمتع بأعلى هامش ربح هو golden teaspoon بنسبة 60%، والأدنى هو لكل من Chai و Jasmine و Matcha و Oolong بنسبة 18%. بالنسبة لمنتج golden teaspoon، فهامش الربح هذا يعني أنه ومن أجل سعر مبيع بالتجزئة قدره 5.00 دولار مع هامش الربح البالغ 60%، سنحصل على 3.00 دولار كإيرادات. كما يمكننا استخدام دالة التجميع AVG لحساب المتوسط الحسابي لهوامش ربح كافّة منتجات محل الشاي. إذ يلعب هذا المتوسط الحسابي دور المعيار لمالكي محل الشاي لتحديد المنتجات الواقعة تحت هذا الرقم ووضع استراتيجيات لتحسينها: mysql> SELECT AVG((product_retail - product_cost) / product_retail) mysql> AS avg_profit_margin mysql> FROM product_information; الخرج +-------------------+ | avg_profit_margin | +-------------------+ | 0.2838391151 | +-------------------+ 1 row in set (0.00 sec) استنادًا إلى نتيجة الحساب أعلاه نستنتج أنّ المتوسط الحسابي لهوامش الربح للمنتجات في محل بيع الشاي هذا يبلغ 28%. وبفرض أنّ مالكي محل الشاي قد قرروا بناءً على هذه المعلومات الجديدة زيادة هامش الربح إلى 31% في الربع القادم لأي منتج بهامش ربح أقل من 27%. ولإنجاز الأمر، سنطرح هامش الربح المُستهدف من 1 أي (1-0.31) ومن ثم سنقسّم سعر التكلفة الأصلي لكل من المنتجات المُعادة (ذات هامش الربح الأقل من 27%) على هذه القيمة. فتكون النتيجة النهائية هي السعر الجديد الذي ينبغي بيع المنتج بالتجزئة وفقًا له لتحقيق هامش ربح 31%: mysql> SELECT product_name, product_cost / (1 - 0.31) mysql> AS new_retail FROM product_information WHERE (product_retail - product_cost) / product_retail < 0.27; الخرج +--------------+------------+ | product_name | new_retail | +--------------+------------+ | chai | 10.724638 | | jasmine | 8.942029 | | matcha | 8.942029 | | oolong | 10.724638 | +--------------+------------+ 4 rows in set (0.00 sec) تُظهر هذه النتائج أسعار المبيع بالتجزئة الجديدة اللازمة للمنتجات ذات الأداء الضعيف لتحقيق هامش ربح قدره 31%. يزوّد هذا النوع من تحليل البيانات مالكي محل الشاي بالقدرة على اتخاذ قرارات تجارية حاسمة حول كيفية تحسين إيراداتهم للربع القادم وفهم ما يجب التطلع إليه. الخلاصة يُمكن لاستخدام التعابير الرياضية في SQL أن يتراوح من حل المسائل الحسابية ببساطة كما تفعل باستخدام الآلة الحاسبة، إلى تنفيذ تحليلاتٍ معقدة تستند إلى بيانات من العالم الواقعي والتي يمكن أن تلعب دورًا في صياغة قرارات الأعمال. وبمجرد اتقانك لكيفية التعامل مع المعاملات الرياضية الرئيسية وقواعد ترتيب العمليات، ستجد أمامك عالمًا واسعًا من الإمكانيات الحسابية. وحين ترنو إلى تحليلٍ أعمق لبياناتك، يتيح لك دمج هذه المعاملات مع دوال التجميع الغوص في الأسئلة الافتراضية من قبيل "ماذا لو"، مما يوفر لك رؤًى قيمة قد تكون بمثابة الأساس للتخطيط الاستراتيجي لمستقبل أعمالك. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Mathematical Expressions and Aggregate Functions in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام بنى الدمج Joins في لغة الاستعلام البنيوية SQL استخدام الدوال في قواعد بيانات MySQL بعض الدوال المساعدة في SQL لغة معالجة البيانات DML الخاصة بلغة SQL مواضيع متقدمة في SQL
  5. غالبًا ما تفصل تصاميم قواعد البيانات المعلومات إلى جداول مختلفة بناءً على العلاقات بين بعض نقاط البيانات. ولكن حتى في مثل هذه الحالات، من المحتمل أن نرغب أحيانًا باسترجاع المعلومات من أكثر من جدول في وقتٍ واحد. إحدى الطرق الشائعة للوصول إلى البيانات من جداول متعددة في عملية واحدة باستخدام لغة الاستعلام البنيوية SQL هي تجميع الجداول باستخدام بنى الدمج JOIN. إذ تُجمّع بنية الدمج JOIN الجداول المنفصلة عن طريق مطابقة السجلات المرتبطة ببعضها البعض من كل جدول مستندةً إلى عمليات الدمج في الجبر العلاقيّ -وهو نظرية تستخدم الهياكل الجبرية لنمذجة البيانات وتحديد الاستعلامات عليها، إذ يُعد إطارًا نظريًا يُستخدم لوصف العمليات على البيانات في قواعد البيانات العلاقية، مثل الدمج والاختيار، ويساعد في تشكيل الأساس الرياضي للغات الاستعلام مثل SQL) -. وعادةً ما تُبنى العلاقة بين الجداول المطلوب دمجها على زوجٍ من الأعمدة - عمود من كل جدول - والتي تتشارك قيمًا مشتركة، كأن نختار مفتاح خارجي لجدول مع مفتاح أساسي لجدول آخر يُشير إليه المفتاح الخارجي آنف الذكر. يوضّح هذا المقال كيفية بناء مجموعة متنوعة من استعلامات SQL التي تتضمن بنية الدمج JOIN. كما يُسلط الضوء على أنماط مختلفة من بنى الدمج وكيفية تجميع البيانات من جداول متعددة وكيفية استخدام الأسماء البديلة alias للأعمدة لجعل كتابة عمليات الدمج JOIN أقل تعقيدًا. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في دليل الإعداد الأولي للخادم مع الإصدار 20.04 من أوبنتو، كما يمكنك الاطلاع على المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على استخدام عمليات الدمج JOIN. لذا ننصحك بمتابعة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسمjoinsDB: mysql> CREATE DATABASE joinsDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرج كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات joinsDB، نفّذ تعليمة USE التالية: mysql> USE joinsDB; الخرج Database changed الآن، بعد اختيار قاعدة البيانات joinsDB لننشئ بعض الجداول ضمنها. لمتابعة الأمثلة المستخدمة في هذا المقال، تخيل أنك تدير مصنعًا وقررت البدء في تتبع المعلومات حول كل من خط الإنتاج، والموظفين في فريق المبيعات، ومبيعات الشركة، وذلك في قاعدة بيانات SQL. فقررت البدء بثلاثة جداول، أولها سيخزن معلومات حول المنتجات، متضمنًا ثلاثة أعمدة: productID: رقم تعريف كل منتج، معبرًا عنه بنمط البيانات int. سيعمل هذا العمود كمفتاح أساسي للجدول، مما يعني أن كل قيمة فيه ستلعب دور مُعرّف فريد للسجل الخاص بها. وبما أن كل قيمة في المفتاح الأساسي يجب أن تكون فريدة، ستطبّق على هذا العمود القيد UNIQUE. productName: اسم كل منتج، معبرًا عنه باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. price: سعر كل منتج، معبرًا عنه باستخدام نمط البيانات decimal. وتُحدد القيم في هذا العمود بحد أقصى قدره أربعة أرقام، بواقع رقمين على يمين الفاصلة العشرية. وبالتالي، تتراوح القيم المسموح بها من -99.99 إلى 99.99. إذًا، أنشئ جدولًا باسم products يحتوي على هذه الأعمدة الثلاثة على النحو التالي: mysql> CREATE TABLE products ( mysql> productID int UNIQUE, mysql> productName varchar(20), mysql> price decimal (4,2), mysql> PRIMARY KEY (productID) mysql>); في حين سيخزّن الجدول الثاني معلومات حول الموظفين في فريق مبيعات الشركة. فارتأيت أن هذا الجدول يحتاج أيضًا إلى ثلاثة أعمدة: empID: مشابه لعمود productID، إذ سيحتوي على مُعرّف فريد لكل موظف في فريق المبيعات مُعبرًا عنه بنمط البيانات int. وبالمثل، سيُطبق على هذا العمود قيد UNIQUE وسيعمل كمفتاح أساسي لجدول الفريق. empName: اسم كل مندوب مبيعات، مُعبرًا عنه باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. productSpecialty: بفرض أنك قررت تخصيص منتج لكل عضو في فريق المبيعات؛ إذ يمكنه بيع أي منتج تصنعه الشركة ولكن تركيزه العام سيكون على المنتج المُخصّص له. وللإشارة إلى هذا الأمر في الجدول، أنشأنا هذا العمود الذي يحتوي على قيمة productID للمنتج المُخصّص لكل موظف. ولضمان أنّ عمود productSpecialty لن يتضمّن سوى قيم تُمثّل مُعرّفات صالحة للمنتجات، يمكنك إنشاء قيد مفتاح خارجي عليه بحيث يُشير إلى عمود productID في جدول المنتجات products. يُعد قيد المفتاح الخارجي طريقة لتحديد علاقة بين جدولين، إذ يفرض أن تكون القيم في العمود المُطبّق عليه موجودة بالفعل في العمود المُشار إليه. يشترط قيد المفتاح الخارجي في تعليمة CREATE TABLE أدناه أن تكون كل قيمة تُضاف إلى عمود productSpecialty من جدول الفريق team موجودة مسبقًا في عمود productID من جدول المنتجات products. أنشئ جدولًا باسم team يحتوي على هذه الأعمدة الثلاثة: mysql> CREATE TABLE team ( mysql> empID int UNIQUE, mysql> empName varchar(20), mysql> productSpecialty int, mysql> PRIMARY KEY (empID), mysql> FOREIGN KEY (productSpecialty) REFERENCES products (productID) ); أمّا الجدول الأخير فسيتضمّن سجلات مبيعات الشركة. وسيكون لهذا الجدول أربعة أعمدة: saleID: مشابه لعمودي productID وempID، إذ سيحتوي هذا العمود على مُعرّف فريد لكل عملية بيع مُعبرًا عنه بنمط البيانات int. سنطبق على هذا العمود أيضًا قيد UNIQUE ليلعب دور المفتاح الأساسي لجدول المبيعات sales. quantity: عدد الوحدات من كل منتج مُباع، مُعبرًا عنه بنمط البيانات int. productID: مُعرّف المنتج المُباع، مُعبرًا عنه بنمط البيانات int. salesperson: مُعرّف الموظف الذي أجرى عملية البيع. وعلى نحوٍ مشابه لعمود productSpecialty من جدول الفريق team، لنُطبّق قيد FOREIGN KEY على كل من عموديّ productID وsalesperson. الأمر الذي سيضمن أنّ هذه الأعمدة لن تتضمّن سوى قيم موجودة بالفعل في عمود productID من جدول المنتجات products وعمود empID من جدول الفريق team على التوالي. لننشئ إذًا جدولًا باسم sales يحتوي على هذه الأعمدة الأربعة: mysql> CREATE TABLE sales ( mysql> saleID int UNIQUE, mysql> quantity int, mysql> productID int, mysql> salesperson int, mysql> PRIMARY KEY (saleID), mysql> FOREIGN KEY (productID) REFERENCES products (productID), mysql> FOREIGN KEY (salesperson) REFERENCES team (empID) mysql> ); ومن ثم املأ جدول المنتجات products ببعض البيانات التجريبية النموذجية عبر تنفيذ عملية INSERT INTO التالية: mysql> INSERT INTO products mysql> VALUES mysql> (1, 'widget', 18.99), mysql> (2, 'gizmo', 14.49), mysql> (3, 'thingamajig', 39.99), mysql> (4, 'doodad', 11.50), mysql> (5, 'whatzit', 29.99); ثم املأ جدول الفريق team ببعض البيانات التجريبية النموذجية: mysql> INSERT INTO team mysql> VALUES mysql> (1, 'Florence', 1), mysql> (2, 'Mary', 4), mysql> (3, 'Diana', 3), mysql> (4, 'Betty', 2); املأ جدول المبيعات sales ببعض البيانات النموذجية أيضًا: mysql> INSERT INTO sales mysql> VALUES mysql> (1, 7, 1, 1), mysql> (2, 10, 5, 4), mysql> (3, 8, 2, 4), mysql> (4, 1, 3, 3), mysql> (5, 5, 1, 3); ونهايةً، تخيّل أن شركتك حققت بعضًا من المبيعات دون مشاركة أحد من فريق المبيعات. لتسجيل هذه المبيعات، أضف ثلاث سجلات إلى جدول المبيعات sales لا تتضمن قيمة لعمود موظف المبيعات salesperson عبر تنفيذ العملية التالية: mysql> INSERT INTO sales (saleID, quantity, productID) mysql> VALUES mysql> (6, 1, 5), mysql> (7, 3, 1), mysql> (8, 4, 5); بهذا، تغدو مستعدًا لمتابعة باقي المقال وبدء تعلم كيفية دمج الجداول معًا في SQL. فهم صيغة عمليات بنى الدمج JOIN يمكن استخدام بنى الدمج JOIN في مجموعة متنوعة من تعليمات SQL، بما في ذلك عمليات التحديث UPDATE والحذف DELETE. ولكن ولأغراض التوضيح، سنستخدم في الأمثلة في هذا المقال استعلامات SELECT لإظهار كيفية عمل بنى الدمج JOIN. يُظهر المثال التالي الصيغة العامة لتعليمة SELECT التي تتضمن بنية الدمج JOIN: mysql> SELECT table1.column1, table2.column2 mysql> FROM table1 JOIN table2 mysql> ON search_condition; تبدأ هذه الصيغة بتعليمة SELECT التي ستعيد عمودين من جدولين منفصلين. لاحظ أنه نظرًا لقدرة بنى JOIN على مقارنة البيانات من عدة جداول، فإن صيغة هذا المثال تُوضّح الجدول المُستهدف لكل عمود بوضع اسم الجدول متبوعًا بنقطة قبل اسم العمود، وهذا ما يُعرف بالإشارة الكاملة والمؤهلة للعمود fully qualified column reference. يمكنك استخدام الإشارة الكاملة والمؤهلة للعمود في أي عملية على نحوٍ اختياريّ، في حين يغدو استخدامها ضرورةً فقط في العمليات التي يكون فيها عمودان يشتركان في نفس الاسم من جداول مختلفة. ومن الجيد عمومًا استخدامها لدى التعامل مع جداول متعددة، إذ يمكن أن تساعد في جعل عمليات الدمج JOIN أسهل للقراءة والفهم. وتأتي بنية FROM بعد بنية SELECT. إذ تُحدد بنية FROM في أي استعلام مجموعة البيانات التي ينبغي البحث فيها لإعادة البيانات المطلوبة. ولعلّ الاختلاف الوحيد هنا هو أن بنية FROM تتضمن جدولين مفصولين بالكلمة المفتاحية JOIN. ومن الطرق المفيدة في فهم الاستعلامات لدى كتابتها هي تذكر أنك تختار (SELECT) الأعمدة التي تريد إعادتها من (FROM) الجدول الذي ترغب في الاستعلام عنه. يلي ذلك بنية ON، والتي تصف كيفية ربط الاستعلام للجدولين معًا عن طريق تحديد شرط بحث. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعابير القادرة على تقييم فيما إذا كان شرط معيّن "محققًا True" أو "غير محقق False" أو "غير محدد Unknown". وبالتالي، من المفيد فهم عملية الدمج JOIN على أنها عملية تجميع كافة السجلات من جدولين، لتعيد بعد ذلك أي سجلات يُقيم شرط البحث في بنية ON من أجلها على أنه "محقق True". ولعلّه من المنطقي في بنية ON أن نُضمّن شرط بحث يختبر ما إذا كان عمودين مرتبطين – من قبيل مفتاح خارجي لجدول ما ومفتاح أساسي لجدول آخر يُشير إليه ذلك المفتاح الخارجي - يتضمنان قيمًا متساوية. وهذا ما يُشار إليه أحيانًا بالدمج عند التساوي equi join. وكمثال على كيفية مطابقة "الدمج عند التساوي" للبيانات من جداول متعددة، لنُنفّذ الاستعلام التالي باستخدام البيانات التجريبية النموذجية المُضافة سابقًا. إذ ستعمل هذه التعليمة على دمج جدولي products وteam باستخدام شرط بحث يختبر تطابق القيم في عمودي productID وproductSpecialty من الجدولين آنفي الذكر، معيدًا أسم كل عضو من فريق المبيعات واسم المنتج المُخصص له وسعر هذا المنتج: mysql> SELECT team.empName, products.productName, products.price mysql> FROM products JOIN team mysql> ON products.productID = team.productSpecialty; وتكون مجموعة نتائج هذا الاستعلام على النحو: الخرج +----------+-------------+-------+ | empName | productName | price | +----------+-------------+-------+ | Florence | widget | 18.99 | | Mary | doodad | 11.50 | | Diana | thingamajig | 39.99 | | Betty | gizmo | 14.49 | +----------+-------------+-------+ 4 rows in set (0.00 sec) ولتوضيح كيفية دمج SQL لهذه الجداول وتشكيل مجموعة النتائج، دعونا نلقي نظرة أقرب على هذه العملية. ومن المهم في هذه المرحلة التنويه إلى كون الخطوات التالية لا نُمثّل بالضبط ما يحدث عند دمج جدولين في نظام إدارة قواعد البيانات، ولكن من المفيد تصوّر عمليات الدمج JOIN وكأنها تتبع خطوات مشابهة لها. أولًا، يعرض الاستعلام كافة سجلات وأعمدة الجدول الأول ضمن بنية FROM، وهو في حالتنا الجدول products: مثال على عملية الدمج +-----------+-------------+-------+ | productID | productName | price | +-----------+-------------+-------+ | 1 | widget | 18.99 | | 2 | gizmo | 14.49 | | 3 | thingamajig | 39.99 | | 4 | doodad | 11.50 | | 5 | whatzit | 29.99 | +-----------+-------------+-------+ بعد ذلك، يخضع كل سجل من جدول products للتحليل ليُطابق مع أي سجل من جدول الفريق team حيث تكون قيمة العمود productSpecialty مطابقة لقيمة productID في السجل المعني: مثال على عملية الدمج +-----------+-------------+-------+-------+----------+------------------+ | productID | productName | price | empID | empName | productSpecialty | +-----------+-------------+-------+-------+----------+------------------+ | 1 | widget | 18.99 | 1 | Florence | 1 | | 2 | gizmo | 14.49 | 4 | Betty | 2 | | 3 | thingamajig | 39.99 | 3 | Diana | 3 | | 4 | doodad | 11.50 | 2 | Mary | 4 | | 5 | whatzit | 29.99 | | | | +-----------+-------------+-------+-------+----------+------------------+ تُستبعد بعد ذلك كافة السجلات التي لا تتطابق فيها قيمة العمود productSpecialty مع قيمة productID، ثم يُعاد تنظيم الأعمدة وفق ترتيب ورودها ضمن بنية SELECT، مع حذف أي أعمدة لم تُحدد في الاستعلام، وأخيرًا يُعاد فرز السجلات المتبقية وتُقدم كمجموعة النتائج النهائية: مثال على عملية الدمج +----------+-------------+-------+ | empName | productName | price | +----------+-------------+-------+ | Florence | widget | 18.99 | | Mary | doodad | 11.50 | | Diana | thingamajig | 39.99 | | Betty | gizmo | 14.49 | +----------+-------------+-------+ 4 rows in set (0.00 sec) ولعلّ استخدام الدمج عند التساوي هو الأسلوب الأكثر شيوعًا لربط الجداول، في حين من الممكن استخدام عوامل SQL أخرى ضمن شروط بحث بنية ON، من قبيل <، >، LIKE، NOT LIKE، أو حتى BETWEEN. ويُنصح بالانتباه إلى أن استخدام شروط بحث أعقد قد يجعل التنبؤ بالبيانات التي ستظهر في مجموعة النتائج أصعب. يُمكنك دمج الجداول في معظم تقديمات SQL باستخدام أي مجموعة من الأعمدة، شرط امتلاكها لما يُشار إليه في المعيار القياسي لـ SQL باسم "نمط بيانات مؤهل للدمج". ما يعني عمومًا أنّه من الممكن دمج عمود يحتوي على بيانات رقمية مع أي عمود آخر يحمل بيانات رقمية، وذلك بغض النظر عن أنماط بياناتهما الدقيقة. وبالمثل، من الممكن عادةً دمج أي عمود يحتوي على قيم محرفية مع آخر يحمل بيانات محرفية. ولكن وكما ذكرنا سابقًا، عادةً ما تكون الأعمدة التي نختارها لدمج جدولين هي تلك التي تُشير بالفعل إلى علاقة بين الجداول، من قبيل مفتاح خارجي مع المفتاح الأساسي لجدول آخر والذي يُشير إليه ذلك المفتاح الخارجي. كما تسمح العديد من تقديمات SQL بدمج الأعمدة التي تحمل نفس الاسم باستخدام الكلمة المفتاحية USING بدلًا من ON. وتبدو صيغة مثل هذه العملية على النحو الآتي: mysql> SELECT table1.column1, table2.column2 mysql> FROM table1 JOIN table2 mysql> USING (related_column); في صيغة هذا المثال، تعادل بنية USING استخدام ON table1.related_column = table2.related_column;. وبما أنّ لكل من جدولي sales وproducts عمود باسم productID، فمن الممكن دمجهما عبر مطابقة هذه الأعمدة باستخدام الكلمة المفتاحية USING. وهذا ما يُمثّل مهمّة الأمر التالي، والذي يُعيد saleID لكل عملية بيع، وكمية الوحدات المباعة، واسم كل منتج تم بيعه وسعره. كما يفرز مجموعة النتائج تصاعديًا استنادًا إلى قيمة saleID: mysql> SELECT sales.saleID, sales.quantity, products.productName, products.price mysql> FROM sales JOIN products mysql> USING (productID) mysql> ORDER BY saleID; الخرج +--------+----------+-------------+-------+ | saleID | quantity | productName | price | +--------+----------+-------------+-------+ | 1 | 7 | widget | 18.99 | | 2 | 10 | whatzit | 29.99 | | 3 | 8 | gizmo | 14.49 | | 4 | 1 | thingamajig | 39.99 | | 5 | 5 | widget | 18.99 | | 6 | 1 | whatzit | 29.99 | | 7 | 3 | widget | 18.99 | | 8 | 4 | whatzit | 29.99 | +--------+----------+-------------+-------+ 8 rows in set (0.00 sec) إذ قد يعيد نظام قاعدة البيانات أحيانًا ترتيب السجلات بطرق لا يسهل التنبؤ بها عند دمج الجداول. ولعلّ الحل يكون بتضمين جملة ORDER BY كما في المثال أعلاه، ما قد يُساعد في جعل مجموعات النتائج أكثر تناسقًا وسهولة في القراءة. دمج أكثر من جدولين نحتاج أحيانًا إلى دمج البيانات من أكثر من جدولين. إذ يُمكننا دمج أي عدد من الجداول معًا عن طريق تضمين بنى دمج JOIN ضمن بنى JOIN أخرى. وتُمثّل الصيغة التالية مثالًا لحالة دمج ثلاثة جداول: mysql> SELECT table1.column1, table2.column2, table3.column3 mysql> FROM table1 JOIN table2 mysql> ON table1.related_column = table2.related_column mysql> JOIN table3 mysql> ON table3.related_column = table1_or_2.related_column; تبدأ صيغة البنية FROM في هذا المثال بدمج الجدول table1 مع table2. لتبدأ عملية دمج JOIN ثانية بعد بنية ON الخاصة بعملية الدمج الأولى، والتي تدمج مجموعة الجداول المدموجة الأولية السابقة مع الجدول table3. ونلاحظ هنا أنه من الممكن دمج الجدول الثالث مع عمود موجود إما في الجدول الأول أو الثاني. ولتوضيح الأمر، لنفرض أننا نرغب بمعرفة قيمة الإيرادات المُحققة من مبيعات الموظفين، لكن اهتمامنا منصب فقط على سجلات المبيعات التي تشمل موظفًا باع المنتج الذي يتخصص فيه تحديدًا دون مبيعاته لأي منتج آخر. وللحصول على هذه المعلومات، يمكننا تنفيذ الاستعلام التالي. والذي يبدأ بدمج جدولي products وsales معًا عن طريق مطابقة عمودي productID في كل منهما. ثم يدمج جدول team مع الجدولين السابقين بمطابقة كل سجل من عملية الدمج الأولية بعمود productSpecialty لكل موظف. بعد ذلك، يعمل الاستعلام على تصفية النتائج باستخدام بنية WHERE بهدف إعادة السجلات التي يكون فيها الموظف، الذي تم تخصيصه للمنتج المُباع، هو نفسه من أتم عملية البيع بالفعل. يتضمن هذا الاستعلام أيضًا بنية ORDER BY التي تفرز النتائج النهائية تصاعديًا استنادًا إلى القيم في عمود saleID. mysql> SELECT sales.saleID, mysql> team.empName, mysql> products.productName, mysql> (sales.quantity * products.price) mysql> FROM products JOIN sales mysql> USING (productID) mysql> JOIN team mysql> ON team.productSpecialty = sales.productID mysql> WHERE team.empID = sales.salesperson mysql> ORDER BY sales.saleID; نلاحظ أنّه من بين الأعمدة المدرجة في بنية SELECT لهذا الاستعلام يوجد تعبير يضرب قيم عمود quantity الموجود في جدول sales بقيم السعر الموجودة في عمود price بجدول products. والذي يُعيد حاصل ضرب هذه القيم في السجلات المتطابقة. الخرج +--------+----------+-------------+-----------------------------------+ | saleID | empName | productName | (sales.quantity * products.price) | +--------+----------+-------------+-----------------------------------+ | 1 | Florence | widget | 132.93 | | 3 | Betty | gizmo | 115.92 | | 4 | Diana | thingamajig | 39.99 | +--------+----------+-------------+-----------------------------------+ 3 rows in set (0.00 sec) استعرضت كافّة الأمثلة حتى الآن نفس نوع بنية الدمج، ألا وهي: الدمج الداخلي INNER JOIN. وللحصول على نظرة عامّة حول كل من الدمج الداخلي والدمج الخارجي OUTER JOIN وأوجه اختلافهما، تابع قراءة القسم التالي. عمليات الدمج الداخلي مقابل الدمج الخارجي يوجد نوعان رئيسيان من بنى الدمج: الدمج الداخلي INNER والدمج الخارجي OUTER. ويكمن الفارق بين هذين النوعين من الدمج بالبيانات التي يعيدها كل منهما. إذ تُعيد عمليات الدمج الداخلي INNER فقط السجلات التي تمتلك تطابقات من كل جدول مدمج، في حين تُعيد عمليات الدمج الخارجي OUTER السجلات مع أو بدون تطابقات. استخدمنا في صيغ الأمثلة والاستعلامات من الأقسام السابقة بنى الدمج الداخلي رغم عدم تضمين الكلمة المفتاحية INNER صراحةً في أي منها. فمعظم تطبيقات SQL تعامل أي بنية دمج على أنها INNER ما لم يُذكر خلاف ذلك صراحةً. تجمع الاستعلامات التي تستخدم الدمج الخارجي OUTER JOIN بين عدة جداول لتعيد السجلات مع أو بدون تطابقات. وهذا الأمر مفيد في الكشف عن السجلات التي تنقصها بعض القيم، أو في الحالات التي تكون فيها التطابقات الجزئية مقبولة. يمكن تقسيم عمليات الدمج الخارجي OUTER JOIN إلى ثلاثة أنواع فرعية، وهي: الدمج الخارجي الأيسر LEFT OUTER، والدمج الخارجي الأيمن RIGHT OUTER، والدمج الخارجي الكامل FULL OUTER. يعيد الدمج الخارجي الأيسر، أو ببساطة الدمج الأيسر LEFT JOIN جميع السجلات التي تمتلك تطابقات من كلا الجدولين المدمجين بالإضافة إلى السجلات دون تطابقات من الجدول الموجود على الجانب الأيسر. إذ يعدّ الجدول "الأيسر" في سياق عمليات الدمج ذلك الجدول الأوّل المُحدّد مباشرةً بعد الكلمة المفتاحية FROM وقبل كلمة JOIN. وبالمثل، يعدّ الجدول "الأيمن" هو الجدول الثاني، أو الجدول الذي يلي كلمة JOIN مباشرةً، ويُعيد الدمج الخارجي الأيمن RIGHT OUTER كافة السجلات ذات التطابقات من الجداول المدمجة بالإضافة إلى كل سجل دون تطابقات من الجدول "الأيمن". في حين يعيد الدمج الخارجي الكامل FULL OUTER JOIN كافة السجلات من كلا الجدولين، بما في ذلك تلك التي لا تمتلك أي مطابقات. لتوضيح كيف تعيد هذه الأنواع المختلفة من بنى الدمج البيانات، لننفذ الاستعلامات التالية على الجداول المُنشأة في الفقرة السابقة "الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية". مع ملاحظة أن هذه الاستعلامات متطابقة باستثناء أن كل واحد منها يستخدم نوعًا مختلفًا من بنى الدمج JOIN. يستخدم هذا المثال الأول الدمج الداخلي INNER JOIN لدمج جدولي sales وteam معًا بمطابقة عمودي salesperson وempID من كل منهما على التوالي. ونؤكّد مجددًا أنّ الاستعلام يعدّ داخليًا حتى وإن لم نُضمّن الكلمة المفتاحية INNER صراحةً ما لم نشير إلى خلاف ذلك ضمن بنية الاستعلام. mysql> SELECT sales.saleID, sales.quantity, sales.salesperson, team.empName mysql> FROM sales JOIN team mysql> ON sales.salesperson = team.empID; نظرًا لأن هذا الاستعلام يستخدم بنية الدمج الداخلي INNER JOIN، فإنه يُعيد فقط السجلات ذات التطابقات من كلا الجدولين: الخرج +--------+----------+-------------+----------+ | saleID | quantity | salesperson | empName | +--------+----------+-------------+----------+ | 1 | 7 | 1 | Florence | | 4 | 1 | 3 | Diana | | 5 | 5 | 3 | Diana | | 2 | 10 | 4 | Betty | | 3 | 8 | 4 | Betty | +--------+----------+-------------+----------+ 5 rows in set (0.00 sec) سنستخدم الآن في هذه النسخة من الاستعلام بنية الدمج الخارجي الأيسر LEFT OUTER JOIN، على النحو: mysql> SELECT sales.saleID, sales.quantity, sales.salesperson, team.empName mysql> FROM sales LEFT OUTER JOIN team mysql> ON sales.salesperson = team.empID; وكما هو الحال مع الاستعلام السابق، يعيد هذا الاستعلام أيضًا كافّة القيم ذات التطابقات من كلا الجدولين. لكنه يعيد أيضًا أي قيم من الجدول "الأيسر" (وهو الجدول sales في هذه الحالة) التي لا تمتلك أي تطابقات مع الجدول "الأيمن" (team). ولكن وبما أنّ هذه السجلات في الجدول الأيسر لا تمتلك تطابقات في الجدول الأيمن، فتُعاد القيمة الفارغة NULL بدلًا من القيم غير المتطابقة من الجدول الأيمن. الخرج +--------+----------+-------------+----------+ | saleID | quantity | salesperson | empName | +--------+----------+-------------+----------+ | 1 | 7 | 1 | Florence | | 2 | 10 | 4 | Betty | | 3 | 8 | 4 | Betty | | 4 | 1 | 3 | Diana | | 5 | 5 | 3 | Diana | | 6 | 1 | NULL | NULL | | 7 | 3 | NULL | NULL | | 8 | 4 | NULL | NULL | +--------+----------+-------------+----------+ 8 rows in set (0.00 sec) أمّا الآن فسنستخدم بنية الدمج الأيمن RIGHT JOIN، على النحو: mysql> SELECT sales.saleID, sales.quantity, sales.salesperson, team.empName mysql> FROM sales RIGHT JOIN team mysql> ON sales.salesperson = team.empID; لاحظ أننا اكتفينا باستخدام التعليمة RIGHT JOIN بدلًا من RIGHT OUTER JOIN في بنية الدمج من هذا الاستعلام. فكما هو الحال من حيث عدم اشتراط استخدام الكلمة المفتاحية INNER لتحديد بنية دمج داخلي INNER JOIN، فإن كلمة OUTER تُطبّق تلقائيًا بدورها بمجرد كتابة LEFT JOIN أو RIGHT JOIN. نتيجة هذا الاستعلام هي العكس تمامًا من الاستعلام السابق، بمعنى أنها تعيد كافّة السجلات ذات التطابقات من كلا الجدولين، والسجلات دون تطابقات من الجدول "الأيمن" فقط: الخرج: +--------+----------+-------------+----------+ | saleID | quantity | salesperson | empName | +--------+----------+-------------+----------+ | 1 | 7 | 1 | Florence | | NULL | NULL | NULL | Mary | | 4 | 1 | 3 | Diana | | 5 | 5 | 3 | Diana | | 2 | 10 | 4 | Betty | | 3 | 8 | 4 | Betty | +--------+----------+-------------+----------+ 6 rows in set (0.00 sec) ملاحظة: انتبه لكون MySQL لا تدعم بنى الدمج الخارجي الكامل FULL OUTER JOIN. ولتوضيح البيانات التي كان من الممكن أن يُعيدها هذا الاستعلام في حال استخدام بنية FULL OUTER JOIN، إليك كيف ستبدو مجموعة النتائج في قاعدة بيانات PostgreSQL: Joinsdb=# SELECT sales.saleID, sales.quantity, sales.salesperson, team.empName Joinsdb=# FROM sales FULL OUTER JOIN team Joinsdb=# ON sales.salesperson = team.empID; الخرج saleid | quantity | salesperson | empname --------+----------+-------------+---------- 1 | 7 | 1 | Florence 2 | 10 | 4 | Betty 3 | 8 | 4 | Betty 4 | 1 | 3 | Diana 5 | 5 | 3 | Diana 6 | 1 | | 7 | 3 | | 8 | 4 | | | | | Mary (9 rows) نلاحظ من النتائج أعلاه أنّ الدمج الكامل FULL JOIN يُعيد كافّة السجلات من كلا الجدولين بما في ذلك تلك التي لا تمتلك تطابقات. تسمية الجداول والأعمدة بأسماء بديلة في بنى الدمج لدى دمج جداول ذات أسماء طويلة أو وصفية للغاية، قد تصبح مهمة كتابة الإشارة الكاملة والمؤهلة للعمود مرهقة. ولتجنب ذلك، يجد المستخدمون أحيانًا أنّه من المفيد تزويد أسماء الجداول أو الأعمدة بأسماء بديلة أقصر. الأمر الذي يمكننا تنفيذه في SQL بإلحاق تعريف الجدول في بنية FROM بكلمة AS المفتاحية، ليأتي بعدها الاسم البديل الذي اخترناه، على النحو التالي: mysql> SELECT t1.column1, t2.column2 mysql> FROM table1 AS t1 JOIN table2 AS t2 mysql> ON t1.related_column = t2.related_column; استخدمنا في صيغة هذا المثال الأسماء البديلة في بنية SELECT رغم أننا لم نُعرف هذه الأسماء إلّا في بنية FROM. وهذا الأمر ممكن لأنّ ترتيب تنفيذ الاستعلامات في SQL يبدأ ببنية FROM أولًا. قد تكون هذه الطريقة مربكة، ولكن من المفيد تذكرها والتفكير في الأسماء البديلة قبل البدء في كتابة الاستعلام. كمثال، لننفذ الاستعلام التالي الذي يدمج جدولي sales وproducts ويزودهما بالأسماء البديلة S وP على التوالي: mysql> SELECT S.saleID, S.quantity, mysql> P.productName, mysql> (P.price * S.quantity) AS revenue mysql> FROM sales AS S JOIN products AS P mysql> USING (productID); نلاحظ أن هذا المثال يُنشئ أيضًا اسمًا بديلًا ثالثًا وهو revenue لحاصل ضرب قيم العمود quantity من الجدول sales بقيمها المقابلة في عمود price من الجدول products. ولن يظهر هذا الاسم سوى ضمن مجموعة النتائج كاسم عمود، لكنه مهم لتوضيح المعنى أو الغرض من نتائج الاستعلام: الخرج +--------+----------+-------------+---------+ | saleID | quantity | productName | revenue | +--------+----------+-------------+---------+ | 1 | 7 | widget | 132.93 | | 2 | 10 | whatzit | 299.90 | | 3 | 8 | gizmo | 115.92 | | 4 | 1 | thingamajig | 39.99 | | 5 | 5 | widget | 94.95 | | 6 | 1 | whatzit | 29.99 | | 7 | 3 | widget | 56.97 | | 8 | 4 | whatzit | 119.96 | +--------+----------+-------------+---------+ 8 rows in set (0.00 sec) وتجدر الملاحظة إلى أنّ استخدام كلمة AS عند تحديد الأسماء البديلة اختياري من الناحية التقنية، إذ من الممكن أيضًا كتابة المثال السابق على النحو: mysql> SELECT S.saleID, S.quantity, P.productName, (P.price * S.quantity) revenue mysql> FROM sales S JOIN products P mysql> USING (productID); ورغم كون كلمة AS ليست ضرورية لتحديد اسم بديل، إلّا أنّ تضمينها يعدّ من الممارسات الجيدة. إذ يُمكن أن يساعد في الحفاظ على وضوح غرض الاستعلام وتحسين مقروئيته. الخلاصة باطلاعك على هذا المقال، اكتسبت المعرفة حول كيفية استخدام عمليات الدمج JOIN لدمج جداول متفرقة ضمن مجموعة نتائج استعلام واحدة. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Joins in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام محارف البدل في لغة الاستعلام البنيوية SQL الدمج بين الجداول في SQL الاستعلام عن البيانات في SQL مدخل إلى أهم الاستعلامات (queries) في MySQL مواضيع متقدمة في SQL
  6. تسمح لغة الاستعلام البنيوية SQL باستخدام مجموعة متنوعة من محارف البدل (Wildcards) كما هو الحال في العديد من لغات البرمجة. وتُعرّف محارف البدل بأنها محارف مواضع مؤقتة خاصة قادرة على تمثيل محرف آخر أو قيمة أخرى واحدة أو أكثر، وهي ميزة مفيدة في SQL، إذ تمكننا من البحث في قاعدة البيانات عن البيانات دون الحاجة لمعرفة القيم الدقيقة المخزنة فيها. سيتناول هذا المقال كيفية الاستعلام عن البيانات باستخدام محارف البدل المحددة في SQL. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على الإصدار 20.04 من توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في دليل الإعداد الأولي للخادم مع الإصدار 20.04 من أوبنتو، كما يمكنك الاطلاع على المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدّمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة الدقيقة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على استخدام محارف البدل. لذا ننصحك بقراءة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إنشاء قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم بعيد، اتصل بهذا الخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسمwildcardsDB: mysql> CREATE DATABASE wildcardsDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات wildcardsDB، نفّذ تعليمة USE التالية: mysql> USE wildcardsDB; الخرج Database changed الآن، وبعد اختيار قاعدة البيانات، يمكننا إنشاء جدول ضمنها باستخدام الأمر التالي. وبفرض أننا نرغب في إنشاء جدول باسم user_profiles لتخزين معلومات ملفات المستخدمين لتطبيق ما، سيحتوي هذا الجدول على الأعمدة الخمسة التالية: user_id: مُعرّف كل مستخدم مُعبّرًا عنه بنمط بيانات الأعداد الصحيحة int. سيكون هذا العمود هو المفتاح الأساسي للجدول، إذ ستلعب كل قيمة فيه دور مُعرّف فريد لسجلها الموافق. name: اسم كل مستخدم، معبرًا عنه باستخدام نمط البيانات varchar بحد أقصى 30 محرفًا. email: سيحتوي هذا العمود على عناوين البريد الإلكتروني للمستخدمين، معبرًا عنها أيضًا باستخدام نمط البيانات varchar ولكن بحد أقصى 40 محرفًا. birthdate: سيحتوي هذا العمود على تاريخ ميلاد كل مستخدم باستخدام نمط البيانات date. quote: الاقتباس المفضل لكل مستخدم. وبهدف توفير عدد كافٍ من المحارف للاقتباسات، سنستخدم في هذا العمود أيضًا نمط البيانات varchar، ولكن بحد أقصى 300 محرف. سنشغّل الآن الأمر التالي لإنشاء هذا الجدول التجريبي النموذجي: mysql> CREATE TABLE user_profiles ( mysql> user_id int, mysql> name varchar(30), mysql> email varchar(40), mysql> birthdate date, mysql> quote varchar(300), mysql> PRIMARY KEY (user_id) mysql> ); الخرج Database changed والآن سنملأ جدولنا الفارغ ببعضٍ من البيانات النموذجية التجريبية، على النحو: INSERT INTO user_profiles VALUES mysql> (1, 'Kim', 'bd_eyes@example.com', '1945-07-20', '"Never let the fear of striking out keep you from playing the game." -Babe Ruth'), mysql> (2, 'Ann', 'cantstandrain@example.com', '1947-04-27', '"The future belongs to those who believe in the beauty of their dreams." -Eleanor Roosevelt'), mysql> (3, 'Phoebe', 'poetry_man@example.com', '1950-07-17', '"100% of the people who give 110% do not understand math." -Demitri Martin'), mysql> (4, 'Jim', 'u_f_o@example.com', '1940-08-13', '"Whoever is happy will make others happy too." -Anne Frank'), mysql> (5, 'Timi', 'big_voice@example.com', '1940-08-04', '"It is better to fail in originality than to succeed in imitation." -Herman Melville'), mysql> (6, 'Taeko', 'sunshower@example.com', '1953-11-28', '"You miss 100% of the shots you don\'t take." -Wayne Gretzky'), mysql> (7, 'Irma', 'soulqueen_NOLA@example.com', '1941-02-18', '"You have brains in your head. You have feet in your shoes. You can steer yourself any direction you choose." -Dr. Seuss'), mysql> (8, 'Iris', 'our_town@example.com', '1961-01-05', '"You will face many defeats in life, but never let yourself be defeated." -Maya Angelou'); الخرج Query OK, 8 rows affected (0.00 sec) Records: 8 Duplicates: 0 Warnings: 0 وبذلك، تغدو جاهزًا لمتابعة باقي المقال وبدء التعلم حول كيفية استخدام محارف البدل للاستعلام عن البيانات في SQL. الاستعلام عن البيانات باستخدام محارف البدل في SQL كما ذكرنا في المقدمة، محارف البدل عبارة عن محارف مواضع مؤقتة خاصة، تتميز بقدرتها على تمثيل محرف مختلف أو قيمة مختلفة واحدة أو أكثر. ولا يوجد سوى نمطين فقط من محارف البدل المُعرّفة في SQL، وهما: الشرطة السفلية _ حيث تمثل عند استخدامها كمحرف بدل محرفًا واحدًا. فيتطابق مثلًا النمط s_mmy مع الأسماء sammy أو sbmmy أو sxmmy. محرف إشارة النسبة المئوية % الذي يمثل صفرًا أو أكثر من المحارف. إذ يتطابق مثلًا النمط s%mmy مع كل من الأسماء التالية sammy أو saaaaaammy أو smmy. ملاحظة: تُستخدم محارف البدل هذه حصريًا ضمن بنية WHERE من الاستعلام، مقترنةً بأحد عاملي LIKE أو NOT LIKE. لتوضيح كيفية استخدامها مع البيانات النموذجية المذكورة في قسم مستلزمات العمل، لنفترض أننا نعلم بوجود مستخدم في جدول user_profiles يحمل اسمًا مكونًا من ثلاثة أحرف ينتهي بالمقطع "im"، لكننا غير متأكدين من هويته. بما أن الغموض يكمن فقط في المحرف الأول من الاسم، يمكننا تشغيل الاستعلام التالي الذي يستخدم محرف البدل _ للكشف عن هوية هذا المستخدم، على النحو: mysql> SELECT * FROM user_profiles WHERE name LIKE '_im'; الخرج +---------+------+---------------------+------------+---------------------------------------------------------------------------------+ | user_id | name | email | birthdate | quote | +---------+------+---------------------+------------+---------------------------------------------------------------------------------+ | 1 | Kim | bd_eyes@example.com | 1945-07-20 | "Never let the fear of striking out keep you from playing the game." -Babe Ruth | | 4 | Jim | u_f_o@example.com | 1940-08-13 | "Whoever is happy will make others happy too." -Anne Frank | +---------+------+---------------------+------------+---------------------------------------------------------------------------------+ 2 rows in set (0.00 sec) ملاحظة: ألحقنا الكلمة المفتاحية SELECT بعلامة النجمة (*) مباشرةً في هذا المثال، وهي الطريقة المختصرة في SQL للإشارة إلى "جميع الأعمدة". تُستخدم علامات النجمة *كمحارف بدل في بعض التطبيقات ولغات البرمجة، وحتى بعض تقديمات SQL، إذ تُمثّل صفرًا أو أكثر من المحارف، كما هو الحال تمامًا مع علامة النسبة المئوية المستخدمة في هذا المثال. ولكن النجمة في المثال أعلاه ليست بمحرف بدل، إذ أنّها تُمثّل أمرًا محددًا - ألا وهو: كل الأعمدة في جدول user_profiles - ولا تعبّر عن محرف أو أكثر غير معروف. وبالعودة إلى موضوعنا، تجدر الإشارة إلى أنّه لعامل NOT LIKE تأثير معاكس لعامل LIKE. فبدلاً من إرجاع كل سجل يطابق نمط محرف البدل، سيعيد كل سجل لا يطابق ذلك النمط. ولتوضيح الأمر، لنعد تنفيذ الاستعلام السابق مجددًا مستبدلين العامل LIKE بالعامل NOT LIKE، على النحو: mysql> SELECT * FROM user_profiles WHERE name NOT LIKE '_im'; في هذه المرة، تُستثنى من مجموعة النتائج كل السجلات التي لا تتطابق قيمتها في عمود الاسم name مع النمط _im. الخرج +---------+--------+----------------------------+------------+--------------------------------------------------------------------------------------------------------------------------+ | user_id | name | email | birthdate | quote | +---------+--------+----------------------------+------------+--------------------------------------------------------------------------------------------------------------------------+ | 2 | Ann | cantstandrain@example.com | 1947-04-27 | "The future belongs to those who believe in the beauty of their dreams." -Eleanor Roosevelt | | 3 | Phoebe | poetry_man@example.com | 1950-07-17 | "100% of the people who give 110% do not understand math." -Demitri Martin | | 5 | Timi | big_voice@example.com | 1940-08-04 | "It is better to fail in originality than to succeed in imitation." -Herman Melville | | 6 | Taeko | sunshower@example.com | 1953-11-28 | "You miss 100% of the shots you don't take." -Wayne Gretzky | | 7 | Irma | soulqueen_NOLA@example.com | 1941-02-18 | "You have brains in your head. You have feet in your shoes. You can steer yourself any direction you choose." -Dr. Seuss | | 8 | Iris | our_town@example.com | 1961-01-05 | "You will face many defeats in life, but never let yourself be defeated." -Maya Angelou | +---------+--------+----------------------------+------------+--------------------------------------------------------------------------------------------------------------------------+ 6 rows in set (0.00 sec) كمثال آخر، بفرض أننا نعلم بأنّ أسماء عدة مستخدمين مُدرجين في قاعدة البيانات لدينا تبدأ بحرف "I"، ولكننا لا نستطيع تذكرهم جميعًا. فمن الممكن في هذه الحالة استخدام محرف البدل % لعرض قائمة بجميع هؤلاء المستخدمين، كما هو موضح بالاستعلام التالي: mysql> SELECT user_id, name, email FROM user_profiles WHERE name LIKE 'I%'; الخرج +---------+------+----------------------------+ | user_id | name | email | +---------+------+----------------------------+ | 7 | Irma | soulqueen_NOLA@example.com | | 8 | Iris | our_town@example.com | +---------+------+----------------------------+ 2 rows in set (0.00 sec) وتجدر الإشارة إلى أنّ عاملي LIKE وNOT LIKE في MySQL لا يتميزان بالحساسية تجاه حالة الأحرف افتراضيًا. ما يعني أنّ الاستعلام السابق سيعيد نفس النتائج حتى لو لم نكتب حرف "I" ضمن نمط محرف البدل في حالته الكبيرة: mysql> SELECT user_id, name, email FROM user_profiles WHERE name LIKE 'i%'; الخرج +---------+------+----------------------------+ | user_id | name | email | +---------+------+----------------------------+ | 7 | Irma | soulqueen_NOLA@example.com | | 8 | Iris | our_town@example.com | +---------+------+----------------------------+ 2 rows in set (0.00 sec) ولا بدّ من الانتباه إلى أنّ محارف البدل تختلف عن التعابير النمطية regular expressions. فعادةً ما يُشير محرف البدل إلى محرف مُستخدم في مطابقة الأنماط العامّة glob-style pattern matching، وهو أسلوب لمطابقة الأنماط يُستخدم في أنظمة الأوامر وملفات التشغيل لتحديد مجموعات من أسماء الملفات بأنماط معينة. في حين تعتمد التعابير النمطية على لغة نمطية regular language لمطابقة أنماط السلاسل النصية، إذ تُعرّف اللغة النمطية بأنها نوع من اللغات الشكلية التي يمكن وصفها أو التعبير عنها باستخدام التعابير النمطية. تمتاز هذه اللغات ببنية بسيطة نسبيًا يمكن تحليلها باستخدام آلات حالة محدودة Finite State Machines، وهي نماذج رياضية بسيطة تصف كيف يمكن التحول من حالة إلى أخرى استجابةً لبعض المدخلات. تجاهل محارف البدل قد نرغب في بعض الأحيان بالبحث عن مدخلات تحتوي على أحد محارف البدل الخاصة بلغة SQL. وهنا يمكننا استخدام محرف هروب، والذي سيوجّه SQL لتجاهل وظيفة الإبدال لكل من محرفي البدل % أو _ واعتبارهما كنص عادي. على سبيل المثال، بفرض أنّنا نعلم بوجود مستخدمين في قاعدة البيانات لديهم اقتباس مفضل يتضمن علامة النسبة المئوية، ولكننا غير متأكدين من هويتهم على وجه التحديد. لذا سنحاول تنفيذ الاستعلام التالي: mysql> SELECT user_id, name, quote FROM user_profiles WHERE quote LIKE '%'; إلّا أنّ هذا الاستعلام لن يكون مفيدًا إلى ذلك الحد، إذ ستلعب علامة النسبة المئوية دور البديل لأي سلسلة من المحارف وبأي طول، وستعيد بالتالي كافّة سجلات الجدول: الخرج +---------+--------+--------------------------------------------------------------------------------------------------------------------------+ | user_id | name | quote | +---------+--------+--------------------------------------------------------------------------------------------------------------------------+ | 1 | Kim | "Never let the fear of striking out keep you from playing the game." -Babe Ruth | | 2 | Ann | "The future belongs to those who believe in the beauty of their dreams." -Eleanor Roosevelt | | 3 | Phoebe | "100% of the people who give 110% do not understand math." -Demitri Martin | | 4 | Jim | "Whoever is happy will make others happy too." -Anne Frank | | 5 | Timi | "It is better to fail in originality than to succeed in imitation." -Herman Melville | | 6 | Taeko | "You miss 100% of the shots you don't take." -Wayne Gretzky | | 7 | Irma | "You have brains in your head. You have feet in your shoes. You can steer yourself any direction you choose." -Dr. Seuss | | 8 | Iris | "You will face many defeats in life, but never let yourself be defeated." -Maya Angelou | +---------+--------+--------------------------------------------------------------------------------------------------------------------------+ 8 rows in set (0.00 sec) لتجاهل علامة النسبة المئوية، يمكننا وضع خط مائل خلفي (\) قبلها، وهو محرف الهروب الافتراضي في MySQL، على النحو: mysql> SELECT * FROM user_profiles WHERE quote LIKE '\%'; إلا أنّ هذا الاستعلام لن يكون مفيدًا بدوره، نظرًا لكونه يحدد أنّ محتويات عمود الاقتباس quote يجب أن تتكون من علامة نسبة مئوية فقط. وبالتالي، ستكون مجموعة النتائج فارغة: الخرج Empty set (0.00 sec) لتصحيح الأمر، لا بدّ من تضمين محارف بدل علامة النسبة المئوية في بداية ونهاية نمط البحث الذي يتبع عامل LIKE، على النحو التالي: mysql> SELECT user_id, name, quote FROM user_profiles WHERE quote LIKE '%\%%'; الخرج +---------+--------+----------------------------------------------------------------------------+ | user_id | name | quote | +---------+--------+----------------------------------------------------------------------------+ | 3 | Phoebe | "100% of the people who give 110% do not understand math." -Demitri Martin | | 6 | Taeko | "You miss 100% of the shots you don't take." -Wayne Gretzky | +---------+--------+----------------------------------------------------------------------------+ 2 rows in set (0.00 sec) يعمل الخط المائل العكسي في هذا الاستعلام على تجاهل علامة النسبة المئوية الثانية فقط، في حين تبقى كل من الأولى والثالثة كمحارف بدل. وبالتالي، سيعيد هذا الاستعلام كل سجل يحتوي عمود الاقتباس quote فيه على علامة نسبة مئوية واحدة على الأقل. وتجدر الملاحظة إلى أنّه من الممكن تحديد محارف هروب مخصصة باستخدام بنية ESCAPE، كما في المثال التالي: mysql> SELECT user_id, name, email FROM user_profiles WHERE email LIKE '%@_%' ESCAPE '@'; الخرج +---------+--------+----------------------------+ | user_id | name | email | +---------+--------+----------------------------+ | 1 | Kim | bd_eyes@example.com | | 3 | Phoebe | poetry_man@example.com | | 4 | Jim | u_f_o@example.com | | 5 | Timi | big_voice@example.com | | 7 | Irma | soulqueen_NOLA@example.com | +---------+--------+----------------------------+ 5 rows in set (0.00 sec) يُعرّف هذا الاستعلام علامة @ كمحرف هروب، ويعيد كل سجل يحتوي عمود البريد الإلكتروني email فيه على شرطة سفلية واحدة على الأقل. أمّا إذا أزلنا بنية ESCAPE، فإنّ الاستعلام سيعيد كافة سجلات الجدول، نظرًا لأن كل منها يتضمّن علامة @ واحدة على الأقل. الخلاصة باطلاعك على هذا المقال، اكتسبت المعرفة حول كيفية استخدام محارف البدل وتجاهلها في قواعد بيانات SQL التي تتعامل مع محارف البدل. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Wildcards in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام عوامل المقارنة والعامل IS NULL في لغة الاستعلام البنيوية SQL كيفية استخدام عوامل BETWEEN و IN في لغة الاستعلام البنيوية SQL كيفية إنشاء وإدارة الجداول في SQL الاستعلام عن البيانات في SQL البحث والتنقيب والترشيح في SQL
  7. نشرح في هذا المقال المزيد عن طريقة كتابة شروط بنى WHERE التي تتحكم في السجلات التي ستتأثر بعملية معينة. إذ تُحدّد هذه البنى معايير يجب أن تنطبق على كل سجل ليتأثر بالعملية، والتي تُعرف بشروط البحث. إذ تتألّف شروط البحث من تابع شرطي واحد أو أكثر، وهي تعابير خاصة تُقييم لتكون "صحيحة True" أو "خاطئة False" أو "غير معروفة Unknown"، ولا تؤثر العمليات إلا على السجلات التي يُقيّم فيها كل تابع شرطي على أنّه "صحيح True" ضمن بنية WHERE. تمكّن SQL المستخدمين من صياغة شروط بحث تضم أنماط متعددة من التوابع الشرطية، كل منها يستخدم عاملًا خاصًا لتقييم السجلات وسنركز في مقال اليوم على نمطين من التوابع الشرطية والعوامل المستخدمة فيها: عوامل المقارنة وعامل IS NULL. على الرغم من أن هذا المقال يعتمد تحديدًا على تعليمات SELECT في أمثلته، إلّا أنّ المفاهيم الموضحة هنا قابلة للتطبيق على عدد من العمليات في SQL. إذ تعد بنى WHERE وشروط البحث المرتبطة بها عناصر أساسية في عمليات التحديث UPDATE والحذف DELETE على وجه الخصوص. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز حاسوب يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ستحتاج أيضًا إلى قاعدة بيانات بجداول مُحمّلة ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على كتابة استعلامات تتضمّن بنى WHERE. لذا ننصحك بقراءة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية الاتصال بخادم MySQL وإنشاء قاعدة البيانات التجريبية المُستخدمة في الأمثلة خلال هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الخرج عند تنفيذها على أنظمة مختلفة عن MySQL. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p الآن ومن نافذة سطر الأوامر، أنشئ قاعدة بيانات باسم comparison_null_db: mysql> CREATE DATABASE comparison_null_db; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات comparison_null_db، نفّذ تعليمة USE التالية: mysql> USE comparison_null_db; الخرج Database changed بعد اختيار قاعدة البيانات comparison_null_db، أنشئ جدولًا ضمنها. لمتابعة الأمثلة المستخدمة في هذا المقال، تخيل أنك قررت مع مجموعة من أصدقائك أن تصبحوا أنشط بدنيًا وأن تبدأوا في ممارسة الجري. ولتحقيق ذلك، حدّد كل من أصدقائك هدفًا شخصيًا لعدد الأميال التي يرغب في قطعها خلال الشهر المقبل. فقررت تتبع أهداف عدد الأميال التي عيّنها أصدقائك، بالإضافة إلى عدد الأميال التي قطعوها فعليًا ضمن جدول SQL يحتوي على الأعمدة الثلاثة التالية: name: أسماء كل من أصدقائك، معبرًا عنها باستخدام نمط البيانات varchar بحد أقصى 15 محرفًا. goal: هدف كل صديق لعدد الأميال التي أمل في قطعها خلال الشهر الماضي، معبرًا عنه كعدد صحيح باستخدام نمط بيانات int. result: عدد الأميال التي قطعها كل صديق في نهاية الشهر، معبرًا عنه أيضًا كعدد صحيح باستخدام نمط بيانات int. نفّذ التعليمة CREATE TABLE التالية لإنشاء جدول باسم running_goals يحتوي على هذه الأعمدة الثلاثة: mysql> CREATE TABLE running_goals ( mysql> name varchar(15), mysql> goal int, mysql> result int mysql> ); الخرج: Query OK, 0 rows affected (0.012 sec) الآن سنملأ جدول running_goals ببعض البيانات التجريبية. نفذ العملية INSERT INTO التالية لإضافة سبع سجلات من البيانات تمثل سبعة من أصدقائك وأهدافهم للجري ونتائجهم: mysql> INSERT INTO running_goals mysql> VALUES mysql> ('Michelle', 55, 48), mysql> ('Jerry', 25, NULL), mysql> ('Milton', 45, 52), mysql> ('Bridget', 40, NULL), mysql> ('Wanda', 30, 38), mysql> ('Stewart', 35, NULL), mysql> ('Leslie', 40, 44); الخرج: Query OK, 7 rows affected (0.004 sec) Records: 7 Duplicates: 0 Warnings: 0 لاحظ أن قيمة العمود result لثلاثة من هذه السجلات هي قيم فارغة NULL. إذ سنفرض كمثال أنّ هؤلاء الأصدقاء لم يُبلغوا بعد عن عدد الأميال التي قطعوها خلال الشهر الماضي لذلك أدخلنا قيم نتائجهم على أنها NULL. وبذلك غدوتَ جاهزًا للمتابعة بباقي أقسام المقال وبدء تعلم كيفية استخدام عوامل المقارنة والعامل IS NULL في SQL. فهم التوابع الشرطية الخاصة ببنية WHERE يمكنك إدراج بنية WHERE بعد بنية FROM في أي عملية SQL تقرأ البيانات من جدول موجود أصلًا وذلك لتحديد البيانات التي ستتأثر بهذه العملية، إذ تُعرّف بنى WHERE شرط بحث، وأي سجل لا يحقق هذا الشرط سيُستثنى من العملية، على عكس السجلات التي تحققه. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعبيرات القادرة على تقييم تعبير قيمة واحد أو أكثر لتعيد نتيجة تكون إمّا "صحيحة True" أو "خاطئة False" أو "غير محددة Unknown". يُعرّف تعبير القيمة في لغة SQL - والذي يُشار إليه أحيانًا باسم التعبير ذو القيمة المفردة - بأنّه أي تعبير يُعيد قيمة واحدة. يمكن أن يكون تعبير القيمة عبارة عن قيمة مُصنفة النوع من قبيل سلسلة نصية أو قيمة عددية، أو تعبير رياضي. ولكن غالبًا ما يكون على الأقل أحد تعبيرات القيمة في شرط بحث بنية WHERE هو اسم عمود من الجدول المُشار إليه ضمن بنية FROM للعملية. ولدى تشغيل استعلامات SQL المُتضمّنة لبنية WHERE، سيطبق نظام إدارة قاعدة البيانات DBMS شرط البحث على كل سجل في الجدول المنطقي المحدد في بنية FROM. ليُعيد فقط السجلات التي يُقيّم من أجلها كل تابع شرطي في شرط البحث على أنه "محقق TRUE". يُحدّد معيار SQL ثمانية عشر نمطًا مختلفًا من التوابع الشرطية، وعلى الرغم من أنّها لا تتوفر كاملةً في كافّة أنظمة إدارة قواعد البيانات العلاقية RDBMS. إليك خمسة من أكثر أنواع التوابع الشرطية شيوعًا في شروط البحث والعوامل المستخدمة في كل منها: المقارنة: تقارن التوابع الشرطية المقارنِة بين تعبيري قيمة، وفي معظم الاستعلامات يكون أحد هذين التعبيرين هو اسم عمود. وعوامل المقارنة الستة هي كالتالي: =: يختبر ما إذا كانت القيمتان متساويتين. <>: يختبر ما إذا كانت القيمتان غير متساويتين. <: يختبر ما إذا كانت القيمة الأولى أقل من الثانية. >: يختبر ما إذا كانت القيمة الأولى أكبر من الثانية. <=: يختبر ما إذا كانت القيمة الأولى أقل من أو تساوي الثانية. >=: يختبر ما إذا كانت القيمة الأولى أكبر من أو تساوي الثانية. القيم الفارغة: تختبر التوابع الشرطية التي تستخدم عامل IS NULL ما إذا كانت القيم في عمود معين فارغة. النطاق: تستخدم التوابع الشرطية النطاقية عامل BETWEEN لاختبار ما إذا كان تعبير قيمة ما يقع بين تعبيري قيمة آخرين. العضوية: يستخدم هذا النوع من التوابع الشرطية عامل IN لاختبار ما إذا كانت قيمة ما تُمثّل عضوًا في مجموعة معينة. تطابق الأنماط: تستخدم توابع مطابقة الأنماط الشرطية عامل LIKE لاختبار ما إذا كانت قيمة ما تطابق نمطًا نصيًا يحتوي على محارف بدل. وكما ذكرنا في المقدمة، يركز هذا المقال على توضيح كيفية استخدام عوامل المقارنة والعامل IS NULL في SQL لتصفية البيانات. فإذا كنت ترغب في معرفة كيفية استخدام العوامل BETWEEN وIN في SQL مع التوابع الشرطية النطاقية وتلك الخاصة بالعضوية، ننصحك بقراءة مقالنا السابق حول كيفية استخدام عوامل BETWEEN وIN في لغة الاستعلام البنيوية SQL، أمّا إذا كنت ترغب في معرفة كيفية استخدام عامل LIKE لتصفية البيانات بناءً على نمط نصي يحتوي على محارف بدل، فننصحك بقراءة المقال كيفية استخدام محارف البدل في SQL. وللاطلاع على المزيد حول بنى WHERE عمومًا، ننصحك بقراءة مقالنا حول كيفية استخدام بنى WHERE في لغة الاستعلام البنيوية SQL. التوابع الشرطية للمقارنة في بنية WHERE تستخدم التوابع الشرطية للمقارنة في بنية WHERE واحدًا من ستة عوامل مقارنة وذلك لمقارنة تعبير قيمة مع آخر، والتي تتبع عادةً الصيغة العامة التالية: mysql> SELECT column_list mysql> FROM table_name mysql> WHERE column_name OPERATOR value_expression; ويأتي بعد كلمة WHERE المفتاحية تعبير قيمة، والذي يكون في معظم عمليات SQL عبارة عن اسم عمود. إنّ توفير اسم عمود كتعبير قيمة في شرط البحث يخبر نظام إدارة قواعد البيانات العلاقية (RDBMS) باستخدام قيمة كل سجل من العمود المحدد كجزء من تعبير القيمة للتكرار الخاص بكل سجل في شرط البحث. وبما أن نظام قاعدة البيانات يطبق شروط البحث على كل سجل تباعًا، سيعمل عامل المقارنة بالتالي على تضمين أو تصفية السجل بناءً على ما إذا كان شرط البحث "صحيحًا True” لقيمته في العمود المحدد. للتوضيح، لننفّذ الاستعلام التالي. والذي سيُعيد قيم من عمودي name وgoal في جدول running_goals لأي سجلات تكون فيها قيمة goal تساوي 40 وذلك وفق التابع الشرطي للمقارنة المُستخدم ضمن بنية WHERE: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal = 40; وقد كان في مثالنا هدف اثنين فقط من الأصدقاء قطع مسافة 40 ميلًا بالتحديد خلال الشهر الماضي، لذا سيعيد الاستعلام هذين السجلين فقط: الخرج: +---------+------+ | name | goal | +---------+------+ | Bridget | 40 | | Leslie | 40 | +---------+------+ 2 rows in set (0.00 sec) لتوضيح كيفية عمل عوامل المقارنة الأخرى، لنُنفّذ الاستعلامات التالية المُشابهة للمثال السابق باستثناء كون كل واحد منها يستخدم عامل مقارنة مختلف. يختبر العامل <> ما إذا كانت قيمتان غير متساويتين، لذا سيُعيد هذا الاستعلام كل سجل تكون فيه قيمة goal غير مساوية للقيمة 40: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal <> 40; الخرج: +----------+------+ | name | goal | +----------+------+ | Michelle | 55 | | Jerry | 25 | | Milton | 45 | | Wanda | 30 | | Stewart | 35 | +----------+------+ 5 rows in set (0.00 sec) يختبر العامل < ما إذا كان تعبير القيمة الأول أقل من تعبير القيمة الثاني: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal < 40; الخرج: +---------+------+ | name | goal | +---------+------+ | Jerry | 25 | | Wanda | 30 | | Stewart | 35 | +---------+------+ 3 rows in set (0.00 sec) يختبر العامل > ما إذا كان تعبير القيمة الأول أكبر من تعبير القيمة الثاني: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal > 40; الخرج: +----------+------+ | name | goal | +----------+------+ | Michelle | 55 | | Milton | 45 | +----------+------+ 2 rows in set (0.00 sec) يختبر العامل <= ما إذا كانت القيمة الأولى أقل من أو تساوي القيمة الثانية: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal <= 40; الخرج: +---------+------+ | name | goal | +---------+------+ | Jerry | 25 | | Bridget | 40 | | Wanda | 30 | | Stewart | 35 | | Leslie | 40 | +---------+------+ 5 rows in set (0.00 sec) يختبر العامل >= ما إذا كانت القيمة الأولى أكبر من أو تساوي القيمة الثانية: mysql> SELECT name, goal mysql> FROM running_goals mysql> WHERE goal >= 40; الخرج: +----------+------+ | name | goal | +----------+------+ | Michelle | 55 | | Milton | 45 | | Bridget | 40 | | Leslie | 40 | +----------+------+ 4 rows in set (0.00 sec) تتعامل عوامل المساواة (=) وعدم المساواة (<>) مع القيم من نمط السلاسل النصية كما هو متوقّع. فمثلًا، يُعيد الاستعلام التالي كل سجل تكون فيه قيمة عمود الاسم name تساوي السلسلة النصية 'Leslie': mysql> SELECT name mysql> FROM running_goals mysql> WHERE name = 'Leslie'; ونظرًا لوجود سجل واحد فقط في الجدول حيث قيمة العمود name تساوي "Leslie"، يقتصر الاستعلام على إعادة هذا السجل فقط. الخرج: +--------+ | name | +--------+ | Leslie | +--------+ 1 row in set (0.00 sec) عند مقارنة القيم من نمط السلاسل النصية، تقيّم كل من عوامل المقارنة <، >، <=، و >= كيفية ترتيب السلاسل أبجديًا. بمعنى آخر، إذا كتبنا تابعًا شرطيًا يختبر ما إذا كانت سلسلة نصية "أقل" من أخرى، فنحن نختبر ما إذا كانت السلسلة الأولى تأتي قبل الثانية أبجديًا. وبالمثل، إذا كان التابع الشرطي يختبر ما إذا كانت سلسلة نصية "أكبر" من أخرى، فنحن نختبر ما إذا كانت السلسلة الأولى تأتي بعد الثانية أبجديًا. لتوضيح الفكرة، لنُنفّذ الاستعلام التالي الذي سيُعيد قيم كل من العمودين name وgoal لكل سجل تكون قيمة name "أقل" من الحرف 'M'. بمعنى آخر، سيُقيّم شرط البحث على أنّه "صحيح True" لكل سجل تأتي قيمته في العمود name أبجديًا قبل الحرف 'M': mysql> SELECT name mysql> FROM running_goals mysql> WHERE name < 'M'; الخرج: +---------+ | name | +---------+ | Jerry | | Bridget | | Leslie | +---------+ 3 rows in set (0.00 sec) نلاحظ أن مجموعة النتائج هذه لا تتضمن الاسمين Michelle أو Milton. السبب في ذلك هو أن الحرف "M" يأتي أبجديًا قبل أي سلسلة نصية تبدأ بالحرف "M" وتحتوي على أكثر من حرف، لذا يُستثنى هذين الاسمين من مجموعة النتائج. التوابع الشرطية للقيمة الفارغة في SQL، NULL عبارة كلمة محجوزة تُستخدم لتمثيل القيم المفقودة أو غير المعروفة. فالقيمة الفارغة Null هي حالة، وليست قيمة فعلية؛ إذ أنّها لا تُمثّل الصفر أو السلسلة النصية الفارغة. يمكنك استخدام عامل IS NULL لاختبار ما إذا كان تعبير قيمة معين فارغًا: mysql> . . . mysql> WHERE column_name IS NULL mysql> . . . سيتتبع نظام قاعدة البيانات في هذا النوع من التوابع الشرطية قيمة كل سجل من العمود المحدد، مُقيّمًا إياها ما إذا كانت فارغة أم لا. فإذا كانت القيمة في العمود فارغة بالفعل، سيُقيّم شرط البحث على أنّه "صحيح True" لذلك السجل وبالتالي سيُضمّن في مجموعة النتائج. للتوضيح، لنُنفّذ الاستعلام التالي الذي يعيد عموديّ name وresult: mysql> SELECT name, result mysql> FROM running_goals mysql> WHERE result IS NULL; يختبر شرط البحث في بنية WHERE لهذا الاستعلام ما إذا كانت قيمة العمود result لكل سجل فارغة. فإذا كان الأمر كذلك، يُقيّم التابع الشرطي على أنّه "صحيح True" ويتم تضمين السجل في مجموعة النتائج: الخرج +---------+--------+ | name | result | +---------+--------+ | Jerry | NULL | | Bridget | NULL | | Stewart | NULL | +---------+--------+ 3 rows in set (0.00 sec) سُجلّت تلك القيم على أنها فارغة NULL لأن ثلاثة من أصدقائك لم يُبلغوا بعد عن عدد الأميال التي قطعوها خلال الشهر الماضي. ونتيجةً لذلك، يُقيّم شرط البحث في الاستعلام بأنه "صحيح True" لهذه السجلات الثلاثة، ولذا فهي السجلات الوحيدة المُضمنة في مجموعة النتائج. الخلاصة باطلاعك على هذا المقال، اكتسبت المعرفة حول كيفية استخدام عوامل المقارنة والعامل IS NULL في بنى WHERE لتحديد السجلات التي ستتأثر بعملية معينة في SQL. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Comparison and IS NULL Operators in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام عوامل BETWEEN و IN في لغة الاستعلام البنيوية SQL لماذا يجب عليك تعلم SQL فهم قواعد البيانات العلاقية مواضيع متفرقة في SQL
  8. سنتعرف في مقال اليوم على طريقة استخدام بنى WHERE في بعض تعليمات لغة الاستعلام البنيوية SQL لتحديد سجلات معينة ستتأثر بكل عملية نجريها، وذلك بتعريف معايير محددة تُعرف باسم شروط البحث، إذ يجب أن يستوفيها كل سجل ليتأثر بالعملية. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعبيرات القادرة على تقييم تعبير قيمة واحد أو أكثر لتعيد نتيجة تكون إمّا "صحيحة True" أو "خاطئة False" أو "غير محددة Unknown"، إذ تؤثر العمليات فقط على تلك السجلات التي يُقيّم من أجلها كل تابع شرطي في بنية WHERE على أنّه "صحيح True". تتيح لغة SQL للمستخدمين استرجاع مجموعات نتائج دقيقة من قاعدة البيانات، وذلك بتوفيرها مجموعة متنوعة من أنماط التوابع الشرطية، إذ يستخدم كل منها عاملًا محددًا لتقييم السجلات. وسنوضّح في هذا المقال نمطين من التوابع الشرطية وهي: التوابع الشرطية للنطاق والتي تستخدم عامل BETWEEN. والتوابع الشرطية لعضوية المجموعة التي تستخدم عامل IN. رغم أننا سنستخدم في أمثلتنا بهذا المقال تعليمات SELECT على وجه التحديد، إلّا أنّه من الممكن استخدام المفاهيم الموضحة فيه في العديد من عمليات SQL. إذ تعد بنى WHERE في الواقع مكونات أساسية في عمليات التحديث UPDATE والحذف DELETE. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ستحتاج أيضًا إلى قاعدة بيانات بجداول مُحمّلة ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على كتابة استعلامات تتضمّن بنى WHERE. لذا ننصحك بقراءة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية الاتصال بخادم MySQL وإنشاء قاعدة البيانات التجريبية المُستخدمة في الأمثلة خلال هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الخرج عند تنفيذها على أنظمة مختلفة عن MySQL. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p الآن ومن نافذة سطر الأوامر، أنشئ قاعدة بيانات باسم between_in_db: mysql> CREATE DATABASE between_in_db; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات between_in_db، نفّذ تعليمة USE التالية: mysql> USE between_in_db; الخرج Database changed بعد اختيار قاعدة البيانات between_in_db، أنشئ جدولًا ضمنها. لمتابعة الأمثلة المستخدمة في هذا المقال، لنفرض أننا نرغب بإدارة فريق مبيعات لشركة ما، وبفرض أن هذه الشركة تبيع ثلاث منتجات فقط: أجهزة صغيرة، وأدوات متنوعة، وأجهزة مبتكرة. وقررنا تتبع عدد الوحدات من كل منتج يبيعه كل عضو في الفريق ضمن قاعدة بيانات SQL. وارتأينا أنّ هذه القاعدة ستحتوي على جدول واحد بأربعة أعمدة: name: أسماء أعضاء فريق المبيعات، معبرًا عنها باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. widgets: العدد الإجمالي للأجهزة الصغيرة التي باعها كل بائع، معبرًا عنها بنمط بيانات الأعداد الصحيحة int. doodads: عدد الأدوات المتنوعة التي باعها كل بائع، معبرًا عنها أيضًا بنمط بيانات الأعداد الصحيحة int. gizmos: عدد الأجهزة المبتكرة التي باعها كل بائع، معبرًا عنها أيضًا بنمط بيانات الأعداد الصحيحة int. لننفّذ تعليمة CREATE TABLE التالية لإنشاء جدول باسم sales يحتوي على هذه الأعمدة الأربعة: mysql> CREATE TABLE sales ( mysql> name varchar(20), mysql> widgets int, mysql> doodads int, mysql> gizmos int mysql> ); الخرج Query OK, 0 rows affected (0.01 sec) الآن سنملأ جدول sales بعض البيانات التجريبية. نفّذ العملية INSERT INTO التالية لإضافة سبع سجلات من البيانات تمثل أعضاء فريق المبيعات وعدد ما باعوه من كل منتج: mysql> INSERT INTO sales mysql> VALUES mysql> ('Tyler', 12, 22, 18), mysql> ('Blair', 19, 8, 13), mysql> ('Lynn', 7, 29, 3), mysql> ('Boris', 16, 16, 15), mysql> ('Lisa', 17, 2, 31), mysql> ('Maya', 5, 9, 7), mysql> ('Henry', 14, 2, 0); وبذلك غدوتَ جاهزًا للمتابعة بباقي أقسام المقال وبدء تعلم كيفية استخدام عوامل BETWEEN وIN لتصفية البيانات. فهم التوابع الشرطية الخاصة ببنية WHERE يمكنك إدراج بنية WHERE بعد بنية FROM في أي عملية SQL تقرأ البيانات من جدول موجود أصلًا وذلك لتحديد البيانات التي ستتأثر بهذه العملية، إذ تُعرّف بنى WHERE شرط بحث، وأي سجل لا يحقق هذا الشرط سيُستثنى من العملية، على عكس السجلات التي تحققه. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعبيرات القادرة على تقييم تعبير قيمة واحد أو أكثر لتعيد نتيجة تكون إمّا "صحيحة True" أو "خاطئة False" أو "غير محددة Unknown". يُعرّف تعبير القيمة في لغة SQL - والذي يُشار إليه أحيانًا باسم التعبير ذو القيمة المفردة - بأنّه أي تعبير يُعيد قيمة واحدة. ويمكن أن يكون تعبير القيمة عبارة عن قيمة مُصنفة النوع من قبيل سلسلة نصية أو قيمة عددية، أو تعبير رياضي. ولكن غالبًا ما يكون على الأقل أحد تعبيرات القيمة في شرط بحث بنية WHERE هو اسم عمود. ومن الجدير بالملاحظة أنّه في معظم الأحيان نستخدم على الأقل اسم عمود من الجدول المُشار إليه ضمن بنية FROM من العملية كأحد تعابير القيمة في التابع الشرطي ضمن بنية WHERE ولدى تشغيل استعلامات SQL المُتضمّنة لبنية WHERE، سيطبق نظام إدارة قاعدة البيانات (DBMS) شرط البحث على كل سجل في الجدول المنطقي المحدد في بنية FROM ليُعيد فقط السجلات التي يُقيّم من أجلها كل تابع شرطي في شرط البحث على أنه "محقق TRUE". يُحدّد معيار SQL ثمانية عشر نمطًا مختلفًا من التوابع الشرطية، وعلى الرغم من أنّها لا تتوفر كاملةً في كافّة أنظمة إدارة قواعد البيانات العلاقية RDBMS. إليك خمسة من أكثر أنواع التوابع المستخدمة في شروط البحث والعوامل المستخدمة في كل منها: المقارنة: تقارن التوابع الشرطية المقارنِة بين تعبيري قيمة، وفي معظم الاستعلامات يكون أحد هذين التعبيرين هو اسم عمود. وعوامل المقارنة الستة هي: =: يختبر ما إذا كانت القيمتان متساويتين. <>: يختبر ما إذا كانت القيمتان غير متساويتين. <: يختبر ما إذا كانت القيمة الأولى أقل من الثانية. >: يختبر ما إذا كانت القيمة الأولى أكبر من الثانية. <=: يختبر ما إذا كانت القيمة الأولى أقل من أو تساوي الثانية. >=: يختبر ما إذا كانت القيمة الأولى أكبر من أو تساوي الثانية. القيم الفارغة: تختبر التوابع الشرطية التي تستخدم عامل IS NULL ما إذا كانت القيم في عمود معين فارغة. النطاق: تستخدم التوابع الشرطية النطاقية عامل BETWEEN لاختبار ما إذا كان تعبير قيمة ما يقع بين تعبيري قيمة آخرين. العضوية: يستخدم هذا النوع من التوابع الشرطية عامل IN لاختبار ما إذا كانت قيمة ما تُمثّل عضوًا في مجموعة معينة. تطابق الأنماط: تستخدم توابع مطابقة الأنماط الشرطية عامل LIKE لاختبار ما إذا كانت قيمة ما تطابق نمطًا نصيًا يحتوي على محارف بدل. وكما ذكرنا في المقدمة، يركز هذا المقال على توضيح كيفية استخدام عوامل BETWEEN وIN في SQL لتصفية البيانات. فإذا كنت ترغب في معرفة كيفية استخدام عوامل المقارنة أو عامل IS NULL، ننصحك بقراءة مقالنا حول كيفية استخدام عوامل المقارنة وIS NULL في لغة الاستعلام البنيوية SQL. أمّا إذا كنت ترغب في معرفة كيفية استخدام عامل LIKE لتصفية البيانات بناءً على نمط نصي يحتوي على محارف بدل، فننصحك بقراءة المقال كيفية استخدام محارف البدل في SQL. وللاطلاع على المزيد حول بنى WHERE عمومًا، ننصحك بقراءة مقالنا حول كيفية استخدام بنى WHERE في لغة الاستعلام البنيوية SQL. التوابع الشرطية النطاقية تستخدم التوابع الشرطية النطاقية عامل BETWEEN لاختبار ما إذا كان تعبير قيمة محصور بين تعبيري قيمة آخرين. وتتبع بنية WHERE المُتضمنة لتابع شرطي نطاقي في شرط البحث الخاص بها الصيغة العامة التالية: mysql> SELECT column_list mysql> FROM table_name mysql> WHERE column_name BETWEEN value_expression1 AND value_expression2; ويأتي بعد كلمة WHERE المفتاحية تعبير قيمة، والذي يكون في معظم عمليات SQL عبارة عن اسم عمود. وبما أن نظام قاعدة البيانات يطبق شروط البحث على كل سجل تباعًا، فإن توفير اسم عمود كتعبير قيمة في شرط البحث يخبر نظام إدارة قواعد البيانات العلاقية RDBMS باستخدام قيمة كل سجل من العمود المحدد كجزء من تعبير القيمة للتكرار الخاص بكل سجل في شرط البحث. ويأتي بعد اسم العمود عامل BETWEEN وتعبيري قيمة آخرين مفصولين بعامل AND. وسيُقيّم شرط البحث على أنه "صحيح True" لأي سجلات تكون قيمتها في العمود المحدد أكبر من أو تساوي القيمة الأولى من القيمتين المفصولتين بعامل AND وأقل من أو تساوي القيمة الثانية. لتوضيح كيفية عمل التوابع الشرطية النطاقية، لننفّذ الاستعلام التالي. والذي سيُعيد قيم عمودي name وwidgets لأي سجلات تكون فيها قيمة widgets محصورة بين 14 و19ضمنًا: mysql> SELECT name, widgets mysql> FROM sales mysql> WHERE widgets BETWEEN 14 AND 19; الخرج +-------+---------+ | name | widgets | +-------+---------+ | Blair | 19 | | Boris | 16 | | Lisa | 17 | | Henry | 14 | +-------+---------+ 4 rows in set (0.00 sec) تذكر أن النطاق الذي تحدده بعد عامل BETWEEN يمكن أن يتكون من أي زوج من تعابير القيمة، بما في ذلك أسماء الأعمدة. يعيد الاستعلام التالي جميع الأعمدة من جدول sales. وفيه، وبدلًا من تحديد كل عمود على حدة، ألحقنا الكلمة المفتاحية SELECT بعلامة النجمة (*) مباشرةً، وهي الطريقة المختصرة في SQL للإشارة إلى "جميع الأعمدة". وتحدّ بنية WHERE في هذا الاستعلام من إرجاعه للسجلات التي تكون فيها قيمة gizmos أكبر من قيمة doodads لكنها أقل من قيمة widgets: mysql> SELECT * mysql> FROM sales mysql> WHERE gizmos BETWEEN doodads AND widgets; لا يمتلك سوى عضو واحد من فريق المبيعات قيمة في عمود gizmos محصورة بين القيمتين widgets وdoodads. لذا فإنّ السجل الخاص به فقط هو الذي يظهر في مجموعة النتائج. الخرج +-------+---------+---------+--------+ | name | widgets | doodads | gizmos | +-------+---------+---------+--------+ | Blair | 19 | 8 | 13 | +-------+---------+---------+--------+ 1 row in set (0.00 sec) ويجب أن تكون على دراية بترتيب تعابير القيمة المُحدّدة للنطاق: فالقيمة الأولى بعد عامل BETWEEN تُمثّل دائمًا الحد الأدنى للنطاق، أمّا القيمة الثانية فتُمثّل الحد الأعلى. الاستعلام التالي مطابق للسابق، باستثناء أنه يعكس ترتيب الأعمدة التي تُحدد طرفيّ النطاق: mysql> SELECT * mysql> FROM sales mysql> WHERE gizmos BETWEEN widgets AND doodads; يعيد الاستعلام هذه المرة سجلين حيث تكون في كل منهما قيمة gizmos أكبر من أو تساوي قيمة widgets للسجل ولكنها أقل من أو تساوي قيمة doodads. وكما تدل النتائج، يمكن أن يؤدي تغيير الترتيب بهذه الطريقة إلى استرجاع مجموعة نتائج مختلفة تمامًا. الخرج +-------+---------+---------+--------+ | name | widgets | doodads | gizmos | +-------+---------+---------+--------+ | Tyler | 12 | 22 | 18 | | Maya | 5 | 9 | 7 | +-------+---------+---------+--------+ 2 rows in set (0.00 sec) وعلى نحوٍ مشابه لحالة استخدام عوامل المقارنة <، >، <=، و >= لتقييم عمود مُتضمّن لقيم من نمط السلاسل النصية، سيُحدد العامل BETWEEN ما إذا كانت هذه القيم تقع أبجديًا بين قيمتين من نمط السلاسل النصية. لتوضيح الفكرة، لنُنفّذ الاستعلام التالي الذي يعيد قيم العمود name الموافقة لأي سجل من الجدول sales حيث تكون قيمة name محصورة أبجديَا بين الحرفين A و M. استخدمنا في هذا المثال سلسلتين نصيتين مجردتين كتعبيريّ قيمة يمثلان طرفي النطاق. ومن الجدير بالملاحظة أنّه يجب حصر هذه القيم المحرفية بين علامات اقتباس مفردة أو مزدوجة، وإلا سيبحث نظام إدارة قواعد البيانات عن أعمدة باسم A و M، مما قد يؤدي إلى فشل الاستعلام. mysql> SELECT name mysql> FROM sales mysql> WHERE name BETWEEN 'A' AND 'M'; الخرج +-------+ | name | +-------+ | Blair | | Lynn | | Boris | | Lisa | | Henry | +-------+ 5 rows in set (0.00 sec) نلاحظ أن مجموعة النتائج هذه لا تتضمن الاسم Maya رغم كون النطاق المحدد في شرط البحث يمتد من A إلى M. السبب في ذلك هو أن الحرف M يأتي أبجديًا قبل أي سلسلة نصية تبدأ بالحرف M وتحتوي على أكثر من حرف، لذا يُستثنى الاسم Maya من مجموعة النتائج هذه، بالإضافة إلى أي بائعين آخرين لا تقع أسماؤهم ضمن النطاق المحدد. التوابع الشرطية للعضوية تتيح التوابع الشرطية للعضوية إمكانية تصفية نتائج الاستعلام بناءً على ما إذا كانت قيمة ما تُمثّل عضوًا من مجموعة محددة من البيانات. وعادةً ما تتبع الصيغة التالية في بنى WHERE: mysql>. . . mysql> WHERE column_name IN (set_of_data) mysql>. . . يأتي بعد الكلمة المفتاحية WHERE تعبير القيمة، وعادةً ما يكون هذا التعبير عبارة عن اسم عمود. يلي ذلك عامل IN، الذي يُتبع بدوره بمجموعة من البيانات. يمكن تحديد هذه المجموعة بوضوح عن طريق إدراج أي عدد من تعابير القيمة الصالحة مفصولةً بعلامات فاصلة، سواء كانت قيمًا مجردة أو أسماء أعمدة أو تعابير رياضية تتضمن أيًا من العناصر آنفة الذكر. لتوضيح الأمر، لنُنفذ الاستعلام التالي. والذي سيعيد قيم عموديّ name وgizmos لكل سجل تُمثّل فيه قيمة gizmos عضوًا من المجموعة المعرّفة بعد عامل IN: mysql> SELECT name, doodads mysql> FROM sales mysql> WHERE doodads IN (1, 2, 11, 12, 21, 22); نلاحظ أن قيم العمود doodads لثلاثة أعضاء فقط من فريق المبيعات تساوي أي من القيم الموجودة ضمن هذه المجموعة، ولذلك فإن السجلات الخاصة بهم فقط هي المُسترجعة. الخرج +-------+---------+ | name | doodads | +-------+---------+ | Tyler | 22 | | Lisa | 2 | | Henry | 2 | +-------+---------+ 3 rows in set (0.00 sec) وبدلًا من كتابة كل عنصر في مجموعة بنفسك، يُمكنك تكوين مجموعة من خلال إلحاق عامل IN بالاستعلام الفرعي. يُعرف الاستعلام الفرعي أيضًا بالاستعلام المُدمج أو الداخلي، وهو تعليمة SELECT مُدرجة ضمن إحدى بنى تعليمة SELECT أخرى. يُمكن للاستعلام الفرعي أن يستخرج المعلومات من أي جدول ضمن نفس قاعدة البيانات المحددة في بنية FROM للعملية الرئيسة 'الخارجية'. ملاحظة: عند كتابة استعلام فرعي لتحديد مجموعة ضمن التابع الشرطي للعضوية، تأكد من استخدام استعلام فرعي ذو قيمة مفردة، أي استعلام يُعيد عمودًا واحدًا فقط. تميل أنظمة إدارة قواعد البيانات إلى عدم السماح باستعلامات فرعية تُعيد عدة أعمدة ضمن التابع الشرطي للعضوية، نظرًا لعدم وضوح العمود الذي يجب تقييمه لتحديد العناصر المنتمية للمجموعة. كمثال على استخدام الاستعلام الفرعي لتحديد مجموعة في تابع شرطي للعضوية، نفذ التعليمة التالية لإنشاء جدول يُسمى example_set_table يحتوي على عمود واحد فقط. سنسمى هذا العمود prime_numbers وسيحتوي على قيم من نمط البيانات int، على النحو: mysql> CREATE TABLE example_set_table ( mysql> prime_numbers int mysql> ); لنملأ الآن هذا الجدول ببضعة سجلات من البيانات التجريبية النموذجية. وفقًا لاسم العمود الوحيد في الجدول، ستُضيف التعليمة INSERT التالية عشرة سجلات إلى الجدول، ويحتوي كل منها على أحد الأعداد الأولية العشرة الأولى: mysql>INSERT INTO example_set_table mysql>VALUES mysql> (2), mysql> (3), mysql> (5), mysql> (7), mysql> (11), mysql> (13), mysql> (17), mysql> (19), mysql> (23), mysql> (29); لنُشغّل الآن الاستعلام التالي الذي يُعيد قيمًا من أعمدة name و widgets في جدول sales، إذ تختبر بنية WHERE ما إذا كانت كل قيمة في عمود widgets تنتمي للمجموعة التي حصلنا عليها من الاستعلام الفرعي SELECT prime_numbers FROM example_set_table: mysql> SELECT name, widgets mysql> FROM sales mysql> WHERE widgets IN (SELECT prime_numbers FROM example_set_table); الخرج +-------+---------+ | name | widgets | +-------+---------+ | Blair | 19 | | Lynn | 7 | | Lisa | 17 | | Maya | 5 | +-------+---------+ 4 rows in set (0.00 sec) بما أنّ أربعة فقط من أعضاء فريق المبيعات باعوا عددًا من الأجهزة الصغيرة يُطابق أحد الأعداد الأولية الموجودة في جدول example_set_table، سيُعيد هذا الاستعلام تلك السجلات الأربعة فقط. الخلاصة باطلاعك على هذا المقال، ستكون قد اكتسبت المعرفة حول كيفية استخدام العامل BETWEEN في SQL لتحديد ما إذا كانت قيم في عمود ما تندرج ضمن نطاق محدد، كما تعلمت كيفية استخدام عامل IN لتحديد ما إذا كانت قيم في عمود ما تنتمي إلى مجموعة معينة. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة المقالات المنشورة تحت وسم سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use the BETWEEN and IN Operators in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام بنى WHERE في لغة الاستعلام البنيوية SQL الاستعلام عن البيانات في SQL الاستعلامات الفرعية والإجراءات في SQL التوثيق العربي للغة SQL
  9. سنتعرف في مقال اليوم على دور البنية WHERE في تعليمات لغة الاستعلام البنيوية SQL لتحديد السجلات التي ستتأثر بكل عملية استعلام، وذلك بتعريف معايير أو شروط محددة تُعرف باسم شروط البحث والتي يجب أن يستوفيها كل سجل ليتأثر بالعملية المطلوب تنفيذها، حيث سنوفر شرحًا للصيغة العامة المستخدمة في بنية WHERE. ونوضح كيفية دمج عدة شروط بحث في بنية واحدة لتصفية البيانات بدقة أكبر، كما سنوضح كيفية استخدام العامل NOT لاستثناء السجلات التي تلبي شرط بحث معين بدلًا من تضمينها. وبالرغم من أننا سنستخدم في أمثلتنا بهذا المقال تعليمات SELECT على وجه التحديد، إلّا أنّه من الممكن استخدام المفاهيم الموضحة فيه في العديد من عمليات SQL، إذ يمكن استخدام بنية WHERE كذلك في عمليات تحديث البيانات UPDATE أو حذفها DELETE. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية التي تستخدم لغة SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة الثالثة من هذا المقال. ستحتاج أيضًا إلى قاعدة بيانات بجداول مُحمّلة ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على كتابة استعلامات تتضمّن بنى WHERE. لذا ننصحك بقراءة الفقرة التالية التي تشرح طريقة الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول تنفيذ الأمثلة المُستخدمة خلال هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL، فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات الطفيفة في الصيغة أو الخرج عند تنفيذها على أنظمة مختلفة عن MySQL. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p الآن ومن نافذة سطر الأوامر، أنشئ قاعدة بيانات باسم where_db كما يلي: mysql> CREATE DATABASE where_db; بمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات where_db، نفّذ تعليمة USE التالية: mysql> USE where_db; الخرج Database changed بعد اختيار قاعدة البيانات where_db، أنشئ جدولًا ضمنها. ولمتابعة الأمثلة المستخدمة في هذا المقال، سنفترض أننا نرغب بإدارة دوري للغولف في ملعب غولف محلي. وقررنا تتبع المعلومات حول أداء كل لاعب في الدوري خلال الجولات التي سيشارك بها، لذا قررنا تخزين المعلومات في قاعدة بيانات تتضمن جدولًا باسم golfers. وارتأينا أنّ هذا الجدول يحتاج إلى ستة أعمدة وهي: name: أسماء لاعبي الغولف، مُعبر عنها باستخدام نمط البيانات varchar بحد أقصى 20 محرفًا. rounds_played: إجمالي عدد الجولات التي لعبها كل لاعب غولف كاملةً، مُعبر عنها بنمط بيانات الأعداد الصحيحة int. best: أفضل أو أدنى نتيجة لكل لاعب جولف لجولة فردية، مُعبرًا عنها أيضًا بنمط الأعداد الصحيحة int. worst: أسوأ أو أعلى نتيجة لكل لاعب جولف لجولة فردية، مُعبرًا عنها كذلك بنمط الأعداد الصحيحة int. average: متوسط تقريبي لنتائج كل لاعب غولف عبر الجولات التي لعبوها، سيحتوي هذا العمود على قيم من نوع decimal، محددة بحد أقصى 4 أرقام، واحد منها على يمين الفاصلة العشرية. wins: عدد الجولات التي حقق فيها كل لاعب غولف أقل نتيجة بين جميع المشاركين في المجموعة، مُعبر عنها باستخدام نمط int. ملاحظة: قد تستغرب لماذا تكون النتيجة الأفضل best لكل لاعب أدنى من نتيجته الأسوأ worst، وهذا يعود لطبيعة قواعد لعبة الغولف التي تقتضي أن يكون الفائز هو من ينجز اللعبة بأقل عدد من الضربات. بالتالي، وبخلاف الرياضات الأخرى، تُعد النتيجة الأدنى هي الأفضل للاعب الغولف. لإنشاء جدول باسم golfers يحتوي على هذه الأعمدة الستة، نفّذ التعليمة CREATE TABLE التالية: mysql> CREATE TABLE golfers ( mysql> name varchar(20), mysql> rounds_played int, mysql> best int, mysql> worst int, mysql> average decimal (4,1), mysql> wins int mysql> ); الآن سنملأ جدول golfers ببعض البيانات التجريبية. نفّذ العملية INSERT INTO التالية لإضافة سبع سجلات من البيانات تمثل سبعة من لاعبي الدوري للغولف: mysql> INSERT INTO golfers mysql> VALUES mysql> ('George', 22, 68, 103, 84.6, 3), mysql> ('Pat', 25, 65, 74, 68.7, 9), mysql> ('Grady', 11, 78, 118, 97.6, 0), mysql> ('Diane', 23, 70, 92, 78.8, 1), mysql> ('Calvin', NULL, 63, 76, 68.5, 7), mysql> ('Rose', NULL, 69, 84, 76.7, 4), mysql> ('Raymond', 18, 67, 92, 81.3, 1); نلاحظ أن قيم rounds_played لبعض سجلات اللاعبين فارغة NULL. فلنفترض جدلًا أنّ هؤلاء اللاعبين لم يُبلغوا بعد عن عدد الجولات التي شاركوا فيها، ولهذا السبب سُجلت هذه القيم على أنّها فارغة NULL. وبذلك غدوتَ جاهزًا للمتابعة بباقي أقسام المقال وبدء تعلم كيفية استخدام بنى WHERE في SQL. تصفية البيانات باستخدام بنى WHERE تُعرّف التعليمة البرمجية في SQL بأنّها أي عملية تُرسَل إلى نظام قاعدة البيانات لتنفيذ مهمّة محددة، من قبيل إنشاء جدول أو إدخال أو حذف بيانات أو تغيير هيكلية عمود أو جدول. وتتألف تعليمات SQL من بنى مختلفة تتكون بدورها من كلمات مفتاحية معينة مع المعلومات التي تتطلبها هذه الكلمات. وكما ذكرنا في المقدمة، تتيح بنى WHERE إمكانية تصفية بعض السجلات كي لا تتأثر بعملية SQL محددة. وتأتي بنية WHERE في الاستعلامات، بعد بنية FROM، كما في المثال التالي: mysql> SELECT columns_to_query mysql> FROM table_to_query mysql> WHERE search_condition; يأتي شرط البحث بعد كلمة WHERE المفتاحية. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعبيرات القادرة على تقييم تعبير قيمة واحد أو أكثر لتعيد نتيجة تكون إمّا "صحيحة True" أو "خاطئة False" أو "غير محددة Unknown"، ومن الجدير بالملاحظة أنّه في الحالات التي يحتوي فيها شرط البحث على تابع شرطي واحد فقط، يكون المصطلحان "شرط البحث" و"التابع الشرطي" مترادفين. قد تأخذ التوابع الشرطية في شرط البحث الخاص ببنية WHERE أشكالًا عديدة، لكنها عادةً ما تتبع الصيغة التالية: WHERE column_name OPERATOR value_expression يُعرّف تعبير القيمة في لغة SQL - والذي يُشار إليه أحيانًا باسم التعبير ذو القيمة المفردة - بأنّه أي تعبير يُعيد قيمة واحدة. يمكن أن يكون تعبير القيمة عبارة عن قيمة مُصنفة النوع من قبيل سلسلة نصية أو قيمة عددية، أو تعبير رياضي. ولكن غالبًا ما يكون على الأقل أحد تعبيرات القيمة في شرط بحث بنية WHERE هو اسم عمود. فلدى تشغيل استعلامات SQL التي تحتوي على بنية WHERE، سيطبّق نظام إدارة قاعدة البيانات شرط البحث تتابعيًا على كل سجل في الجدول المحدد في بنية FROM. ولن يعيد سوى السجلات التي يُقيّم فيها كل شرط بحث على أنه صحيح True. لتوضيح هذه الفكرة، شغّل الاستعلام التالي. والذي سيعيد كل قيمة من العمود name في الجدول golfers: mysql> SELECT name mysql> FROM golfers mysql> WHERE (2 + 2) = 4; يتضمن هذا الاستعلام بنية WHERE، لكن بدلًا من تحديد اسم عمود فإنها تستخدم (2 + 2) كتعبير قيمة أول وتختبر ما إذا كان مساويًا لتعبير القيمة الثاني 4. ونظرًا لأنّ التعبير (2 + 2) يساوي دائمًا 4، فإن شرط البحث هذا يُقيّم على أنه صحيح لكل سجل في الجدول. ونتيجةً لذلك، تُرجع قبم كافّة السجلات في مجموعة النتائج، على النحو التالي: الخرج +---------+ | name | +---------+ | George | | Pat | | Grady | | Diane | | Calvin | | Rose | | Raymond | +---------+ 7 rows in set (0.01 sec) لعلّ بنية WHERE هذه ليست مفيدة بدرجة كبيرة، إذ إنها تُقيم دائمًا على أنها "صحيحة True" وبالتالي تُرجع دائمًا كافة سجلات الجدول. وكما ذكرنا سابقًا، عادةً ما نستخدم اسم عمود واحد على الأقل كأحد تعبيري القيمة في شرط البحث ضمن بنية WHERE. فلدى تشغيل الاستعلامات، سيطبق نظام قاعدة البيانات شرط البحث على كل سجل على حدة تباعًا. ومن خلال توفير اسم عمود كتعبير قيمة في شرط بحث، فإنك تُخبر نظام إدارة قاعدة البيانات باستخدام قيمة كل سجل من العمود المحدد كجزء من تعبير القيمة للتكرار الخاص بكل سجل في شرط البحث. تُطبّق بنية WHERE في الاستعلام التالي شرط بحث أكثر تحديدًا على كل سجل. إذ ستُرجع قيم كل من عمودي name وwins من أي سجل تساوي فيه قيمة wins الرقم 1: mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins = 1; يُرجع هذا الاستعلام سجليّن للاعبي جولف، نظرًا لوجود لاعبين قد فازا بجولة واحدة فقط، فيكون الخرج على هذا النحو: الخرج +---------+------+ | name | wins | +---------+------+ | Diane | 1 | | Raymond | 1 | +---------+------+ 2 rows in set (0.01 sec) من الجدير بالملاحظة أنّنا استخدمنا في الأمثلة السابقة علامة المساواة = لاختبار ما إذا كان تعبيرا القيمة متكافئين، إلا أنّ العامل المُستخدم يعتمد على نوع التابع الشرطي الذي ترغب باستخدامه لتصفية مجموعات النتائج الخاصة بك. يُحدّد معيار SQL ثمانية عشر نمطًا مختلفًا من التوابع الشرطية، على الرغم من أنّها لا تتوفر كاملةً في كافّة تقديمات SQL. إليك خمسة من أكثر أنماط التوابع الشرطية استخدامًا، بالإضافة إلى شرح موجز لكل منها والعوامل المستخدمة فيها: المقارنة تقارن التوابع الشرطية المقارنِة بين تعبيري قيمة باستخدام عامل مقارنة، وفي معظم الاستعلامات يكون أحد هذين التعبيرين هو اسم عمود. وعوامل المقارنة الستة هي: العامل =: يختبر ما إذا كانت القيمتان متساويتين mysql> SELECT name mysql> FROM golfers mysql> WHERE name = 'George'; الخرج +--------+ | name | +--------+ | George | +--------+ 1 row in set (0.00 sec) العامل <>: يختبر ما إذا كانت القيمتان غير متساويتين mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins <> 1; الخرج +--------+------+ | name | wins | +--------+------+ | George | 3 | | Pat | 9 | | Grady | 0 | | Calvin | 7 | | Rose | 4 | +--------+------+ 5 rows in set (0.00 sec) العامل <: يختبر ما إذا كانت القيمة الأولى أقل من الثانية تمامًا mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins < 1; الخرج +-------+------+ | name | wins | +-------+------+ | Grady | 0 | +-------+------+ 1 row in set (0.00 sec) العامل >: يختبر ما إذا كانت القيمة الأولى أكبر من الثانية تمامًا mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins > 1; الخرج +--------+------+ | name | wins | +--------+------+ | George | 3 | | Pat | 9 | | Calvin | 7 | | Rose | 4 | +--------+------+ 4 rows in set (0.00 sec) العامل <=: يختبر ما إذا كانت القيمة الأولى أقل من أو تساوي الثانية mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins <= 1; الخرج +---------+------+ | name | wins | +---------+------+ | Grady | 0 | | Diane | 1 | | Raymond | 1 | +---------+------+ 3 rows in set (0.00 sec) العامل >=: يختبر ما إذا كانت القيمة الأولى أكبر من أو تساوي القيمة الثانية mysql> SELECT name, wins mysql> FROM golfers mysql> WHERE wins >= 1; الخرج +---------+------+ | name | wins | +---------+------+ | George | 3 | | Pat | 9 | | Diane | 1 | | Calvin | 7 | | Rose | 4 | | Raymond | 1 | +---------+------+ 6 rows in set (0.00 sec) القيم الفارغة تختبر التوابع الشرطية التي تستخدم عامل IS NULL ما إذا كانت القيم في عمود معين فارغة، فإذا كان الأمر كذلك فعلًا، سيقيّم التابع الشرطي على أنه "صحيح"، وبالتالي سيُضمّن السجل في مجموعة النتائج: mysql> SELECT name, rounds_played mysql> FROM golfers mysql> WHERE rounds_played IS NULL; الخرج +--------+---------------+ | name | rounds_played | +--------+---------------+ | Calvin | NULL | | Rose | NULL | +--------+---------------+ 2 rows in set (0.00 sec) النطاق تستخدم التوابع الشرطية النطاقية عامل BETWEEN لاختبار ما إذا كانت قيم العمود محصورة بين تعبيري قيمة آخرين. mysql> SELECT name, best mysql> FROM golfers mysql> WHERE best BETWEEN 67 AND 73; الخرج +---------+------+ | name | best | +---------+------+ | George | 68 | | Diane | 70 | | Rose | 69 | | Raymond | 67 | +---------+------+ 4 rows in set (0.00 sec) الانتماء أو العضوية يستخدم هذا النوع من التوابع الشرطية عامل IN لاختبار ما إذا كانت قيمة ما تنتمي لمجموعة معينة. mysql> SELECT name, best mysql> FROM golfers mysql> WHERE best IN (65, 67, 69, 71); الخرج +---------+------+ | name | best | +---------+------+ | Pat | 65 | | Rose | 69 | | Raymond | 67 | +---------+------+ 3 rows in set (0.00 sec) تطابق الأنماط تستخدم توابع مطابقة الأنماط الشرطية عامل LIKE لاختبار ما إذا كانت قيمة ما تطابق نمطًا نصيًا يحتوي على محرف بدل واحد أو أكثر. وتُعرّف SQL محرفي بدل هما % و _: محرف _: شرطة سفلية تُمثّل محرفًا واحدًا مجهولًا mysql> SELECT name, rounds_played mysql> FROM golfers mysql> WHERE rounds_played LIKE '2_'; الخرج +--------+---------------+ | name | rounds_played | +--------+---------------+ | George | 22 | | Pat | 25 | | Diane | 23 | +--------+---------------+ 3 rows in set (0.00 sec) محرف %: علامة النسبة المئوية تُمثّل صفر أو أكثر من المحارف المجهولة mysql> SELECT name, rounds_played mysql> FROM golfers mysql> WHERE name LIKE 'G%'; الخرج +--------+---------------+ | name | rounds_played | +--------+---------------+ | George | 22 | | Grady | 11 | +--------+---------------+ 2 rows in set (0.00 sec) لن نفصّل بشرح كل نمط من التوابع الشرطية فهو خارج مجال هذا المقال، ولمعرفة المزيد حول هذه الأنماط، ننصحك بالاطلاع على المقالات التالية: كيفية استخدام عوامل المقارنة وIS NULL في لغة الاستعلام البنيوية SQL كيفية استخدام عوامل BETWEEN وIN في لغة الاستعلام البنيوية SQL كيفية استخدام محارف البدل في SQL دمج التوابع الشرطية المتعددة باستخدام عوامل AND وOR قد نضطر في الكثير من الأحيان إلى تصفية النتائج على نحوٍ أدق مما توفره بنية WHERE ذات تابع شرطي وحيد. ومن ناحيةٍ أخرى، قد تكون السجلات التي تحقق واحدًا من شروط بحث متعددة مقبولة ضمن مجموعة النتائج في بعض الأحيان. وفي هذه الحالات، يمكنك كتابة بنى WHERE تتضمن توابع شرطية متعددة باستخدام عوامل AND أو OR على التوالي. للبدء باستخدام هذه العوامل، لنشغّل الاستعلام التالي الذي يعيد قيم من أعمدة name وbest وworst وaverage في جدول golfers. إذ تتضمن بنية WHERE الخاصة بهذا الاستعلام تابعين شرطيين مفصولين بعامل AND: mysql> SELECT name, best, worst, average mysql> FROM golfers mysql> WHERE best < 70 AND worst < 96; يختبر التابع الشرطي الأول ما إذا كانت أفضل قيمة (قيمة العمود best) لكل سجل تقل عن 70، في حين يختبر التابع الشرطي الثاني ما إذا كانت أسوأ قيمة (قيمة العمود worst) لكل سجل تقل عن 96. الآن وفي حال تقييم أي من الشرطين على أنه "خاطئ False" لسجل ما، فإنّ هذا السجل لن يُعاد ضمن مجموعة النتائج: الخرج +---------+------+-------+---------+ | name | best | worst | average | +---------+------+-------+---------+ | Pat | 65 | 74 | 68.7 | | Calvin | 63 | 76 | 68.5 | | Rose | 69 | 84 | 76.7 | | Raymond | 67 | 92 | 81.3 | +---------+------+-------+---------+ 4 rows in set (0.00 sec) الآن، لنشغّل الاستعلام التالي، وهو كما تلاحظ مطابق للمثال السابق، باستثناء فصل التابعين الشرطيين بعامل OR بدلًا من AND: mysql> SELECT name, best, worst, average mysql> FROM golfers mysql> WHERE best < 70 OR worst < 96; نلاحظ أنّ مجموعة النتائج في هذا المثال تتضمّن سجلين أكثر من المثال السابق، وذلك نظرًا لأنّه مع استخدام عامل OR يكفي تقييم تابع شرطي واحد على أنّه "صحيح True" للسجل لكي يُعاد ضمن مجموعة النتائج: الخرج +---------+------+-------+---------+ | name | best | worst | average | +---------+------+-------+---------+ | George | 68 | 103 | 84.6 | | Pat | 65 | 74 | 68.7 | | Diane | 70 | 92 | 78.8 | | Calvin | 63 | 76 | 68.5 | | Rose | 69 | 84 | 76.7 | | Raymond | 67 | 92 | 81.3 | +---------+------+-------+---------+ 6 rows in set (0.00 sec) يمكنك إدراج العديد من التوابع الشرطية في بنية WHERE واحدة بشرط دمجها باستخدام الصياغة المناسبة. ولكن قد يصعب تنبؤ بالبيانات التي ستُصفى مع زيادة تعقيد شروط البحث. من الجدير بالذكر أن أنظمة قواعد البيانات تميل لإعطاء الأولوية لعوامل AND. هذا يعني أن التوابع الشرطية التي تفصل بينها عوامل AND تعدّ كشرط بحث موحد مستقل يُختبر قبل أي توابع شرطية أخرى في بنية WHERE. لتوضيح ذلك، شغّل الاستعلام التالي الذي يسترجع قيمًا من أعمدة name وaverage وworst وrounds_played لكل سجل يستوفي شروط البحث المحددة في بنية WHERE: mysql> SELECT name, average, worst, rounds_played mysql> FROM golfers mysql> WHERE average < 85 OR worst < 95 AND rounds_played BETWEEN 19 AND 23; يبدأ هذا الاستعلام باختبار ما إذا كان كلا التابعين الشرطيين (worst < 95 و rounds_played BETWEEN 19 AND 23) المفصولين بعامل AND يقيمان على أنهما محققان بالنسبة للسجل قيد الاختبار الحالي. فإذا كان الأمر كذلك، سيظهر هذا السجل ضمن مجموعة النتائج. أمّا إذا قُيّم أحدهما أو كلاهما على أنه غير محقق "false"، فسينتقل الاستعلام لفحص ما إذا كانت قيمة average للصف الحالي أقل من 85. وفي حال كانت كذلك، سيظهر الصف ضمن مجموعة النتائج. الخرج +---------+---------+-------+---------------+ | name | average | worst | rounds_played | +---------+---------+-------+---------------+ | George | 84.6 | 103 | 22 | | Pat | 68.7 | 74 | 25 | | Diane | 78.8 | 92 | 23 | | Calvin | 68.5 | 76 | NULL | | Rose | 76.7 | 84 | NULL | | Raymond | 81.3 | 92 | 18 | +---------+---------+-------+---------------+ 6 rows in set (0.00 sec) يمكنك منح الأولوية لمجموعة من تابعين شرطيين فأكثر بوضعهم داخل أقواس. المثال التالي يطابق المثال السابق، لكنه يضع التابعين الشرطيين average < 85 وworst < 95، المفصولين بعامل OR، ضمن أقواس. mysql> SELECT name, average, worst, rounds_played mysql> FROM golfers mysql> WHERE (average < 85 OR worst < 95) AND rounds_played BETWEEN 19 AND 23; نظرًا لأن التابعين الشرطيين في البداية مُحاطين بأقواس، فيعاملهما عامل AND الذي يليهما كشرط بحث مستقل، والذي يجب أن يُقيّم على أنه محقق “True”. فإذا قُيّم كلا التابعين الشرطيين (average < 85 وworst < 95) على أنهما غير محققين “false”، فسيُعتبر شرط البحث بأكمله غير محقق “False” وبالتالي سيستبعد الاستعلام هذا السجل من مجموعة النتائج على الفور قبل أن ينتقل لتقييم السجل التالي. أمّا إذا قُيّم أي من التابعين الشرطيين بين القوسين على أنه محقق “True”، فيبدأ الاستعلام حينها في اختبار ما إذا كانت قيمة rounds_played للاعب الجولف تقع ضمن النطاق 19 و 23. وفي حال تحقق ذلك، يُضم السجل إلى مجموعة النتائج المُعادة. الخرج +--------+---------+-------+---------------+ | name | average | worst | rounds_played | +--------+---------+-------+---------------+ | George | 84.6 | 103 | 22 | | Diane | 78.8 | 92 | 23 | +--------+---------+-------+---------------+ 2 rows in set (0.00 sec) كما تُظهر هذه النتائج، بإعطاء الأولوية لمجموعات التوابع الشرطية وضمها ضمن أقواس، يمكن أن تُنتج الاستعلامات المتشابهة ظاهريًا مجموعات نتائج متباينة بدرجة كبيرة. مع أنه لا يُشترط دومًا اللجوء إلى هذا الأسلوب، إلا أنه يُوصى باستخدام الأقواس عند دمج أكثر من تابعين شرطيين ضمن شرط بحث واحد ليُعزز من مقروئية الاستعلامات ويسهّل فهمها. استثناء النتائج باستخدام عامل NOT ركّزت جميع الأمثلة في هذا المقال حتى الآن على كيفية كتابة استعلامات باستخدام بنية WHERE، والتي تُضمّن فقط السجلات التي تُحقّق شروط البحث المُحددة في مجموعة النتائج. ولكن يُمكنك كتابة استعلامات تستثني سجلات مُحددة عبر إدراج عامل NOT ضمن بنية WHERE. وعادةً ما تتبع بنى التوابع الشرطية للنطاق والعضوية وتطابق الأنماط التي تشمل عامل NOT الصياغة العامة التالية: mysql> . . . mysql> WHERE column_name NOT OPERATOR value_expression mysql> . . . للتوضيح، شغّل الاستعلام التالي الذي سيعيد قيمًا من عمود name في جدول golfers، ولكن وجود عامل NOT في بنية WHERE سيُلزم نظام إدارة قاعدة البيانات باستثناء أي سجلات تُطابق نمط محرف البدل: mysql> SELECT name mysql> FROM golfers mysql> WHERE name NOT LIKE 'R%'; الخرج +--------+ | name | +--------+ | George | | Pat | | Grady | | Diane | | Calvin | +--------+ 5 rows in set (0.00 sec) تتغير الأمور قليلًا عند إضافة عامل NOT إلى توابع IS NULL الشرطية. في هذه الحالات، يُدرج العامل NOT بين IS وNULL كما في المثال التالي. يُعيد هذا الاستعلام قيم name وrounds_played لجميع لاعبي الغولف الذين لا تكون قيمة rounds_played الخاصة بهم فارغة، على النحو: mysql> SELECT name, rounds_played mysql> FROM golfers mysql> WHERE rounds_played IS NOT NULL; الخرج +---------+---------------+ | name | rounds_played | +---------+---------------+ | George | 22 | | Pat | 25 | | Grady | 11 | | Diane | 23 | | Raymond | 18 | +---------+---------------+ 5 rows in set (0.00 sec) كما يُمكنك وضع عامل NOT مباشرةً بعد الكلمة المفتاحية WHERE. الأمر الذي يعدّ مفيدًا لا سيما في حال رغبتك باستثناء سجلات بناءً على ما إذا كانت تحقق عدة شروط بحث، كما في الاستعلام التالي الذي يعيد قيم كل من أعمدة name و average و best و wins للاعبي الغولف. mysql> SELECT name, average, best, wins mysql> FROM golfers mysql> WHERE NOT (average < 80 AND best < 70) OR wins = 9; الخرج +---------+---------+------+------+ | name | average | best | wins | +---------+---------+------+------+ | George | 84.6 | 68 | 3 | | Pat | 68.7 | 65 | 9 | | Grady | 97.6 | 78 | 0 | | Diane | 78.8 | 70 | 1 | | Raymond | 81.3 | 67 | 1 | +---------+---------+------+------+ 5 rows in set (0.00 sec) بالنظر إلى السجل الثاني من مجموعة النتائج أعلاه، نلاحظ أنّ متوسط نتيجة اللاعبة Pat في العمود average أقل من 80 وأفضل نتيجة لها في العمود best أقل من 70. ومع ذلك سجلها مُضمّن في مجموعة النتائج، وذلك نظرًا لأن عامل NOT يُلغي فقط شروط البحث المُحاطة بالأقواس. تذكر أنه عند إحاطة توابع شرطية متعددة مفصولة بعوامل AND أو OR بأقواس، تُعطى الأولوية لهذه التوابع في SQL وتُعامل كشرط بحث مستقل. بالتالي، يستثني عامل NOT السجلات بناءً على أول تابعي شرط فقط (average<80 و best<70). في حين تُضمّن السجلات بناءً على التابع الشرطي الثالث (wins=9). يمكنك إعادة كتابة الاستعلام لاستبعاد السجلات بناءً على التابع الشرطي الثالث أيضًا بالإضافة إلى التابعين الأول والثاني وذلك بإحاطة التوابع الثلاثة ضمن أقواس، كما في المثال التالي: mysql> SELECT name, average, best, wins mysql> FROM golfers mysql> WHERE NOT ((average < 80 AND best < 70) OR wins = 9); الخرج +---------+---------+------+------+ | name | average | best | wins | +---------+---------+------+------+ | George | 84.6 | 68 | 3 | | Grady | 97.6 | 78 | 0 | | Diane | 78.8 | 70 | 1 | | Raymond | 81.3 | 67 | 1 | +---------+---------+------+------+ 4 rows in set (0.00 sec) قد يعدّ نظام قاعدة البيانات لديك صيغة الاستعلام غير صالحة في حال تضمين العامل NOT قبل عامل مقارنة وذلك تبعًا لتقديم SQL المُستخدم. كمثال، لنجرب تشغيل الاستعلام التالي: mysql> SELECT name mysql> FROM golfers mysql> WHERE name NOT = 'Grady'; سيؤدي استخدام هذا الأمر في MySQL أو أي من الأنظمة المستندة إليها إلى حدوث خطأ. الخرج ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '= 'Grady'' at line 1 السبب وراء هذا الخطأ هو أنّ عامل NOT لا يُستخدم عادةً مع عوامل المقارنة (= و<> و< و<= و> و>=)، وذلك نظرًا لإمكانية تحقيق الأثر المعاكس لعامل مقارنة معيّن باستبداله بآخر يعيد السجلات التي كان الأول سيستثنيها. على سبيل المثال، يمكنك استبدال عامل المساواة (=) بعامل عدم المساواة (<>). الخلاصة باطلاعك على هذا المقال،تكون قد اكتسبت المعرفة حول كيفية كتابة بنى WHERE بطريقة تجعل الاستعلام يُعيد فقط السجلات التي تستوفي شرطًا معينًا. كما تعلمت كيفية دمج عدّة توابع شرطية وشروط بحث في استعلام واحد، ناهيك عن كيفية استخدام العامل NOT لاستثناء معلومات من مجموعات النتائج. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة المقالات المنشورة تحت وسم سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use WHERE Clauses in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية الاستعلام عن السجلات من الجداول في SQL ما هي محركات قواعد البيانات؟ أنواع قواعد البيانات وأهم مميزاتها واستخداماتها التعامل مع قواعد البيانات
  10. ينطوي التعامل مع قواعد البيانات بشكلٍ أساسيّ على استخراج المعلومات حول البيانات المُخزّنة ضمنها. ويُطلق مصطلح الاستعلام query على أي عملية لاسترجاع المعلومات من جدول في أنظمة إدارة قواعد البيانات العلاقية. سنبحث في هذا المقال في بنية الاستعلامات بلغة الاستعلام البنيوية SQL وبعض الوظائف والعوامل الأكثر استخدامًا فيها. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، وللقيام بذلك يمكنك الاطلاع على المقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية RDBMS لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على كتابة الاستعلامات. لذا ننصحك بقراءة القسم القادم الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p الآن ومن نافذة سطر الأوامر، أنشئ قاعدة بيانات باسم queries_DB: mysql> CREATE DATABASE queries_DB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات queries_DB، نفّذ تعليمة USE التالية: mysql> USE queries_DB; الخرج Database changed بعد تحديد قاعدة البيانات queries_db، سننشئ عدة جداول داخلها. للمتابعة مع الأمثلة المستخدمة في هذا المقال، تخيل أنك تدير مبادرة لتنظيف المنتزهات العامة في مدينة ما. إذ تضم المبادرة عددًا من المتطوعين الملتزمين بتنظيف منتزه المدينة الأقرب لمنازل كل منهم من خلال جمع القمامة بانتظام. ولدى الانضمام إلى المبادرة، يضع كل متطوع هدفًا لعدد أكياس القمامة التي يود جمعها كل أسبوع. وبفرض أننا قررنا تخزين المعلومات حول أهداف المتطوعين في قاعدة بيانات SQL باستخدام جدول يحتوي على خمسة أعمدة، على النحو: vol_id: مُعرّف كل متطوع، مُعبرًا عنه بنمط بيانات الأعداد الصحيحة int. سيكون هذا العمود هو المفتاح الأساسي للجدول، مما يعني أن كل قيمة منه ستلعب دور المعرّف الفريد لسجلها المقابل. ونظرًا لأن كل قيمة في المفتاح الأساسي يجب أن تكون فريدة، فسيتم تطبيق قيد UNIQUE على هذا العمود أيضًا. name: اسم كل متطوع، مُعبرًا عنه بنمط البيانات varchar بحد أقصى 20 محرفًا. park: اسم المنتزه التي سيجمع فيها كل متطوع القمامة، مُعبرًا عنه بنمط البيانات varchar بحد أقصى 20 محرفًا. لاحظ أنه يمكن لعدة متطوعين تنظيف نفس المنتزه. weekly_goal: هدف كل متطوع لعدد أكياس القمامة التي يرغب في جمعها في الأسبوع، مُعبرًا عنه بنمط البيانات int. max_bags: الرقم القياسي الشخصي لكل متطوع لأكبر عدد من أكياس القمامة التي جمعها في أسبوع واحد، مُعبرًا عنه بنمط البيانات عدد صحيح int. أنشئ جدولًا باسم volunteers يحتوي على هذه الأعمدة الخمسة بتنفيذ تعليمة CREATE TABLE على النحو: mysql> CREATE TABLE volunteers ( mysql> vol_id int UNIQUE, mysql> name varchar(20), mysql> park varchar(30), mysql> weekly_goal int, mysql> max_bags int, mysql> PRIMARY KEY (vol_id) mysql> ); بعد إنشاء جدول volunteers، املأه ببعض البيانات النموذجية. نفّذ تعليمة INSERT INTO التالية لإضافة سبع سجلات تمثل سبعة من متطوعي البرنامج: mysql> INSERT INTO volunteers mysql> VALUES mysql> (1, 'Gladys', 'Prospect Park', 3, 5), mysql> (2, 'Catherine', 'Central Park', 2, 2), mysql> (3, 'Georgeanna', 'Central Park', 2, 1), mysql> (4, 'Wanda', 'Van Cortland Park', 1, 1), mysql> (5, 'Ann', 'Prospect Park', 2, 7), mysql> (6, 'Juanita', 'Riverside Park', 1, 4), mysql> (7, 'Georgia', 'Prospect Park', 1, 3); وبذلك غدوتَ جاهزًا لمتابعة باقي المقال والبدء في تعلم كيفية إنشاء الاستعلامات في لغة الاستعلام البنيوية SQL. مكونات الاستعلام الأساسية: بنيتي SELECT و FROM تُعرّف التعليمة البرمجية في SQL بأنّها أي عملية تُرسَل إلى نظام قاعدة البيانات لتنفيذ مهمّة محددة، من قبيل إنشاء جدول أو إدخال أو حذف بيانات أو تغيير هيكلية عمود أو جدول. وما الاستعلام سوى تعليمة برمجية في SQL تسترجع معلومات حول البيانات المحفوظة في قاعدة البيانات. لن يُغير الاستعلام أي بيانات موجودة في جدول من تلقاء نفسه، إذ سيقتصر الأمر فقط على استرجاع المعلومات حول البيانات التي يطلبها مُنشئ الاستعلام بشكلٍ صريح. ويُشار إلى المعلومات التي يُعيدها استعلام معين بمجموعة نتائجه. والتي عادةً ما تتكون من عمود واحد أو أكثر من جدول محدد، وكل عمود يُعاد في مجموعة النتائج يمكن أن يحتوي على سجل واحد أو أكثر من المعلومات. فيما يلي الصيغة العامة لاستعلام SQL: mysql> SELECT columns_to_return mysql> FROM table_to_query; تتألف تعليمات SQL من بنى مختلفة، والتي تتكون بدورها من كلمات مفتاحية معينة مع المعلومات التي تتطلبها هذه الكلمات. وفي هذا السياق تتطلب الاستعلامات في SQL منك تضمين بنيتين على الأقل، وهما: SELECT و FROM. ملاحظة: كُتبت كل بنية في هذه صيغة هذا المثال في سطرها الخاص. ولكن يمكن كتابة أي تعليمة برمجية في SQL على سطرٍ واحد على النحو التالي: mysql> SELECT columns_to_return FROM table_to_query; يتبع هذا المقال القاعدة الشائعة في أسلوب SQL بتقسيم التعليمات البرمجية على سطور متعددة بحيث يحتوي كل سطر على بنية واحدة فقط. والهدف من ذلك تحسين قابلية القراءة والفهم لكل مثال، لكن ينبغي الإشارة إلى أنه بإمكانك كتابة استعلام على سطر واحد أو توزيعه على عدة سطور، طالما أنه لا يحتوي على أخطاء في الصياغة. تبدأ كل استعلامات SQL عادةً ببنية SELECT، ولذلك يُشار إليها عمومًا بتعليمات SELECT. وتأتي بعد كلمة SELECT المفتاحية قائمة بأسماء الأعمدة التي ترغب في استرجاعها في مجموعة النتائج، والتي يتم اختيارها من الجدول المُحدد في بنية FROM. يبدأ تنفيذ العملية في استعلامات SQL من بنية FROM. الأمر الذي قد يبدو محيرًا نظرًا لأن بنية SELECT تُكتب قبل FROM، ولكن من الضروري أن يحدد نظام إدارة قواعد البيانات أولًا مجموعة البيانات الكاملة التي سيتم الاستعلام عنها قبل البدء في استرجاع المعلومات منها. يُفيد التفكير في الاستعلامات على أنها عملية اختيار SELECT لأعمدة محددة من FROM جدول مُعين. ومن المهم التذكير بأن كل تعليمة SQL يجب أن تختتم بفاصلة منقوطة (;). كمثال، نفذ الاستعلام التالي الذي يسترجع عمود name من جدول volunteers: mysql> SELECT name mysql> FROM volunteers; إليك مجموعة نتائج هذا الاستعلام: الخرج +------------+ | name | +------------+ | Gladys | | Catherine | | Georgeanna | | Wanda | | Ann | | Juanita | | Georgia | +------------+ 7 rows in set (0.00 sec) على الرغم من أن هذه العملية استعرضت جدول volunteers بأكمله، إلا أنها استرجعت فقط العمود المُحدد، ألا وهو name. يمكنك استرجاع المعلومات من عدة أعمدة عن طريق فصل اسماء الأعمدة بعلامات فاصلة، كما في الاستعلام التالي. إذ سيعيد هذا الاستعلام أعمدة vol_id, name, و park من جدول volunteers: mysql> SELECT park, name, vol_id mysql> FROM volunteers; الخرج +-------------------+------------+--------+ | park | name | vol_id | +-------------------+------------+--------+ | Prospect Park | Gladys | 1 | | Central Park | Catherine | 2 | | Central Park | Georgeanna | 3 | | Van Cortland Park | Wanda | 4 | | Prospect Park | Ann | 5 | | Riverside Park | Juanita | 6 | | Prospect Park | Georgia | 7 | +-------------------+------------+--------+ 7 rows in set (0.00 sec) لاحظ أنّ هذه النتيجة تُعيد العمود park أولًا، يليه عمود name ثم vol_id. تُعيد قواعد البيانات SQL الأعمدة عمومًا بالترتيب الذي تم سرده في بنية SELECT. قد تحتاج في بعض الأحيان إلى استرجاع كل الأعمدة من جدول ما. فبدلًا من كتابة أسماء كافّة الأعمدة في استعلامك، يمكنك ببساطة إدخال علامة النجمة (*). وهي الطريقة المختصرة في SQL، للدلالة على "كل الأعمدة". الاستعلام التالي سيُعيد كل الأعمدة من جدول volunteers: mysql> SELECT * mysql> FROM volunteers; الخرج +--------+------------+-------------------+-------------+----------+ | vol_id | name | park | weekly_goal | max_bags | +--------+------------+-------------------+-------------+----------+ | 1 | Gladys | Prospect Park | 3 | 5 | | 2 | Catherine | Central Park | 2 | 2 | | 3 | Georgeanna | Central Park | 2 | 1 | | 4 | Wanda | Van Cortland Park | 1 | 1 | | 5 | Ann | Prospect Park | 2 | 7 | | 6 | Juanita | Riverside Park | 1 | 4 | | 7 | Georgia | Prospect Park | 1 | 3 | +--------+------------+-------------------+-------------+----------+ 7 rows in set (0.00 sec) لاحظ كيف تُعرَض الأعمدة في هذه النتيجة بنفس الترتيب المُحدّد في تعليمة CREATE TABLE من فقرة الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية السابقة. وهي الطريقة التي ستُرتب بها معظم أنظمة قواعد البيانات العلاقية الأعمدة في مجموعة النتائج عند تنفيذ استعلام يستخدم علامة النجمة (*) بدلًا من أسماء الأعمدة الفردية. ومن الجدير بالملاحظة أنّه من الممكن استرجاع المعلومات من عدة جداول في نفس الاستعلام باستخدام الكلمة المفتاحية JOIN. ونشجعك في هذا الصدد على قراءة مقال استخدام عمليات الدمج في SQL. استبعاد القيم المكررة باستخدام DISTINCT تُعيد أنظمة إدارة قواعد البيانات العلاقية افتراضيًا جميع القيم من أعمدة الجدول المُستعلم عنه، بما في ذلك القيم المكررة. كمثال، نفّذ الاستعلام التالي. إذ سيُرجع القيم من عمود park في جدول volunteers: mysql> SELECT park mysql> FROM volunteers; الخرج +-------------------+ | park | +-------------------+ | Prospect Park | | Central Park | | Central Park | | Van Cortland Park | | Prospect Park | | Riverside Park | | Prospect Park | +-------------------+ 7 rows in set (0.00 sec) لاحظ أن مجموعة النتائج تتضمّن قيمتين مكررتين هما Prospect Park وCentral Park. وهو أمر منطقي، إذ يمكن لعدد من المتطوعين التعاون في تنظيف نفس المتنزه. ومع ذلك، قد ترغب في بعض الأحيان بمعرفة القيم الفريدة المخزنة في عمود ما ويمكنك تحقيق ذلك وإزالة القيم المكررة من نتائج استعلامك باستخدام الكلمة المفتاحية DISTINCT بعد SELECT. سيُرجع الاستعلام التالي كافة القيم الفريدة من عمود park، مُستبعدًا أية قيم مكررة. وهو مُتشابه للاستعلام السابق ولكن مع إضافة كلمة DISTINCT، كما يلي: mysql> SELECT DISTINCT park mysql> FROM volunteers; الخرج +-------------------+ | park | +-------------------+ | Prospect Park | | Central Park | | Van Cortland Park | | Riverside Park | +-------------------+ 4 rows in set (0.00 sec) تحتوي مجموعة نتائج هذا الاستعلام على ثلاث سجلات أقل من الاستعلام السابق، بسبب إزالة إحدى قيم Central Park واثنتين من قيم Prospect Park. تجدر الإشارة إلى أن SQL تُعامل كل سجل في مجموعة النتائج كسجل فردي، وتُزيل DISTINCT القيم المكررة فقط في حال تشاركت عدة سجلات بنفس القيم في كل الأعمدة. لتوضيح ذلك، لنُصدر الاستعلام التالي المُتضمّن للكلمة المفتاحية DISTINCT والذي يُرجع كلا العمودين name وpark: mysql> SELECT DISTINCT name, park mysql> FROM volunteers; الخرج +------------+-------------------+ | name | park | +------------+-------------------+ | Gladys | Prospect Park | | Catherine | Central Park | | Georgeanna | Central Park | | Wanda | Van Cortland Park | | Ann | Prospect Park | | Juanita | Riverside Park | | Georgia | Prospect Park | +------------+-------------------+ 7 rows in set (0.00 sec) تشتمل مجموعة النتائج هذه على قيم مكررة في عمود park، بواقع ثلاث تكرارات لقيم Prospect Park وتكرارين لقيم Central Park، وذلك على الرغم من إدراج الكلمة المفتاحية DISTINCT في الاستعلام. فعلى الرغم من احتواء الأعمدة الفردية في مجموعة النتائج على قيم مكررة، لكن يجب أن تكون السجلات متطابقة بالكامل لتُستبعد من قبل DISTINCT. وفي حالتنا، ونظرًا لأن القيم في عمود name فريدة لكل سجل، فإن DISTINCT لن تزيل أي من السجلات لدى تحديد هذا العمود ضمن بنية SELECT. تصفية البيانات باستخدام بنى WHERE قد ترغب في بعض الحالات باسترجاع معلومات أكثر تحديدًا من جداول قاعدة البيانات. إذ يمكنك تصفية سجلات معينة بإدراج بنية WHERE في استعلامك بعد بنية FROM، كما في المثال التالي: mysql> SELECT columns_to_return mysql> FROM table_to_query mysql> WHERE search_condition; يأتي شرط البحث بعد كلمة WHERE في الصيغة المذكورة بالمثال، والذي يُحدّد على وجه الخصوص أي السجلات مُراد تصفيتها من مجموعة النتائج. وما شرط البحث سوى مجموعة من التوابع الشرطية أو التعبيرات القادرة على تقييم تعبير قيمة واحد أو أكثر. إذ يُعرّف تعبير القيمة في لغة SQL - والذي يُشار إليه أحيانًا باسم التعبير ذو القيمة المفردة - بأنّه أي تعبير يُعيد قيمة واحدة. ويمكن أن يكون تعبير القيمة عبارة عن قيمة محددة النوع (سلسلة نصية أو قيمة عددية)، أو تعبير رياضي، أو اسم عمود. يمكن أن تأخذ التوابع الشرطية في شرط البحث الخاص ببنية WHERE أشكالاً مختلفة، ولكنها عادةً ما تتبع الصيغة التالية: WHERE value expression OPERATOR value_expression فقد وضعنا بعد الكلمة المفتاحية WHERE في هذه الصيغة تعبير قيمة، يليه أحد معاملات SQL الخاصة والمُستخدمة لتقييم قيم الأعمدة بالنسبة لتعبير القيمة (أو تعابير القيم) الآتي بعد العامل. وهناك العديد من هذه العوامل المتاحة في SQL، وسنقدّم في هذا القسم لمحة موجزة عن بعضها، ولكن لغرض التوضيح سنركّز فقط على أحد أكثر العوامل استخدامًا، ألا وهو: علامة المساواة (=). يختبر هذا العامل ما إذا كان تعبيرين يحتويان على قيم متطابقة. دائمًا ما تُعطي التوابع الشرطية الناتج "صحيح True" أو "خاطئ False" أو "غير محدد Unknown". فلدى تشغيل استعلامات SQL التي تحتوي على بنية WHERE، سيطبّق نظام إدارة قاعدة البيانات شرط البحث تتابعيًا على كل سجل في الجدول المحدد في بنية FROM. ولن يعيد سوى السجلات التي يُقيّم فيها كل شرط بحث على أنه "صحيح". لتوضيح هذه الفكرة، نفّذ تعليمة SELECT التالية. يُرجع هذا الاستعلام قيمًا من عمود name في جدول volunteers. لكن بدلًا من تقييم قيم أحد أعمدة الجدول، تختبر بنية WHERE في هذا الاستعلام ما إذا كان التعبير (2 + 2) و 4 متساويين. mysql> SELECT name mysql> FROM volunteers mysql> WHERE (2+2) = 4; ونظرًا لأنّ التعبير (2+2) يساوي 4 دائمًا، فإن شرط البحث هذا يُقيّم بأنه صحيح لكل سجل في الجدول. ونتيجةً لذلك، تُرجع قيمة الاسم لكل سجل في مجموعة النتائج، على النحو: الخرج +------------+ | name | +------------+ | Gladys | | Catherine | | Georgeanna | | Wanda | | Ann | | Juanita | | Georgia | +------------+ 7 rows in set (0.00 sec) ولكن ونظرًا لأن شرط البحث هذا يعيد دائمًا نتيجة "صحيحة"، فهو ليس مفيدًا بدرجة كبيرة. وبالتالي من الممكن في هذه الحالة عدم تضمين بنية WHERE أصلًا، لأن تعليمة SELECT name FROM volunteers; ستعطي نفس مجموعة النتائج كما في حالة استخدام WHERE مع شرط بحث مُحقق دومًا. عادةً ما نستخدم اسم عمود كأحد تعبيري القيمة في شرط البحث ضمن بنية WHERE وذلك بدلاً من مقارنة قيمتين مُحددتي النوع كما في الأسلوب أعلاه. وبذلك نُعلم نظام إدارة قاعدة البيانات باستخدام قيمة كل سجل من العمود المحدد كجزء من تعبير القيمة للتكرار الخاص بكل سجل في شرط البحث. تُطبّق بنية WHERE في الاستعلام التالي شرط بحث أكثر تحديدًا على كل سجل. إذ ستُرجع قيم كل من عمودي name و max_bags من أي سجل تساوي فيه قيمة max_bags الرقم 4: mysql> SELECT name, max_bags mysql> FROM volunteers mysql> WHERE max_bags = 4; يُرجع هذا الاستعلام سجل متطوع واحد فقط، نظرًا لأنّ قيمة max_bags لهذا المتطوع وحده تساوي 4 تمامًا، فيكون الخرج على النحو: الخرج +---------+----------+ | name | max_bags | +---------+----------+ | Juanita | 4 | +---------+----------+ 1 row in set (0.00 sec) يمكنك أيضًا تقييم قيم السلاسل النصية المحرفية في التوابع الشرطية الخاصة بشروط البحث. فمثلًا يُرجع الاستعلام التالي قيم عمودي vol_id و name لكل سجل تكون قيمة العمود name فيه مساوية لـ 'Wanda': mysql> SELECT vol_id, name mysql> FROM volunteers mysql> WHERE name = 'Wanda'; ونظرًا لوجود متطوعة واحدة فقط تُدعى Wanda، فإن الاستعلام يُرجع معلومات سجلها فقط: الخرج +--------+-------+ | vol_id | name | +--------+-------+ | 4 | Wanda | +--------+-------+ 1 row in set (0.00 sec) من الجدير بالملاحظة أنّنا استخدمنا نفس العامل لشرط البحث في جميع أمثلة هذا القسم وهو علامة المساواة وذلك لتوضيح الفكرة. ولكن يوجد العديد من أنواع المعاملات Operators الأخرى التي تتيح لنا إمكانية كتابة مجموعة متنوعة من التوابع الشرطية، مما يوفر مستوى عالٍ من التحكم في المعلومات التي تُرجعها الاستعلامات. يُحدّد معيار SQL ثمانية عشر نمطًا مختلفًا من التوابع الشرطية، وعلى الرغم من أنّها لا تتوفر كاملةً في كافّة أنظمة إدارة قواعد البيانات العلاقية RDBMS. إليك خمسة من أكثر أنواع توابع الشرط شيوعًا في شروط البحث والعوامل المستخدمة في كل منها: المقارنة: تقارن التوابع الشرطية المقارنِة بين تعبيري قيمة، وفي معظم الاستعلامات يكون أحد هذين التعبيرين هو اسم عمود. وعوامل المقارنة الستة هي: = يختبر ما إذا كانت القيمتان متساويتين. <> يختبر ما إذا كانت القيمتان غير متساويتين. < يختبر ما إذا كانت القيمة الأولى أقل من الثانية. > يختبر ما إذا كانت القيمة الأولى أكبر من الثانية. <= يختبر ما إذا كانت القيمة الأولى أقل من أو تساوي الثانية. >= يختبر ما إذا كانت القيمة الأولى أكبر من أو تساوي الثانية. القيم الفارغة: تختبر التوابع الشرطية التي تستخدم عامل IS NULL ما إذا كانت القيم في عمود معين فارغة. النطاق: تستخدم التوابع الشرطية النطاقية عامل BETWEEN لاختبار ما إذا كان تعبير قيمة ما يقع بين تعبيري قيمة آخرين. العضوية: يستخدم هذا النوع من التوابع الشرطية عامل IN لاختبار ما إذا كانت قيمة ما تُمثّل عضوًا في مجموعة معينة. تطابق الأنماط: تستخدم توابع مطابقة الأنماط الشرطية عامل LIKE لاختبار ما إذا كانت قيمة ما تطابق نمطًا نصيًا يحتوي على محارف بدل. لمعرفة المزيد حول هذه الأنواع من التوابع الشرطية، ننصحك بالاطلاع على المقالات التالية: كيفية استخدام عوامل المقارنة و IS NULL في لغة الاستعلام البنيوية SQL كيفية استخدام عوامل BETWEEN و IN في لغة الاستعلام البنيوية SQL كيفية استخدام محارف البدل في SQL وللاطلاع على المزيد حول بنى WHERE عمومًا، ننصحك بقراءة مقالنا حول كيفية استخدام بنى WHERE في لغة الاستعلام البنيوية SQL. فرز نتائج الاستعلام باستخدام بنية ORDER BY قد تُعيد الاستعلامات في بعض الأحيان المعلومات بطرق غير واضحة أو غير متوافقة تمامًا مع احتياجاتك. لذا يمكنك فرز أو ترتيب نتائج الاستعلام بتضمين بنية ORDER BY في نهاية تعليمة الاستعلام. فيما يلي الصيغة العامّة لاستعلام يتضمن بنية ORDER BY: mysql> SELECT columns_to_return mysql> FROM table_to_query mysql> ORDER BY column_name; لتوضيح الأمر، بفرض أننا نرغب في معرفة أي من المتطوعين يحقق أعلى قيمة في عمود max_bags. لنُنفّذ الاستعلام التالي الذي يُعيد قيم عمودي name وmax_bags من جدول volunteers: $ SELECT name, max_bags $ FROM volunteers; ولكن يفرز هذا الاستعلام مجموعة النتائج حسب الترتيب الذي أُضيف فيه كل سجل. الخرج +------------+----------+ | name | max_bags | +------------+----------+ | Gladys | 5 | | Catherine | 2 | | Georgeanna | 1 | | Wanda | 1 | | Ann | 7 | | Juanita | 4 | | Georgia | 3 | +------------+----------+ 7 rows in set (0.00 sec) ولعلّ فرز مجموعة بيانات صغيرة نسبيًا كهذه ليس بتلك الأهمية، إذ يمكننا ببساطة مراجعة قيم max_bags في مجموعة النتائج لإيجاد القيمة الأعلى لكن هذا الأمر قد يصبح متعبًا عند العمل مع كميات أكبر من البيانات. يمكننا تنفيذ الاستعلام ذاته ولكن مع إضافة بنية ORDER BY التي تفرز مجموعة النتائج تبعًا لقيم max_bags للسجلات: $ SELECT name, max_bags $ FROM volunteers $ ORDER BY max_bags; الخرج +------------+----------+ | name | max_bags | +------------+----------+ | Georgeanna | 1 | | Wanda | 1 | | Catherine | 2 | | Georgia | 3 | | Juanita | 4 | | Gladys | 5 | | Ann | 7 | +------------+----------+ 7 rows in set (0.00 sec) كما يُظهر هذا الخرج، فالسلوك الافتراضي لاستعلامات SQL التي تتضمن بنية ORDER BY هو فرز قيم العمود المحدد تصاعديًا (من الأصغر إلى الأكبر). ويمكننا تغيير هذا السلوك وفرزها بترتيب تنازلي عن طريق إضافة الكلمة المفتاحية DESC إلى بنية ORDER BY. $ SELECT name, max_bags $ FROM volunteers $ ORDER BY max_bags DESC; الخرج +------------+----------+ | name | max_bags | +------------+----------+ | Ann | 7 | | Gladys | 5 | | Juanita | 4 | | Georgia | 3 | | Catherine | 2 | | Georgeanna | 1 | | Wanda | 1 | +------------+----------+ 7 rows in set (0.00 sec) الخلاصة باطلاعك على هذا المقال، اكتسبت المعرفة حول كيفية كتابة استعلامات أساسية، وكيفية تصفية وفرز مجموعات نتائج الاستعلام، ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL، لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكل أمر فيها ومجموعة خياراته الكاملة. وللمزيد حول SQL، نشجعك على متابعة المقالات المنشورة تحت وسم سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To SELECT Rows FROM Tables in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية حذف البيانات في لغة الاستعلام البنيوية SQL أنواع قواعد البيانات وأهم مميزاتها واستخداماتها تعرف على مكونات قاعدة البيانات ما هي محركات قواعد البيانات؟ فهم قواعد البيانات العلاقية
  11. سنتعرف في مقال اليوم على تعليمة DELETE في لغة الاستعلام البنيوية SQL واحدة من أقوى العمليات المتاحة للمستخدمين. وهي كما يوحي اسمها، تحذف سجلًا أو أكثر من جدول قاعدة البيانات على نحوٍ لا يمكن التراجع عنه. ومن المهم لمستخدمي SQL فهم كيف تعمل تعليمة DELETE باعتبارها جانبًا أساسيًا من إدارة البيانات. سيغطي المقال كيفية استخدام صيغة DELETE في SQL لحذف البيانات من جدول واحد أو أكثر. كما سيشرح كيف تتعامل SQL مع عمليات الحذف التي قد تتعارض مع قيود المفتاح الخارجي Foreign key. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز حاسوب يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، ويمكنك إعداد الخادم بالاستعانة بمقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على حذف البيانات. لذا ننصحك بقراءة الفقرة التالية الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسمdeleteDB: mysql> CREATE DATABASE deleteDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات deleteDB، نفّذ تعليمة USE التالية: mysql> USE deleteDB; الخرج Database changed بعد اختيار قاعدة البيانات deleteDB، أنشئ جدولين داخلها. كمثال، تخيل أنك وبعض أصدقائك تملكون ناديًا حيث يمكن للأعضاء مشاركة المعدات الموسيقية مع بعضهم البعض. فقررت إنشاء بعض الجداول لتساعدك في تتبع أعضاء النادي ومعداتهم. سيتضمّن الجدول الأول الأعمدة الأربعة التالية: memberID: مُعرّف كل عضو في النادي، مُعبّر عنه بنمط بيانات الأعداد الصحيحة int. سيكون هذا العمود هو المفتاح الأساسي للجدول. name: اسم كل عضو، مُعبّر عنه بنمط بيانات المحارف varchar مع حد أقصى يصل إلى 30 محرفًا. homeBorough: هذا العمود سيخزن المنطقة التي يعيش فيها كل عضو، مُعبر عنه بنمط البيانات varchar ولكن بحد أقصى يصل إلى 15 محرفًا فقط. email: عنوان البريد الإلكتروني الذي يمكن من خلاله الاتصال بكل عضو، مُعبر عنه بنمط البيانات varchar مع حد أقصى يصل إلى 30 محرفًا. لننشئ إذًا جدولًا باسم clubMembers يحتوي على هذه الأعمدة الأربعة. mysql> CREATE TABLE clubMembers ( mysql> memberID int PRIMARY KEY, mysql> name varchar(30), mysql> homeBorough varchar(15), mysql> email varchar(30) mysql> ); أمّا الجدول الثاني فسيتضمّن الأعمدة التالية: equipmentID: معرّف فريد لكل قطعة من المعدات. إذ ستكون القيم في هذا العمود من نمط بيانات الأعداد الصحيحة int. وسيكون هذا العمود هو المفتاح الأساسي للجدول على نحوٍ مشابه لعمود memberID في جدول clubMembers، equipmentType: نوع الآلة أو الأداة التي يُمثّلها كل سجل (على سبيل المثال الغيتار guitar ومازج الأصوات mixer ومضخّم الصوتamplifier وما إلى ذلك). سنعبّر عن هذه القيم باستخدام نمط البيانات varchar مع حد أقصى يصل إلى 30 محرفًا. brand: العلامة التجارية التي أنتجت كل قطعة من المعدات، معبّر عنها أيضًا بنمط البيانات varchar مع حد أقصى يصل إلى 30 محرفًا. ownerID: هذا العمود سيحتوي على معرّف العضو المالك للقطعة نت المعدات، مُعبر عنه برقم صحيح. ولضمان أنّ عمود ownerID لن يتضمّن سوى قيم تُمثّل مُعرّفات أعضاء صالحة، يمكنك إنشاء قيد مفتاح خارجي عليه بحيث يُشير إلى عمود memberID في جدول clubMembers. يُعد قيد المفتاح الخارجي طريقة لتحديد علاقة بين جدولين، إذ يفرض أن تكون القيم في العمود المُطبّق عليه موجودة بالفعل في العمود المُشار إليه. في المثال التالي، يشترط قيد المفتاح الخارجي أن تكون كل قيمة تُضاف إلى عمود ownerID موجودة مسبقًا في عمود memberID. أنشئ جدولًا بهذه الأعمدة وهذا القيد باسم clubEquipment: mysql> CREATE TABLE clubEquipment ( mysql> equipmentID int PRIMARY KEY, mysql> equipmentType varchar(30), mysql> brand varchar(15), mysql> ownerID int, mysql> CONSTRAINT fk_ownerID mysql> FOREIGN KEY (ownerID) REFERENCES clubMembers(memberID) mysql> ); ومن الجدير بالملاحظة أنّ هذا المثال يوفّر اسمًا لقيد المفتاح الخارجي، ألا وهو: fk_ownerID. إذ تُنشئ MySQL تلقائيًا اسمًا لأي قيد تضيفه، إلّا أنّ تحديد اسم من قبلنا في هذه الحالة سيكون مفيدًا عندما نحتاج للإشارة إلى هذا القيد لاحقًا. بعد ذلك، نفّذ تعليمة INSERT INTO التالية لملء جدول clubMembers بستة سجلات من البيانات النموذجية: mysql> INSERT INTO clubMembers mysql> VALUES mysql> (1, 'Rosetta', 'Manhattan', 'hightower@example.com'), mysql> (2, 'Linda', 'Staten Island', 'lyndell@example.com'), mysql> (3, 'Labi', 'Brooklyn', 'siffre@example.com'), mysql> (4, 'Bettye', 'Queens', 'lavette@example.com'), mysql> (5, 'Phoebe', 'Bronx', 'snow@example.com'), mysql> (6, 'Mariya', 'Brooklyn', 'takeuchi@example.com'); ثم نفّذ تعليمة INSERT INTO أخرى لملء جدول clubEquipment بعشرين سجلًا من البيانات النموذجية: mysql> INSERT INTO clubEquipment mysql> VALUES mysql> (1, 'electric guitar', 'Gilled', 6), mysql> (2, 'trumpet', 'Yemehe', 5), mysql> (3, 'drum kit', 'Purl', 3), mysql> (4, 'mixer', 'Bearinger', 3), mysql> (5, 'microphone', 'Sure', 1), mysql> (6, 'bass guitar', 'Fandar', 4), mysql> (7, 'acoustic guitar', 'Marten', 6), mysql> (8, 'synthesizer', 'Korgi', 4), mysql> (9, 'guitar amplifier', 'Vax', 4), mysql> (10, 'keytar', 'Poland', 3), mysql> (11, 'acoustic/electric bass', 'Pepiphone', 2), mysql> (12, 'trombone', 'Cann', 2), mysql> (13, 'mandolin', 'Rouge', 1), mysql> (14, 'electric guitar', 'Vax', 6), mysql> (15, 'accordion', 'Nonher', 5), mysql> (16, 'electric organ', 'Spammond', 1), mysql> (17, 'bass guitar', 'Peabey', 1), mysql> (18, 'guitar amplifier', 'Fandar', 3), mysql> (19, 'cello', 'Yemehe', 2), mysql> (20, 'PA system', 'Mockville', 5); وبذلك، غدوتَ جاهزًا لمتابعة باقي المقال وبدء التعلم حول كيفية حذف البيانات باستخدام لغة الاستعلام البنيوية SQL. حذف البيانات من جدول واحد إنّ الصيغة العامة لحذف البيانات في SQL على النحو: mysql> DELETE FROM table_name mysql> WHERE conditions_apply; تحذير: الجزء المهم من هذه الصيغة هو البنية WHERE فمن خلالها يمكنك تحديد السجلات التي يجب حذفها بدقة. وبدونها، سيُنفَّذ أمر مثل DELETE FROM table_name; على نحوٍ صحيح، ولكنه سيحذف كل سجلات البيانات من الجدول! وبالعودة إلى موضوعنا، تذكّر أنّ أي عملية حذف ناجحة لا يمكن التراجع عنها. فإذا نفذّت عملية حذف دون معرفة دقيقة بالبيانات التي ستُحذف، فقد تحذف سجلات عن طريق الخطأ. ومن الطرق التي تفيدك في التأكد من عدم حذف بيانات عن طريق الخطأ هي إجراء استعلام عن البيانات المحددة للحذف باستخدام SELECT لرؤية البيانات التي ستعيدها بنية WHERE ضمن عملية الحذف DELETE من البداية قبل تنفيذ عملية الحذف. ولتوضيح الأمر، لنفترض أنّك تريد إزالة أي سجلات مُتعلقة بمعدات الموسيقى المصنعة من قبل العلامة التجارية Korgi. ولكنك قررت البدء بكتابة استعلام لرؤية كافة سجلات الآلات المُتضمنة لعلامة korgi تحديدًا في عمود brand. لمعرفة الآلات الموسيقية في جدولك المُصنّعة من قبل Korgi، يمكنك تنفيذ الاستعلام المبيّن أدناه. ومن الجدير بالملاحظة أنّه وعلى العكس من الاستعلام باستخدام SELECT أو عملية INSERT INTO، فلا تسمح عمليات الحذف بتحديد أعمدة فردية، لأنها مُخصصّة لحذف سجلات البيانات بالكامل. ولمحاكاة هذا السلوك، أتبعنا الكلمة المفتاحية SELECT بعلامة النجمة (*) في الاستعلام التالي والتي تعدّ اختصارًا في SQL يمثل "كل الأعمدة": mysql> SELECT * FROM clubEquipment mysql> WHERE brand = 'Korgi'; الخرج +-------------+---------------+-------+---------+ | equipmentID | equipmentType | brand | ownerID | +-------------+---------------+-------+---------+ | 8 | synthesizer | Korgi | 4 | +-------------+---------------+-------+---------+ 1 row in set (0.00 sec) ولحذف هذا السجل، عليك تنفيذ عملية حذف DELETE تحتوي على بنيتي FROM وWHERE مطابقتين لاستعلام SELECT السابق، على النحو: mysql> DELETE FROM clubEquipment mysql> WHERE brand = 'Korgi'; الخرج Query OK, 1 row affected (0.01 sec) يُظهر هذا الخرج أن عملية الحذف DELETE قد نُفِّذت على سجل واحد فقط. ولكن بإمكانك حذف عدة سجلات باستخدام بنية WHERE تُعيد أكثر من سجل واحد. أمّا استعلام SELECT التالي فيعيد كافّة السجلات في جدول clubEquipment التي يتضمن عمود equipmentType فيها كلمة electric: mysql> SELECT * FROM clubEquipment mysql> WHERE equipmentType LIKE '%electric%'; الخرج +-------------+------------------------+-----------+---------+ | equipmentID | equipmentType | brand | ownerID | +-------------+------------------------+-----------+---------+ | 1 | electric guitar | Gilled | 6 | | 11 | acoustic/electric bass | Pepiphone | 2 | | 14 | electric guitar | Vax | 6 | | 16 | electric organ | Spammond | 1 | +-------------+------------------------+-----------+---------+ 4 rows in set (0.00 sec) ولحذف هذه السجلات الأربعة، لنعد كتابة الاستعلام السابق مستبدلين SELECT * بتعليمة DELETE: mysql> DELETE FROM clubEquipment mysql> WHERE equipmentType LIKE '%electric%'; الخرج Query OK, 4 rows affected (0.00 sec) كما من الممكن استخدام الاستعلامات الفرعية Subqueries لإعادة وحذف مجموعات نتائج أكثر تفصيلاً. ويُعرّف الاستعلام الفرعي بأنّه عملية استعلام كاملة - تعليمة SQL تبدأ بـ SELECT وتتضمن بنية FROM- مُضمنة ضمن عملية أخرى، تأتي عقب بنية FROM الخاصة بالعملية المحيطة (الاستعلام الرئيسي). على سبيل المثال، لنفترض أنك ترغب في حذف أي معدات مُدرجة في جدول clubEquipment تخص أي عضو يبدأ اسمه بالحرف "L". يمكنك أولاً الاستعلام عن هذه البيانات باستخدام تعليمة SELECT كالتالي: mysql> SELECT * mysql> FROM clubEquipment mysql> WHERE ownerID IN mysql> (SELECT memberID FROM clubMembers mysql> WHERE name LIKE 'L%'); تُعيد هذه العملية كل سجل من جدول clubEquipment تظهر قيمة عمود ownerID الخاصة به ضمن القيم المُعادة من الاستعلام الفرعي الذي يبدأ في السطر الرابع. إذ يُعيد هذا الاستعلام الفرعي مُعرّف العضو memberID لأي سجل تبدأ قيمة العمود name الموافقة له بالحرف "L": الخرج +-------------+------------------+-----------+---------+ | equipmentID | equipmentType | brand | ownerID | +-------------+------------------+-----------+---------+ | 12 | trombone | Cann | 2 | | 19 | cello | Yemehe | 2 | | 3 | drum kit | Purl | 3 | | 4 | mixer | Bearinger | 3 | | 10 | keytar | Poland | 3 | | 18 | guitar amplifier | Fandar | 3 | +-------------+------------------+-----------+---------+ 6 rows in set (0.00 sec) يمكنك بعد ذلك حذف هذه البيانات باستخدام تعليمة DELETE التالية: mysql> DELETE FROM clubEquipment mysql> WHERE ownerID IN mysql> (SELECT memberID FROM clubMembers mysql> WHERE name LIKE 'L%'); الخرج Query OK, 6 rows affected (0.01 sec) حذف البيانات من عدة جداول يمكنك حذف البيانات من أكثر من جدول في عملية واحدة عن طريق تضمين بنية JOIN. تُستخدم بنى JOIN لدمج السجلات من جدولين أو أكثر في نتيجة استعلام واحد. يتم ذلك عن طريق إيجاد عمود مشترك بين الجداول وفرز النتائج على نحوٍ مناسب في الخرج. تبدو صيغة عملية الحذف التي تتضمن بنية JOIN على النحو التالي: mysql> DELETE table_1, table_2 mysql> FROM table_1 JOIN table_2 mysql> ON table_2.related_column = table_1.related_column mysql> WHERE conditions_apply; لاحظ أنه نظرًا لقدرة صيغ الدمج JOIN على مقارنة البيانات من عدة جداول، فإن صيغة هذا المثال تُوضّح الجدول المُستهدف لكل عمود بوضع اسم الجدول متبوعًا بنقطة قبل اسم العمود، وهذا ما يُعرف بالإشارة الكاملة والمؤهلة للعمود. ويُمكنك تحديد الجدول المصدر لكل عمود بهذه الطريقة في أي عملية، على الرغم من عدم ضرورتها عند الاختيار من جدول واحد فقط كما فعلنا في الأمثلة السابقة. لتوضيح مفهوم حذف البيانات باستخدام بنية JOIN، لنفترض أن ناديك قرر تحديد العلامات التجارية للمعدات الموسيقية التي يمكن للأعضاء مشاركتها. نفّذ الأمر التالي لإنشاء جدول باسم prohibitedBrands حيث ستسرد العلامات التجارية التي لم تعد مقبولة في النادي. يحتوي هذا الجدول على عمودين فقط، كلاهما يستخدم نمط بيانات varchar، لتخزين اسم كل علامة تجارية والبلد الذي تعمل فيه: mysql> CREATE TABLE prohibitedBrands ( mysql> brandName varchar(30), mysql> homeCountry varchar(30) mysql> ); ثم املأ هذا الجدول الجديد ببعض البيانات النموذجية التجريبية: mysql> INSERT INTO prohibitedBrands mysql> VALUES mysql> ('Fandar', 'USA'), mysql> ('Givson', 'USA'), mysql> ('Muug', 'USA'), mysql> ('Peabey', 'USA'), mysql> ('Yemehe', 'Japan'); بعد ذلك، يقرر النادي حذف أي سجلات للمعدات من جدول clubEquipment التي تظهر علاماتها التجارية في جدول prohibitedBrands ومقرها في الولايات المتحدة. يمكنك الاستعلام عن هذه البيانات بتنفيذ عملية استعلام مثل التالية باستخدام SELECT. إذ تدمج هذه العملية جدولي clubEquipment وprohibitedBrands معًا، وتُرجع فقط السجلات التي تشترك فيها أعمدة brand وbrandName في نفس القيمة. وتعمل بنية WHERE على إتمام تحديد نتائج الاستعلام بإقصاء أي علامة تجارية لا تتضمن القيمة "USA" في عمود homeCountry: mysql> SELECT * mysql> FROM clubEquipment JOIN prohibitedBrands mysql> ON clubEquipment.brand = prohibitedBrands.brandName mysql> WHERE homeCountry = 'USA'; الخرج +-------------+---------------+--------+---------+-----------+-------------+ | equipmentID | equipmentType | brand | ownerID | brandName | homeCountry | +-------------+---------------+--------+---------+-----------+-------------+ | 6 | bass guitar | Fandar | 4 | Fandar | USA | | 17 | bass guitar | Peabey | 1 | Peabey | USA | +-------------+---------------+--------+---------+-----------+-------------+ 2 rows in set (0.00 sec) هذه هي كل المعلومات التي نبحث عنها؛ وهي كل علامة تجارية مقرها الولايات المتحدة والموجودة في جدول prohibitedBrands والتي تظهر أيضًا في جدول clubEquipment. لحذف هذه العلامات التجارية من جدول prohibitedBrands والمعدات المرتبطة بها من جدول clubEquipment، أعد كتابة استعلام SELECT السابق ولكن استبدل تعليمة SELECT * بتعليمة DELETE متبوعةً بأسماء كلا الجدولين: mysql> DELETE clubEquipment, prohibitedBrands mysql> FROM clubEquipment JOIN prohibitedBrands mysql> ON clubEquipment.brand = prohibitedBrands.brandName mysql> WHERE homeCountry = 'USA'; الخرج Query OK, 4 rows affected (0.01 sec) يشير هذا الخرج إلى أن العملية حذفت أربع سجلات من قاعدة البيانات: سجلين من جدول clubEquipment وسجلين من جدول prohibitedBrands. إذا كنت تريد حذف السجلات من جدول clubEquipment فقط والاحتفاظ بجميع السجلات في جدول prohibitedBrands، فعليك إدراج clubEquipment فقط بعد كلمة DELETE، والعكس صحيح. تغيير سلوك تعليمة DELETE للمفاتيح الخارجية ستفشل أي تعليمة DELETE قد تسبب تعارضًا مع قيد FOREIGN KEY افتراضيًا. بالعودة إلى فقرة الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية في مستلزمات العمل وبتذكّر أنّ العمود ownerID في جدول المعدات clubEquipment هو مفتاح خارجي يشير إلى عمود memberID في جدول الأعضاء clubMembers. فهذا يعني أنّ أي قيمة مُدخلة في عمود ownerID يجب أن تكون موجودة بالفعل في جدول memberID. إذا حاولت إزالة سجل من جدول clubMembers تُستخدم قيمة memberID المُخصصة له في مكان ما ضمن عمود ownerID من جدول clubEquipment، سيؤدي ذلك إلى ظهور خطأ. mysql> DELETE FROM clubMembers mysql> WHERE memberID = 6; الخرج ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails يمكنك تجنب هذا الخطأ بإزالة أي سجلات في الجدول الابن (clubEquipment في هذا المثال) حيث توجد قيمة المفتاح الخارجي في الجدول الأب (clubMembers). كبديل، يمكنك تغيير هذا السلوك بتعديل قيد المفتاح الخارجي القائم بآخر يعالج عمليات الحذف على نحوٍ مختلف. ملاحظة: لا تسمح كافة أنظمة إدارة قواعد البيانات العلاقية أو محركاتها بإضافة أو إزالة قيود من جدول قائم كما هو موضح في الفقرات اللاحقة. فإذا كنت تستخدم نظام RDBMS غير MySQL، يجب عليك مراجعة الوثائق الرسمية الخاصة به لفهم القيود المتعلقة بإدارة القيود. وبالعودة إلى موضوعنا، بذلك ستكون قادرًا على تحديث قيمة clientID لأي سجل في الجدول الأب clubMembers، وستنتقل هذه التغييرات بشكل تلقائي إلى أي سجلات في الجدول الابن clubEquipment المرتبطة به. لاستبدال القيد الحالي، يجب أولًا إزالته باستخدام تعليمة ALTER TABLE. تذكر أننا في تعليمة CREATE TABLE لجدول clubEquipment، قمنا بتحديد fk_ownerID كاسم لقيد المفتاح الخارجي للجدول. mysql> ALTER TABLE clubEquipment mysql> DROP FOREIGN KEY fk_ownerID; الخرج Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 بعد ذلك، أنشئ قيد مفتاح خارجي جديد مُعدّ ليتعامل مع عمليات الحذف DELETE بطريقة تتناسب مع حالة الاستخدام المعطاة. بصرف النظر عن الإعداد الافتراضي الذي يمنع تعليمات DELETE التي تخالف المفتاح الخارجي، هناك خياران آخران متاحان في معظم أنظمة إدارة قواعد البيانات العلاقية: ON DELETE SET NULL: يسمح لك هذا الخيار بحذف السجلات من الجدول الأب، وسيعيد تعيين أي قيم مرتبطة بها في الجدول الابن على أنها قيم فارغة NULL. ON DELETE CASCADE: عند حذف سجل في الجدول الأب، سيدفع هذا الخيار SQL لحذف أي سجلات في الجدول الابن مرتبطة بذلك السجل من الجدول الأب. في سياق هذا المثال، لا يُعد استخدام خيار ON DELETE SET NULL منطقيًا. فإذا غادر أحد الأعضاء النادي وحُذِف سجله من جدول clubMembers، فلن تكون معداته متاحة للأعضاء المتبقين بعد الآن وبالتالي يجب إزالتها من جدول clubEquipment. وفي هذا الصدد يُعدّ خيار ON DELETE CASCADE هو الأنسب لهذا السياق. لإضافة قيد FOREIGN KEY يعمل وفق آلية ON DELETE CASCADE، نفّذ أمر ALTER TABLE التالي لتعديل الجدول. ستنشئ هذه التعليمة قيدًا جديدًا باسم newfk_ownerID يعكس تعريف القيد السابق ولكن مع إضافة خيار ON DELETE CASCADE. mysql> ALTER TABLE clubEquipment mysql> ADD CONSTRAINT newfk_ownerID mysql> FOREIGN KEY (ownerID) mysql> REFERENCES clubMembers(memberID) mysql> ON DELETE CASCADE; الخرج Query OK, 7 rows affected (0.07 sec) Records: 7 Duplicates: 0 Warnings: 0 تشير هذه النتائج إلى أن العملية قد أثرت على جميع السجلات السبعة المتبقية في جدول clubEquipment. ملاحظة: بدلًا من تغيير تعريف جدول مُعرّف مسبقًا لتعديل كيفية تفاعل قيد المفتاح الخارجي مع عمليات DELETE، يُمكنك من البداية تحديد هذا السلوك عند إنشاء الجدول بواسطة تعليمة إنشاء الجدول CREATE TABLE ، وبذلك تُعيّن السلوك المطلوب مُسبقًا، على النحو التالي: mysql> CREATE TABLE clubEquipment ( mysql> equipmentID int PRIMARY KEY, mysql> equipmentType varchar(30), mysql> brand varchar(15), mysql> ownerID int, mysql> CONSTRAINT fk_ownerID mysql> FOREIGN KEY (ownerID) REFERENCES clubMembers(memberID) mysql> ON DELETE CASCADE mysql> ); عقب ذلك، ستكون قادرًا على حذف أي سجل من جدول clubMembers، وستُحذف جميع السجلات المرتبطة به في جدول clubEquipment تلقائيًا: mysql> DELETE FROM clubMembers mysql> WHERE memberID = 6; الخرج Query OK, 1 row affected (0.00 sec) رغم أن هذا الخرج يشير إلى تأثر سجل واحد فقط، إلا أنّ العملية قد حذفت أيضًا كافة سجلات المعدات في جدول clubEquipment ذات القيمة 6 في عمود ownerID. الخلاصة باطلاعك على هذا المقال، اكتسبت المعرفة حول كيفية حذف البيانات الموجودة في جدول واحد أو أكثر باستخدام تعليمة DELETE في SQL. كما تعرفت على كيفية تعامل SQL مع عمليات الحذف التي تتعارض مع قيود المفتاح الخارجي وطرق تغيير هذا السلوك الافتراضي. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكيفية التعامل مع عمليات الحذف والخيارات المتاحة لها. وللمزيد حول SQL، نشجعك على متابعة المقالات المنشورة تحت وسم سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Delete Data in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية تحديث البيانات في لغة الاستعلام البنيوية SQL ما هي تقنية SQL تنظيم شيفرات SQL وتأمينها تصميم الجداول ومعلومات المخطط وترتيب تنفيذ الاستعلامات في SQL
  12. لدى التعامل مع قاعدة بيانات، قد تضطر أحيانًا لتعديل بيانات كانت مدرجة من قبل في جدول أو أكثر. كأن تُضطر مثلًا لتصحيح خطأ إملائي في إدخال معين أو إضافة معلومات جديدة إلى سجل غير مكتمل. وتوفّر لغة الاستعلام البنيوية المعروفة بـ SQL الكلمة المفتاحية UPDATE، التي تُمكّن المستخدمين من تعديل البيانات الموجودة في جدول ما. يشرح هذا المقال كيفية استعمال الصيغة UPDATE في SQL لتعديل البيانات في جدول واحد أو عدة جداول مرتبطة ببعضها. كما يتناول الطريقة التي تتعامل بها SQL مع عمليات UPDATE التي قد تتعارض مع قيود المفتاح الخارجي. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز حاسوب يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، ويمكنك إعداد الخادم بالاستعانة بمقالنا كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، ومُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات مع بعض الجداول المُحمّلة ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على تحديث بيانات SQL. وإذا لم تكن متوفرة لديك، يمكنك مراجعة مقال الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية الاتصال بخادم MySQL وإنشاء قاعدة البيانات التجريبية المُستخدمة في أمثلة هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم updateDB: mysql> CREATE DATABASE updateDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات updateDB، نفّذ تعليمة USE التالية: mysql> USE updateDB; الخرج Database changed وبعد اختيارك لقاعدة البيانات updateDB، أنشئ بعض الجداول ضمنها. ولتوضيح الأمثلة في هذا المقال، تخيّل أنّك تدير وكالة مواهب، وقد قررت تتبّع عملائك وأدائهم عبر قاعدة بيانات SQL، وبأنّك تعتزم البدء بجدولين: الجدول الأوّل لتخزين معلومات عن عملائك. وقد حددت أنّ هذا الجدول يحتاج إلى أربعة أعمدة: clientID: مُعرّف كل عميل، مُعبرًا عنه بنمط بيانات الأعداد الصحيحة int، كما سيُمثّل هذا العمود المفتاح الرئيسي للجدول، بحيث تمثل كل قيمة منه دور المُعرف الفريد للسجل المُرتبط بها. name: اسم كل عميل، مُعبرًا عنه بنمط بيانات varchar بحد أقصى 20 محرفًا. routine: وصف مُختصر لنوع الأداء الرئيسي لكل عميل، مُعبرًا عنه بنمط بيانات varchar بحد أقصى 30 محرفًا. performanceFee: عمود لتسجيل رسوم الأداء القياسية لكل عميل، يستخدم نمط البيانات decimal وتُحدد القيم في هذا العمود بحد أقصى قدره خمسة أرقام، بواقع رقمين على يمين الفاصلة العشرية. وبالتالي، تتراوح القيم المسموح بها من -999.99 إلى 999.99. أنشئ جدولًا باسم clients يشمل هذه الأعمدة الأربعة: mysql> CREATE TABLE clients mysql> (clientID int PRIMARY KEY, mysql> name varchar(20), mysql> routine varchar(30), mysql> standardFee decimal (5,2) mysql> ); أمّا الجدول الثاني فسيكون مخصصًا لتخزين بيانات حول أداء عملائك في مكان عرض محدد محليًا. وبفرض أنك ارتأيت بأنّ هذا الجدول يتطلّب خمسة أعمدة: showID: بمثابة عمود clientID، إذ سيحتفظ هذا العمود بمُعرّف فريد لكل عرض، مُعبرًا عنه بنمط بيانات الأعداد الصحيحة int. كما سيمُثّل هذا العمود المفتاح الرئيسي لجدول العروض shows. showDate: تاريخ كل عرض. يُعبر عن قيم هذا العمود باستخدام نمط بيانات التواريخ date الذي يستخدم الصيغة YYYY-MM-DD (خانتين لليوم وخانتين للشهر وأربع خانات للسنة). clientID: مُعرّف العميل الذي يؤدي في العرض، مُعبرًا عنه كعدد صحيح. attendance: عدد الحضور في كل عرض، مُعبرًا عنه كعدد صحيح. ticketPrice:سعر تذكرة الدخول لكل عرض. يستخدم هذا العمود نمط البيانات decimal وتُحدد القيم في هذا العمود بحد أقصى قدره خمسة أرقام، بواقع رقمين على يمين الفاصلة العشرية. وبالتالي، تتراوح القيم المسموح بها من- 999.99 إلى 999.99. ولضمان أنّ عمود clientID لن يتضمّن سوى قيم تُمثّل مُعرّفات عملاء صالحة، قررتَ تطبيق قيد مفتاح خارجي عليه بحيث يُشير إلى عمود clientID في جدول clients. يُعد قيد المفتاح الخارجي طريقة لتحديد علاقة بين جدولين، إذ يفرض أن تكون القيم في العمود المُطبّق عليه موجودة بالفعل في العمود المُشار إليه. في المثال القادم، يشترط قيد FOREIGN KEY أن تكون كل قيمة تُضاف إلى عمود clientID في جدول shows مُسجلة مسبقًا في عمود clientID بجدول clients. أنشئ جدولًا باسم clients يتضمن هذه الأعمدة الخمسة: mysql> CREATE TABLE shows mysql> (showID int PRIMARY KEY, mysql> showDate date, mysql> clientID int, mysql> attendance int, mysql> ticketPrice decimal (4,2), mysql> CONSTRAINT client_fk mysql> FOREIGN KEY (clientID) mysql> REFERENCES clients(clientID) mysql> ); ومن الجدير بالملاحظة أنّ هذا المثال يوفّر اسمًا لقيد المفتاح الخارجي، ألا وهو: client_fk. إذ تُنشئ MySQL تلقائيًا اسمًا لأي قيد تضيفه، إلّا أنّ تحديد اسم من قبلنا في هذه الحالة سيكون مفيدًا عندما نحتاج للإشارة إلى هذا القيد لاحقًا. بعد ذلك، نفّذ تعليمة INSERT INTO التالية لملء جدول العملاء clients بخمسة سجلات من البيانات النموذجية: mysql> INSERT INTO clients mysql> VALUES mysql> (1, 'Fares', 'song and dance', 180), mysql> (2, 'Camal', 'standup', 99.99), mysql> (3, 'Karam', 'standup', 45), mysql> (4, 'Wael', 'song and dance', 200), mysql> (5, 'Ahmad', 'trained squirrel', 79.99); ثم نفّذ تعليمة INSERT INTO أخرى لملء جدول shows بعشرة سجلات من البيانات النموذجية: mysql> INSERT INTO shows mysql> VALUES mysql> (1, '2019-12-25', 4, 124, 15), mysql> (2, '2020-01-11', 5, 84, 29.50), mysql> (3, '2020-01-17', 3, 170, 12.99), mysql> (4, '2020-01-31', 5, 234, 14.99), mysql> (5, '2020-02-08', 1, 86, 25), mysql> (6, '2020-02-14', 3, 102, 39.5), mysql> (7, '2020-02-15', 2, 101, 26.50), mysql> (8, '2020-02-27', 2, 186, 19.99), mysql> (9, '2020-03-06', 4, 202, 30), mysql> (10, '2020-03-07', 5, 250, 8.99); وبذلك، غدوتَ جاهزًا لمتابعة باقي المقال وبدء تعلم كيفية تحديث البيانات باستخدام لغة الاستعلام البنيوية SQL. تحديث البيانات في جدول واحد تبدو الصيغة العامّة لتعليمة UPDATE على النحو: mysql> UPDATE table_name mysql> SET column_name = value_expression mysql> WHERE conditions_apply; تُتبع الكلمة المفتاحية UPDATE باسم الجدول الذي يحتوي على البيانات المُراد تحديثها. ثم تأتي بنية SET، والتي تُحدّد بيانات العمود المُراد تحديثها وكيفية التحديث. تُعدّ بنية SET وكأنها تعيين لقيم العمود المُحدد لتُصبح مطابقة لأي تعبير قيمة تُقدّمه. يُعرّف تعبير القيمة — الذي يُعرف أحيانًا بالتعبير ذو القيمة المفردة — بأنّه أي تعبير يُعيد قيمة واحدة لكل سجل يُراد تحديثه. يمكن أن تكون القيمة المُعادة عبارة عن سلسلة نصية مجردة، أو عملية رياضية تُجرى على قيم رقمية موجودة في العمود. ولا بُدّ من تضمين عملية إسناد لقيمة واحدة على الأقل في كل تعليمة UPDATE، كما يُمكنك تضمين أكثر من تعليمة واحدة بغية تحديث البيانات في عدة أعمدة. تُتبع بنية SET ببنية WHERE. فإضافة بنية WHERE إلى تعليمة UPDATE كما في صيغة المثال هذه يُمكنّك من تصفية أي سجلات لا ترغب في تحديثها. إنّ بنية WHERE اختيارية تمامًا في تعليمات UPDATE، ولكن إذا لم تُضمنها، ستُحدّث العملية كل سجل في الجدول. لتوضيح كيفية تعامل SQL مع عمليات التحديث UPDATE، ابدأ بالاطلاع على كافة البيانات في جدول العملاء clients. يشتمل الاستعلام التالي على علامة النجمة (*)، وهي اختصار في SQL يُمثّل كل عمود في الجدول، لذا سيُعيد هذا الاستعلام جميع البيانات من كل عمود في جدول clients. $ SELECT * FROM clients; الخرج +----------+------------+------------------+-------------+ | clientID | name | routine | standardFee | +----------+------------+------------------+-------------+ | 1 | Fares | song and dance | 180.00 | | 2 | Camal | standup | 99.99 | | 3 | Karam | standup | 45.00 | | 4 | Wael | song and dance | 200.00 | | 5 | Ahmad | trained squirrel | 79.99 | +----------+------------+------------------+-------------+ 5 rows in set (0.00 sec) لنفترض على سبيل المثال أنّك لاحظت وجود خطأ في تهجئة الاسم Kamal، إذ يجب أن يبدأ بحرف K ولكنه في الجدول يبدأ بحرف C، ولذا قررت تغيير هذه القيمة عبر تنفيذ تعليمة UPDATE التالية. هذه العملية تُحدّث القيم في عمود الاسم name عن طريق تغيير قيمة عمود الاسم name في أي سجل يحتوي على الاسم Camal لتصبح Kamal: mysql> UPDATE clients mysql> SET name = 'Kamal' mysql> WHERE name = 'Camal'; الخرج Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 يُظهر هذا الخرج أن سجلًا واحدًا فقط قد حُدّث. يمكنك التأكد من ذلك بتشغيل استعلام SELECT السابق مجددًا، على النحو: $ SELECT * FROM clients; الخرج +----------+------------+------------------+-------------+ | clientID | name | routine | standardFee | +----------+------------+------------------+-------------+ | 1 | Fares | song and dance | 180.00 | | 2 | Kamal | standup | 99.99 | | 3 | Karam | standup | 45.00 | | 4 | Wael | song and dance | 200.00 | | 5 | Ahmad | trained squirrel | 79.99 | +----------+------------+------------------+-------------+ 5 rows in set (0.00 sec) تُظهر هذه النتائج أن القيمة المُدخلة سابقًا على أنّها Camal قد عُدلت الآن إلى Kamal. لقد حُدثّت قيمة واحدة فقط في عمود الاسم name في هذا المثال. ولكن، يمكنك تحديث عدة قيم باستخدام بنية WHERE أشمل. لإيضاح هذه الفكرة، بفرض أنّك تفاوضت على أجور أداء موحدة لجميع عملائك الذين يؤدون فقرات محددة. ستُحدّث التعليمة التالية القيم في عمود standardFee وتعينها لتكون 140. يرجى ملاحظة أن بنية WHERE في هذا المثال تتضمن المعامل LIKE، لذا فهي تُحدّث قيمة performanceFee لكل عميل تُطابق قيمة routine له النمط المحدد بالمحرف البديل 's%'. بمعنى آخر، سيُحدّث أجر الأداء لأي مؤدي يبدأ نوع عرضه بالحرف "s": mysql> UPDATE clients mysql> SET standardFee = 140 mysql> WHERE routine LIKE 's%'; الخرج Query OK, 4 rows affected (0.00 sec) Rows matched: 4 Changed: 4 Warnings: 0 والآن إذا استعلمت مجددًا عن محتويات جدول العملاء clients، فستؤكد مجموعة النتائج أن أربعة من عملائك غدا لديهم الآن رسوم أداء متطابقة: $ SELECT * FROM clients; الخرج +----------+------------+------------------+-------------+ | clientID | name | routine | standardFee | +----------+------------+------------------+-------------+ | 1 | Fares | song and dance | 140.00 | | 2 | Kamal | standup | 140.00 | | 3 | Karam | standup | 140.00 | | 4 | Wael | song and dance | 140.00 | | 5 | Ahmad | trained squirrel | 79.99 | +----------+------------+------------------+-------------+ 5 rows in set (0.00 sec) في حالة وجود أعمدة بالجدول تحمل قيمًا رقمية، فيُمكن تحديثها بتنفيذ عملية حسابية ضمن بنية SET. لتوضيح الأمر، بفرض أنّك توصلّت لاتفاق على زيادة رسوم الأداء لكل عميل بنسبة أربعين بالمئة، ولتطبيق هذا التغيير على جدول العملاء clients، يمكن تنفيذ عملية UPDATE كالآتي: mysql> UPDATE clients mysql> SET standardFee = standardFee * 1.4; الخرج Query OK, 5 rows affected, 1 warning (0.00 sec) Rows matched: 5 Changed: 5 Warnings: 1 ملاحظة: لاحظ أن الخرج يشير إلى أنّ التحديث قد نتج عنه تحذير. ففي كثير من الأحيان، تُصدر MySQL تحذيرًا عندما تُجبر على إجراء تغيير على بياناتك يتعارض والخصائص أو المحددات القياسية لعمود أو جدول معين. وتوفّر MySQL الاختصار SHOW WARNINGS الذي قد يساعد في شرح أي تحذيرات تتلقاها: mysql> SHOW WARNINGS; الخرج +-------+------+--------------------------------------------------+ | Level | Code | Message | +-------+------+--------------------------------------------------+ | Note | 1265 | Data truncated for column 'standardFee' at row 5 | +-------+------+--------------------------------------------------+ 1 row in set (0.00 sec) يُخبرنا هذا الخرج بأن نظام قاعدة البيانات أصدر التحذير لأنه اضطر إلى اقتطاع إحدى قيم العمود standardFee الجديدة حتى تتوافق مع تنسيق الرقم العشري - خمسة أرقام مع وجود رقمين على يمين الفاصلة العشرية - المُعرّف مسبقًا. لنستعلم عن جدول العملاء clients مجددًا للتأكد من أن رسوم الأداء لكل من العملاء قد ارتفعت بنسبة أربعين بالمئة بالفعل. $ SELECT * FROM clients; الخرج +----------+------------+------------------+-------------+ | clientID | name | routine | standardFee | +----------+------------+------------------+-------------+ | 1 | Fares | song and dance | 196.00 | | 2 | Kamal | standup | 196.00 | | 3 | Karam | standup | 196.00 | | 4 | Wael | song and dance | 196.00 | | 5 | Ahmad | trained squirrel | 111.99 | +----------+------------+------------------+-------------+ 5 rows in set (0.00 sec) كما ذكرنا سابقًا، يمكنك أيضًا تحديث البيانات في عدة أعمدة دفعة واحدة باستخدام تعليمة UPDATE واحدة. للقيام بذلك، يجب تحديد كل عمود ترغب في تحديثه، متبوعًا بالتعبير الخاص بالقيمة المراد تعيينها، ثم تفصل بين كل زوج من اسم عمود وتعبير قيمة بعلامة فاصلة. على سبيل المثال، بفرض أنّك اكتشفت بأنّ القاعة التي يقدم فيها عملاؤك عروضهم قد أخطأت في الإبلاغ عن عدد الحضور لجميع عروض Karam و Wand. وبالصدفة، تبين أيضًا أنك قمت بإدخال أسعار تذاكر خاطئة لكل من عروضهما. قبل الشروع في تحديث البيانات في جدول العروض shows، نفّذ الاستعلام التالي لاسترجاع كافة البيانات الحالية المُخزنة به حاليًا: $ SELECT * FROM shows; الخرج +--------+------------+----------+------------+-------------+ | showID | showDate | clientID | attendance | ticketPrice | +--------+------------+----------+------------+-------------+ | 1 | 2019-12-25 | 4 | 124 | 15.00 | | 2 | 2020-01-11 | 5 | 84 | 29.50 | | 3 | 2020-01-17 | 3 | 170 | 12.99 | | 4 | 2020-01-31 | 5 | 234 | 14.99 | | 5 | 2020-02-08 | 1 | 86 | 25.00 | | 6 | 2020-02-14 | 3 | 102 | 39.50 | | 7 | 2020-02-15 | 2 | 101 | 26.50 | | 8 | 2020-02-27 | 2 | 186 | 19.99 | | 9 | 2020-03-06 | 4 | 202 | 30.00 | | 10 | 2020-03-07 | 5 | 250 | 8.99 | +--------+------------+----------+------------+-------------+ 10 rows in set (0.01 sec) ولتصحيح أعداد الحضور والأسعار لتعبّر عن تلك الفعلية، سنحدّث الجدول لإضافة عشرين حاضرًا إلى كل عرض لهما وزيادة قيم سعر التذكرة ticketPrice لكل عرض بنسبة خمسين في المئة. يمكنك القيام بذلك من خلال عملية على النحو: mysql> UPDATE shows mysql> SET attendance = attendance + 20, mysql> ticketPrice = ticketPrice * 1.5 mysql> WHERE clientID IN mysql> (SELECT clientID mysql> FROM clients mysql> WHERE name = 'Karam' OR name = 'Wael'); الخرج Query OK, 4 rows affected, 1 warning (0.00 sec) Rows matched: 4 Changed: 4 Warnings: 1 لاحظ أن هذا المثال يستخدم استعلامًا فرعيًا في بنية WHERE لإرجاع قيم clientID لكل من Karam و Wael من جدول العملاء clients. وغالبًا ما يكون من الصعب تذكر القيم المجردة من قبيل أرقام التعريف، إلّا أنّ هذه الطريقة التي تستخدم فيها استعلامًا فرعيًا للعثور على قيمة يمكن أن تكون مفيدة في حال معرفتك لبعض السمات فقط حول السجلات المعنية. بعد تحديث جدول العروض shows، لنستعلم عنه مجددًا للتأكد من أن التغييرات قد تمّت كما هو متوقع: $ SELECT * FROM shows; الخرج +--------+------------+----------+------------+-------------+ | showID | showDate | clientID | attendance | ticketPrice | +--------+------------+----------+------------+-------------+ | 1 | 2019-12-25 | 4 | 144 | 22.50 | | 2 | 2020-01-11 | 5 | 84 | 29.50 | | 3 | 2020-01-17 | 3 | 190 | 19.49 | | 4 | 2020-01-31 | 5 | 234 | 14.99 | | 5 | 2020-02-08 | 1 | 86 | 25.00 | | 6 | 2020-02-14 | 3 | 122 | 59.25 | | 7 | 2020-02-15 | 2 | 101 | 26.50 | | 8 | 2020-02-27 | 2 | 186 | 19.99 | | 9 | 2020-03-06 | 4 | 222 | 45.00 | | 10 | 2020-03-07 | 5 | 250 | 8.99 | +--------+------------+----------+------------+-------------+ 10 rows in set (0.00 sec) يشير هذا الخرج إلى أن تعليمة UPDATE قد اكتملت بنجاح. استخدام بنية JOIN لتحديث البيانات في جداول متعددة ركّز هذا المقال حتى الآن على عرض طرق تحديث البيانات في جدول واحد فقط في كل مرة. ولكن، تُتيح بعض الإصدارات من SQL إمكانية تحديث أعمدة متعددة في جداول متعددة من خلال دمج الجداول مؤقتًا باستخدام بنية JOIN. فيما يلي الصيغة العامة التي بإمكانك استخدامها لتحديث عدة جداول دفعة واحدة مستخدمًا بنية JOIN: mysql> UPDATE table_1 JOIN table_2 mysql> ON table_1.related_column = table_2.related_column mysql> SET table_1.column_name = value_expression, mysql> table_2.column_name = value_expression mysql> WHERE conditions_apply; تبدأ صيغة هذا المثال بالكلمة المفتاحية UPDATE متبوعة بأسماء جدولين، يفصل بينهما صيغة JOIN. يلي ذلك صيغة ON، التي توضّح كيف ينبغي للاستعلام أن يدمج الجدولين معًا. في معظم تقديمات SQL، يمكنك دمج الجداول عن طريق إيجاد تطابقات ما بين أي مجموعة من الأعمدة تحتوي على ما يُعرف في معيار SQL باسم "أنواع البيانات المؤهلة للدمج" (JOIN eligible). بمعنى آخر، يُمكن بشكل عام دمج أي عمود يحتوي على بيانات عددية مع أي عمود آخر يحتوي على بيانات عددية، بغض النظر عن أنماط البيانات المحددة لكل منهما. وبالمثل، يمكن دمج أي أعمدة تحتوي على قيم محرفية مع أي عمود آخر يحتوي على بيانات محرفية. لاحظ أنه نظرًا لقدرة بنى JOIN على مقارنة البيانات من عدة جداول، فإن صيغة هذا المثال تُوضّح الجدول المُستهدف لكل عمود بوضع اسم الجدول متبوعًا بنقطة قبل اسم العمود، وهذا ما يُعرف بالإشارة الكاملة والمؤهلة للعمود. يُمكنك تحديد الجدول المصدر لكل عمود بهذه الطريقة في أي عملية، وهي غالبًا ما تُستخدم لزيادة الوضوح عند العمل مع أكثر من جدول. لتوضيح كيفية تنفيذ ذلك باستخدام جداول clients وshows المُنشأة مسبقًا، نفذ تعليمة UPDATE التالية. ما سيدمج جدولي clients وshows بناءً على أعمدة clientID المتطابقة في كلا الجدولين، ومن ثم تحديث قيم routine وticketPrice لسجل Fares في جدول clients وكل عروضها المدرجة في جدول shows. mysql> UPDATE clients JOIN shows mysql> USING (clientID) mysql> SET clients.routine = 'mime', mysql> shows.ticketPrice = 30 mysql> WHERE name = 'Fares'; الخرج Query OK, 2 rows affected (0.01 sec) Rows matched: 2 Changed: 2 Warnings: 0 لاحظ أنّ هذا المثال يدمج الجداول باستخدام الكلمة المفتاحية USING بدلاً من ON المُستخدمة في صيغة المثال السابق. وهذا ممكن لأنّ كل من الجدولين لديهما عمود clientID يتشاركان فيه نفس نوع البيانات. لمزيد من التفاصيل حول عمليات الدمج باستخدام JOIN، ننصحك بقراءة المقال التالي كيفية استخدام عمليات الدمج في SQL. تغيير سلوك تعليمة UPDATE للمفاتيح الخارجية ستفشل أي تعليمة UPDATE قد تسبب تعارضًا مع قيد FOREIGN KEY افتراضيًا. بالعودة إلى فقرة الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية في مستلزمات العمل وبتذكّر أنّ العمود clientID في جدول العروض shows هو مفتاح خارجي يشير إلى عمود clientID في جدول العملاء clients. فهذا يعني أنّ أي قيمة مُدخلة في عمود clientID الخاص بجدول العروض يجب أن تكون موجودة بالفعل في جدول العملاء. فإذا حاولت تحديث قيمة clientID لسجل ما في جدول العملاء والتي تظهر أيضًا في عمود clientID لجدول العروض، فسيؤدي ذلك إلى حدوث خطأ: mysql> UPDATE clients mysql> SET clientID = 9 mysql> WHERE name = 'Ahmad'; الخرج ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails يمكنك تجنب هذا الخطأ بتغيير قيد المفتاح الخارجي الحالي بآخر يتعامل مع عمليات التحديث على نحوٍ مختلف. ملاحظة: لا تسمح كافة أنظمة إدارة قواعد البيانات العلاقية أو محركات قواعد البيانات بإضافة أو إزالة قيد من جدول موجود بالفعل كما هو موضح في الفقرات التالية. فإذا كنت تستخدم نظام RDBMS غير MySQL، يجب عليك الرجوع إلى الوثائق الرسمية الخاصة به لفهم القيود الموجودة بخصوص إدارة القيود. بالعودة إلى موضوعنا، ولاستبدال القيد الحالي، عليك بدايةً إزالته باستخدام تعليمة ALTER TABLE. تذكّر أننا في تعليمة CREATE TABLE الخاصة بجدول العروض shows، حددنا client_fk كاسم لقيد المفتاح الخارجي FOREIGN KEY للجدول: mysql> ALTER TABLE shows mysql> DROP FOREIGN KEY client_fk; الخرج Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 بعد ذلك، أنشئ قيد مفتاح خارجي جديد مُعدّ ليتعامل مع عمليات التحديث UPDATE بطريقة تتناسب مع الحالة الاستخدامية المعطاة. بصرف النظر عن الإعداد الافتراضي الذي يمنع تعليمات UPDATE التي تخالف المفتاح الخارجي، هناك خياران آخران متاحان في معظم أنظمة إدارة قواعد البيانات العلاقية: ON UPDATE SET NULL: يسمح لك هذا الخيار بتحديث السجلات من الجدول الأب، وسيعيد تعيين أي قيم مرتبطة بها في الجدول الابن على أنها قيم فارغة NULL. ON UPDATE CASCADE: عند تحديث سجل في الجدول الأب، سيدفع هذا الخيار SQL لتحديث أي سجلات في الجدول الابن مرتبطة بذلك السجل من الجدول الأب لتتماشى مع القيمة الجديدة المُحدّثة. في سياق هذا المثال، لا يُعد استخدام خيار ON UPDATE SET NULL منطقيًا، فلو غيّرت مُعرّف لأحد العملاء دون حذفه من جدول clients، ينبغي أن يبقى مرتبطًا بعروضه في الجدول shows. ويجب أن يظهر المُعرّف الجديد ضمن سجلات عروضه، وبالتالي يكون استخدام الخيار ON UPDATE CASCADE هو الأنسب لهذا السياق. لإضافة قيد FOREIGN KEY يعمل وفق آلية ON UPDATE CASCADE، نفّذ أمر ALTER TABLE التالي. ستنشئ هذه التعليمة قيد جديد باسم new_client_fk يعكس تعريف القيد السابق ولكن مع إضافة خيار ON UPDATE CASCADE. mysql> ALTER TABLE shows mysql> ADD CONSTRAINT new_client_fk mysql> FOREIGN KEY (clientID) mysql> REFERENCES clients (clientID) mysql> ON UPDATE CASCADE; الخرج Query OK, 10 rows affected (0.02 sec) Records: 10 Duplicates: 0 Warnings: 0 تشير هذه النتائج إلى أن العملية قد أثرت على جميع السجلات العشرة وقامت بتعديلها في جدول العروض shows. ملاحظة: بدلًا من تغيير تعريف جدول مُعرّف مسبقًا لتعديل كيفية تفاعل قيد المفتاح الخارجي مع عمليات UPDATE، يُمكنك من البداية تحديد هذا السلوك عند إنشاء الجدول بواسطة تعليمة CREATE TABLE، وبذلك تُعيّن السلوك المطلوب مُسبقًا. mysql> CREATE TABLE shows mysql> (showID int PRIMARY KEY, mysql> showDate date, mysql> clientID int, mysql> attendance int, mysql> ticketPrice decimal (4,2), mysql> CONSTRAINT client_fk mysql> FOREIGN KEY (clientID) mysql> REFERENCES clients(clientID) mysql> ON UPDATE CASCADE mysql> ); عقب ذلك، ستكون قادرًا على تحديث قيمة clientID لأي سجل في جدول clients، وستنتقل هذه التغييرات بشكل تلقائي إلى جميع السجلات المرتبطة بها في جدول shows. mysql> UPDATE clients mysql> SET clientID = 9 mysql> WHERE name = 'Ahmad'; الخرج Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 رغم أن هذا الخرج يشير إلى تأثر سجل واحد فقط، إلا أنّ العملية قد حدّثت في الواقع قيمة clientID لكل سجلات جدول العروض المرتبطة بـ Ahmad في جدول shows. وللتحقق من ذلك، نفّذ الاستعلام التالي لاسترجاع كافة البيانات من الجدول shows: $ SELECT * FROM shows; الخرج +--------+------------+----------+------------+-------------+ | showID | showDate | clientID | attendance | ticketPrice | +--------+------------+----------+------------+-------------+ | 1 | 2019-12-25 | 4 | 144 | 22.50 | | 2 | 2020-01-11 | 9 | 84 | 29.50 | | 3 | 2020-01-17 | 3 | 190 | 19.49 | | 4 | 2020-01-31 | 9 | 234 | 14.99 | | 5 | 2020-02-08 | 1 | 86 | 30.00 | | 6 | 2020-02-14 | 3 | 122 | 59.25 | | 7 | 2020-02-15 | 2 | 101 | 26.50 | | 8 | 2020-02-27 | 2 | 186 | 19.99 | | 9 | 2020-03-06 | 4 | 222 | 45.00 | | 10 | 2020-03-07 | 9 | 250 | 8.99 | +--------+------------+----------+------------+-------------+ 10 rows in set (0.00 sec) كما هو متوقع، تسبب التحديث الذي أُجري على عمود clientID في جدول clients في تحديث السجلات المرتبطة في جدول shows. الخلاصة بوصولك إلى نهاية هذا المقال، ستكون قد اكتسبت المعرفة حول كيفية تعديل السجلات الموجودة في جدول واحد أو أكثر باستخدام تعليمة UPDATE في SQL. كما تعرفت على كيفية تعامل SQL مع عمليات التحديث التي تتعارض مع قيود المفتاح الخارجي وطرق تغيير هذا السلوك الافتراضي. ومن المفترض أن تعمل الأوامر المشروحة في هذا المقال مع أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن تذكر أن لكل قاعدة بيانات SQL تقديمها الخاص للغة، لذا ينبغي مراجعة التوثيق الرسمي لنظام إدارة قواعد البيانات الخاص بك للحصول على وصف أكثر تفصيلاً لكيفية التعامل مع عمليات التحديث والخيارات المتاحة لها. وللمزيد حول SQL، نشجعك على متابعة المقالات المنشورة تحت وسم سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Update Data in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية إدراج البيانات في SQL المرجع المتقدم إلى لغة SQL تصميم الجداول ومعلومات المخطط وترتيب تنفيذ الاستعلامات في SQL حذف الجداول وقواعد البيانات في SQL أهمية قواعد البيانات
  13. تُقدّم لغة الاستعلامات البنيوية SQL، مرونةً هائلةً من حيث الطرق الممكنة لإدراج البيانات في الجداول. فعلى سبيل المثال، يمكنك تحديد سجلات بيانات فردية باستخدام الكلمة المفتاحية VALUES، أو نسخ مجموعات كاملة من البيانات من الجداول الحالية باستخدام الاستعلامات SELECT. كما يمكن تعريف الأعمدة بأساليب تسمح للغة SQL بإدراج البيانات فيها تلقائيًا. في هذا المقال، سنستعرض كيفية استخدام الصيغة INSERT INTO في SQL لإضافة البيانات إلى الجداول وفق كل من هذه الأساليب. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز حاسوب يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقيَّة RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. نظام إدارة قواعد البيانات MySQL مثبت على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في سياق المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقيَّة تستخدم تقديمات فريدة خاصة بها للغة SQL. فعلى الرغم من أن الأوامر المُوضحة في هذا المقال ستعمل على نحوٍ سليم في معظم هذه الأنظمة، ولكن قد تختلف الصياغة الدقيقة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات يمكنك استخدامها للتدرّب على إدراج البيانات. إذا لم تكن لديك قاعدة بيانات للتجربة، اطلع على فقرة الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية** التالي للحصول على تفاصيل حول كيفية إنشائها. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم بعيد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ mysql -u user -p ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم insertDB: mysql> CREATE DATABASE insertDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات insertDB، نفّذ تعليمة USE التالية: mysql> USE insertDB; الخرج Database changed بعد اختيار قاعدة البيانات insertDB، أنشئ جدولًا داخلها. فعلى سبيل المثال، بفرض لديك مصنع وترغب في إنشاء جدول لتخزين بعض المعلومات عن موظفيك. سيحتوي هذا الجدول على الأعمدة الخمسة التالية: name: اسم كل موظف، ويُعبّر عنه باستخدام نمط البيانات varchar مع حد أقصى للطول قدره 30 محرفًا. position: يخزن هذا العمود الموقع الوظيفي لكل موظف، ويُعبّر عنه أيضًا باستخدام نمط البيانات varchar مع حد أقصى للطول قدره 30 محرفًا. department: يمثل القسم الذي يعمل فيه كل موظف، ويُعبّر عنه باستخدام نمط البيانات varchar ولكن بحد أقصى للطول قدره 20 محرفًا فقط. hourlyWage: عمود لتسجيل أجر كل موظف بالساعة، ويستخدم نمط البيانات decimal، إذ تقتصر أي قيم في هذا العمود على حد أقصى قدره أربعة أرقام، مع وجود رقمين من هذه الأرقام على يمين الفاصلة العشرية. وبالتالي، تتراوح القيم المسموح بها في هذا العمود من ‎-99.99 إلى 99.99. startDate: تاريخ تعيين كل موظف، ويُعبّر عنه باستخدام نمط البيانات date. ويجب أن تتوافق القيم من هذا النوع مع التنسيق YYYY-MM-DD. أنشئ جدولًا باسم factoryEmployees يحتوي على هذه الأعمدة الخمسة، على النحو: mysql> CREATE TABLE factoryEmployees ( mysql> name varchar(30), mysql> position varchar(30), mysql> department varchar(20), mysql> hourlyWage decimal(4,2), mysql> startDate date mysql> ); وبهذا، أصبحت جاهزًا لمتابعة الخطوات في المقال وبدء التعلم حول كيفية إدراج البيانات باستخدام SQL. إدراج البيانات يدويًا تبدو الصيغة العامة لإدراج البيانات في SQL كالتالي: mysql> INSERT INTO table_name mysql> (column1, column2, . . . columnN) mysql> VALUES mysql> (value1, value2, . . . valueN); للتوضيح، نفّذ الأمر INSERT INTO التالي لتحميل جدول factoryEmployees مع سجل واحد من البيانات: mysql> INSERT INTO factoryEmployees mysql> (name, position, department, hourlyWage, startDate) mysql> VALUES mysql> ('Agnes', 'thingamajig foreman', 'management', 26.50, '2017-05-01'); الخرج Query OK, 1 row affected (0.00 sec) يبدأ هذا الأمر بالكلمات المفتاحية INSERT INTO، يليها اسم الجدول الذي ترغب في إدراج البيانات فيه. وبعد اسم الجدول، تأتي قائمة بأسماء الأعمدة التي ستضيف البيانات إليها والتي تكون محصورة ضمن أقواس هلالية. وبعد قائمة الأعمدة، تأتي الكلمة المفتاحية VALUES، ومن ثم مجموعة من القيم محاطة بأقواس هلالية ومفصولة برموز فاصلة. الترتيب الذي تُدرج فيه الأعمدة ليس بالأمر المُلزم، ولكن من الأساسي ضمان تطابق ترتيب القيم مع ترتيب الأعمدة المُدرجة. إذ ستُدرج SQL القيمة الأولى في العمود الأول المُحدد، والقيمة الثانية في العمود الثاني، وهكذا. وللتوضيح لاحظ أن الأمر INSERT في المثال التالي سيضيف سجلًا جديدًا من البيانات، ولكن بترتيب مختلف للأعمدة: mysql> INSERT INTO factoryEmployees mysql> (department, hourlyWage, startDate, name, position) mysql> VALUES mysql> ('production', 15.59, '2018-04-28', 'Jim', 'widget tightener'); الخرج Query OK, 1 row affected (0.00 sec) إذا لم تتطابق القيم على نحوٍ صحيح مع ترتيب الأعمدة، قد تُدرج SQL البيانات في الأعمدة الخاطئة. ناهيك عن إمكانية حدوث خطأ في حال تعارضت أي من القيم مع نوع البيانات المحدد للعمود، كما في المثال التالي: mysql> INSERT INTO factoryEmployees mysql> (name, hourlyWage, position, startDate, department) mysql> VALUES mysql> ('Louise', 'doodad tester', 16.50, '2017-05-01', 'quality assurance'); الخرج ERROR 1366 (HY000): Incorrect decimal value: 'doodad tester' for column 'hourlyWage' at row 1 وعلى الرغم من ضرورة توفير قيمة لكل عمود تحدده، ولكن بالمقابل ليس من الضروري تحديد كل عمود في الجدول عند إضافة سجل جديد من البيانات. ففي حال عدم وجود قيود على الأعمدة التي تتجاهلها (كالقيد NOT NULL)، ستضع MySQL القيمة الفارغة NULL في الأعمدة غير المُحددة. mysql> INSERT INTO factoryEmployees mysql> (name, position, hourlyWage) mysql> VALUES mysql> ('Harry', 'whatzit engineer', 26.50); الخرج Query OK, 1 row affected (0.01 sec) فإذا كنت تخطط لإدخال سجل يتضمّن قيمًا لكل عمود في الجدول، فليس من الضروري تضمين أسماء الأعمدة على الإطلاق. ومع ذلك، عليك التأكد من أن القيم المُدخلة تتوافق مع الترتيب الذي عُرّفت الأعمدة وفقًا له لدى تعريف الجدول. في هذا المثال، تتوافق القيم المدرجة مع الترتيب الذي عُرّفت الأعمدة وفقًا له في تعليمة CREATE TABLE الخاصة بالجدول factoryEmployee: mysql> INSERT INTO factoryEmployees mysql> VALUES mysql> ('Marie', 'doodad welder', 'production', 27.88, '2018-03-29'); الخرج Query OK, 1 row affected (0.00 sec) كما يمكنك إضافة سجلات متعددة في وقت واحد عبر فصل كل سجل بعلامة فاصلة، كما في المثال التالي: mysql> INSERT INTO factoryEmployees mysql> VALUES mysql> ('Giles', 'gizmo inspector', 'quality assurance', 26.50, '2019-08-06'), mysql> ('Daphne', 'gizmo presser', 'production', 32.45, '2017-11-12'), mysql> ('Joan', 'whatzit analyst', 'quality assurance', 29.00, '2017-04-29'); الخرج Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 نسخ البيانات باستخدام تعليمات SELECT يمكنك نسخ سجلات البيانات المتعددة من جدول ما وإدراجها في جدول آخر باستخدام استعلام SELECT بدلًا من تحديد البيانات سجلًا تلو الآخر. تبدو صيغة هذه العملية كالتالي: mysql> INSERT INTO table_A (col_A1, col_A2, col_A3) mysql> SELECT col_B1, col_B2, col_B3 mysql> FROM table_B; فبدلًا من إلحاق قائمة الأعمدة بالكلمة المفتاحية VALUES، ألحقنا الصيغة ضمن هذا المثال بتعليمة SELECT. إذ تشمل تعليمة SELECT في هذه الصيغة بنية FROM فقط، ولكن يمكن لأي استعلام صالح أن يعمل هنا على نحوٍ صحيح. لتوضيح ذلك، نفّذ العملية CREATE TABLE التالية لإنشاء جدول جديد باسم showroomEmployees. لاحظ أن أعمدة هذا الجدول لها نفس الأسماء وأنماط البيانات كما في ثلاثة أعمدة من الجدول factoryEmployees المُستخدم في القسم السابق: mysql> CREATE TABLE showroomEmployees ( mysql> name varchar(30), mysql> hourlyWage decimal(4,2), mysql> startDate date mysql> ); الخرج Query OK, 0 rows affected (0.02 sec) الآن يمكنك ملء هذا الجدول الجديد ببعض البيانات من الجدول factoryEmployees المُنشأ سابقًا عن طريق إضافة استعلام SELECT ضمن تعليمة INSERT INTO. إذا أعاد استعلام SELECT نفس عدد الأعمدة وبنفس الترتيب كما في أعمدة الجدول الهدف، وكان لها أيضًا نفس أنماط البيانات، فيمكنك حينها تجاهل قائمة الأعمدة في تعليمة INSERT INTO، على النحو: mysql> INSERT INTO showroomEmployees mysql> SELECT mysql> factoryEmployees.name, mysql> factoryEmployees.hourlyWage, mysql> factoryEmployees.startDate mysql> FROM factoryEmployees mysql> WHERE name = 'Agnes'; الخرج Query OK, 1 row affected (0.01 sec) Records: 1 Duplicates: 0 Warnings: 0 ملاحظة: كل من الأعمدة المدرجة في الاستعلام SELECT الخاص بالعملية أعلاه مسبوقة باسم الجدول factoryEmployees متبوعًا برمز النقطة. وعندما نُحدد اسم جدول بهذه الطريقة عند الإشارة إلى عمود، فيُطلق عليها مصطلح الإشارة الكاملة للعمود fully qualified column reference. الأمر غير الضروري في هذه الحالة الخاصة في المثال أعلاه. ففي الواقع، ستعطي التعليمة INSERT INTO في الأمثلة التالية نفس النتيجة كالتعليمة السابقة: mysql> INSERT INTO showroomEmployees mysql> SELECT mysql> name, mysql> hourlyWage, mysql> startDate mysql> FROM factoryEmployees mysql> WHERE name = 'Agnes'; وقد استخدمنا في الأمثلة ضمن هذا القسم طريقة الإشارة الكاملة للعمود من باب التوضيح، ولكن من المستحسن استخدامها دائمًا. فهي لا تساعد في جعل لغة الاستعلام SQL أكثر وضوحًا فحسب، ولكنها تغدو كضرورة في العمليات التي تتضمن الإشارة إلى أكثر من جدول، كالاستعلامات التي تحتوي على بنية الدمج JOIN. تشمل تعليمة الاستعلام SELECT في هذه العملية البنية WHERE، وبوجودها سيُعيد الاستعلام من الجدول factoryEmployees السجلات التي تحتوي على القيمة Agnes في العمود name فقط. وبما أن هناك سجلًا واحدًا فقط في الجدول المصدر موافق لهذه القيمة، فينسخ هذا السجل فقط إلى الجدول showroomEmployees. لتأكيد ذلك، نفّذ الاستعلام التالي لاسترجاع جميع السجلات في جدول showroomEmployees: mysql> SELECT * FROM showroomEmployees; الخرج +-------+------------+------------+ | name | hourlyWage | startDate | +-------+------------+------------+ | Agnes | 26.50 | 2017-05-01 | +-------+------------+------------+ 1 row in set (0.00 sec) يمكنك إدراج عدة سجلات من البيانات باستخدام أي استعلام يُعيد أكثر من سجل من الجدول المصدر. فعلى سبيل المثال، سيُعيد الاستعلام في التعليمة التالية جميع سجلات قاعدة البيانات factoryEmployees التي لا تبدأ قيمها ضمن العمود name بالحرف J: mysql> INSERT INTO showroomEmployees mysql> SELECT mysql> factoryEmployees.name, mysql> factoryEmployees.hourlyWage, mysql> factoryEmployees.startDate mysql> FROM factoryEmployees mysql> WHERE name NOT LIKE 'J%'; الخرج Query OK, 5 rows affected (0.01 sec) Records: 5 Duplicates: 0 Warnings: 0 نفّذ هذا الاستعلام مجددًا لاسترجاع جميع السجلات في الجدول showroomEmployees: mysql> SELECT * FROM showroomEmployees; +--------+------------+------------+ | name | hourlyWage | startDate | +--------+------------+------------+ | Agnes | 26.50 | 2017-05-01 | | Agnes | 26.50 | 2017-05-01 | | Harry | 26.50 | NULL | | Marie | 27.88 | 2018-03-29 | | Giles | 26.50 | 2019-08-06 | | Daphne | 32.45 | 2017-11-12 | +--------+------------+------------+ 6 rows in set (0.00 sec) نلاحظ وجود سجلين متطابقين يحملان القيمة Agnes في العمود name. فعند تنفيذ تعليمة INSERT INTO التي تستخدم الاستعلام SELECT, تُعامل SQL نتيجة الاستعلام هذا كمجموعة جديدة من البيانات. فبدون فرض قيود محددة على الجدول أو الاعتماد على استعلامات أدق وأكثر تحديدًا، لن يكون هناك ما يمنع وجود تكرار في السجلات عند إضافة البيانات بهذه الطريقة. إدراج البيانات تلقائيًا يمكن تطبيق بعض السمات على الأعمدة لدى إنشاء جدول، والتي تجعل نظام إدارة قواعد البيانات العلاقيّ يملؤها بالبيانات تلقائيًا. للتوضيح، نفّذ التعليمة التالية لتعريف جدول باسم interns. إذ ستُنشئ هذه العملية جدولًا باسم interns يحتوي على ثلاثة أعمدة. العمود الأول في هذا المثال، وهو internID، يحتوي على بيانات من النمط int. ولكن لاحظ أنه يشمل أيضًا سمة AUTO_INCREMENT. هذه السمة ستجعل SQL تُنشئ قيمة رقمية فريدة تلقائيًا لكل سجل جديد، تبدأ افتراضيًا بالرقم 1 وتزداد تلقائيًا بخطوة مقدارها واحد مع كل سجل تالي. وبالمثل، يشمل تعريف العمود الثاني الذي يُدعى department الكلمة المفتاحية DEFAULT. ما سيجعل نظام إدارة قواعد البيانات العلاقيّ يدرج القيمة الافتراضية – وهي الكلمة 'production' في مثالنا - تلقائيًا وذلك في حال حذف العمود department من قائمة الأعمدة ضمن تعليمة INSERT INTO، على النحو التالي: mysql> CREATE TABLE interns ( mysql> internID int AUTO_INCREMENT PRIMARY KEY, mysql> department varchar(20) DEFAULT 'production', mysql> name varchar(30) mysql> ); ملاحظة: تعدّ السمة AUTO_INCREMENT ميزة خاصة بنظام إدارة قواعد البيانات MySQL، ولكن العديد من أنظمة إدارة قواعد البيانات العلاقيّة لديها طريقتها الخاصة لتحقيق التزايد في الأرقام الصحيحة. وللحصول على فهم أفضل حول كيفية تعامل نظام إدارة قواعد البيانات العلاقي مع مسألة الزيادة التلقائية، يُفضل الرجوع إلى التوثيق الرسمي الخاص بهذا النظام. وإليك التوثيق الرسمي بخصوص هذا الموضوع لبعض قواعد البيانات المفتوحة المصدر الشهيرة: توثيق سمة AUTO_INCREMENT لـ MySQL توثيق نمط البيانات serial لـ PostgreSQL توثيق الكلمة المفتاحية Autoincrement لـ SQLite. ولتوضيح هذه الميزات، لنحمّل الجدول interns ببعض البيانات، وذلك بتنفيذ تعليمة INSERT INTO التالية. إذ تُحدّد هذه العملية القيم للعمود name فقط: mysql> INSERT INTO interns (name) VALUES ('Pierre'), ('Sheila'), ('Francois'); الخرج Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 ومن ثمّ شغّل هذا الاستعلام ليعيد كافّة السجلات من الجدول: mysql> SELECT * FROM interns; الخرج +----------+------------+----------+ | internID | department | name | +----------+------------+----------+ | 1 | production | Pierre | | 2 | production | Sheila | | 3 | production | Francois | +----------+------------+----------+ 3 rows in set (0.00 sec) يُظهر هذا الخرج أنّه وبسبب تعريفات الأعمدة فإنّ التعليمة INSERT INTO السابقة أضافت قيمًا إلى العمودين internID وdepartment، على الرغم من عدم تحديدهما. لإضافة قيمة مختلفة عن تلك الافتراضية لعمود department، يجب تحديد ذلك العمود ضمن تعليمة INSERT INTO، كالتالي: mysql> INSERT INTO interns (name, department) mysql> VALUES mysql> ('Jacques', 'management'), mysql> ('Max', 'quality assurance'), mysql> ('Edith', 'management'), mysql> ('Daniel', DEFAULT); الخرج Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0 لاحظ أن السجل الأخير من القيم المُقدمة في هذا المثال يشتمل على الكلمة المفتاحية DEFAULT بدلًا من قيمة نصية. سيؤدي ذلك إلى جعل قاعدة البيانات تدرج القيمة الافتراضية ('production'). mysql> SELECT * FROM interns; الخرج +----------+-------------------+----------+ | internID | department | name | +----------+-------------------+----------+ | 1 | production | Pierre | | 2 | production | Sheila | | 3 | production | Francois | | 4 | management | Jacques | | 5 | quality assurance | Max | | 6 | management | Edith | | 7 | production | Daniel | +----------+-------------------+----------+ 7 rows in set (0.00 sec) الخلاصة تعرفت في هذا المقال على العديد من طرق إدراج البيانات في الجداول، بما في ذلك تحديد سجلات البيانات على نحوٍ فرديّ باستخدام الكلمة المفتاحية VALUES، ونسخ مجموعات كاملة من البيانات باستخدام استعلامات SELECT، وتعريف الأعمدة التي ستُدرج فيها SQL البيانات تلقائيًا. ومن الجدير بالذكر أن الأوامر التي شرحناها هنا يجب أن تعمل على أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن عليك مراعاة أن كل قاعدة بيانات SQL تستخدم تقديمًا فريدًا للّغة، لذلك يُفضّل الرجوع إلى التوثيقات الرسمية لنظام إدارة قواعد البيانات الخاص بك للحصول على توصيف أدق لكيفية التعامل مع تعليمة INSERT INTO والخيارات المتاحة لها. للمزيد حول كيفية التعامل مع SQL، ننصحك بالاطلاع على سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Constraints in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية استخدام القيود في SQL جلب الاستعلامات عبر SELECT في SQL التعامل مع البيانات (الإدخال، الحذف والتعديل) في SQL مدخل إلى SQL تحديث الجداول في SQL
  14. عند تصميم قاعدة بيانات باستخدام SQL، قد تُضطر أحيانًا إلى فرض قيود على نمط البيانات التي يمكن إضافتها إلى أعمدة معينة ضمن جدول. إذ توفّر لك SQL هذه الإمكانية من خلال ما يُعرف بالقيود constraints. فبمجرد تطبيق قيد على عمود أو جدول، ستفشل أي محاولة لإضافة بيانات لا تتوافق مع هذا القيد. ولعلّ لكل نظام من أنظمة قواعد البيانات التي تستخدم SQL طريقته الخاصة في التعامل مع القيود. يهدف هذا المقال إلى تقديم نظرة عامة حول الصيغة المُتبعة في العديد من أنظمة إدارة قواعد البيانات للتعامل مع القيود، مع التركيز على MySQL كمثال رئيسي في هذا الإطار. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز حاسوب يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العِلاقيَّة RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. MySQL مثبتة ومؤمنة على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقيَّة تستخدم تقديمات فريدة خاصة بها للغة SQL. فعلى الرغم من أن الأوامر المُوضحة في هذا المقال ستعمل على نحوٍ سليم في معظم هذه الأنظمة، ولكن قد تختلف الصياغة الدقيقة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، فمن المفيد أيضًا أن يكون لديك فهم عام حول قيود SQL وكيفية عملها. وللحصول على نظرة عامة حول هذا المفهوم، يمكنك الرجوع إلى مقال فهم قيود SQL. كما ستحتاج إلى قاعدة بيانات يمكنك استخدامها للتدرّب على إنشاء الجداول مع القيود. إذا لم تكن لديك قاعدة بيانات للتجربة، اطلع على القسم إعداد قاعدة بيانات تجريبية نموذجية والاتصال بها التالي للحصول على تفاصيل حول كيفية إنشائها. إعداد قاعدة بيانات تجريبية نموذجية والاتصال بها إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم عن بُعد، اتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو: $ ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p أنشئ قاعدة بيانات باسم constraintsDB: mysql> CREATE DATABASE constraintsDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات constraintsDB، نفّذ تعليمة USE التالية: mysql> USE constraintsDB; الخرج Database changed وبذلك تغدو جاهزًا للخوض في الخطوات التالية من مقالنا هذا لتنطلق في تعلّم كيفية إنشاء وإدارة الجداول في SQL. إنشاء الجداول مع القيود عادةً ما تُعرّف القيود أثناء إنشاء الجدول. فمثلًا تُنشئ الصيغة التالية لتعليمة CREATE TABLE جدولًا باسم employeeInfo (معلومات الموظفين) يحتوي على ثلاثة أعمدة: empId (لتخزين مُعرّف الموظف) وempName (لتخزين اسم الموظف) و empPhoneNum (لتخزين رقم هاتف الموظف). كما تُطبّق هذه التعليمة أيضًا القيد UNIQUE على العمود empId. ما سيمنع وجود أي قيم متطابقة فيه: mysql> CREATE TABLE employeeInfo ( mysql> empId int UNIQUE, mysql> empName varchar(30), mysql> empPhoneNum int mysql> ); تُعرّف هذه التعليمة القيد UNIQUE مباشرةً بعد العمود empId، ما يعني أن القيد ينطبق فقط على هذا العمود. فإذا حاولت إضافة أي بيانات إلى هذا الجدول، سيراقب نظام إدارة قواعد البيانات المحتوى الحالي للعمود empId للتأكد من أنّ القيم الجديدة التي تضيفها إليه فريدة بالفعل. وهذا ما يُسمّى بالقيد على مستوى العمود. كما من الممكن تطبيق القيد خارج تعريفات العمود. ففي المثال التالي، نُنشئ جدولًا باسم racersInfo (معلومات المتسابقين) يحتوي على ثلاثة أعمدة: racerId (لتخزين مُعرّف المتسابق) وracerName (لتخزين اسم المتسابق) وfinish (لتخزين ترتيب إنهاء المتسابق للسباق). وأسفل تعريفات الأعمدة، نُطبّق القيد CHECK على العمود finish لضمان أن ترتيب كل متسابق أكبر من أو يساوي 1 (إذ لا يمكن أن يكون ترتيب أي متسابق أقل من المركز الأول): mysql> CREATE TABLE racersInfo ( mysql> racerId int, mysql> finish int, mysql> racerName varchar(30), mysql> CHECK (finish > 0) mysql> ); ونظرًا لتطبيق القيد خارج تعريف أي من الأعمدة الفردية، فيتعين عليك تحديد اسم الأعمدة التي ترغب بتطبيق القيد عليها بين قوسين هلاليين. دائمًا في حال تحديد قيد خارج تعريفات الأعمدة الفردية، نُسمّي هذا القيد بقيد على مستوى الجدول. فالقيود على مستوى العمود تنطبق فقط على الأعمدة الفردية، في حين قد تُطبّق قيود الجدول على عدة أعمدة. تسمية القيود عندما تحدد قيدًا، يولد نظام إدارة قواعد البيانات العِلاقيَّة اسمًا له تلقائيًا. يُستخدم هذا الاسم للإشارة إلى القيد في رسائل الخطأ وفي الأوامر المستخدمة لإدارة القيود. قد يكون من المريح لمدراء قواعد البيانات في بعض الأحيان توفير اسماء خاصة للقيود. فعادةً لا تكون أسماء القيود المُنشأة تلقائيًا وصفية، لذلك قد يساعد توفير الاسم بنفسك في تذكر الغرض من القيد. لتسمية قيد، ضع الكلمة المفتاحية CONSTRAINT متبوعة بالاسم الذي تختاره وذلك قبل نوع القيد. فمثلًا تعيد التعليمات التالية إنشاء جدول racersInfo مع تسميته newRacersInfo وإضافة الاسم noNegativeFinish للقيد CHECK: mysql> CREATE TABLE newRacersInfo ( mysql> racerId int, mysql> finish int, mysql> racerName varchar(30), mysql> CONSTRAINT noNegativeFinish mysql> CHECK (finish >= 1) mysql> ); ملاحظة: إذا لم تُحدّد اسمًا للقيد، أو حددته ونسيته لاحقًا، فمن المحتمل أن تعثر عليه بالرجوع إلى تخطيطات معلومات قاعدة البيانات information schemas لنظام إدارة قواعد البيانات الخاص بك. إذ توفّر العديد من أنظمة قواعد البيانات الحديثة وعملاؤها اختصارًا لعرض تعليمات CREATE الداخلية والتي تشير إلى اسم القيد. وفيما يلي روابط التوثيقات الرسمية لهذه الاختصارات لكل من MySQL وPostgreSQL: MySQL: تتضمّن MySQL التعليمة SHOW CREATE TABLE، والتي تعيد كامل تعليمة CREATE TABLE التي أنشأت الجدول المطوب، على النحو: mysql> SHOW CREATE TABLE table_name; PostgreSQL: يتضمن عميل PostgreSQL المُسمّى psql على العديد من الخيارات التي يمكنك استخدامها للكشف عن معلومات حول جدول معين. فالخيار d\ مثلًا يعيد بياناتٍ وصفية حول الجدول المطلوب: Postgres=# \d table_name إدارة القيود يمكنك إضافة القيود إلى الجداول الموجودة أصلًا في MySQL أو حذفها باستخدام أوامر ALTER TABLE. على سبيل المثال، الأمر التالي يضيف قيد UNIQUE إلى العمود empName في الجدول employeeInfo المُنشأ مسبقًا: mysql> ALTER TABLE employeeInfo ADD UNIQUE (empName); عند إضافة قيد إلى جدول موجود، يمكنك أيضًا استخدام الكلمة المفتاحية CONSTRAINT لتوفير اسم لتعريف القيد. فالأمر في المثال التالي يضيف قيد UNIQUE باسم uID إلى العمود racerId من الجدول racersInfo المُنشأ مسبقًا: mysql> ALTER TABLE racersInfo ADD CONSTRAINT uID UNIQUE (racerId); وستفشل التعليمة ALTER TABLE إذا أدخلت أي سجلات قد تنتهك شرط القيد الجديد قبل إضافته وفق هذه الطريقة. لحذف قيد، استخدم الصيغة DROP CONSTRAINT، متبوعة باسم القيد الذي ترغب في حذفه. فمثلًا الأمر التالي سيحذف القيد uID المُنشأ في الأمر السابق: mysql> ALTER TABLE racersInfo DROP CONSTRAINT uID; الخلاصة تعلمت في هذا المقال كيفية إضافة وحذف القيود للأعمدة والجداول باستخدام SQL. ومن الجدير بالذكر أن الأوامر التي شرحناها هنا يجب أن تعمل على أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن عليك مراعاة أن كل قاعدة بيانات SQL تستخدم تقديمًا فريدًا للّغة، لذلك يُفضّل الرجوع إلى التوثيقات الرسمية لنظام إدارة قواعد البيانات الخاص بك للحصول على توصيف أدق لكل أمر والخيارات المتاحة. للمزيد حول كيفية التعامل مع SQL، ننصحك بالاطلاع على سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Use Constraints in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: كيفية إنشاء وإدارة الجداول في SQL نظرة سريعة على لغة الاستعلامات الهيكلية SQL مدخل إلى أهم الاستعلامات (queries) في MySQL مدخل إلى برنامج إدارة قواعد البيانات MySQL
  15. الجداول هي الهياكل التنظيمية الأساسية في قواعد بيانات SQL. وهي تتكون من عدد من الأعمدة التي تعكس السمات الفردية لكل صف أو سجل في الجدول. ولعلّ من المهم لكل من يعمل مع قواعد البيانات العلاقية أن يفهم كيفية إنشاء وتغيير وحذف الجداول حسب الحاجة كونها جزءًا أساسيًا من آلية تنظيم البيانات. سنتناول في هذا المقال كيفية إنشاء الجداول في SQL، بالإضافة إلى كيفية تعديل وحذف الجداول الموجودة أصلًا في قاعدة البيانات. مستلزمات العمل لمتابعة الخطوات في هذا المقال، ستحتاج إلى جهاز كمبيوتر يُشغّل أحد أنواع أنظمة إدارة قواعد البيانات العلاقية RDBMS التي تستخدم SQL. وقد اختبرنا الأوامر البرمجية والأمثلة في هذا المقال مستخدمين البيئة التالية: خادم عامل على توزيعة أوبنتو، مع مستخدم ذو صلاحيات مسؤول مختلف عن المستخدم الجذر، وجدار حماية مكوّن باستخدام UFW، كما هو موضح في مقال كيفية تثبيت توزيعة أوبنتو من لينكس بأبسط طريقة. نظام إدارة قواعد البيانات MySQL مثبت على الخادم، كما هو موضح في المقال كيفية تثبيت MySQL على أوبونتو. وقد نفذنا خطوات هذا المقال باستخدام مستخدم MySQL مختلف عن المستخدم الجذر، مُنشأ وفق الطريقة الموضحة في الخطوة 3 من هذا المقال. ملاحظة: تجدر الإشارة إلى أنّ الكثير من أنظمة إدارة قواعد البيانات العلاقية لها تقديماتها الفريدة من لغة SQL. فبالرغم من كون الأوامر المُقدمة في هذا المقال ستعمل مع معظم هذه الأنظمة، ولكن قد تجد بعض الاختلافات الطفيفة في الصيغة أو الناتج عند تنفيذها على أنظمة مختلفة عن MySQL. وبالعودة إلى مستلزمات العمل، ستحتاج أيضًا إلى قاعدة بيانات وجدول مُحمّل ببعض البيانات التجريبية النموذجية لتتمكن من التدرب على استخدام محارف البدل. وإذا لم تكن متوفرة لديك، يمكنك مراجعة الفقرة التالية الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية للمزيد من التفاصيل حول كيفية إعداد قاعدة بيانات وجدول لاستخدامهما في الأمثلة خلال هذا المقال. الاتصال بـ MySQL وإعداد قاعدة بيانات تجريبية نموذجية إذا كان نظام قاعدة بيانات SQL الخاص بك يعمل على خادم بعيد، فاتصل بالخادم مُستخدمًا بروتوكول SSH من جهازك المحلي على النحو التالي: $ ssh user@your_server_ip ثم افتح واجهة سطر الأوامر في خادم MySQL، مُستبدلًا user باسم حساب مستخدم MySQL الخاص بك: $ mysql -u user -p الآن أنشئ قاعدة بيانات باسم tablesDB: mysql> CREATE DATABASE tablesDB; وبمجرّد إنشاء قاعدة البيانات بنجاح ستحصل على خرجٍ كالتالي: الخرج Query OK, 1 row affected (0.01 sec) ولاختيار قاعدة البيانات tablesDB، نفّذ تعليمة USE التالية: mysql> USE tablesDB; الخرج Database changed وبذلك تغدو جاهزًا لتجربة الخطوات التالية من مقالنا هذا لتنطلق في تعلّم كيفية إنشاء وإدارة الجداول في SQL. إنشاء الجداول لإنشاء جدول في SQL، استخدم الأمر CREATE TABLE متبوعًا بالاسم الذي ترغب بتسمية الجدول به: mysql> CREATE TABLE table_name; وكما هو الحال مع كل تعليمات SQL، انتبه إلى وجوب انتهاء تعليمات CREATE TABLE برمز الفاصلة المنقوطة ;. تُنشئ الصيغة في المثال السابق جدولًا فارغًا بدون أي أعمدة. أمّا لإنشاء جدول يتضمّن أعمدة محددة، فيجب أن نُتبع اسم الجدول بقائمة تتضمن أسماء الأعمدة وأنماط البيانات الموافقة لكل منها والقيود المتعلقة بها، محصورةً بين أقواس هلالية ومفصولة برمز الفاصلة، على النحو التالي: mysql> CREATE TABLE table_name ( mysql> column1_name column1_data_type, mysql> column2_name column2_data_type, mysql> . . . mysql> columnN_name columnN_data_type mysql> ); كمثال، لنفترض أنك ترغب بإنشاء جدول لتسجيل بعض المعلومات حول المنتزهات التي تفضلها في مدينة نيويورك. فبعد اتخاذ قرار بشأن السمات التي تود تسجيلها حول كل منتزه، ستحدّد أسماء الأعمدة لكل من تلك السمات وكذلك نمط البيانات المناسب لكل منها: parkName: اسم كل منتزه. ونظرًا لوجود تباين واسع في أطوال أسماء المنتزهات، سيكون استخدام نمط البيانات varchar بطول أعظمي يبلغ 30 محرفًا مناسبًا. yearBuilt: السنة التي بُني فيها المنتزه. على الرغم من أنّ MySQL تتضمّن نمط بيانات يُسمّى year (لتخزين السنوات الميلادية)، إلّا أنّه يسمح فقط بقيم تتراوح بين 1901 و 2155. ولأن هناك عدة منتزهات في مدينة نيويورك بُنيت قبل عام 1901، لذلك من الأفضل استخدام نمط بيانات الأعداد الصحيحة int كبديل. firstVisit: تاريخ أول زيارة لك لكل منتزه. تتضمّن MySQL نمط بيانات date لتخزين التواريخ، ومن المناسب استخدامه لهذا العمود، إذ يُخزّن البيانات بالتنسيق YYYY-MM-DD (اليوم بخانتين-الشهر بخانتين-السنة بأربع خانات). lastVisit: تاريخ زيارتك الأخيرة لكل منتزه. ويمكنك هنا استخدام نمط البيانات date مجددًا. ولإنشاء جدول باسم faveParks يحتوي على أعمدة بهذه الأسماء وأنماط البيانات، نفّذ الأمر التالي: mysql> CREATE TABLE faveParks ( mysql> parkName varchar(30), mysql> yearBuilt int, mysql> firstVisit date, mysql> lastVisit date mysql> ); الخرج Query OK, 0 rows affected (0.01 sec) ومن الجدير بالملاحظة أن الأمر أعلاه يُنشئ هيكلية الجدول فقط، إذ أنّك لم تُضف أي بيانات إلى الجدول بعد. كما يمكنك إنشاء جداول جديدة استنادًا إلى جداول موجودة أصلًا باستخدام الصيغة CREATE TABLE AS على النحو التالي: mysql> CREATE TABLE new_table_name AS ( mysql> SELECT column1, column2, . . . columnN mysql> FROM old_table_name mysql> ); فبدلًا من إلحاق اسم الجدول الجديد (new_table_name) بقائمة من أسماء الأعمدة وأنماط البيانات الموافقة، نُتبعه بتعليمه AS ومن ثم تعليمة SELECT بين قوسين هلاليين والتي تُرجع الأعمدة والبيانات التي نرغب في نسخها من الجدول الأصلي (old_table_name) إلى الجدول الجديد. ومن الجدير بالملاحظة أنّه إذا كانت أعمدة الجدول الأصلي تتضمّن أي بيانات، فسيتم نسخ تلك البيانات إلى الجدول الجديد أيضًا. وللتوضيح، تشمل صيغة المثال أعلاه استعلام باستخدام SELECT والذي يحتوي فقط على بنية FROM المطلوبة. ولكن أي تعليمة صحيحة من تعليمات SELECT ستعمل على نحوٍ سليم في هذا المكان. ولمزيد من التوضيح، يُنشئ الأمر التالي جدولًا باسم parkInfo من خلال استخدام عمودين من الجدول faveParks المُنشأ سابقًا: mysql> CREATE TABLE parkInfo AS ( mysql> SELECT parkName, yearBuilt mysql> FROM faveParks mysql> ); الخرج Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 فإذا كان الجدول faveParks يحتوي على أي بيانات، فستنسخ البيانات الموجودة في أعمدة parkName وyearBuilt إلى جدول parkInfo أيضًا. ولكن في حالتنا، كلا الجدولين سيكون فارغًا. أمّا إذا حاولت إنشاء جدول مُستخدمًا اسم جدول موجود مُسبقًا من خلال التعليمات التالية: mysql> CREATE TABLE parkInfo ( mysql> name varchar(30), mysql> squareFootage int, mysql> designer varchar(30) mysql> ); الخرج ERROR 1050 (42S01): Table 'parkInfo' already exists فسيؤدي ذلك إلى وقوع خطأ يشير إلى أنّ الاسم parkinfo موجود أصلًا. ولتجنب هذا الخطأ، يمكنك إضافة الخيار IF NOT EXISTS ضمن أمر CREATE TABLE الخاص بك. إذ سيُخبر هذا الخيار قاعدة البيانات بالتحقق من وجود جدول موجود مسبقًا بنفس الاسم المحدد، ففي حال كان موجودًا، سيظهر تحذير بدلًا من رسالة الخطأ. mysql> CREATE TABLE IF NOT EXISTS parkInfo ( mysql> name varchar(30), mysql> squareFootage int, mysql> designer varchar(30) mysql> ); الخرج Query OK, 0 rows affected, 1 warning (0.00 sec) سيفشل هذا الأمر في إنشاء جدول جديد، إذ أنّ الجدول بالاسم parkInfo ما زال موجودًا أصلًا. ولكن نلاحظ أن الخرج يشير إلى كون تعليمة CREATE TABLE أدت إلى ظهور تحذير. ولعرض رسالة التحذير، نفّذ الأمر التشخيصي SHOW WARNINGS كالتالي: mysql> SHOW WARNINGS; الخرج | Level | Code | Message | +-------+------+---------------------------------+ | Note | 1050 | Table 'parkInfo' already exists | +-------+------+---------------------------------+ 1 row in set (0.00 sec) وكما يشير هذا الخرج، فقد سُجّل نفس الخطأ الذي تلقيته سابقًا على أنه تحذير، ذلك لأنك قد ضمّنت الخيار IF NOT EXISTS. وهذا الأمر قد يكون مفيدًا في بعض الحالات، كما هو الحال عند تنفيذ المعاملات Transactions التي تمثل سلسلة من عمليات SQL تُجرى على قاعدة بيانات وتعامل كما لو كانت عملية واحدة، بحيث إما أن تُنفَّذ جميعها أو لا تنفذ بأكملها، في حين يشير التحذير إلى فشل التعليمة الذي تسببت به فقط. تعديل الجداول قد تضطر في بعض الحالات إلى تغيير تعريف جدول معين وهذا الأمر مختلف عن تحديث البيانات ضمن الجدول فهو يعني تغيير هيكلية الجدول نفسه. وللقيام بذلك، يمكنك استخدام الصيغة ALTER TABLE على النحو التالي: mysql> ALTER TABLE table_name ALTER_OPTION sub_options . . . ; وبعد تضمين تعليمة ALTER TABLE، حدّد اسم الجدول الذي تود تعديله. ومن ثم مرّر الخيارات المتاحة بناءً على نظام إدارة قواعد البيانات الخاص بك لإجراء التعديل المطلوب. على سبيل المثال، قد ترغب في تغيير اسم الجدول أو إضافة عمود جديد أو حذف عمود قديم أو تغيير تعريف أحد الأعمدة. لنطبق بعض الأمثلة على جدول المنتزهات المُفضّلة faveParks الذي أنشأناه سابقًا في قسم إنشاء الجداول لمزيد من التدريب على كتابة تعليمات التعامل مع الجداول. لتغيير اسم الجدول faveParks، يمكنك استخدام صيغة RENAME TO. يغير المثال التالي اسم جدول faveParks إلى faveNYCParks: تحذير: كن حذرًا عند تغيير اسم جدول. فالقيام بذلك قد يسبب مشكلات في حال كان هناك تطبيق يستخدم الجدول أو إذا كانت هناك جداول أخرى في قاعدة البيانات تشير إليه. mysql> ALTER TABLE faveParks RENAME TO faveNYCParks; الخرج Query OK, 0 rows affected (0.01 sec) لإضافة عمود جديد للجدول، استخدم الخيار ADD COLUMN. يضيف المثال التالي عمود بالاسم borough، والذي يُخزّن بيانات من النمط varchar، ولكن بطول أقصى يبلغ 20 محرفًا إلى الجدول faveNYCParks: mysql> ALTER TABLE faveNYCParks ADD COLUMN borough varchar(20); الخرج Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 لحذف عمود من جدول مع أي بيانات يحتوي عليها، يمكنك استخدام الصيغة DROP TABLE. يحذف الأمر في المثال التالي العمود borough: mysql> ALTER TABLE faveNYCParks DROP COLUMN borough; تسمح العديد من تقديمات SQL بتغيير تعريف العمود باستخدام الصيغة ALTER TABLE. ويستخدم المثال التالي بنية MODIFY COLUMN في MySQL، إذ يقوم بتغيير العمود yearBuilt لاستخدام بيانات من النمط smallint بدلًا من النمط int الأصلي: mysql> ALTER TABLE faveNYCParks MODIFY COLUMN yearBuilt smallint; ومما يجب أخذه في الحسبان أنّ كل نظام إدارة قواعد بيانات يتضمّن خيارات مختلفة بخصوص ما يمكنك تغييره باستخدام تعليمة ALTER TABLE. ولتحقيق فهمٍ كامل حول ما يمكنك تنفيذه باستخدام ALTER TABLE, راجع التوثيق الرسمي لنظام إدارة قواعد البيانات الذي تستخدمه لمعرفة الخيارات المتاحة للتعليمة. إليك التوثيق الرسمي حول هذا الموضوع لبعضٍ من أشهر قواعد البيانات مفتوحة المصدر: توثيق ALTER TABLE في MySQL توثيق ALTER TABLE في PostgreSQL توثيق ALTER TABLE في SQLite حذف الجداول لحذف جدول وجميع البيانات الموجودة فيه، استخدم الصيغة DROP TABLE: تحذير: كن حذرًا عند تنفيذ الأمر DROP TABLE، إذ سيقوم بحذف الجدول وجميع بياناته نهائيًا. mysql> DROP TABLE table_name; كما يمكنك حذف عدة جداول باستخدام تعليمة DROP واحدة وذلك بفصل أسماء الجداول برمز فاصلة ومحرف مسافة، كما في المثال التالي: mysql> DROP TABLE table1, table2, table3; للتوضيح، سيحذف الأمر التالي كل من جدولي faveNYCParks و parkInfo المُنشأين سابقًا في هذا المقال: mysql> DROP TABLE IF EXISTS faveNYCParks, parkInfo; لاحظ أن هذا المثال يتضمن الخيار IF EXISTS، والذي يؤدي الوظيفة المعاكسة لخيار IF NOT EXISTS المتاح لأمر إنشاء الجداول CREATE TABLE. إذ سيجعل خيار IF EXISTS تعليمة DROP TABLE تعيد تحذيرًا بدلًا من رسالة خطأ في حال لم يكن أحد الجداول المُحدّدة موجودًا. الخلاصة تعلمت في هذا المقال كيفية إنشاء وتغيير وحذف الجداول في قواعد البيانات المبنية على SQL. ومن الجدير بالذكر أن الأوامر التي شرحناها هنا يجب أن تعمل على أي نظام لإدارة قواعد البيانات يستخدم SQL. لكن عليك مراعاة أن كل قاعدة بيانات SQL تستخدم تقديمًا فريدًا للّغة، لذلك يُفضّل الرجوع إلى التوثيقات الرسمية لنظام إدارة قواعد البيانات الخاص بك للحصول على توصيف أدق لكل أمر والخيارات المتاحة. للمزيد حول كيفية التعامل مع SQL، ننصحك بالاطلاع على سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال How To Create and Manage Tables in SQL لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: فهم قيود SQL المرجع المتقدم إلى لغة SQL تصميم الجداول ومعلومات المخطط وترتيب تنفيذ الاستعلامات في SQL حذف الجداول وقواعد البيانات في SQL أهمية قواعد البيانات
  16. لدى تصميمك لقاعدة بيانات، قد تحتاج أحيانًا إلى وضع قيود على البيانات المسموح إدخالها ضمن أعمدة معينة في هذه القاعدة. على سبيل المثال، إذا كنت تنوي تصميم جدول لتخزين معلومات حول ناطحات سحاب، فلا بدّ من أنك سترغب في منع إدخال قيم سالبة في العمود الخاص بارتفاع كل مبنى. تُمكنّك أنظمة إدارة قواعد البيانات العلاقية RDBMSs من التحكّم بالبيانات التي يُسمح بإضافتها إلى الجدول من خلال ما يُسمّى بالقيود constraints. والقيد عبارة عن قاعدة خاصة تُفرض على عمود واحد أو أكثر، أو حتى على الجدول كاملًا، وتحدد هذه القاعدة التغييرات المتاح إجراؤها على بيانات الجدول، سواء كان هذا التغيير من هو أمر إدراج بيانات جديدة INSERT، أو تحديث بيانات موجوة UPDATE، أو حذف بيانات معينة DELETE. سنعرض في هذا المقال مفهوم القيود وكيفية استخدامها في أنظمة RDBMS بالتفصيل، كما سنستعرض القيود الخمسة المُعرفّة في معايير SQL ونشرح الوظيفة الأساسية لكل منها. ما هي القيود؟ القيد constraint في SQL هو أي قاعدة تُطبق على عمود أو جدول بهدف تحديد طبيعة البيانات التي يمكن إدخالها فيه. فعند كل محاولة لإجراء عملية من شأنها تغيير البيانات الموجودة في جدول – من خلال أمر إدراج INSERT أو تحديث UPDATE أو حذف DELETE للبيانات مثلًا – ستتحقق نظم إدارة قواعد البيانات العلاقية فيما إذا كانت هذه البيانات تخالف أي من القيود الموجودة أم لا، وستعيد خطأ إذا كانت تخالفها بالفعل أو ستنفذها في حال كانت تحقق المعايير المطلوبة. يعتمد مسؤولو قواعد البيانات على القيود لضمان اتباع قاعدة البيانات لمجموعة مُحددة من قواعد وتوجيهات العمل، وفي إطار قواعد البيانات، تُعرّف القاعدة بأنها أي سياسة أو إجراء تتبعه الشركة أو أي منظمة أخرى لتحدد من خلالها كيفية تخزين البيانات في قاعدة البيانات، إذ يجب أن تتوافق بياناتها مع هذه القواعد تمامًا. على سبيل المثال، إذا كنت تعمل على تطوير قاعدة بيانات لرصد مخزون متجر لأحد العملاء وطلب منك هذا العميل التأكد من وجود رقم تعريف فريد لكل سجل (منتج)، فبإمكانك وضع القيد UNIQUE (المُعبّر عن كون كل سجل في العمود المعني فريد) على العمود المُخصص للرقم التعريفي لضمان عدم وجود أي تكرار في القيم المدخلة لأرقام المنتجات المختلفة. تلعب القيود دورًا مهمًا في الحفاظ على دقة وصحة البيانات أو ما يعرف بسلامة البيانات data integrity. إذ يُشير مصطلح سلامة البيانات إلى مدى دقة واتساق ومنطقية البيانات المحفوظة في قاعدة البيانات وفقًا لسياق استخدامها. فغالبًا ما تكون الجداول ضمن قاعدة البيانات مرتبطة ببعضها البعض، فقد تعتمد بعض الأعمدة في جدول معين على القيم الموجودة في جدول آخر. ونظرًا لأن الأخطاء البشرية واردة الحدوث أثناء إدخال البيانات، فإن وضع القيود أمر أساسي في مثل هذه الحالات، إذ أنها تضمن عدم تأثير البيانات المُدخلة على نحوٍ خاطئ على هذه العلاقات بما قد يؤثّر سلبًا بالنتيجة على سلامة البيانات. بفرض أنك تُصمّم قاعدة بيانات تتضمّن جدولين: الأول لإدراج الطلاب الحاليين في مدرسة ما، والثاني لإدراج أعضاء فريق كرة السلة في تلك المدرسة. يمكنك في هذه الحالة تطبيق قيد FOREIGN KEY (قيد المفتاح الخارجي) على أحد أعمدة جدول فريق كرة السلة ليشير إلى أحد أعمدة جدول الطلاب. وهذا ما سينشئ علاقة بين الجدولين، إذ يتطلب أي إدخال جديد في جدول الفريق أن يشير إلى إدخال موجود بالفعل في جدول الطلاب. ولدى تصميم جدول في قاعدة البيانات، يمكن للمستخدمين تحديد القيود أثناء إنشاء هذا الجدول أو إضافتها لاحقًا باستخدام إحدى تعليمات ALTER TABLE، بشرط ألّا تتعارض هذه القيود المُضافة مع البيانات الموجودة أصلًا في الجدول. وعند إنشاء قيد جديد، سيولّد نظام قاعدة البيانات تلقائيًا اسمًا له، ولكن وفي العديد من تطبيقات SQL يمكنك تحديد اسم مُخصّص للقيد. إذ يُستخدم هذا الاسم للإشارة إلى القيد ضمن مجموعة التعليمات ALTER TABLE المُتعلّقة بتعديل القيد أو إزالته. يُعرّف المعيار الرسمي للغة SQL خمس قيود فقط وهي: قيد المفتاح الأساسي PRIMARY KEY قيد المفتاح الخارجي FOREIGN KEY قيد القيم الفريدة UNIQUE قيد التحقق CHECK القيد غير فارغ NOT NULL ملاحظة: تتضمّن العديد من نظم إدارة قواعد البيانات العلاقية الكلمة المفتاحية DEFAULT لتحديد قيمة ابتدائية افتراضية غير فارغة NULL تسند لسجلات العمود في حال عدم تحديد قيمة لحظة إضافة السجل. وتصف توثيقات بعض قواعد البيانات DEFAULT على أنها قيد، نظرًا لأن طريقة استخدامها تشابه لطريقة استخدام القيود الأخرى مثل UNIQUE أو CHECK. ولكن ومن الناحية التقنية، لا تعدّ DEFAULT قيدًا حقيقيًا لأنها لا تُحدّد أو تقيّد القيم التي يمكن إدخالها في العمود. بالعودة إلى موضوعنا، وبعد حصولك على فهمٍ عام حول كيفية استخدام القيود، لنلقِ نظرة أعمق على كل من هذه القيود الخمسة. القيد PRIMARY KEY يتطلب قيد المفتاح الأساسي PRIMARY KEY أن يكون كل مُدخل في العمود المعني غير فارغ وغير مكرر، سامحًا لك باستخدام هذا العمود لتعريف كل سجل في الجدول على نحوٍ فريد. والمفتاح key عمومًا في النموذج العلاقي عبارة عن عمود أو مجموعة من الأعمدة في جدول تضمن أن كل قيمة فيها فريدة وغير فارغة. أمّا المفتاح الأساسي فهو مفتاح خاص يستخدم لتعريف وتمييز سجلات الجدول على نحوٍ فردي، ويمكن استخدام العمود أو الأعمدة التي تكوّن مفتاح أساسي لتعريف الجدول وتحديده في باقي أجزاء قاعدة البيانات. وهذا جانب مهم في قواعد البيانات العلاقية فبوجود المفتاح الأساسي، لا يُضطر المستخدمون لمعرفة مكان تخزين البيانات فيزيائيًا على الجهاز، إذ يمكن لنظام إدارة قاعدة البيانات أن يتتبّع كل سجل ويسترجعه حسب الطلب. ما يعني بدوره عدم وجود ترتيب منطقي محدد للسجلات، وبالتالي يتمكّن المستخدمون من استرجاع بياناتهم بأي ترتيب يفضلونه أو وفق أي مُرشحات (عوامل تصفية) يختارونها. يمكنك إنشاء مفتاح أساسي في SQL باستخدام القيد PRIMARY KEY، والذي يُعدّ بالأصل مزيجًا من القيدين UNIQUE و NOT NULL. وبعد تعريف مفتاح أساسي، يُنشئ نظام إدارة قاعدة البيانات تلقائيًا فهرس index مرتبط به. والفهرس هو هيكلية في قاعدة البيانات تساعد في استرجاع البيانات من جدول ما بسرعة أكبر، وما على الاستعلامات إلّا استعراض الإدخالات الموافقة للعمود المُفهرس للعثور على القيم المترابطة (التابعة لسجل واحد) بآلية مُشابهة لاستخدام الفهرس في الكتب النصيّة العادية. الأمر الذي يسمح للمفتاح الأساسي بلعب دور المُعرّف لكل سجل في الجدول. يمكن لكل جدول أن يتضمّن مفتاح أساسي واحد فقط، والذي قد يشتمل على عدّة أعمدة كما هو الحال بالنسبة للمفاتيح النمطية regular keys، ولكن من مميزات المفاتيح الأساسية هو استعانتها بأقل عدد ممكن من السمات لتمييز كل سجل في الجدول على نحوٍ فريد. ولتبسيط هذه الفكرة، تخيل جدولًا يُخزّن معلومات الطلاب في مدرسة ما من خلال ثلاثة أعمدة كالتالي: studentID: يُستخدم لتخزين رقم التعريف (مُعرّف) الخاص بكل طالب. firstName: يُستخدم لتخزين الاسم الأول لكل طالب. lastName: يُستخدم لتخزين اسم العائلة لكل طالب. ولكن قد تتطابق الأسماء الأولى لبعض الطلاب في المدرسة، ما يجعل من العمود firstName خيارًا غير مناسب للاستخدام كمفتاح أساسي. وينطبق الأمر نفسه على العمود lastName. وبالتالي، قد يكون من المناسب استخدام مفتاح أساسي يتكون من الاسم الأول والأخير معًا، ولكن يبقى احتمال وجود طلاب بنفس الاسم الكامل قائمًا. وبالتالي، يمكن الاعتماد على مفتاح أساسي يتألف من عمود رقم تعريف الطالب studentID بالإضافة إلى عمود الاسم الأول firstName أو عمود الاسم الأخير lastName. ولكن وبما أنّ رقم تعريف كل طالب هو قيمة فريدة، فإنّ إضافة أحد أعمدة الأسماء للمفتاح الأساسي لا يُمثّل أي قيمة مُضافة. ولهذا، يعدّ استخدام عمود رقم تعريف الطالب وحده الخيار الأمثل ليكون المفتاح الأساسي لهذا الجدول. إذا كان المفتاح مكوّنًا من بيانات تطبيقية واقعية (أي بيانات تُمثّل كيانات أو أحداث أو سمات من العالم الواقعي)، فيُطلق عليه اسم مفتاح طبيعي natural key. أمّا إذا أُنشئ المفتاح داخليًا دون أن يُمثّل أي شيء خارج قاعدة البيانات، فيُعرف حينها باسم مفتاح بديل surrogate أو مفتاح مُصنّع synthetic key. تُوصي بعض أنظمة قواعد البيانات بتجنب استخدام المفاتيح الطبيعية، فحتى البيانات التي تبدو ثابتة قد تتغير بطرق غير متوقعة. القيد FOREIGN KEY يفرض القيد FOREIGN KEY أن تكون كل قيمة في العمود المعني موجودة بالفعل في عمود معين من جدول آخر. بفرض لديك جدولين، ووددت ربطهما ببعضهما البعض، فإحدى الطرق التي يمكنك استخدامها لهذا الغرض هي تعريف مفتاح خارجي باستخدام القيد FOREIGN KEY. والمفتاح الخارجي هو عمود في جدول معين (ويطلق عليه الجدول "الابن") تأتي قيمه من مفتاح في جدول آخر (ويطلق عليه "الأب" أو الجدول الرئيسي). وهذا ما يُعبّر عن علاقة بين الجدولين: فالقيد FOREIGN KEY يفرض أن تكون القيم في العمود المعني موجودة بالفعل في العمود المُشار إليه من الجدول الآخر. يُظهر المخطط التالي هذا النوع من العلاقة بين جدولين: إذ يُستخدم الأول لتسجيل معلومات حول الموظفين في شركة، ويُستخدم الثاني لتتبع مبيعات الشركة، وقد استخدمنا في هذا المثال مفتاح خارجي في جدول المبيعات SALES يشير إلى المفتاح الأساسي من جدول الموظفين EMPLOYEES: فإذا حاولت إضافة سجل إلى الجدول الابن مع كون القيمة المُدخلة في عمود المفتاح الخارجي من هذا السجل غير موجودة في عمود المفتاح الأساسي للجدول الأب فستكون تعليمة الإدراج هذه غير صالحة. الأمر الذي يضمن الحفاظ على نزاهة مستوى العلاقة بين الجدولين، بضمان ربط السجلات في الجدولين على النحو الصحيح دائمًا. فغالبًا ما يكون المفتاح الخارجي للجدول الابن هو المفتاح الأساسي للجدول الأب، ولكن الحال ليس على هذا النحو دائمًا. ففي معظم أنظمة إدارة قواعد البيانات العلاقية يمكن للمفتاح الخارجي في الجدول الابن الإشارة إلى أي عمود مُطبّق عليه أحد القيدين PRIMARY KEY أو UNIQUE في الجدول الأب. القيد UNIQUE يمنع القيد UNIQUE إضافة قيم مكررة إلى العمود المعني، وكما يوحي اسمه، يتطلب هذا القيد أن تكون كل قيمة تقوم بإدخالها في العمود المعني فريدةـ وأي محاولة لإضافة قيمة موجودة بالفعل فيه ستؤدي إلى ظهور خطأ. تفيد القيود UNIQUE في فرض علاقات من نوع واحد-إلى-واحد one-to-one بين الجداول. فرغم إمكانية تكوين علاقات بين الجداول باستخدام المفتاح الخارجي كما أشرنا سابقًا، إلّا أنّ هناك عدّة أنواع من العلاقات التي قد تظهر بين الجداول: واحد-إلى-واحد: نقول بأنّ علاقة من نوع واحد-إلى-واحد تربط بين جدولين إذا كان كل سجل في الجدول الأب مرتبط بسجل واحد فقط في الجدول الابن. واحد-إلى-عديد: في علاقة واحد-إلى-عديد، يمكن أن يرتبط كل سجل في الجدول الأب مع عدة سجلات في الجدول الابن، لكن كل سجل في الجدول الابن يمكن أن يرتبط بسجل واحد فقط من الجدول الأب. عديد-إلى-عديد: نقول بأنّ علاقة من نوع عديد-إلى-عديد تربط بين الجدولين إذا كان بإمكان السجلات في الجدول الأب أن ترتبط مع عدّة سجلات من الجدول الابن، والعكس صحيح. بإضافة القيد UNIQUE إلى عمود قد طُبّق عليه القيد FOREIGN KEY أيضًا، يمكنك التأكد من أن كل مُدخل في الجدول الأب يظهر مرة واحدة فقط في الجدول الابن، وبذلك تكون قد أنشأت علاقة من نوع واحد-إلى-واحد بين الجدولين. ومن الجدير بالذكر أنّه يمكن تعريف القيود UNIQUE على مستوى الجدول أو على مستوى العمود. فعند تعريف القيد UNIQUE على مستوى الجدول، يمكن أن يشمل حينها أكثر من عمود. وفي مثل هذه الحالات، يمكن لكل عمود مُدرج ضمن القيد أن يحتوي على قيم مكررة، ولكن يجب أن تكون لكل سجل مجموعة فريدة من القيم في الأعمدة الخاضعة للقيد. القيد CHECK يُعرّف القيد CHECK قاعدة يجب أن تُطبق على عمود معين على هيئة عبارة شرطية predicate ويجب أن تُحقق جميع القيم المُدخلة في ذلك العمود الشرط المسند لهذا العمود. حيث تُكتب شروط القيد CHECK بشكل تعبير شرطي يأخذ القيمة TRUE (صحيح/مُحقق) أو FALSE (خاطئ/غير مُحقق)، أو حتى UNKNOWN (غير معروف وهو ما يحدث في حالة وجود قيم فارغة NULL في العمود). فإذا حاولت إدخال قيمة في عمود خاضع للقيد CHECK وتسببت هذه القيمة في تقييم الشرط إلى TRUE أو UNKNOWN فستنجح العملية وتخزن القيمة في العمود، وإذا تم تقييم التعبير إلى FALSE ستفشل ولن يسمح لك بإدخالها. وتعتمد شروط القيد CHECK في كثير من الأحيان على عامل مقارنة رياضي (من قبيل <، >، <=، أو >=) لتحديد نطاق البيانات المسموح بإدخالها في العمود المُحدّد. فعلى سبيل المثال، من الاستخدامات الشائعة للقيود CHECK هي منع استقبال قيم سالبة في بعض الأعمدة وذلك في الحالات التي لا تكون فيها القيم السالبة مقبولة، كما سنوضح في المثال التالي. سنكتب تعليمة CREATE TABLE لإنشاء جدول يخزن بيانات المنتجات وسنطلق عليه اسم productInfo، يحتوي هذا الجدول على أعمدة لتخزين اسم كل منتج ورقم التعريف الخاص به وسعره. ونظرًا لأن سعر المنتج لا يمكن أن يكون سالبًا من الناحية المنطقية، فنكتب قيد CHECK على عمود السعر price لضمان احتوائه على قيم موجبة فقط بالشكل التالي: mysql> CREATE TABLE productInfo ( mysql> productID int, mysql> name varchar(30), mysql> price decimal(4,2) mysql> CHECK (price > 0) mysql> ); لا يُشترط استخدام عامل مقارنة رياضي في كل شرط من الشروط المنطقية الإسنادية الخاصة بالقيد CHECK. إذ يُمكنك استخدام أي معامل من معاملات SQL التي يمكن تقييمها في عبارة الشرط المنطقي إلى TRUE أو FALSE أو UNKNOWN، مثل LIKE و BETWEEN و IS NOT NULL وغيرها. وتسمح بعض تقديمات SQL -ولكن ليس كلها- بإضافة استعلام فرعي داخل العبارة الشرطية. في حين لا تسمح معظم التقديمات بالإشارة إلى جدول آخر داخل هذه العبارة الشرطية. القيد NOT NULL يحظرك القيد NOT NULL من إضافة أي قيم فارغة NULL إلى العمود المعني، فإذا أضفت سجل من البيانات ولم تُحدد فيه قيمة لعمود معين، فسوف يُمثّل نظام قواعد البيانات في معظم تقديمات SQL البيانات المفقودة افتراضيًا على أنها قيمة فارغة NULL. وفي لغة SQL تعد كلمة NULL مصطلحًا خاصًّا يُستخدم لتمثيل قيمة مجهولة أو مفقودة أو غير مُحددة. ولكن NULL ليست قيمة بحد ذاتها، بل هي حالة تُعبّر عن قيمة مجهولة أو غير متوفرة. ولتوضيح هذا الفارق، تخيل جدولًا يُستخدم لتتبع العملاء في شركة لاستقطاب المواهب ويحتوي هذا الجدول على أعمدة لأسماء العملاء الأولى والأخيرة. فإذا كان العميل معروفًا باسمٍ واحدٍ فقط، فسيُدخل مدير قاعدة البيانات هذا الاسم في عمود الاسم الأول فقط، مما يتسبب في إدخال قيمة فارغة NULL في عمود الاسم الأخير. في مثل هذه الحالة لا تعدّ قاعدة البيانات أن الاسم الأخير للعميل هو حرفيًا "فارغ". بل يعني ذلك فقط أن القيمة الموجودة في عمود الاسم الأخير لذلك السجل مجهولة أو غير متوفرة. وبالتالي، وكما يوحي اسمه، يمنعك القيد NOT NULL من إدخال قيم فارغة NULL أو تجاهل إدخال قيمة في العمود المعني. وبالتالي ستكون مجبورًا على تحديد قيمة لأي عمود طُبِّق عليه قيد NOT NULL عند إضافة سجل جديد، وإلا ستفشل عملية الإدراج. الخلاصة تعرفنا في مقال اليوم على مفهوم القيود التي تعد أحد المفاهيم الأساسية لأي شخص يرغب في تصميم قاعدة بيانات بمستوى عالٍ من نزاهة وأمان البيانات وسلامتها. إذ يمكنك التأكد من الحفاظ على العلاقات بين الجداول على النحو الصحيح من خلال تحديد البيانات التي يُسمح بإدخالها ضمن عمود معين، ما يضمن توافق قاعدة البيانات مع القواعد المطلوبة التي تحدد الغرض من إنشائها. للمزيد حول كيفية إنشاء وإدارة القيود في SQL، يمكنك مراجعة مقالنا كيفية استخدام القيود في SQL. وللمزيد حول SQL عمومًا، نشجعك على متابعة سلسلة تعلم SQL في أكاديمية حسوب. ترجمة -وبتصرف- للمقال Understanding SQL Constraints لصاحبه Mark Drake. اقرأ أيضًا المقال السابق: لماذا يجب عليك تعلم SQL؟ المفاهيم الأساسية في قواعد البيانات وتصميمها دليلك الشامل إلى قواعد البيانات DataBase البيانات في SQL: أنواعها والقيود عليها ما هي محركات قواعد البيانات؟
  17. قد تبدو لغة SQL أو لغة الاستعلام الهيكلية مُعقدة ومربكة للبعض في البداية، لكن مع التعلم والتعمق في اللغة ستصبح الأمور أوضح وأبسط فلغة SQL هي لغة سهلة التعلم بالعموم، وهي تُستخدم بشكل أساسي لتعريف ومعالجة والاستعلام عن البيانات المُخزنّة في قواعد البيانات العلاقية Relational، التي تعد أحد أشهر أنواع قواعد البيانات والتي تُنظّم البيانات فيها بدقة لتُناسب هيكلية أسطر (سجلات) وأعمدة (حقول) مُحدّدة مُسبقًا بشكل محدد وواضح. ملاحظة: يمكنك الاطلاع على تفاصيل أكثر حول قواعد البيانات العلاقية، بما في ذلك تاريخها والمفاهيم الرئيسية المُتعلّقة بها واستخداماتها الشائعة، بالعودة إلى المقال السابق فهم قواعد البيانات العلاقية. وبالعودة إلى موضوعنا، فقد ازدادت شهرة وشعبية لغة SQL منذ ظهورها في السبعينيات، وتوسّعت مجالات استخدامها على نحوٍ كبير منذ ذلك الحين. وتعدّ SQL في أيامنا من أبرز وأنضج اللغات المستخدمة في الاستعلام عن البيانات ومعالجتها في معظم القطاعات ومن أكثر الأدوات شهرةً. وبالتالي، فإن فرصة استخدام لغة SQL في مجال وظيفتك البرمجية كبيرة، فسواء كنت تعمل كمدير لقاعدة بيانات أو مصممًا لها أو مهندس برمجيات أو محلل بيانات، وحتى لو لم تكن تعمل في أي من الوظائف آنفة الذكر فستستفيد بالفعل من فهمك لأساسيات هذه اللغة وإن لم تستخدمها بشكل مباشر. يُبرز هذا المقال أهمية تعلم لغة SQL، ويشرح كيف وأين يمكنك تطبيق معرفتك لهذه اللغة والاستفادة منها. لغة SQL منتشرة بشكل واسع في عالم قواعد البيانات العلاقية إذا تعاملت مع أي قاعدة بيانات علاقية مثل MySQL أو PostgreSQL أو Microsoft SQL أو Oracle SQL أو غيرها الكثير، فستصادف بالتأكيد تعليمات SQL. إذ تُستخدم هذه اللغة على نطاق واسع لتعريف ومعالجة والاستعلام عن البيانات في هذا النوع من قواعد البيانات، فهي الوسيلة الأساسية للتفاعل مع محرك قاعدة البيانات. وبالرغم من أنّ العديد من أنظمة قواعد البيانات تُقدّم واجهات رسومية سهلة الاستخدام للتعامل مع بنية قاعدة البيانات وتعديل البيانات المُخزّنة فيها، إلا أن هذا لا يقلل من أهمية تقنية SQL أو يجعلها غير ضرورية. فبالرغم من إمكانية إنجاز المهام البسيطة بسرعة دون الحاجة إلى كتابة تعليمات SQL، ستتطلّب المهام الأعقد في معالجة البيانات أو الاستعلام عنها استخدام لغة SQL لبناء الاستعلامات وجلب البيانات التي تحتاجها بكفاءة مستفيدًا من قوة محرك قاعدة البيانات. وبالتالي بفهمك للغة SQL، ستتمكن من التعامل بسرعة مع أي نظام قاعدة بيانات علاقية مُستخدم اليوم تقريبًا، ولن تقتصر مهاراتك على برنامج محدد أو جهة عمل معينة. SQL مُستخدمة أيضًا على نحو واسع في مجالات عديدة أخرى استطاعت SQL بفضل شهرتها واستخدامها في أنظمة قواعد البيانات العلاقية المُعتَمَدة على نحوٍ متزايد، أن تجد طريقها إلى أنظمة قواعد بيانات وأدوات تحليل بيانات أخرى. إذ تتمتّع SQL والعديد من اللغات المُشتقّة منها بالدعم في العديد من أنظمة التخزين ومحركات تحليل البيانات وأدوات ذكاء الأعمال وأدوات التنقيب عن البيانات، وحتى في العديد من قواعد البيانات غير العلاقية وقواعد البيانات التحليلية Online analytical processing - OLAP وحلول البيانات الضخمة. وبغض النظر عن البرمجيات التي قد تصادفها لدى تحليل مجموعات البيانات الكبيرة، فهناك احتمالية عالية بأن تستفيد من معرفتك بلغة SQL للتعامل مع البيانات ضمن تلك الأدوات. إذ ستتمكن من اتباع منهجية مشابهة مع قواعد البيانات المتنوعة، الأمر الذي سيسهل عليك العمل ويجعله أشمل عبر مصادر البيانات المختلفة. وبالتالي تعدّ معرفة SQL من الأدوات الأساسية والضرورية التي يعتمد عليها محللو البيانات وعلماء البيانات على الدوام. كما تُعد لغة SQL ضمن أول عشر لغات برمجة وفق مؤشر مجتمع البرمجة TIOBE، وهو مؤشر يُظهر شعبية لغات البرمجة وانتشارها. تساعدك لغة SQL في التواصل مع الآخرين بخصوص البيانات لربما تُمثّل مناقشة البيانات تحديًا في العديد من الأحيان، فقد يفهم الأشخاص اللغة المحكية بطرق مختلفة قليلًا عما هو مطلوب، ما يُنشئ غموضًا قد يؤدي بدوره إلى سوء الفهم وأخطاء في التواصل. ولكن بمعرفتك لأساسيات SQL، كفهم كيفية هيكلة البيانات وكيفية الاستعلام عنها بنفسك، وستكون أدق لدى التواصل مع زملائك وأعضاء فريقك. فحتى إن لم تكن تنوي كتابة تعليمات SQL بنفسك، يمكنك استخدام خبرتك فيها لتوضيح متطلباتك وتوقعاتك بدقة. كما ستكون قادرًا على تحديد المشكلات المتعلقة بالبيانات التي تتلقاها، وتقديم تغذية راجعة واضحة وعملية لمحللي البيانات الذين يساعدونك في إعداد تلك البيانات. وبالتالي، يمكنك النظر إلى SQL على أنها لغة مشتركة يفهمها الجميع في عالم البيانات. فحتى إن لم تستخدمها على نحوٍ مباشر، فإن الاستناد إلى المفاهيم المُستمدّة منها ستجعل تواصلك بخصوص البيانات أدق وأوضح. تساعدك لغة SQL في تصميم قواعد بيانات أفضل من الضروري لدى تكليفك بتصميم قاعدة بيانات، أن تأخذ أنماط البيانات التي ستُخزّن وكيفية الوصول إليها أو التعامل معها مُستقبلًا في الحسبان. فبالرغم أنّه من الممكن بالطبع تصميم قواعد البيانات اعتمادًا على الفهم الجيد لنظرية تصميم قواعد البيانات فقط، إلّا أنّ الانتقال من التصميم النظري إلى قاعدة البيانات الفعلية قد يكون صعبًا، وستعترض المصمم مفاجآت غير متوقعة في الغالب. إذ سيُستخدم نوع من استعلامات SQL مع كل عملية استرجاع بيانات من قاعدة البيانات، وسواءً كتبت هذه الاستعلامات من قبل مُحلل البيانات أو أُنشئت عبر برمجيات مُتخصّصة. فإن فهمك لأنماط الاستخدام المطلوبة ومعرفتك لكيفية ترجمتها إلى استعلامات SQL قابلة للتنفيذ، سيمكنك من فهم كيفية وصول SQL إلى قاعدة البيانات الأساسية لاسترداد البيانات، وما الذي سيتعين على محرّك قاعدة البيانات إنجازه كاستجابةً لهذا الاستعلام. وبالتالي، يمكنك تصميم قواعد بيانات تلبي الغرض المطلوب باستخدام المعرفة التي اكتسبتها. وبمراعاتك لحالات الاستخدام التي يجب أن تدعمها قاعدة البيانات، يمكنك اختيار هيكلية لها تُسهّل إجراء استعلامات أبسط وأكفأ تُناسب سيناريوهات الاستخدام الشائعة. كما يمكنك هيكلة الجداول على نحوٍ مدروس، مُسهلًا الوصول إلى البيانات باستخدام أنماط البيانات والعلاقات المُعتمدة على المفاتيح الخارجية والفهارس. بمعنى أنّك ستتعلم كيفية تصميم قواعد بيانات تُناسب أهدافك على نحوٍ أفضل. تساعدك لغة SQL في كتابة تطبيقات أفضل تستفيد العديد من أطُر التطوير الحديثة مثل أطُر تطوير الويب المشهورة لارافيل Laravel أو Symfony أو جانغو Django أو Ruby on Rails من طبقات تجريد البيانات، لاسيما أدوات التخطيط بين الكائنات وقواعد البيانات العلاقيَّة object-relational mappers إذ تهدف هذه الأدوات إلى تبسيط عملية الوصول إلى البيانات وتقديمها بشكل مبسّط للمبرمج. فبينما توفر هذه الأطُر طرقًا بديلة للتفاعل مع البيانات اعتمادًا على التعليمات البرمجية البسيطة الخاصّة بها موفرّةً البيانات التي تطلبها بسلاسة، إلا أن فهمك للغة SQL سيمكّنك من التعامل مع البيانات بشكل أكثر فعالية ودقة، حتى وإن لم تستخدم SQL مباشرة في عملية التطوير. فبغض النظر عن العديد من الاختصارات والسهولة الكبيرة التي تقدمها أطر العمل، فإنها تُبنى استعلامات في محرك قاعدة البيانات الأساسي باستخدام جمل SQL خلف الكواليس من المدخلات الخاصة بك. وبالتالي فإن فهم كيفية عمل SQL يمكن أن يساعدك في استخدام ميزات الإطار البرمجي الذي اخترته لجعل الاستعلامات أسرع وأكثر فعالية، وبهذا يمكنك الاعتماد على معرفتك بكيفية بناء الإطار للاستعلامات في توجيه بنائها وتنفيذها على نحوٍ أمثل وصولًا إلى البيانات المطلوبة. ناهيك عن إمكانية تصحيح أي مشكلات في الاستعلام عن البيانات ومعالجتها على نحوٍ أسهل. فغالبًا ما تُعيد محركات قواعد البيانات أخطاء تُشير إلى الجزء الذي فشل من الاستعلام المُنفّذ فعليًا باستخدام SQL. وبالرغم من دقة هذه الأخطاء، يبقى تحديد مصدر المشكلة ضمن الصياغة الخاصة بالإطار المستخدم أمرًا صعبًا، ولكن فهمك لأخطاء قواعد البيانات سيمكنك من تحديد المشكلة بدقة. وأخيرًا وليس آخرًا، ستكون أكثر وعيًا بالمخاطر الأمنية الناتجة عن استخدام استعلامات SQL على نحوٍ غير صحيح وخطير، مثل هجمات حقن SQL، كما ستكون قادرًا على مواجهتها والتصدي لها. فبفضل معرفتك بلغة SQL، ستمتلك السيطرة الكاملة على كيفية الوصول إلى البيانات، سواء قررت استخدام SQL مباشرةً أو اخترت العمل مع طبقات التجريد وأدوات التخطيط بين الكائنات وقواعد البيانات العلاقية ضمن الأطُر البرمجية. لغة SQL سهلة التعلم للمبتدئين هناك العديد من الفوائد المرتبطة بفهم وتطبيق لغة الاستعلام الهيكلية في الواقع العملي. ولعلّ أحد أبرز ما يميزها هو سهولة تعلمها للمبتدئين. إذ تمتاز هذه اللغة بوضوحها ودقتها واستخدامها لكلمات إنجليزية شائعة كأسماء للعمليات والمُرشحات (عوامل التصفية) وغيرها من الواصفات (العناصر التي تغير أو تحدد سلوك الاستعلام). فيمكنك قراءة استعلامات SQL في كثير من الأحيان على هيئة جمل مكتوبة باللغة الإنجليزية، ما يجعلها مفهومة حتى دون وجود خلفية برمجية. ربما تجد بعض الجوانب الصعبة والمعقدة في اللغة والتي تحتاج إلى جهد لفهمها والتمرن عليها. ولكن بالمقابل يمكنك فهم وتعلم أساسيات لغة SQL بشكل ميسر. ومع استخدامك المتواصل لها في عملك اليومي، ستصبح عملية تعلم الأمور الأصعب أكثر فعالية لأنها تتم وفقًا لاحتياجاتك الفعلية للبيانات. وبالتالي يمكنك البدء مع أساسيات SQL، ومن ثم تطوير فهمك للغة تدريجيًا كلما احتجت لاستخدام طرق استعلام جديدة. والجيد في الأمر أنّ إجراء التجارب والتعامل مع SQL أثناء الاستعلام عن البيانات أمر سهل وخالٍ من المخاطر من ناحية فقدان البيانات، ما يوفّر لك الشعور بالأمان والراحة أثناء رحلة التعلّم. ستحصل بتعلمك للغة SQL على فوائد تتجاوز بكثير الوقت المستغرق في ذلك، وتكتسب طرقًا جديدة لاسترجاع البيانات من عدة المصادر وتحليلها وتحقق الاكتفاء الذاتي في حاجتك لتحليل البيانات، ما يفتح آفاق وظيفية جديدة في مجالات متعددة. الخلاصة غدت لغة SQL أكثر لغات الاستعلام عن البيانات ومعالجتها شيوعًا وشهرة بفضل مرونتها وسهولة استخدامها وتطبيقها في مجالات متعددة ذات صلة بالبيانات. وبالتالي، سيوفر لك تعلمها العديد من الفوائد الجليّة، حتى وإن لم تكن وظيفتك الرئيسية مرتبطة مباشرةً بقواعد البيانات أو بتطوير البرمجيات. ترجمة -وبتصرف- للمقال Why You Should Learn SQL لصاحبه Mateusz Papiernik. اقرأ أيضًا المقال السابق: فهم قواعد البيانات العلاقية ما هي تقنية SQL؟ أهمية قواعد البيانات أنواع قواعد البيانات وأهم مميزاتها واستخداماتها تعرف على مكونات قاعدة البيانات ما هي محركات قواعد البيانات؟
  18. تعدّ أنظمة إدارة قواعد البيانات Relational database management system أو اختصارًا DMBS برامج حاسوبية تسمح للمستخدمين بالتفاعل مع قاعدة البيانات وتتيح لهم إمكانية التحكّم بالوصول إلى قاعدة البيانات وكتابة البيانات فيها وتنفيذ الاستعلامات Queries التي تستخلص البيانات أو تعدلها وإنجاز أي مهمّة أخرى ذات صلة بإدارة قواعد البيانات. وبالتالي كي تتمكن من إنجاز أي من هذه المهام، يجب أن تمتلك أنظمة DMBS نموذجًا أساسيًا يُعرّف كيفية تنظيم البيانات. وإحدى منهجيات تنظيم البيانات التي لاقت استخدامًا واسعًا في برمجيات قواعد البيانات منذ ابتكارها في أواخر ستينيات القرن الماضي هي النموذج العلاقيّ أو العلائقي Relational، لدرجة أنّه وحتى وقت كتابة هذا المقال فإنّ أربعة من بين أكثر خمسة أنظمة لإدارة قواعد البيانات شيوعًا هي من النمط العلاقيّ. يوضّح هذا المقال المفاهيمي النظري تاريخ النموذج العلاقيّ وكيفية تنظيم البيانات في قواعد البيانات العلاقيَّة وكيفية استخدامها في تطبيقاتنا المختلفة. تاريخ النموذج العلاقي قواعد البيانات عبارة عن مجموعات من المعلومات أو البيانات المُنظّمة منطقيًا. إذ تُعدّ أي مجموعة من البيانات كقاعدة بيانات، بغض النظر عن كيفية أو مكان تخزينها. فحتى درج الملفات المُتضمّنة لمعلومات الرواتب يُعدّ قاعدة بيانات، كذلك الأمر بالنسبة لأي مجموعة مُكدّسة من البيانات مثل نماذج المرضى في مستشفى، أو معلومات مجموعة من عملاء شركة موزعة عبر عدّة مواقع. فقبل أن تغدو عملية تخزين وإدارة البيانات باستخدام الحواسيب شائعة، كانت قواعد البيانات المادية كهذه في الأمثلة السابقة هي الوحيدة المتاحة للحكومات والمنظمات التجارية التي كانت بحاجة لتخزين المعلومات. وفي منتصف القرن العشرين تقريبًا، شهدت علوم الحاسوب تطورات كبيرة أفضت إلى ظهور أجهزة ذات قدرة معالجة أعلى وسعات تخزين محلية وخارجية أكبر. جعلت هذه التطورات علماء الحاسوب يُدركون القدرات الهائلة لهذه الأجهزة في تخزين وإدارة كميات أكبر من البيانات. ولكن لم تكن هناك حينها نظريات مُحدّدة حول كيفية تنظيم البيانات في الحواسيب بطرق منطقية ذات معنى فقد كان من السهل تخزين البيانات عشوائيًا على جهاز ما، لكن تصميم أنظمة قواعد البيانات سيتيح إمكانية إضافة واسترجاع وترتيب وإدارة هذه البيانات بطرق عملية وثابتة هو تحدٍ أكبر. فقد أسفرت الحاجة إلى وجود نظام منطقي لتخزين وتنظيم البيانات إلى ظهور العديد من الاقتراحات حول كيفية استخدام الحواسيب في إدارة البيانات. ولعلّ أحد النماذج الأولية لقواعد البيانات كان النموذج الهرمي، حيث تنظم البيانات وفق هذا النموذج ضمن هيكلية أو بنية شبيهة بالشجرة بأسلوب مماثل لأنظمة الملفات. ويوضّح المثال التالي الشكل الذي قد يبدو عليه جزء من مخطط قاعدة بيانات هرمية تستخدم لتصنيف الحيوانات: لقد طُبِّق النموذج الهرمي على نحوٍ واسع في أنظمة إدارة قواعد البيانات الأولية، لكنه أظهر بالمقابل بعض الصعوبات من حيث المرونة. ففي هذا النموذج يمكن للسجل الواحد امتلاك عدّة أبناء Children، لكن لا يمكنه أن يتبع سوى لأب Parent واحد ضمن الهيكلية الهرمية، الأمر الذي يجعل قواعد البيانات الهرمية قادرة على التعبير فقط عن علاقات من نوع "واحد-إلى-واحد (one-to-one)" و"واحد-إلى-عديد (one-to-many)" ولا يسمح بالتعبير عن العلاقات من نوع "عديد-إلى-عديد (many-to-many)"، وهذا القصور قد يفضي إلى حدوث مشاكل لدى التعامل مع نقاط بيانات تتبع لأكثر من أب. إلى أن ابتكر عالم الحاسوب إدجار فرانك كود Edgar F. Codd والذي كان يعمل في شركة IBM النموذج العلاقيّ لإدارة قواعد البيانات وذلك في أواخر ستينيات القرن الماضي. إذ سمح نموذج Codd العلاقيّ هذا بربط السجل الواحد بأكثر من جدول، ما سمح بإمكانية التعبير عن العلاقات من نوع "عديد-إلى-عديد" بين نقاط البيانات ممكنة، بالإضافة إلى إمكانية التعبير عن العلاقات من نوع "واحد-إلى-عديد". الأمر الذي أتاح المزيد من المرونة مقارنةً بالنماذج الموجودة حينها لتصميم هياكل قواعد البيانات وأثبت قدرة أنظمة إدارة قواعد البيانات العِلاقيَّة (RDBMSs) على تلبية مجموعة أوسع بكثير من احتياجات الأعمال. كما اقترح العالم كود Codd لغة لإدارة البيانات العلاقيَّة عُرِفت باسم ألفا (Alpha)، والتي أثّرت في تطوير لغات قواعد البيانات اللاحقة. فقد أنشأ اثنان من زملائه في IBM وهما دونالد تشامبرلين Donald Chamberlin وريمون بويس Raymond Boyce لغة مستوحاة من لغة ألفا، أطلقا عليها اسم SEQUEL اختصارًا لعبارة Structured English Query Language (لغة الاستعلام الهيكلية بالإنجليزية)، ولكن بسبب وجود علامة تجارية موجودة مسبقًا بنفس الاسم، اختصروا اسم لغتهم إلى SQL والتي يُشار إليها على نحوٍ أكثر رسمية باسم لغة الاستعلام الهيكلية Structured Query Language. كانت قواعد البيانات العلاقيَّة الأولية بطيئة للغاية وغير عملية بسبب قيود العتاد، واحتاجت بعض الوقت حتى تنتشر، ولكن ومع حلول منتصف الثمانينات، طُبّق نموذج Codd العِلاقيّ في العديد من المنتجات التجارية لإدارة قواعد البيانات، سواء من قبل شركة IBM أو منافسيها. إذ قام هؤلاء المُصنعّون أيضًا باتباع خطى IBM من خلال تطوير وتنفيذ لهجاتهم الخاصة من لغة SQL. وبحلول عام 1987، صادق كل من المعهد الوطني الأمريكي للمعايير American National Standards Institute والمنظمة الدولية للمعايير International Organization for Standardization على معايير لغة SQL ونشروها، ما جعل منها اللغة المعترف بها لإدارة أنظمة قواعد البيانات العلاقيَّة. ولعلّ انتشار استخدام النموذج العلاقيّ في مجموعة متنوعة من المجالات جعل منه النموذج المعياري لإدارة البيانات. وحتى مع ظهور العديد من قواعد البيانات غير العلاقية NoSQL في السنوات الأخيرة، تبقى قواعد البيانات العلاقية هي السائدة لتخزين وتنظيم البيانات. كيف تنظم قواعد البيانات العلاقية البيانات الآن وبعد حصولك على فهمٍ عام لتاريخ النموذج العلاقيّ، لنلقِ نظرة أعمق على كيفية تنظيم البيانات وفق هذا النموذج. لعلّ أهم عناصر النموذج العلاقيّ هي العلاقات Relations، والتي تُعرف لدى المستخدمين وفي أنظمة إدارة قواعد البيانات العِلاقيَّة المعاصرة بمسمى الجداول Tabels. فالعلاقة هي مجموعة من الصفوف أو السجلات في جدول، ويشترك كل صف بمجموعة من السمات أو الأعمدة (الحقول): العمود هو أصغر هيكل تنظيمي في قاعدة البيانات العلاقيَّة، وهو يُمثّل الخصائص المختلفة التي تُعرّف السجلات ضمن الجدول. ولذا، تُعرف هذه الخصائص رسميًا باسم السمات. يمكن فهم كل صف على أنّه نسخة فريدة تًمثّل نوع ما من الأشخاص أو الكائنات أو الأحداث أو الروابط المحفوظة في الجدول. وقد تُمثّل هذه النسخ موظفي شركة أو مبيعات شركة تجارية عبر الإنترنت أو نتائج اختبارات مخبرية. على سبيل المثال، في جدول يحتوي على سجلات المدرسين في مدرسة ما، قد تتضمن الصفوف سمات مثل: الاسم name والمقررات الدراسية subjects وتاريخ بدء العمل start_date، وما إلى ذلك. ولدى إنشاء الأعمدة، يتعيّن عليك تحديد نمط البيانات الخاص بكل عمود، وهو ما يُحدّد القيم التي يمكن استقبالها فيه. وعادةً ما تُقدّم أنظمة إدارة قواعد البيانات العِلاقيَّة أنماطًا فريدة خاصة بها من البيانات والتي قد لا تكون متوافقة مباشرةً مع مثيلاتها في الأنظمة الأخرى. ومن أنماط البيانات شائعة الاستخدام كل من التواريخ، والسلاسل النصية، والأعداد الصحيحة، والقيم المنطقية. يتضمّن كل جدول في النموذج العِلاقيّ عمودًا واحدًا على الأقل يُستخدم لتمييز كل سجل على نحوٍ فريد، ويُعرف باسم المفتاح الأساسي Primary Key. وهو عمود مهم جدًا، إذ لا يحتاج المستخدمون لمعرفة مكان تخزين بياناتهم فيزيائيًا داخل الجهاز. وبدلاً من ذلك، يتولى نظام إدارة قواعد البيانات متابعة وتعقّب كل سجل واسترجاعه حسب الطلب. وبالتالي، لا يوجد ترتيب منطقي مُحدّد للسجلات، ويتمتّع المستخدمون بالحرية في استرجاع بياناتهم بالترتيب الذي يرغبون به أو باستخدام عوامل التصفية التي يختارونها. فبفرض كان لديك جدولين وتود ربطهما ببعضهما البعض، فإحدى الطرق لإنجاز ذلك هي باستخدام المفتاح الخارجي Foreign key، وهو نسخة من المفتاح الأساسي لجدول (يُعرف بالجدول "الأب") تُدرج في عمود داخل جدول آخر (يُعرف بالجدول "الابن"). يُظهر المثال التالي العلاقة بين جدولين، الأول يُستخدم لتسجيل المعلومات حول الموظفين في شركة، والثاني لتتبع مبيعات الشركة. وفي هذا المثال، يُستخدم المفتاح الأساسي لجدول الموظفين EMPLOYEES كمفتاح أجنبي لجدول المبيعات SALES: فإذا حاولت إضافة سجل إلى الجدول الابن وكانت القيمة المُدخلة في عمود المفتاح الأجنبي غير موجودة في المفتاح الأساسي للجدول الأب، فإن تعليمة الإدراج ستكون غير صالحة، وهذا يساعد في الحفاظ على صحّة مستوى العلاقة بين الجدولين (أو نزاهة العلاقة إن صح التعبير)، وبهذا ستكون السجلات في الجدولين مرتبطة على النحو الصحيح دائمًا. لقد ساعدت عناصر النموذج العلاقيّ الهيكلية في الحفاظ على تنظيم البيانات أثناء تخزينها، ولكن تخزين البيانات لن يكون مفيدًا إلا إذا كان من الممكن استرجاعها. ولاسترجاع البيانات من RDBMS، يُمكنك إنشاء ما يُسمّى بالاستعلام query وهو عبارة عن طلب منظّم لمجموعة من المعلومات. وتستخدم معظم قواعد البيانات العِلاقيَّة لغة SQL لإدارة البيانات والاستعلام عنها كما أشرنا سابقًا. إذ تتيح لغة SQL إمكانية تصفية نتائج الاستعلام ومعالجتها باستخدام مجموعة من البنى clauses، والجمل الشرطية predicates التي تقيم كصحيح TRUE أو خاطئ FALSE أو غير معروف، والتعبيرات expressions، ما يمنحك التحكم الدقيق في البيانات التي ستظهر ضمن نتائج الاستعلام. مزايا وقيود قواعد البيانات العلاقية لنفكّر في بعض مزايا وعيوب قواعد البيانات العلاقيَّة بأخذ هيكلها التنظيمي الأساسي في الحسبان. إنّ لغة SQL وقواعد البيانات التي تستخدمها في أيامنا هذه تختلف عما قدمه العالم كود Codd في نموذجه العلاقيّ الأول في عدة نواحٍ. فعلى سبيل المثال، يملي نموذج كود Codd بأنّ كل سجل في الجدول يجب أن يكون فريدًا. في حين تسمح العديد من قواعد البيانات العلاقيَّة المعاصرة بوجود سجلات مكررة. وهناك بالمقابل من يرى أن قواعد البيانات التي تستخدم لغة SQL ولا تتبع جميع مواصفات نموذج Codd لا يمكن عدها قواعد بيانات علاقيَّة حقيقية. ولكن وفي الواقع، أي نظام يستخدم SQL ويتبع نموذج Codd إلى حد معين يعدّ عادةً نظامًا لإدارة قواعد بيانات علاقيَّة. ورغم شهرة قواعد البيانات العِلاقيَّة التي ازدادت بسرعة، إلا أنّ بعض أوجه القصور المتعلقة في النموذج العِلاقيّ بدأت بالظهور مع تزايد قيمة البيانات وتوجه الشركات لتخزين كميات أكبر منها. ومن هذه التحديات والمعوقات، صعوبة توسيع قاعدة البيانات العلاقيَّة أفقيًا. والمقصود بالتوسع الأفقي أو الخارجي عملية إضافة المزيد من الأجهزة إلى نظام حالي لتوزيع الحمولة واستيعاب حركة مرور أكبر وتحقيق معالجة أسرع. وغالبًا ما تُقارن هذه العملية بالتوسع العمودي، الذي يتضمن تحسين مواصفات الخادم الحالي من خلال توسيع ذاكرة الوصول العشوائي RAM أو وحدة المعالجة المركزية CPU. والسبب وراء صعوبة توسيع قاعدة البيانات العلاقيَّة أفقيًا يعود إلى أنّ النموذج العلاقيّ مصمم لضمان الاتساق أو انسجام البيانات consistency، ما يعني أنّه عند استعلام العملاء ضمن قاعدة البيانات نفسها سيحصلون دائمًا على البيانات نفسها. فإذا قمت بتوسيع قاعدة البيانات العلاقيَّة أفقيًا عبر عدة أجهزة، سيصبح من الصعب ضمان هذا الاتساق، إذ قد يقوم العملاء بكتابة البيانات على عقدة معينة أو (جهاز) دون الباقي، وقد يكون هناك تأخير بين لحظة الكتابة الأولية والوقت الذي تُحدّث فيه العقد الأخرى لتعكس هذه التغييرات، ما يؤدي إلى عدم اتساق بينها. لقد واجهت أنظمة إدارة قواعد البيانات العلاقيَّة بعض التحديات، من بينها أن النموذج العلاقيّ قد صُمّم خصيصًا للتعامل مع البيانات المهيكلة structured data، أي البيانات التي تتوافق مع نمط بيانات مُحدد مسبقًا أو على الأقل تلك المُنظمّة على نحوٍ مُحدد. لكن مع انتشار الحواسيب الشخصية وظهور الإنترنت في أوائل التسعينات، أصبحت البيانات غير المهيكلة unstructured data، كالرسائل الإلكترونية والصور والفيديوهات أكثر شيوعًا. لا يعني كل ذلك أن قواعد البيانات العلاقيَّة باتت غير مفيدة. بل على العكس تمامًا، فالنموذج العلاقيّ ما زال هو الإطار السائد لإدارة البيانات منذ أكثر من 40 عامًا. وتدلّ شهرتها واستمراريتها على أنّ قواعد البيانات العلاقيَّة تقنية ناضجة، وهذا بحد ذاته من أبرز مزاياها. هناك العديد من التطبيقات التي صُممت للعمل مع النموذج العِلاقيّ، بالإضافة إلى وجود العديد من مدراء قواعد البيانات المحترفين الخبراء في مجال قواعد البيانات العلاقيَّة. كما تتوفر مجموعة واسعة من الموارد المطبوعة والإلكترونية متاحة لكل من يرغب في بدء التعامل مع قواعد البيانات العِلاقيَّة. ومن مميزات قواعد البيانات العلاقيَّة دعم معظمها لمجموعة من التعاملات transactions التي تضمن سلامة البيانات وتكاملها. والمعاملة transaction هي مجموعة من أوامر SQL التي تُنفذ وفق تسلسل معيّن كوحدة عمل مُنفصلة. إذ تعتمد المعاملات على مبدأ الكل أو لا شيء، بمعنى أنّ كل أمر في المعاملة يجب أن يكون صحيحًا، وإلا ستُلغى المعاملة بالكامل وبهذا نضمن صحّة البيانات ودقتها (نزاهة البيانات) عند التعديل على عدة سجلات أو جداول في الوقت نفسه. وأخيرًا، تعدّ قواعد البيانات العلاقيَّة مرنة للغاية. فقد استُخدمت في تطوير مجموعة واسعة من التطبيقات المختلفة، وتستمر في العمل بكفاءة حتى مع كميات ضخمة من البيانات. كما تعدّ لغة SQL قوية جدًا، إذ تتيح لك إمكانية إضافة وتعديل البيانات على نحوٍ فوري، بالإضافة إلى تغيير هيكلية تخطيط قواعد البيانات والجداول دون التأثير على البيانات الموجودة. الخلاصة تعرفنا في مقال اليوم على قواعد البيانات العلاقيَّة التي لا تزال وسيلة أساسية لإدارة وتخزين البيانات بعد أكثر من خمسين عامًا على وضع التصور الأولي لها، وذلك بفضل مرونتها وتصميمها الذي يضمن صحة البيانات ودقتها. وحتى مع ظهور قواعد البيانات غير العِلاقيَّة NoSQL في السنوات الأخيرة، لا زال فهم النموذج العلاقيّ ومعرفة كيفية التعامل مع أنظمة إدارة قواعد البيانات العلاقيَّة أمر أساسي لأي شخص يرغب في بناء التطبيقات المُعتمدة على قوة البيانات. وللتعرّف على المزيد حول بعض من أنظمة إدارة قواعد البيانات العِلاقيَّة الأشهر، وقواعد البيانات عمومًا، ننصحكم بالرجوع إلى قسم قواعد البيانات من أكاديمية حسوب. ترجمة -وبتصرف- للمقال Understanding Relational Databases لصاحبه Mark Drake. اقرأ أيضًا مدخل إلى تصميم قواعد البيانات تعرف على مكونات قاعدة البيانات نموذج الكيان والعلاقة ER لتمثيل البيانات وتخزينها في قاعدة البيانات ما هي محركات قواعد البيانات؟ أنواع قواعد البيانات وأهم مميزاتها واستخداماتها
  19. تمثل البرمجة الوظيفية Functional Programming نموذجًا paradigm يؤكد على كتابة دوال تُجري العمليات الحسابية دون إجراء تعديلات على المتغيرات العامة أو على الحالات خارج الكائنات، مثل الملفات على القرص الصلب أو الاتصالات بالإنترنت أو قواعد البيانات. ويتمحور تصميم بعض لغات البرمجة مثل Erlang و Lisp و Haskell كثيرًا حول مفاهيم البرمجة الوظيفية، أما لغة بايثون ورغم عدم تقيدها بنموذج البرمجة الوظيفية، إلا أنها تحمل بعضًا من ميزاتها، وأهم ما يمكن لبرامج بايثون استخدامه من ميزات البرمجة الوظيفية هي الدوال خالية الآثار الجانبية side-effect-free functions والدوال عالية المستوى higher-order functions والدوال المجهولة lambda functions. يستعرض هذا المقال نموذج البرمجة الوظيفية ومزايا استخدام الدوال وفقًا لهذا النموذج. الآثار الجانبية Side Effects تُعرّف الآثار الجانبية side effects بأنها أي تغييرات تجريها الدالة للأجزاء من البرنامج الواقعة خارج شيفرتها الخاصة ومتغيراتها المحلية، ولتوضيح هذه الفكرة، ننشئ دالة للطرح باسم ()subtract تستخدم عامل الطرح في بايثون (-): >>> def subtract(number1, number2): ... return number1 - number2 ... >>> subtract(123, 987) -864 ليس للدالة ()subtract السابقة آثار جانبية، وذلك لأنها لا تؤثر على أي جزء من البرنامج خارج حدود شيفرتها الخاصة، إذ لا يمكن معرفة ما إذا استُدعيت هذه الدالة سابقًا لمرة أو مرتين أو مليون مرة اعتمادًا على حالة البرنامج أو الحاسوب. قد تعدّل الدالة على متغيراتٍ محلية ضمنها، إلا أن هذه التغيرات تبقى معزولة عن باقي أجزاء البرنامج. لنطلع الآن على الدالة التالية المسماة ()addToTotal، والتي تضيف الوسطاء العددية خاصتها إلى متغير عام باسم TOTAL على النحو التالي: >>> TOTAL = 0 >>> def addToTotal(amount): ... global TOTAL ... TOTAL += amount ... return TOTAL ... >>> addToTotal(10) 10 >>> addToTotal(10) 20 >>> addToTotal(9999) 10019 >>> TOTAL 10019 للدالة ()addToTotal أثر جانبي، وذلك لأنها تعدّل عنصرًا متوجدًا خارجها وهو المتغير العام TOTAL، وقد تكون الآثار الجانبية أكثر من مجرد تغييرات تطرأ على متغير عام، إذ أنها تتضمن تحديث وحذف الملفات أو طباعة النصوص على الشاشة أو فتح اتصال قاعدة بيانات أو المصادقة مع خادم ما أو أي تغييرات تحدث خارج حدود الدالة، إذ يُعد أي أثر يتركه استدعاء الدالة بعد إعادة القيمة أثرًا جانبيًا. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن تشمل الآثار الجانبية أيضًا التغييرات المكانية على الكائنات المتغيرة التي تشير إلى ما هو خارج الدالة. على سبيل المثال، تعدّل الدالة ()removeLastCatFromList التالية وسيط القائمة مكانيًا: >>> def removeLastCatFromList(petSpecies): ... if len(petSpecies) > 0 and petSpecies[-1] == 'cat': ... petSpecies.pop() ... >>> myPets = ['dog', 'cat', 'bird', 'cat'] >>> removeLastCatFromList(myPets) >>> myPets ['dog', 'cat', 'bird'] يحمل كل من المتغير myPets والمعامل petSpecies في المثال السابق مرجعًا إلى نفس القائمة. أي تعديلات مكانية تجري على كائن القائمة ضمن الدالة ستجري أيضًا خارجها، ما يجعل هذا التعديل أثرًا جانبيًا. تُعد الدوال الحتمية deterministic functions أحد المفاهيم ذات الصلة هنا، والتي تعرّف بأنها الدوال التي تعيد دومًا القيمة نفسها من أجل نفس الوسطاء، فمثلًا سيعيد الاستدعاء (subtract(123, 987 دومًا القيمة "864-"، وكذلك تعيد دالة بايثون المبنية مسبقًا ()round (والتي تعمل على تقريب العدد إلى أقرب عدد صحيح) الرقم 3 لدى تمرير العدد 3.14 وسيطًا إليها. لا تعيد الدوال غير الحتمية بالضرورة نفس القيمة من أجل نفس الوسطاء، فعلى سبيل المثال، يعيد الاستدعاء (random.randint(1, 10 قيمةً عشوائيةً محصورةً بين 1 و10، والدالة ()time.time رغم عدم احتوائها على وسطاء إلا أنها تعيد قيمة مختلفة تبعًا للتوقيت الذي تشير إليه ساعة الحاسب لحظة استدعائها؛ ففي حالة الدالة ()time.time، تعد الساعة مصدرًا خارجيًا يمثّل دخلًا للدالة كما يفعل الوسيط. لا تُعد جميع الدوال المعتمدة على مصادر من خارجها، سواء كانت متغيرات عامة، أو ملفات على القرص الصلب، أو قواعد بيانات، أو اتصالات بالإنترنت دوالًا حتمية. إحدى فوائد الدوال الحتمية هي إمكانية التخزين المؤقت لقيمها، فما من ضرورة مثلًا لاستدعاء الدالة ()subtract أكثر من مرة لحساب الفرق بين نفس العددين 123 و987 طالما أنها قادرة على تذكّر القيمة المعادة من المرة الأولى لاستدعائها مع تمرير نفس هذين الوسيطين، وبالتالي تتيح لنا الدوال الحتمية إمكانية المفاضلة ما بين الزمن والمساحة التخزينية، بمعنى زيادة سرعة تنفيذ دالة على حساب المساحة التخزينية اللازمة في الذاكرة لتخزين النتائج السابقة لهذه الدالة. وتدعى الدالة الحتمية خالية الآثار الجانبية بالدالة النقية pure function. تسعى البرمجة الوظيفية لإنشاء دوال نقية فقط في برامجها. إذ توفّر الدوال النقية العديد من المزايا، ومنها: مناسبة تمامًا لاختبار وحدة مستقلةً، إذ أنها لا تتطلب إعداد أي مصادر خارجية. يسهل في الدوال النقية إعادة الحصول على الخطأ نفسه باستدعائها من أجل نفس الوسطاء، لفهم أسباب الخطأ وإصلاحه. الدوال النقية قادرة على استدعاء دوال نقية أخرى مع بقائها نقية. تكون الدوال النقية في البرامج متعددة المستخدمين المتزامنين multithreaded programs آمنة من الخيوط thread-safe، إذ يمكن تشغيلها في وقتٍ واحد بأمان. موضوع البرامج متعددة المستخدمين المتزامنين multithreaded programs خارج اهتمامات مقالنا هذا. يمكن إجراء استدعاءات متعددة متزامنة وبأي ترتيب للدوال النقية من قبل نوى وحدة المعالجة المركزية CPU المتوازية، أو برنامج متعدد مستخدمين كونها لا تعتمد على أي مصدر خارجي يفرض تشغيلها بترتيب معين. يمكنك، لا بل ينبغي عليك كتابة دوال نقية في بايثون متى استطعت ذلك. بُنيت دوال بايثون لتكون نقيةً بحكم العرف والعادة ولكن ما من إعداد معيّن يجعل مفسر بايثون يفرض على الدوال أن تكون نقية، ولعل الطريقة الأكثر شيوعًا لجعل الدوال نقية تكون بتجنب استخدام المتغيرات العامة فيها والتأكد من كون الدوال لا تتعامل مع الملفات، أو الإنترنت، أو ساعة النظام، أو الأعداد العشوائية، أو غيرها من المصادر الخارجية. الدوال عالية المستوى Higher-Order Functions يمكن للدوال عالية المستوى Higher-Order Functions استقبال الدوال الأخرى مثل وسطاء لها، أو أن تعيد دوال أخرى مثل قيمة معادة، فعلى سبيل المثال، لنعرّف دالة باسم ()callItTwice تعمل على استدعاء أي دالة أخرى مرتين: >>> def callItTwice(func, *args, **kwargs): ... func(*args, **kwargs) ... func(*args, **kwargs) ... >>> callItTwice(print, 'Hello, world!') Hello, world! Hello, world! تعمل الدالة ()callItTwice السابقة مع أي دالة تمرر إليها، إذ تعد الدوال في بايثون كائنات من الدرجة الأولى، بمعنى أنها كغيرها من الكائنات من الممكن تخزينها ضمن متغيرات أو تمريرها مثل وسطاء أو استخدامها مثل قيم معادة. الدوال المجهولة anonymous functions الدوال لامدا lambda functions أو الدوال المجهولة anonymous functions أو الدوال عديمة الاسم nameless functions هي دوال بسيطة عديمة الأسماء وتتألف شيفرتها من تعليمة return فقط، وتُستخدم عادةً الدوال المجهولة لدى تمرير الدوال مثل وسطاء لدول أخرى، فعلى سبيل المثال، من الممكن إنشاء دالة عادية تستقبل قائمة متضمنة لطول وعرض مستطيل بأبعاد 10 و4 على النحو التالي: >>> def rectanglePerimeter(rect): ... return (rect[0] * 2) + (rect[1] * 2) ... >>> myRectangle = [4, 10] >>> rectanglePerimeter(myRectangle) 28 فتبدو الدالة المجهولة المكافئة كما يلي: lambda rect: (rect[0] * 2) + (rect[1] * 2) نستخدم الكلمة المفتاحية lambda للتصريح عن دالة مجهولة في بايثون متبوعةً بقائمة بالمعاملات (في حال وجودها) مفصولةً فيما بينها بمحارف فاصلة، ومن ثم نقطتين رأسيتين ونهايةً تعبير برمجي يُمثّل القيمة المعادة. بما أن الدوال هي كائنات من الدرجة الأولى في بايثون، يمكن إسناد الدالة المجهولة إلى متغير ما، على غرار ما تؤديه التعليمة def، على النحو التالي: >>> rectanglePerimeter = lambda rect: (rect[0] * 2) + (rect[1] * 2) >>> rectanglePerimeter([4, 10]) 28 أسندنا في الشيفرة السابقة الدالة المجهولة إلى متغير اسمه rectanglePerimeter، ما يعطي بالنتيجة دالةً باسم ()rectanglePerimeter، وبذلك نجد أن الدوال المُنشأة باستخدام التعليمة lambda تماثل تلك المُنشأة باستخدام التعليمة def. ملاحظة: يفضل في الشيفرات الواقعية استخدام التعليمة def على إسناد دالة مجهولة إلى متغير، إذ أن الغرض الأساسي من وجود الدوال المجهولة هو استخدامها في الحالات التي لا تحتاج فيها الدالة إلى اسم. صياغة الدوال المجهولة مفيدة في تخصيص دوال صغيرة لتعمل مثل وسطاء عند استدعاء دوال أخرى. على سبيل المثال، تمتلك الدالة ()sorted وسيطًا مسمى يدعى key يسمح بتحديد دالة، فبدلًا من فرز العناصر في قائمة ما وفقًا لقيمها، تفرزها وفقًا للقيمة المعادة من تلك الدالة الممررة إلى الوسيط key. مررنا في المثال التالي دالةُ مجهولةً إلى الدالة ()sorted تعيد محيط مستطيل مُعطى الأبعاد، ما يجعل الدالة ()sorted تفرز العناصر اعتمادًا على المحيط المحسوب من طوله وعرضه [width, height] الواردان في القائمة، بدلًا من فرزها اعتمادًا على قيم الطول والعرض نفسها، على النحو التالي: >>> rects = [[10, 2], [3, 6], [2, 4], [3, 9], [10, 7], [9, 9]] >>> sorted(rects, key=lambda rect: (rect[0] * 2) + (rect[1] * 2)) [[2, 4], [3, 6], [10, 2], [3, 9], [10, 7], [9, 9]] بدلًا من فرز القيم [10,2] أو [3,6] مثلًا، أصبحت الدالة تفرزها اعتمادًا على قيمة المحيط المعادة المتمثلة بالأعداد الصحيحة 24 و18 على التوالي. تعد الدوال المجهولة اختصارًا مناسبًا للصياغة، إذ من الممكن تعريف دالة مجهولة صغيرة مؤلفة من سطر برمجي واحد، بدلًا من تعريف دالة مسماة جديدة باستخدام التعليمة def. الربط والترشيح باستخدام بنى اشتمال القوائم List Comprehensions كانت الدالتين ()map و ()filter في الإصدارات الأقدم من بايثون من الدوال عالية المستوى الشائعة القادرة على ربط القوائم وترشيحها، الأمر الذي كان يجري غالبًا بمساعدة الدوال المجهولة. تستطيع عملية الربط Mapping إنشاء قائمة قيم اعتمادًا على قيم قائمة أخرى؛ بينما ينشئ الترشيح قائمةً تتضمن فقط القيم التي تحقق معيارًا معينًا من قائمة أخرى. على سبيل المثال، لو أردنا إنشاء قائمة جديدة تتضمن قيم من نوع السلاسل النصية بدلًا من بدلًا من الأعداد الصحيحة في القائمة التالية [7 ,6 ,1 ,12 ,19 ,18 ,16 ,8]، فمكن الممكن تمرير كل من القائمة هذه والتابع المجهول (lambda n: str(n إلى دالة الربط ()map، على النحو التالي: >>> mapObj = map(lambda n: str(n), [8, 16, 18, 19, 12, 1, 6, 7]) >>> list(mapObj) ['8', '16', '18', '19', '12', '1', '6', '7'] تعيد الدالة ()map كائنًا من النوع map، والذي يمكن تحويله إلى قائمة بتمريره إلى الدالة ()list، وبذلك تتضمن القائمة المربوطة الآن قيمًا من نوع سلاسل محرفية موافقة للأعداد الصحيحة الموجودة في القائمة الأصلية. تعمل دالة الترشيح ()filter بآلية مشابهة، إلا أن وسيط الدالة المجهولة في هذه الحالة يحدد العناصر من القائمة التي ستبقى (عند إعادة الدالة المجهولة للقيمة True من أجل هذا العنصر) وتلك التي ستُرشّح (عند إعادة الدالة المجهولة للقيمة False من أجل هذا العنصر). على سبيل المثال، يمكن تمرير الدالة المجهولة lambda n: n % 2 == 0 لترشيح أي أعداد فردية في السلسلة على النحو التالي: >>> filterObj = filter(lambda n: n % 2 == 0, [8, 16, 18, 19, 12, 1, 6, 7]) >>> list(filterObj) [8, 16, 18, 12, 6] تعيد الدالة ()filter كائن ترشيح من النوع filter، والذي يمكن أيضًا تمريره إلى الدالة ()list، وبذلك تبقى الأعداد الزوجية فقط في القائمة بعد الترشيح. تمثل الدالتان ()map و ()filter طرقًا قديمة في إنشاء قوائم مربوطة أو مُرشّحة في بايثون. فالآن أصبح من الممكن إنشاء هذه القوائم باستخدام بنى اشتمال القوائم، والتي لا تتطلب كتابة دوال مجهولة ناهيك عن سرعتها مقارنةً بالدالتين ()map و ()filter. سنعيد فيما يلي مثال الدالة ()map ولكن باستخدام بنية اشتمال قوائم: >>> [str(n) for n in [8, 16, 18, 19, 12, 1, 6, 7]] ['8', '16', '18', '19', '12', '1', '6', '7'] نلاحظ أن الجزئية (str(n من بنية اشتمال القوائم في الشيفرة أعلاه تشابه في وظيفتها الدالة المجهولة (lambda n: str(n من الطريقة السابقة. وفيما يلي نعيد مثال الدالة ()filter ولكن باستخدام بنية اشتمال قوائم: >>> [n for n in [8, 16, 18, 19, 12, 1, 6, 7] if n % 2 == 0] [8, 16, 18, 12, 6] نلاحظ أن الجزئية n % 2 == 0 من بنية اشتمال القوائم في الشيفرة أعلاه تشابه في وظيفتها الدالة المجهولة lambda n: n % 2 == 0 من الطريقة السابقة. تتعامل العديد من اللغات مع مفهوم الدوال على أنها كائنات من الدرجة الأولى، ما يسمح بوجود دوال عالية المستوى بما يتضمن دوال الربط والترشيح. ينبغي على القيم المعادة أن تتضمن دوما نمط البيانات نفسه تعد بايثون لغة برمجة ديناميكية الأنماط، ما يعني أن توابع بايثون ودوالها تمتلك حرية إعادة قيم من أي نمط بيانات، ولكن وبغية تجنب السلوكيات غير المتوقعة للدوال، ينبغي أن نسعى لجعلها تعيد قيمًا من نمط بيانات واحد فقط. على سبيل المثال، لدينا في الشيفرة التالية دالة تعتمد على رقم عشوائي لتعيد إما عددًا صحيحًا أو سلسلةً نصية: >>> import random >>> def returnsTwoTypes(): ... if random.randint(1, 2) == 1: ... return 42 ... else: ... return 'forty two' لدى كتابة شيفرة تستدعي هذه الدالة، سيكون من السهل نسيان وجوب التعامل مع عدة أنماط بيانات ممكنة. واستكمالًا لهذا المثال، لنفرض أننا سنستدعي الدالة ()returnsTwoTypes ونريد تحويل العدد الذي تعيده إلى نظام العد السداسي عشري على النحو التالي: >>> hexNum = hex(returnsTwoTypes()) >>> hexNum '0x2a' تعيد دالة بايثون ()hex المبنية مسبقًا سلسلةً نصيةً لقيمة العدد الصحيح الممرر إليها في نظام العد السداسي عشري. ستعمل الشيفرة السابقة على نحو سليم طالما أن الدالة ()returnsTwoTypes تعيد قيمةً عدديةً صحيحة، ما يوحي بأن الشيفرة خالية الأخطاء، إلا أنه بمجرد إعادة الدالة ()returnsTwoTypes لسلسلة نصية، سيظهر الاستثناء التالي: >>> hexNum = hex(returnsTwoTypes()) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object cannot be interpreted as an integer فمن الضروري أن نتذكر دومًا ضرورة التعامل مع كافة أنماط البيانات التي قد تعيدها الدالة، ولكن في الواقع من السهل نسيان هذا الأمر. لتجنب هذا النوع من الأخطاء، علينا أن نحاول دومًا جعل القيم المعادة من الدوال تعود لنمط بيانات واحد، ولا يمكن عد ذلك توجيهًا صارمًا، ففي بعض الحالات لا مفر من جعل الدالة تعيد قيمًا من أنماط بيانات مختلفة، ولكن إجمالًا كلما كانت الدوال أقرب لإعادة قيم من نمط بيانات واحد، كلما كانت أسهل وأقل عرضةً للأخطاء. توجد حالة عملية ينبغي الانتباه إليها وهي ألا نجعل الدالة تعيد القيمة None إلا إذا كانت لا تعيد سواها، إذ أن القيمة None هي الوحيدة ضمن نمط البيانات None، فقد نرغب بجعل الدالة تعيد None للدلالة على حدوث خطأ ما (الأمر الذي سنناقشه في الفقرة التالية "إظهار الاستثناءات مقابل إعادة رموز الأخطاء")، ولكن عليك حصر استخدام None فقط مثل قيمة معادة للدوال التي لا تمتلك أي قيمة معادة ذات معنى، والسبب في ذلك هو أن إعادة القيمة None للدلالة على وقوع خطأ يعد مصدرًا شائعًا للاستثناء غير المعلوم الخاص بنمط البيانات None والدال على استخدام سمة ليست من سمات الكائن 'NoneType' object has no attribute، كما في المثال: >>> import random >>> def sometimesReturnsNone(): ... if random.randint(1, 2) == 1: ... return 'Hello!' ... else: ... return None ... >>> returnVal = sometimesReturnsNone() >>> returnVal.upper() 'HELLO!' >>> returnVal = sometimesReturnsNone() >>> returnVal.upper() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'upper' رسالة الخطأ السابقة غامضة نسبيًا، وقد تتطلب بعض الجهد لتتبع سببها عودةً إلى دالة تعيد في الأحوال الطبيعية قيمة متوقعة ولكنها قد تعيد القيمة None في حال وقوع خطأ، وقد وقع الخطأ في مثالنا السابق لأن الدالة ()sometimesReturnsNone أعادت القيمة None والتي أسندناها إلى المتغير returnVal. في حين أن رسالة الخطأ توحي بأن الخطأ حصل في استدعاء التابع ()upper. إظهار الاستثناءات Exceptions مقابل إعادة رموز الأخطاء Error Codes للمصطلحين استثناء exception وخطأ error نفس المعنى تقريبًا في بايثون وهو: ظرف أو حالة استثنائية في البرنامج تشير عادةً إلى وجود مشكلة. أصبحت الاستثناءات شائعة وبمثابة ميزة للغات البرمجة في الثمانينات والتسعينات وذلك في لغتي C++‎ وجافا، إذ حلت الاستثناءات محل استخدام رموز الأخطاء والتي تمثّل القيم المعادة من الدالة لتشير إلى وجود مشكلة. لعل فائدة الاستثناءات تكمن في كونها تعيد قيمًا متعلقة بعمل الدالة نفسه بدلًا من الإشارة إلى وجود خطأ فحسب. قد تسبب رموز الأخطاء بحد ذاتها مشاكلًا في البرنامج، فعلى سبيل المثال، تعيد الدالة ()find الخاصة بالتعامل مع السلاسل النصية في بايثون عادةً الفهرس الموافق لمكان وجود سلسلة نصية فرعية ضمن سلسلة نصية رئيسية ما، وفي حال عدم عثورها على السلسلة الفرعية المطلوبة، فإنها تعيد القيمة "1-" مثل رمز خطأ، ولكن وبما أننا قد نستخدم القيمة "1-" للدلالة على رقم فهرس محرف ابتداءً من نهاية السلسلة النصية، فقد يؤدي استخدام "1-" رمزًا لخطأ إلى وقوع خطأ غير مقصود. لنكتب ما يلي ضمن الصدفة التفاعلية كمثال: >>> print('Letters after b in "Albert":', 'Albert'['Albert'.find('b') + 1:]) Letters after b in "Albert": ert >>> print('Letters after x in "Albert":', 'Albert'['Albert'.find('x') + 1:]) Letters after x in "Albert": Albert تقيّم الشيفرة السابقة الجزئية ('Albert'.find('x' إلى رمز الخطأ "1-" (نظرًا لعدم وجود المحرف x ضمن السلسة النصية Albert)، وهذا ما يجعل بدوره التعبير البرمجي [:Albert'['Albert'.find('x') + 1' يُقيّم إلى [:Albert'[-1 + 1' والذي يُقيّم هو الآخر إلى [:Albert'[0' وبالتالي إلى القيمة 'Albert'. لا تمثّل هذه النتيجة تلك المقصودة من الشيفرة، إذ سيظهر استدعاء التابع ()index بدلًا من ()find كما في [:Albert'['Albert'.index('x') + 1' استثناءً، ما يجعل المشكلة جليةً وواضحة. يعيد التابع ()index الخاص بالسلاسل النصية من جهة أخرى استثناء خطأ القيمة ValueError في حال عدم العثور على الدالة الفرعية، وإذا لم نتعامل مع هذا الاستثناء، سيتوقف البرنامج عن العمل، الأمر الأفضل من عدم ملاحظة وجود خطأ أصلًا. تنتهي عادةً أسماء أصناف الاستثناءات بالكلمة "Error" وذلك عندما يشير الاستثناء إلى خطأ فعلي، مثل ValueError أو NameError للدلالة على خطأ في الاسم أو SyntaxError للدلالة على خطأ صياغي، أما أصناف الاستثناءات التي تشير إلى حالات استثنائية والتي لا تمثل أخطاء بالضرورة فتتضمن StopIteration أو KeyboardInterrupt أو SystemExit. الخلاصة رغم كون بايثون ليست بلغة برمجة وظيفية، إلا أنها تمتلك العديد من الميزات التي تستخدمها هذه اللغات، إذ أن الدوال في بايثون هي كائنات من الدرجة الأولى، ما يعني إمكانية تخزينها ضمن متغيرات وتمريرها مثل وسطاء لدوال أخرى (إذ تدعى تلك الأخيرة بالدوال عالية المستوى)، كما توفر الدوال المجهولة اختصارًا في الصياغة لحالات الحاجة لاستخدام دوال عديمة الاسم ومجهولة مثل وسطاء لدوال عالية المستوى. لعل دالة الربط ()map ودالة الترشيح ()filter من أشهر الدوال عالية المستوى في بايثون، رغم إمكانية تنفيذ نفس وظيفتهما على نحوٍ أسرع بالاعتماد على بنى اشتمال القوائم. ينبغي أن تنتمي القيم المعادة من الدالة دومًا إلى نفس نمط البيانات، كما ينبغي عليك تجنب استخدام رموز الأخطاء مثل قيم معادة، إذ تُمثّل القيمة None إحدى القيم المُستخدمة على نحو خاطئ على أنها رمزٌ للخطأ. ترجمة -وبتصرف- للجزء الثاني من الفصل العاشر "كتابة دوال فعالة في بايثون" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: كتابة دوال فعالة في بايثون مقدمة إلى البرمجة الوظيفية Functional Programming البرمجة كائنية التوجه
  20. تعدّ الدوال functions برامجًا صغيرةً موجودةً ضمن البرنامج الأساسي، سامحةً لنا بتجزئة الشيفرة إلى وحداتٍ أصغر، كما تجنبنا عناء كتابة شيفراتٍ مُكررة، والتي قد تتسبب بحدوث الأخطاء، إلا أن كتابة دوال فعالة يتطلب اتخاذ العديد من القرارات حول اسم الدالة وحجمها ومعاملاتها ومدى تعقيدها. يستعرض هذا المقال الطرق المختلفة لكتابة الدوال، موضحًا مزايا وعيوب المفاضلات المختلفة، إذ سنتعمق في كيفية المفاضلة ما بين الدوال الصغيرة والكبيرة، وفي كيفية تأثير عدد المعاملات على تعقيد الدالة، وفي كيفية كتابة دوال باستخدام أعداد متغيرة من الوسطاء باستخدام العاملين * و **. أسماء الدوال تتبع أسماء الدوال عمومًا الاصطلاحات نفسها المُستخدمة في تسمية المعرفات بأنواعها، إلا أن اسم الدالة يتضمن عادةً صيغةً فعليةً، إذ أنها تُنفذ أمرًا ما غالبًا، كما يمكن تضمين صيغة اسمية لتوصيف ما تفعله الدالة؛ فعلى سبيل المثال، تدل أسماء دوال مثل ()refreshConnection على تحديث الإتصال و ()setPassword على تعيين كلمة مرور و ()extract_version على استخراج رقم الإصدار، ويوضّح كل منها ما تفعله الدالة ولماذا تفعله. قد لا نحتاج لصيغة اسمية ضمن أسماء التوابع المُنشأة على أنها جزء من صنف أو وحدة ما، مثل تابع باسم ()reset من ضمن صنف باسم SatelliteConnection أو الدالة ()open من الوحدة webbrowser، إذ يوفر في هذه الحالة اسم الصنف أو الوحدة المعلومات اللازمة لفهم سياق عمل الدالة أو التابع؛ فمن الواضح في المثال السابق أن التابع ()reset يعيد تعيين الاتصال عبر الأقمار الصناعية satellite connection وأن الدالة ()open تفتح متصفح الويب. يُفضّل استخدام أسماء طويلة تؤدي المعنى على أسماء مختصرة وقصيرة جدًا، إذ سيفهم المختص بالرياضيات مباشرةً أن دالةً باسم ()gcd ستعيد القاسم المشترك الأكبر لعددين، أما بالنسبة لغير المختصين، فسيجدون الاسم ()getGreatestCommonDenominator أوضح. وتذكّر ألا تستخدم أي من أسماء الدوال أو الوحدات المبنية مسبقًا في بايثون لتسمية دوالك، مثل: all any date email file format hash id input list min max object open random set str sum test type دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن مفاضلات أحجام الدوال يرى بعض المبرمجين أنه على الدوال أن تكون أقصر ما يمكن وألا تتجاوز ما يمكن عرضه على شاشة واحدة دون الحاجة إلى التمرير لأسفل؛ فالدالة المكونة من اثني عشر سطر برمجي ستكون سهلة للفهم نسبيًا، على الأقل لدى مقارنتها بأخرى مكونة من مئات الأسطر البرمجية، لكن قد ينطوي جعل الدوال أقصر عن طريق تجزئتها إلى عدة دوال فرعية صغيرة على عيوب أيضًا. لنتعرف بدايةً على فوائد الدوال الصغيرة: شيفرة الدالة أسهل للفهم. تحتاج الدالة غالبًا إلى عدد أقل من المعاملات. احتمالية أن يكون للدالة آثار سلبية جانبية أقل كما هو موضح في الفقرة "البرمجة الوظيفية" من هذا المقال. سيكون اختبار الدالة وتنقيحها أسهل. ستعرض الدالة غالبًا أنواع مختلفة أقل من الاستثناءات. تنطوي الدوال القصيرة أيضًا على بعض العيوب ومنها: يعني استخدام دوال قصيرة غالبًا الحاجة لاستخدام عدد أكبر من الدوال في البرنامج. يجعل استخدام عدد أكبر من الدوال البرنامج أعقد. يعني استخدام المزيد من الدوال الحاجة لابتكار المزيد من الأسماء الدقيقة التوصيفية ذات المعنى، وهي مهمة ليست بالسهلة. ينطوي استخدام المزيد من الدوال على الحاجة لكتابة المزيد من التوثيقات والتعليقات. تصبح العلاقات بين الدوال أعقد. يتمسّك البعض بالمبدأ التوجيهي القائل: "الأقصر أفضل" بمبالغة، مفترضين أنه على كل دالة أن تكون بحدود ثلاثة أو أربعة أسطر برمجية على الأكثر، وهذا منافٍ للمنطق، فعلى سبيل المثال، لنأخذ دالة قراءة انتقالات اللاعب ()getPlayerMove المستخدمة في بناء لعبة برج هانوي لاحقًا، ولا يهمنا الآن فهم كيفية عمل هذه الشيفرات، وإنما الهدف منها فقط الاطلاع على الهيكلية العامة لهذه الدالة: def getPlayerMove(towers): """Asks the player for a move. Returns (fromTower, toTower).""" while True: # الاستمرار بالطلب من المستخدم حتى إدخال انتقال صحيح print('Enter the letters of "from" and "to" towers, or QUIT.') print("(e.g. AB to moves a disk from tower A to tower B.)") print() response = input("> ").upper().strip() if response == "QUIT": print("Thanks for playing!") sys.exit() # التأكّد من أنّ المستخدم قد أدخل أحرفًا صحيحة لاسم للبرج if response not in ("AB", "AC", "BA", "BC", "CA", "CB"): print("Enter one of AB, AC, BA, BC, CA, or CB.") continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا # استخدام أسماء ذات توصيفية أعلى fromTower, toTower = response[0], response[1] if len(towers[fromTower]) == 0: # لا يمكن أن يكون فارغًا "from" برج البداية print("You selected a tower with no disks.") continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا elif len(towers[toTower]) == 0: # فارغ "to" يمكن نقل أي قرص إلى برج وجهة return fromTower, toTower elif towers[toTower][-1] < towers[fromTower][-1]: print("Can't put larger disks on top of smaller ones.") continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا else: # الانتقال صحيح، لذا سنعيد اسم برج البداية وبرج الوجهة المُحددان return fromTower, toTower تمتد الدالة السابقة على 34 سطر برمجي، ورغم أنها تغطي عدّة مهام تتضمن السماح للّاعب بإجراء انتقال جديد والتحقق من صحة هذا الانتقال والطلب منه اختيار انتقال آخر في حال كون السابق غير مسموح، إلا أن كل من المهام السابقة تندرج تحت بند قراءة انتقالات اللاعب، وفي حال كنا مصممين على كتابة دوال قصيرة، فمن الممكن تجزئة شيفرات الدالة ()getPlayerMove إلى دوال أصغر، على النحو التالي: def getPlayerMove(towers): """Asks the player for a move. Returns (fromTower, toTower).""" while True: # الاستمرار بالطلب من المستخدم حتى إدخال انتقال صحيح response = askForPlayerMove() terminateIfResponseIsQuit(response) if not isValidTowerLetters(response): continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا # استخدام أسماء ذات توصيفية أعلى fromTower, toTower = response[0], response[1] if towerWithNoDisksSelected(towers, fromTower): continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا elif len(towers[toTower]) == 0: # فارغ "to" يمكن نقل أي قرص إلى برج وجهة return fromTower, toTower elif largerDiskIsOnSmallerDisk(towers, fromTower, toTower): continue # الطلب من المستخدم لإدخال الانتقال المطلوب مجددًا else: # الانتقال صحيح، لذا سنعيد اسم برج البداية وبرج الوجهة المُحددان return fromTower, toTower def askForPlayerMove(): """Prompt the player, and return which towers they select.""" print('Enter the letters of "from" and "to" towers, or QUIT.') print("(e.g. AB to moves a disk from tower A to tower B.)") print() return input("> ").upper().strip() def terminateIfResponseIsQuit(response): """Terminate the program if response is 'QUIT'""" if response == "QUIT": print("Thanks for playing!") sys.exit() def isValidTowerLetters(towerLetters): """Return True if `towerLetters` is valid.""" if towerLetters not in ("AB", "AC", "BA", "BC", "CA", "CB"): print("Enter one of AB, AC, BA, BC, CA, or CB.") return False return True def towerWithNoDisksSelected(towers, selectedTower): """Return True if `selectedTower` has no disks.""" if len(towers[selectedTower]) == 0: print("You selected a tower with no disks.") return True return False def largerDiskIsOnSmallerDisk(towers, fromTower, toTower): """Return True if a larger disk would move on a smaller disk.""" if towers[toTower][-1] < towers[fromTower][-1]: print("Can't put larger disks on top of smaller ones.") return True return False تمتد الدوال الستة السابقة على 56 سطر، أي بحدود ضعف ما كانت عليه شيفرة الدالة الأصلية مع أدائها للمهام ذاتها، ورغم أن كل دالة من الدوال الستة أسهل للفهم وحدها من فهم الدالة ()getPlayerMove الأصلية كاملةً، إلا أن تجميع هذه الدوال معًا يزيد من التعقيد، فقد يواجه قراء شيفرة كهذه صعوبةً في فهم كيفية توافق هذه الدوال مع بعضها بعضًا، كما أن الدالة ()getPlayerMove من بين الدوال الستة هي الوحيدة التي ستُستدعى في باقي أجزاء البرنامج، في حين أن الدوال الخمسة المُتبقية لن تُستدعى سوى لمرة واحدة ومن قِبل الدالة ()getPlayerMove نفسها، إلا أنّ كتلة الدوال السابقة لا تعبّر بضخامتها عن هذه الحقيقة، ناهيك عن الحاجة لابتكار أسماء وسلاسل توثيق نصية docstrings جديدة (السلسلة النصية المحصورة بين علامات اقتباس ثلاثية أسفل كل تعليمة تصريح عن دالة def) لكل دالة جديدة، والذي يؤدي إلى وجود أسماء متشابهة تسبب الارتباك، مثل الدالتين ()getPlayerMove و ()askForPlayerMove. لا تزال الدالة ()getPlayerMove الجديدة بعد التقسيم تتجاوز ثلاث أو أربع أسطر برمجية، فلو كنا نتبع المبدأ التوجيهي "الأقصر أفضل" بحرفيته لكنا مضطرين لتقسيمها إلى المزيد من التوابع الفرعية الأصغر؛ ففي مثل هذه الحالة، قد تؤدي سياسة استخدام دوال قصيرة جدًا إلى الحصول على دوال أبسط، إلا أن التعقيد الإجمالية للبرنامج سيزداد جذريًا. وفقًا لرأي صاحب المقال: يجب ألا تتجاوز التوابع في الحالة المثالية 30 سطرًا برمجيًا، وما عدا ذلك ألا تتجاوز طبعًا حدود 200 سطر برمجي. اجعل دوالك أقصر ما يمكن ضمن حدود الممكن والمعقول وليس أكثر. معاملات ووسطاء الدوال تُعرف معاملات الدالة Function Parameters بأنها أسماء المتغيرات الموجودة بين قوسي الدالة ضمن تعليمة def المسؤولة عن التصريح عن الدالة، في حين أن وسطاء الدالة Function Arguments هي القيم الممررة بين قوسي الدالة عند استدعائها. وكلما زاد عدد معاملات الدالة، زادت إمكانية ضبط وتعميم شيفرتها، ولكن في الوقت نفسه زاد تعقيدها. لعل إحدى القواعد الجيدة لاتباعها في هذا الصدد تنص على أن استخدام من 0 إلى 3 معاملات هو أمر مناسب، ولكن ما يزيد عن 5 أو 6 معاملات يعد غالبًا أكثر من اللازم؛ فبمجرد أن تصبح الدالة شديدة التعقيد، من الأفضل التفكير بكيفية تقسيمها إلى دوال أصغر بعدد معاملات أقل لكل منها. الوسطاء الافتراضية Default Arguments تتمثل إحدى طرق تقليص تعقيد معاملات الدالة في كتابة الوسيط الافتراضي للمعاملات، إذ يُعرّف الوسيط الافتراضي بأنه قيمة تُستخدم وسيطًا للدالة في حال استدعائها دون تمرير وسيط إليها؛ فعند استخدام معظم استدعاءات الدالة قيمةً محددةً للمعامل، نجعل هذه القيمة وسيطًا افتراضيًا مُتجنبين إدخالها عدة مرات عند كل استدعاء للدالة. نحدد وسيطًا افترضيًا ضمن تعليمة def بكتابته عقب اسم المعامل وإشارة مساواة. على سبيل المثال، في الدالة ()introduction التالية، للمعامل المسمى greetings القيمة Hello وهي قيمة افتراضية تُستخدم في حال عدم تحديد قيمة له لدى استدعاء الدالة: >>> def introduction(name, greeting='Hello'): ... print(greeting + ', ' + name) ... >>> introduction('Alice') Hello, Alice >>> introduction('Hiro', 'Ohiyo gozaimasu') Ohiyo gozaimasu, Hiro عند استدعاء الدالة ()introduction دون تمرير قيمة للوسيط الثاني فيها، تستخدم السلسلة النصية Hello افتراضيًا. نلاحظ أن المعاملات ذات الوسطاء الافتراضية تأتي دومًا بعد تلك التي بدون وسطاء افتراضية. يمكنك العودة إلى مقال البنى الصحيحة المؤدية إلى الأخطاء الشائعة في بايثون الذي وضحنا فيه أهمية عدم استخدام الكائنات المتغيّرة mutable objects مثل القائمة الفارغة [] أو القاموس الفارغ {} على أنها قيم افتراضية وذلك ضمن الفقرة "لا تستخدم القيم المتغيرة من أجل وسطاء افتراضية"، إذ بيّنا فيها المشكلة التي تتسبب بها هذه المنهجية وكيفية حلها. استخدام * و ** لتمرير الوسطاء إلى الدوال من الممكن استخدام الصيغة * أو ** (وتُلفظ نجمة star ونجمة نجمة star star على التوالي) لتمرير مجموعة من الوسطاء إلى الدوال مثل قيم منفصلة؛ إذ نستخدم الصيغة * لتمرير العناصر ضمن كائن تكراري، مثل القائمة أو الصف؛ أما الصيغة ** فتسمح بتمرير أزواج مفتاح-قيمة في كائن مفهرس بالمفاتيح mapping object مثل القاموس بمثابة وسطاء منفصلة. يمكن على سبيل المثال تمرير عدة وسطاء إلى الدالة ()print، فتوضع مسافات فيما بينها افتراضيًا، كما هو مُبيّن في الشيفرة التالية: >>> print('cat', 'dog', 'moose') cat dog moose تدعى هذه الوسطاء بالوسطاء الموضعية positional، إذ يحدد موقعها ضمن استدعاء الدالة الوسيط المُحدد لكل معامل. ولكن ماذا لو خُزّنت السلاسل النصية هذه ضمن قائمة وحاولنا تمرير القائمة كاملةً إلى الدالة؟ ستعتقد الدالة ()print أنك ترغب بطباعة السلسلة كاملةً على أنها قيمة واحدة على النحو التالي: >>> args = ['cat', 'dog', 'moose'] >>> print(args) ['cat', 'dog', 'moose'] نلاحظ أن تمرير القائمة إلى الدالة ()print يطبع القائمة كما هي، بما في ذلك الأقواس المعقوفة وعلامات الاقتباس ومحارف الفاصلة. وإحدى طرق طباعة عناصر القائمة منفردةً تكون بتجزئة القائمة إلى عدة وسطاء بتمرير فهرس كل عنصر إلى الدالة على حدى، ما يجعل الشيفرة أصعب للقراءة: >>> # مثال عن شيفرة أصعب للقراءة >>> args = ['cat', 'dog', 'moose'] >>> print(args[0], args[1], args[2]) cat dog moose توجد طريقة أسهل لتمرير هذه العناصر إلى الدالة ()print، إذ يمكن استخدام الصيغة * لتفسير العناصر ضمن قائمة أو أي نمط بيانات تكراري على أنها وسطاء موضعية منفردة ضمن الدالة. لنكتب المثال التالي في الصدفة التفاعلية: >>> args = ['cat', 'dog', 'moose'] >>> print(*args) cat dog moose تتيح الصيغة * إمكانية تمرير عناصر القائمة إلى الدالة مثل قيم مفردة بغض النظر عن عدد العناصر في القائمة، كما يمكن استخدام الصيغة ** لتمرير أنماط البيانات المفهرسة بالمفاتيح، مثل القواميس على أنها وسطاء مسماة Keyword argument مستقلة، إذ تُسبق الوسطاء المسماة باسم المعامل وإشارة مساواة. على سبيل المثال، لدى الدالة ()print وسيطًا مسمى يدعى sep والذي يحدد سلسلةً نصيةً لتوضع ما بين الوسطاء التي ستُعرض، وتُعين افتراضيًا لتكون مسافةً فارغة ' '. يمكن إسناد قيمة جديدة للوسيط المسمى وذلك إما باستخدام تعليمة إسناد أو الصيغة **. لنكتب ما يلي في الصدفة التفاعلية مثلًا: >>> print('cat', 'dog', 'moose', sep='-') cat-dog-moose >>> kwargsForPrint = {'sep': '-'} >>> print('cat', 'dog', 'moose', **kwargsForPrint) cat-dog-moose نلاحظ أن التعليمات السابقة تعطي خرجًا متماثلًا، إذ استخدمنا في المثال سطرًا برمجيًا واحدًا لإعداد القاموس kwargsForPrint المتضمن قيمة الوسيط المسمى الخاص بالدالة ()print، ولكن قد نحتاج في الحالات الأعقد للمزيد من الشيفرات لدى إعداد قاموس بالوسطاء المسماة. تتيح الصيغة ** لنا إمكانية إنشاء قاموس مخصص بإعدادات الضبط لنمرره عند استدعاء الدالة، وهذا أمر مفيد لا سيما للدوال والتوابع ذات العدد الكبير من الوسطاء المسماة. بذلك نجد بأنه مع استخدام صيغتي * و **، مع تعديل قائمة أو قاموس أثناء التنفيذ، يمكن تزويد الدالة عند استدعائها بعددٍ متغير من الوسطاء. استخدام * لإنشاء دوال مرنة Variadic Functions يمكن أيضًا استخدام الصيغة * ضمن تعليمة التصريح عن الدوال def بغية إنشاء دوال مرنة تستقبل عددًا متغيرًا من الوسطاء الموضعيين. على سبيل المثال، تعد الدالة ()print دالةً مرنة، لأننا نستطيع تمرير أي عدد نريده من السلاسل النصية إليها، مثل ('!print('Hello أو (print('My name is', name، ورغم أننا استخدمنا الصيغة * في الفقرة السابقة عند استدعاء الدوال، سنستخدمها الآن أثناء التصريح عن الدوال. لنلقي نظرةً على المثال التالي، وفيه ننشئ دالةً باسم ()product تستقبل أي عدد من الوسطاء لتوجد جدائها: >>> def product(*args): ... result = 1 ... for num in args: ... result *= num ... return result ... >>> product(3, 3) 9 >>> product(2, 1, 2, 3) 12 ليس المعامل args في الدالة السابقة سوى صف عادي في بايثون يتضمن كافة الوسطاء الموضعية. يمكن من الناحية التقنية، تسمية هذا المعامل بأي اسم نريد، شرط أن يُسبق برمز النجمة *، وقد جرت العادة بتسميته args. تتطلب معرفة التوقيت الأنسب لاستخدام الصيغة * بعض التفكير، فالبديل الآخر بغية إنشاء دالة مرنة هو استخدام معامل وحيد يقبل القائمة مثل قيمة مُمرَّرة، أو أي نمط بيانات تكراري آخر، ليتضمن عددًا متغيرًا من العناصر، وهو المبدأ الذي تستخدمه الدالة ()sum المبنية مُسيقًا في بايثون: >>> sum([2, 1, 2, 3]) 8 تتوقع الدالة ()sum تمرير وسيط واحد تكراري إليها، فتمرير عدة وسطاء يؤدي إلى ظهور استثناء كما يلي: >>> sum(2, 1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sum() takes at most 2 arguments (4 given) في حين تقبل الدالتين ()min و ()max المبنيتين مُسبقًا في بايثون، واللتان توجدان القيمة الأصغر والقيمة الأكبر على التوالي من بين مجموعة قيم، تمرير وسيط تكراري وحيد إليها أو عدة وسطاء منفردة على النحو التالي: >>> min([2, 1, 3, 5, 8]) 1 >>> min(2, 1, 3, 5, 8) 1 >>> max([2, 1, 3, 5, 8]) 8 >>> max(2, 1, 3, 5, 8) 8 الشيء المشترك بين الدوال السابقة هو إمكانية تمرير أعداد متغيرة من الوسطاء لكل منها، والسؤال الذي يطرح نفسه هنا هو ما سبب تصميم معاملات كل منها على نحو مختلف؟ ومتى يجب أن نصمم دوالًا تقبل وسيطًا تكراريًا وحيدًا ومتى يجب تصميمها لتقبل وسطاء متعددة منفصلة باستخدام الصيغة *؟ تعتمد آلية تصميم المعاملات على الطريقة المتوقعة لاستخدام المبرمج للشيفرة، إذ تقبل الدالة ()print عدة وسطاء لأن المبرمجين يمررون غالبًا سلسلةً من السلاسل النصية أو المتغيرات المتضمنة لسلاسل نصية إليها، مثل (print('My name is', name. يُعد تجميع هذه السلاسل النصية ضمن قائمة ما يتطلب تنفيذ عدة خطوات قبل تمريرها إلى الدالة ()print أمرًا غير شائع، وفي حال تمرير قائمة إلى الدالة ()print، ستُطبع كاملةً بأقواسها المعقوفة ومحارف الفاصلة ما بين العناصر، وبالتالي لا يمكن استخدامها لطباعة قيم قائمة منفردةً. أما بالنسبة للدالة ()sum، فما من سبب لاستدعائها مع تمرير وسطاء منفصلة طالما أنه من الممكن في بايثون استخدام عامل الجمع + لهذا الغرض، إذ يمكن ببساطة كتابة شيفرة مثل 8+4+2، وبالتالي ما من ضرورة لإتاحة إمكانية كتابة شيفرة على النحو (2,4,8)sum، ما يفسر سبب وجوب تمرير العدد المتغير من الوسطاء إلى الدالة ()sum على هيئة قائمة. تتيح الدالتان ()min و ()max استخدام كلا الأليتين؛ فإذا مرر المبرمج إلى أي منهما وسيطًا واحدًا، تفترض الدالة بأن هذا الوسيط هو قائمة أو صف من القيم لتعمل على تقييمها؛ أما إذا مرر إليها عدّة وسطاء، فتفترض أن هذه القيم تمثل ما ستقيّمه، فكلا الدالتين قادرتين على التعامل مع القوائم أثناء تنفيذ البرنامج، كما في الاستدعاء (min(allExpenses، كما أن لديهما القدرة على التعامل مع الوسطاء المنفصلة التي يختارها المبرمج أثناء كتابة شيفراته، كما في الاستدعاء (max(0, someNumber. وبالتالي فإن هاتين الدالتين مصممتين للتعامل مع كلا نوعي الوسطاء. توضح الدالة ()myMinFunction التالية الفكرة، والتي تؤدي نفس وظيفة الدالة ()min: def myMinFunction(*args): if len(args) == 1: 1 values = args[0] else: 2 values = args if len(values) == 0: 3 raise ValueError('myMinFunction() args is an empty sequence') 4 for i, value in enumerate(values): if i == 0 or value < smallestValue: smallestValue = value return smallestValue تستخدم الدالة ()myMinFunction الصيغة * لتستقبل أعدادًا مختلفة من الوسطاء على هيئة صف، فإذا احتوى هذا الصف على قيمة وحيدة، نفترض أنها سلسلة من القيم لتُقيّم كما هو مبين في السطر رقم 1 من الشيفرة السابقة، وفيما عدا ذلك، نفترض أن المعامل arg هو صف من القيم لتُقيّم كما هو مبين في السطر رقم 2 من الشيفرة السابقة.ففي كلتا الحالتين ستتضمن المتغيرات المخصصة لاستقبال القيم على سلسلة من القيم لتعمل باقي أجزاء الشيفرة على تقييمها. كما هو الحال في الدالة ()min الفعلية المبنية مسبقًا في بايثون، عملنا على إظهار خطأ قيمة ValueError في حال استدعاء الدالة دون تمرير أي قيمة أو تمرير سلسلة فارغة إليها، وذلك في السطر رقم 3، أما باقي الشيفرة فتمر على القيم المُمرَّرة لتعيد القيمة الأصغر بينها في السطر رقم 4. بغية تبسيط الدالة ()myMinFunction السابقة، جعلناها تتعامل فقط مع القوائم والصفوف من بين القيم التكرارية الممكنة. قد تتساءل عن سبب عدم كتابة الدوال دومًا لتتعامل مع كلا آليتي تمرير أعداد مختلفة من الوسطاء، والجواب هو أنه من الأفضل دومًا جعل الدوال أبسط ما يمكن، فما لم تكن كلتا طريقتي الاستدعاء شائعة الاستخدام، من الأفضل اختيار إحداهما. إذا كانت الدالة تتعامل عادةً مع بنى معطيات تُنشأ أثناء تنفيذ البرنامج، فمن الأفضل تصميمها لتستقبل معاملًا واحدًا، أما إذا كانت تتعامل عادةً مع وسطاء تُحدد من قبل المبرمج أثناء كتابته للشيفرة، فمن الأفضل في هذه الحالة استخدام الصيغة * لاستقبال أعداد مختلفة من المتغيرات. استخدام ** لإنشاء دوال مرنة يمكن للدوال المرنة أن تستخدم الصيغة ** أيضًا، فرغم كون الصيغة * ضمن تعليمة def تدل على عدد متغير من الوسطاء الموضعيين، إلا أن الصيغة ** تدل على عدد متغير من الوسطاء المسماة الاختيارية؛ فلو صرحنا عن دالة تأخذ عددّا من الوسطاء المسماة الاختيارية دون استخدام الصيغة **، فقد يغدو جزء التصريح هذا صعبًا وغير عملي. لنأخذ دالة مفترضة باسم ()formMolecule لتشكيل مركب كيميائي والتي تمتلك معامل لكل عنصر من العناصر الكيميائية المعروفة البالغ عددها 118: >>> def formMolecule(hydrogen, helium, lithium, beryllium, boron, --snip-- سيكون تمرير القيمة 2 لمعامل عدد ذرات الهيدروجين hydrogen والقيمة 1 لمعامل عدد ذرات الأكسجين oxygen للحصول على جزيء الماء water عمليةً شاقةً وصعبة القراءة، إذ أننا سنضطر إلى إسناد القيمة 0 إلى كل معاملات العناصر غير المطلوبة، على النحو التالي: >>> formMolecule(2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 --snip-- 'water' يمكن جعل الدالة أكثر تنظيمًا باستخدام معاملات مسماة مع وسيط افتراضي لكل منها، ما يجنبنا عبء تمرير وسيط إلى كل معامل عند استدعاء الدالة. ملاحظة: رغم التعريف الواضح لكلا مصطلحي الوسيط والمعامل، إلا أن المبرمجين عادةً ما يستخدمون مصطلحي الوسيط المسمى keyword argument والمعامل المسمى keyword parameter تبادليًا. على سبيل المثال، عينا في تعليمة التصريح عن الدالة التالية وسيطًا افتراضيًا يساوي القيمة 0 لكل من المعاملات المسماة، على النحو التالي: >>> def formMolecule(hydrogen=0, helium=0, lithium=0, beryllium=0, --snip-- وهذا ما يجعل من استدعاء الدالة ()formMolecule أسهل، لأننا لن نضطر إلى تحديد وسطاء سوى للمعاملات ذات القيم المختلفة عن قيمة الوسيط الافتراضي، كما من الممكن أيضًا تحديد الوسطاء المسماة بأي ترتيب، كما في المثال التالي: >>> formMolecule(hydrogen=2, oxygen=1) 'water' >>> formMolecule(oxygen=1, hydrogen=2) 'water' >>> formMolecule(carbon=8, hydrogen=10, nitrogen=4, oxygen=2) 'caffeine' مع ذلك، تبقى عبارة التصريح السابقة صعبة وغير عملية بوجود 118 اسم معامل، وماذا لو جرى اكتشاف عناصر كيميائية جديدة؟ سنضطر إلى تحديث تعليمة def مع كامل توثيقات معاملات الدالة. يمكن الاعتماد على حل بديل من خلال تجميع كافة المعاملات مع وسطائها على هيئة أزواج مفتاح-قيمة ضمن قاموس، وذلك باستخدام الصيغة ** للوسطاء المسماة، ويمكن -من الناحية التقنية- تسمية المعامل المسبوق بالصيغة ** بأي اسم، إلا أن العادة جرت على تسميته kwargs، على النحو التالي: >>> def formMolecules(**kwargs): ... if len(kwargs) == 2 and kwargs['hydrogen'] == 2 and kwargs['oxygen'] == 1: ... return 'water' ... # (هنا مكان بقية شيفرات الدالة) ... >>> formMolecules(hydrogen=2, oxygen=1) 'water' تشير الصيغة ** إلى أن المعامل kwargs قادر على التعامل مع كافة الوسطاء المسماة الممررة عند استدعاء الدالة، إذ ستُخزن على هيئة أزواج مفتاح-قيمة ضمن قاموس مخصص للمعامل kwargs. الآن، في حال اكتشاف عناصر كيميائية جديدة، كل ما علينا فعله هو تحديث شيرة الدالة وليس عبارة التصريح عنها، ذلك لأن كافة الوسطاء المسماة موضوعة ضمن المعامل kwarg: 1 >>> def formMolecules(**kwargs): 2 ... if len(kwargs) == 1 and kwargs.get('unobtanium') == 12: ... return 'aether' ... # (هنا مكان بقية شيفرات الدالة) ... >>> formMolecules(unobtanium=12) 'aether' نلاحظ من السطر رقم 1 في الشيفرة السابقة أن تعليمة def بقيت كما في الحالة السابقة، وأننا لم نضطر إلى تعديل سوى شيفرة الدالة فقط المشار إليها بالسطر رقم 2، فمع استخدام الصيغة **، تغدو كتابة كل من تعليمة التصريح عن الدالة واستدعائها أبسط مع الحفاظ على سهولة قراءة الشيفرة وفهمها. استخدام * و ** لإنشاء دوال مغلفة Wrapper Functions لعل إحدى الاستخدامات الشائعة لصيغتي * و ** ضمن تعليمة def هي إنشاء الدوال المغلّفة، والتي تُمرر الوسطاء إلى دالة أخرى لتعيد القيمة المعادة الخاصة بتلك الدالة، إذ يمكن استخدام صيغتي * و ** لتمرير أي من وسطاء دالة ما أو جميعها إلى الدالة المغلّفة. على سبيل المثال، يمكن إنشاء دالة باسم ()printLowercase لتكون مغلّفة لدالة ()print المبنية مسبقًا، إذ أنها تعتمد على الدالة ()print في أداء مهمتها الحقيقية في الطباعة، إلا أنها قبل ذلك تحوّل أحرف وسطاء السلاسل النصية إلى حالة الأحرف الصغيرة، على النحو التالي: 1 >>> def printLower(*args, **kwargs): 2 ... args = list(args) ... for i, value in enumerate(args): ... args[i] = str(value).lower() 3 ... return print(*args, **kwargs) ... >>> name = 'Albert' >>> printLower('Hello,', name) hello, albert >>> printLower('DOG', 'CAT', 'MOOSE', sep=', ') dog, cat, moose تستخدم الدالة ()printLower في السطر رقم 1 من الشيفرة السابقة الصيغة * لتتعامل مع أعداد مختلفة من الوسطاء الموضعيين الموجودة ضمن صف مُسند إلى المعامل args، في حين أن الصيغة ** تعمل على إسناد أي وسطاء مسماة إلى قاموس ضمن المعامل kwargs. إذا استخدمت دالة ما كلا المعاملين args* و kwargs** معًا، فيجب أن يأتي المعامل args* قبل المعامل kwargs**، إذ نمرر كلا المعاملين السابقين إلى الدالة المغلّفة ()print، ولكن قبل ذلك تُعدّل دالتنا الجديدة بعضًا من الوسطاء لتجعل الصف الموجود في المعامل args على هيئة قائمة وذلك في السطر المشار إليه بالرقم 2 من الشيفرة أعلاه. نمرّر -بعد تغيير السلاسل النصية الموجودة في المعامل args إلى حالة الأحرف الصغيرة- كلًا من العناصر الموجودة في هذا المعامل إضافةً إلى أزواج مفتاح-قيمة الموجودة في المعامل kwargs مثل وسطاء منفصلين إلى الدالة ()print باستخدام صيغتي * و ** وذلك في السطر المشار إليه بالرقم 3، كما تُعاد القيمة المعادة من الدالة ()print على أنها قيمة الدالة ()printLower المعادة، وهذه الخطوات كفيلة بتغليف الدالة ()print. الخلاصة تعد الدوال طريقةً شائعةً لتجميع شيفرات البرنامج معًا ضمن مجموعات، الأمر الذي يتطلب اتخاذ بعضٍ من القرارات، مثل اختيار اسم الدالة وحجمها وعدد معاملاتها وعدد الوسطاء التي سنمررها إلى هذه المعاملات. وباستخدام الصيغتين * و ** ضمن تعليمة التصريح عن الدوال def نتمكن من جعل الدالة تستقبل أعدادًا مختلفة من المعاملات، ما يجعلها دوالًا مرنة. ترجمة -وبتصرف- للجزء الأول من الفصل العاشر "كتابة دوال فعالة في بايثون" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: غرائب بايثون المخفية كيفية تعريف الدوال في بايثون تعرف على أهم الدوال المدمجة في لغة بايثون
  21. تُعد مجموعات القواعد التي تُعرّف لغات البرمجة معقدةً، وقد تؤدي إلى شيفرات ذات سلوكيات أو نتائج غريبة وغير متوقعة رغم كونها غير خاطئة. سنتعمق في هذا المقال أكثر بغرائب بايثون الغامضة. من غير المحتمل أن تواجه هذه الحالات لدى كتابة الشيفرات الواقعية، إلا أنها تمثّل استخدامات مثيرة للاهتمام لصياغة بايثون (أو مثل إساءات لاستخدام هذه الصياغة، حسب منظورك للأمر). ستتعرف من خلال دراستك للأمثلة الواردة في هذا المقال على كيفية عمل بايثون في الكواليس بصورة أوضح، فهيا بنا لنستمتع باستكشاف بعضٍ من الصيغ الصحيحة النادرة والمؤدية إلى وقوع أخطاء esoteric gotchas. لماذا 256 هو 256 ولكن 257 ليس 257؟ يقارن العامل == بين كائنين ليتحقق من تساوي قيمتيهما values، في حين يقارن العامل is بينهما للتحقق من تساوي هويتهما IDs، فبالرغم من أن للعدد الصحيح 42 (من النوع integer) والعدد الحقيقي 42.0 (من النوع float) القيمة نفسها، إلا أنهما يمثلان كائنان مختلفان محفوظان في مكانين مختلفين من ذاكرة الحاسوب، ويمكن التأكد من الأمر بالتحقق من هوياتهما المختلفة باستخدام الدالة ()id: >>> a = 42 >>> b = 42.0 >>> a == b True >>> a is b False >>> id(a), id(b) (140718571382896, 2526629638888) فلدى إنشاء بايثون لكائن عدد صحيح جديد وتخزينه في الذاكرة، يستغرق إنشاء هذا الكائن فترةً زمنيةً قصيرة جدًا. يُنشئ المفسر CPython (مُفسّر بايثون المتوفر للتحميل عبر الرابط) كائنات أعداد صحيحة للأعداد من 5- وحتى 256 في بداية كل برنامج مثل نوع من التحسين البسيط، وتدعى هذه الأعداد بالأعداد الصحيحة المُخصصة مُسبقًا، ويُنشئ مفسر بايثون كائنات لهذه الأعداد تلقائيًا نظرًا لكونها شائعة الاستخدام، فمن الشائع أن يُستخدم العدد 0 أو 2 أكثر من العدد 1729 مثلًا، وعند إنشاء كائن عدد صحيح جديد في الذاكرة، يتحقق CPython بدايةً من كونه غير محصور بين 5- و256؛ فإذا كان فعلًا كذلك، يوفر CPython الوقت باستعادة كائن العدد الصحيح المُنشأ أصلًا والموجود بدلًا من إنشاء كائن جديد. توفر هذه الطريقة أيضًا الذاكرة بعدم تخزين نسخ متطابقة لأعداد صحيحة صغيرة، كما هو موضح في الشكل 9-1. الشكل 9-1: توفّر بايثون الذاكرة باستخدام مراجع متعددة لنفس كائن العدد الصحيح (على اليسار)، بدلًا من كائنات أعداد صحيحة مكررة منفصلة من أجل كل مرجع (على اليمين). دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن وبسبب التحسين آنف الذكر، فمن الممكن أن تؤدي بعض الحالات المفتعلة إلى نتائج غريبة، وكمثال على ذلك، لنكتب ما يلي في الشيفرة التفاعلية: >>> a = 256 >>> b = 256 1 >>> a is b True >>> c = 257 >>> d = 257 2 >>> c is d False جميع كائنات العدد 256 هي في الواقع نفس الكائن (المُنشأ تلقائيًا من قبل مفسر بايثون)، وبالتالي فإن العامل is للمتغيرين a و b سيعيد القيمة True دلالةً على تطابق هويتاهما كما في السطر رقم 1، في حين تُنشئ بايثون كائنات مستقلة للعدد 257 في المتغيرين c و d، ما يفسر إعادة العامل is للقيمة False في السطر رقم 2 من الشيفرة السابقة. أما التعبير البرمجي ‎257 is 257 فيُقيّم إلى True، إذ تستخدم بايثون كائن العدد الصحيح نفسه المُنشأ للقيم المجردة المتطابقة ضمن التعليمة الواحدة، كما في المثال التالي: >>> 257 is 257 True ومما لا شك فيه أن البرامج الواقعية تستخدم عادةً قيمة العدد الصحيح وليس هويته، وبالتالي لن نستخدم العامل is لمقارنة الأعداد الصحيحة integers أو العشرية floats أو السلاسل النصية strings أو القيم المنطقية bools أو أي قيم من أنماط البيانات البسيطة الأخرى، ما عدا استثناء وهو حالة استخدام التعبير is None بدلًا من None ==، وفيما عدا ذلك فمن النادر الوقوع في هذا الخطأ. التخزين المشترك للسلاسل النصية المتطابقة String Interning تستخدم بايثون -على نحوٍ مشابه لما سبق- نفس الكائنات للتعبير عن السلاسل النصية المجردة المتطابقة في الشيفرة بدلًا من إنشاء نسخ مستقلة عن نفس السلسلة. لنكتب مثلًا الشيفرة التالية في الصدفة التفاعلية: >>> spam = 'cat' >>> eggs = 'cat' >>> spam is eggs True >>> id(spam), id(eggs) (1285806577904, 1285806577904) لاحظت بايثون في الشيفرة السابقة أن السلسلة النصية المجردة 'cat' المسندة للمتغير eggs هي نفس السلسلة 'cat' المسندة إلى المتغير spam، لذا وبدلًا من إنشاء كائن ثاني مُكرَّر لنفس السلسلة، تُسند بايثون للمتغير eggs مرجعًا إلى نفس كائن السلسلة المُستخدم من قبل المتغير spam، وهذا ما يفسر كون هويات السلاسل النصية لكلا المتغيرين متساوية. يدعى هذا التحسين باسم التخزين المشترك للسلاسل النصية المتطابقة String Interning، وبما يشبه مبدأ التخصيص المُسبق للأعداد الصحيحة، فهذا التحسين ليس أكثر من تفصيل مُقدّم من المُفسر CPython، وبالتالي يجب عدم الاعتماد عليه تمامًا، ناهيك عن كون هذا هذا التحسين غير قادر على اكتشاف جميع السلاسل النصية المتطابقة الممكنة. فالبحث عن كل نسخة من الممكن تطبيق هذا التحسين عليها قد يستغرق وقتًا أطول من ذلك الذي يوفره هذا التحسين أصلًا. فعلى سبيل المثال، لدى محاولة إنشاء السلسلة النصية cat انطلاقًا من السلسلتين c و at في الصدفة التفاعلية، نلاحظ أن CPython قد أنشأ السلسلة النهائية cat ككائن سلسلة نصية جديد بدلًا من إعادة استخدام كائن السلسلة النصية الخاص بالمتغير spam، على النحو التالي: >>> fish = 'c' >>> fish += 'at' >>> spam is fish False >>> id(spam), id(fish) (1285806577904, 1285808207384) التخزين المشترك للسلاسل النصية المتطابقة هو تقنية تحسين تستخدمها المفسرات والمترجمات في العديد من لغات البرمجة. يمكنك الاطلاع على مزيدٍ من التفاصيل من خلال الرابط. عوامل الزيادة والإنقاص الزائفة في بايثون يمكنك زيادة قيمة متغير ما في بايثون أو إنقاصها بمقدار 1 باستخدام عوامل الإسناد المعززة augmented؛ فالشيفرة spam += 1 و spam -= 1 تزيد القيمة العددية في المتغير spam وتنقصها بمقدار 1 على التوالي. في حين أن لغات برمجة أخرى مثل لغة ++C ولغة جافا سكريبت تمتلكان العاملين ++ و -- لعمليات الزيادة والإنقاص. يدل الاسم "++C" بحد ذاته عن ذلك، فهي نكتة من قبيل المبالغة الساخرة تشير لكون "++C" إصدار مُحسّن من لغة C، إذ قد تتضمن الشيفرات بلغة ++C أو جافا سكريبت عمليات مثل spam++ أو ++spam، في حين أن بايثون قد اتخذت قرارها الحكيم بعدم تضمين هذين العاملين كونهما مصدر شهير للأخطاء الدقيقة. ولكن من المسموح تمامًا في بايثون كتابة الشيفرة التالية: >>> spam = --spam >>> spam 42 أول تفصيل ينبغي ملاحظته هو أن أي من العاملين ++ و -- في بايثون لا يَزيد أو يُنقص القيمة في المتغير spam، إذ تدل إشارة - الاستهلالية على عامل النفي الأحادي في بايثون، بمعنى أنه من المسموح كتابة شيفرة بالشكل التالي: >>> spam = 42 >>> -spam -42 وبالتالي من المسموح استخدام عدة عوامل نفي أحادي قبل قيمة ما. سنحصل مع استخدام اثنين منها على القيمة السالبة للقيمة السالبة للقيمة الأساسية، والتي تمثّل القيمة الأصلية نفسها لحالة القيم العددية الصحيحة، على النحو التالي: >>> spam = 42 >>> -(-spam) 42 لعله من السُخف استخدام عملية كتلك أعلاه، ولن تصادف غالبًا عامل نفي أحادي مُستخدم مرتين متتاليتين في الشيفرات الواقعية، وإذا صادفته، فأنت تقرأ غالبًا شيفرةً مكتوبةً من قبل مبرمج قد تعلّم لغة برمجة أخرى وقد كتب شيفرة بايثون على نحوٍ خاطئ. يوجد عامل + أحادي، وهو يقيّم قيمة العدد الصحيح إلى نفس إشارته كما هي في قيمته الأصلية، وبالتالي فهو حرفيًا لا يؤدي أي غرض: >>> spam = 42 >>> +spam 42 >>> spam = -42 >>> +spam -42 تبدو كتابة 42+، أو 42++ بنفس سخافة كتابة 42- -'، فما هدف بايثون من تضمين هذان العاملان الأحاديان؟ إنها موجودة فقط لإتمام العامل-` في حال الحاجة إلى زيادة تحميل هذه العوامل في أصنافك الخاصة. قد تكون المصطلحات في الجملة السابقة غير مألوفة بالنسبة لك، ولكن ستتعرف على مفهوم زيادة تحميل العامل operator overloading في مقال لاحق. ويصلح استخدام كل من العاملين + و - فقط قبل القيم في بايثون وليس بعدها؛ ففي حين أن تعابير مثل --spam و ++spam مسموحة في ++C أو جافا سكريبت، إلا أنها تسبب أخطاء صياغة في بايثون: >>> spam++ File "<stdin>", line 1 spam++ ^ SyntaxError: invalid syntax إذًا، لا تحتوي بايثون على عوامل زيادة وإنقاص، رغم أن صياغة بايثون توحي بامتلاكها لهذه العوامل. الكل من اللاشيء تستقبل دالة ()all المبنية مُسبقًا في بايثون قيمةً متسلسلةً sequence value مثل القائمة، لتعيد True في حال كون جميع القيم في المتسلسلة تحقق معيارًا ما أو ليست خاطئة منطقيًا "Truthly"، وتعيد القيمة False في حال كون إحدى قيم المتسلسلة أو أكثر لا تحقق ذلك المعيار أو أنها خاطئة منطقيًا "Falsey". يمكن استخدام الدالة ()all جنبًا إلى جنب مع بناء اشتمال القوائم list comprehensions لإنشاء قائمة من قيم منطقية بناءً على قائمة أخرى عبر تقييم عناصرها وفق معيار معين. لنكتب مثلًا ما يلي في الصدفة التفاعلية: >>> spam = [67, 39, 20, 55, 13, 45, 44] >>> [i > 42 for i in spam] [True, False, False, True, False, True, True] >>> all([i > 42 for i in spam]) False >>> eggs = [43, 44, 45, 46] >>> all([i > 42 for i in eggs]) True تعيد الدالة ()all القيمة True في حال كون كافة الأعداد في القائمة spam أو eggs أكبر من العدد 42، ولكن ماذا لو مررنا متسلسلة فارغة إلى الدالة ()all، ستعيد دومًا في هذه الحالة القيمة True. لنكتب الشيفرة التالية في الصدفة التفاعلية: >>> all([]) True يُفضّل فهم التعبير ([])all على أنه تقييم للإدعاء القائل: "ليس أي من العناصر في القائمة خاطئ منطقيًا" بدلًا من الإدعاء: "جميع العناصر في القائمة صحيحة منطقيًا"، وعدا ذلك ستحصل على نتائج غريبة لا تتوقعها، فعلى سبيل المثال، لنكتب التالي في الصدفة التفاعلية: >>> spam = [] >>> all([i > 42 for i in spam]) True >>> all([i < 42 for i in spam]) True >>> all([i == 42 for i in spam]) True تبدو الشيفرة السابقة وكأنها تُظهر أن جميع القيم في القائمة spam (والتي هي قائمة فارغة) أكبر من العدد 42، وبأنها بنفس الوقت أصغر من العدد 42 وأنها أيضًا تساوي العدد 42، الأمر المستحيل من الناحية المنطقية، ولكن مع ملاحظة أن كلًا من بنى اشتمال القوائم هذه تُقيّم على أنها قائمة فارغة، يتضح سبب عدم وجود أي عنصر في كل منها ليُعد خاطئ منطقيًا وبالتالي يتضح سبب إعادة الدالة ()all للقيمة True في كل مرة. القيم المنطقية هي في الواقع أعداد صحيحة تمامًا كما تعد بايثون القيمة الحقيقية "42.0" (من النوع العشري float) على أنها تساوي القيمة الصحيحة "42" (من نوع العدد الصحيح integer)، فإنها تعد القيم المنطقية True و False على أنها تكافئ 1 و 0 على التوالي؛ إذ يُعد [نمط بيانات القيم المنطقية bool صنفًا فرعيًا من نمط البيانات int في بايثون، ويمكن استخدام الدالة ()int لتحويل القيم المنطقية إلى أعداد صحيحة على النحو التالي: >>> int(False) 0 >>> int(True) 1 >>> True == 1 True >>> False == 0 True كما يمكن استخدام الدالة ()isinstance للتأكد من عدّ قيمة منطقية على أنها من نمط الأعداد الصحيحة، كما يلي: >>> isinstance(True, bool) True >>> isinstance(True, int) True القيمة True هي من نمط البيانات المنطقية، لكن ونظرًا لكون النمط bool هو صنف فرعي من صنف الأعداد الصحيحة int، تُعد القيمة True أيضًا من النمط int، ما يعني أنه يمكن استخدام كل من القيمتين True و False تقريبًا في كل موضع يمكن استخدام الأعداد الصحيحة فيه، ما قد يعطي شيفرة غريبة إلى حدٍ ما: >>> True + False + True + True # Same as 1 + 0 + 1 + 1 3 >>> -True # Same as -1. -1 >>> 42 * True # Same as 42 * 1 mathematical multiplication. 42 >>> 'hello' * False # Same as 'hello' * 0 string replication. ' ' >>> 'hello'[False] # Same as 'hello'[0] 'h' >>> 'hello'[True] # Same as 'hello'[1] 'e' >>> 'hello'[-True] # Same as 'hello'[-1] 'o' ومما لاشك فيه أن حقيقة إمكانية استخدام القيم المنطقية مثل أعداد لا يعني وجوب ذلك، فالشيفرات السابقة غير مقروءة ويجب عدم استخدامها إطلاقًا في الشيفرات الواقعية، وبايثون بالأساس لم تكن تتضمن نمط بيانات القيم المنطقية bool، إذ لم تُضاف القيم المنطقية إليها حتى الإصدار 2.3، وفيه أُنشأ الصنف الفرعي bool من الصنف int لتسهيل تنفيذه. تعدّ True و False كلمات مفتاحية محجوزة بدءًا من الإصدار 3 من بايثون، ما يعني أنه في الإصدار 2 من بايثون كان من الممكن استخدام True و False مثل أسماء للمتغيرات، ما يؤدي إلى شيفرة توحي بالتناقض كما يلي: Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> True is False False >>> True = False >>> True is False True لحسن الحظ، لم يعد هذا النوع من الشيفرات الغريبة ممكنًا في الإصدار 3 من بايثون، إذ ستتسبب شيفرة مثل تلك الموجودة في المثال أعلاه بخطأ صياغة، بسبب استخدام إحدى الكلمتين المفتاحيتين (المحجوزتين) True أو False اسمًا لمتغير. استخدام سلسلة من العوامل المختلفة قد يؤدي استخدام سلسلة من العوامل مختلفة الأنواع ضمن تعبير برمجي واحد إلى ظهور أخطاء غير متوقعة، فعلى سبيل المثال، في الشيفرة (غير الواقعية) التالية نستخدم كلا العاملين == و in ضمن تعبير برمجي واحد، على النحو التالي: >>> False == False in [False] True تثير النتيجة السابقة المتمثلة بالقيمة True الاستغراب، إذ أننا نتوقع تقييمها بإحدى الطريقتين: [False == False) in [False) والذي يُقيّم إلى False. ([False == (False in [False والذي يُقيّم إلى False أيضًا. إلا أن التعبير البرمجي [False == False in [False لا يكافئ أي من التعبيرين السابقين، إذ أنه يكافئ التعبير التالي: (False == False) and (False in [False]) تمامًا كما التعبير البرمجي: 42 < spam < 99 يكافئ التعبير: (42 < spam) and (spam < 99) فبالنتيجة يًقيّم التعبير في المثال السابق كما في المخطط التالي: يمثّل التعبير البرمجي [False == False in [False أحجية مسلية في بايثون، ولكن من غير المحتمل أن تصادفك في الشيفرات الواقعية. ميزة مقاومة الجاذبية Antigravity في بايثون لتفعيل ميزة مقاومة الجاذبية في بايثون، نكتب ما يلي في الصدفة التفاعلية: >>> import antigravity يمثّل هذا السطر البرمجي حيلةً مخفية تعمل على فتح متصفح الويب على كاريكاتير فكاهي تقليدي حول بايثون من المدونة الكوميدية XKCD على الرابط، وقد تستغرب من قدرة بايثون على فتح متصفح الويب، إلا أن هذه ميزة مبنية مُسبقًا توفرها وحدة متصفح الويب webbrowser، والتي تحتوي على الدالة ()open، وتبحث هذه الدالة عن متصفح الويب الافتراضي في نظام التشغيل لديك، لتفتح نافذةً فيه على عنوان URL معيّن. دعنا نكتب التالي في الصدفة التفاعلية كمثال: >>> import webbrowser >>> webbrowser.open('https://xkcd.com/353/') رغم محدودية الوحدة webbrowser، إلا أنها مفيدة في حال الرغبة بتوجيه المستخدم إلى صفحة ويب ما للحصول على مزيد من المعلومات حول موضوع ما. الخلاصة من السهل نسيان أن من صمم الحواسيب وأيضًا لغات البرمجة هم بشر أي غير معصومين عن الخطأ فالكثير من البرمجيات مبنية اعتمادًا على ابتكارات مصممي اللغات ومهندسي التجهيزات الصلبة، فهم يعملون ما بوسعهم للتأكد من أن أي خطأ يصادفك سيكون ناجمًا عن خطأ في برنامجك نفسه، وليس بسبب برنامج المفسّر أو تجهيزة وحدة المعالجة المركزية العامل عليها، ما يجعلنا نتعامل مع صحة عمل هذه الأدوات مثل مسلمات. تأتي من هنا قيمة تعلّم الدهاليز والغرائب في الحاسوب والبرامج، ففي حال تسببت شيفرتك بظهور خطأ أو توقفت عن العمل أو حتى أعطت نتائج غريبة غير متوقعة، عليك أن تكون على دراية بالصيغ الشائعة الصحيحة المؤدية إلى الأخطاء gotchas في سبيل تنقيح هذه الأخطاء. لن تصادفك غالبًا أي من المشاكل التي استعرضنا في هذا المقال، إلا أن معرفتك لهذه التفاصيل الصغيرة من شأنه أن يجعلك مبرمج بايثون خبير. ترجمة -وبتصرف- للفصل التاسع "غرائب بايثون المخفية" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: البنى الصحيحة المؤدية إلى الأخطاء الشائعة في بايثون كتابة شيفرات بايثون: صيغ شائعة الاستخدام على نحو خاطئ اكتشاف دلالات الأخطاء في شيفرات لغة بايثون ثلاثة أخطاء عليك تفاديها عند تعلم البرمجة بلغة بايثون
  22. رغم كون بايثون لغة البرمجة المفضلة لدى العديد من المبرمجين، إلا أنها لا تخلو من بعض العيوب؛ والتي تتفاوت بين لغة وأخرى، وهنا لا تعد بايثون استثناء، فعلى مبرمجي بايثون الجدد تعلم كيفية تجنّب بعض من البنى الشائعة التي تبدو صحيحة إلا أنها تؤدي بالنتيجة إلى حدوث أخطاء والتي ندعوها "gotchas". يكتسب عادةً المبرمجون هذه المعرفة عشوائيًا مع الخبرة، إلا أن هذا المقال يجمّع هذه البنى الشائعة لك في مكانٍ واحد، إذ ستتمكن من خلال معرفتك للتقاليد البرمجية الكائنة خلف هذه البنى من فهم سلوكيات بايثون الغريبة التي ستصادفك أحيانًا. يوضّح هذا المقال أسباب السلوكيات غير المتوقعة للكائنات المتغيرة، مثل القوائم والقواميس، والتي تظهر لدى تغيير محتوياتها، كما ستتعلم أن تابع الفرز ()sort لا يفرز العناصر وفق ترتيب أبجدي تمامًا، وكيف يمكن لأخطاء التقريب أن تجد طريقها نحو الأعداد ذات الفاصلة العشرية، كما أن لعامل عدم التساوي =! سلوك غير طبيعي يظهر لدى استخدام سلسلة منه، ولابد أيضًا من استخدام فاصلة زائدة إضافية عند كتابة صف وحيد العنصر. باختصار: يوجهك هذا المقال إلى كيفية تجنُّب البنى الصحيحة المسببة للأخطاء gotchas. لا تحذف أو تضيف عناصر إلى قائمة أثناء المرور على عناصرها تؤدي غالبًا إضافة عناصر إلى قائمة أو حذفها منها أثناء المرور على عناصرها باستخدام حلقة for أو while إلى حدوث أخطاء. لنأخذ بالحسبان الحالة التالية مثالًا: نرغب بالمرور على قائمة من السلاسل النصية التي تصف عناصر من الملابس بغية التأكد من أن عدد عناصر الجوارب زوجي، وذلك بإدخال عنصر جوارب موافق جديد لكل مرة يوجد فيها عنصر جوارب في القائمة، وهنا تبدو المهمة بسيطة وواضحة؛ إذ أنها تتمثل بالمرور على السلاسل النصية في القائمة وعندما نجد عنصر جوارب 'sock' في إحدى السلاسل النصية مثل 'red sock' فإننا نُدخل سلسلة 'red sock' أخرى جديدة إلى القائمة. لن تعمل شيفرة كهذه، لأنها ستدور في حلقة لا نهائية، ولن نستطيع إيقافها ما لم نضغط على مفتاحي "CTRL-C" لإيقافها قسريًا، على النحو التالي: >>> clothes = ['skirt', 'red sock'] >>> for clothing in clothes: # Iterate over the list. ... if 'sock' in clothing: # Find strings with 'sock'. ... clothes.append(clothing) # Add the sock's pair. ... print('Added a sock:', clothing) # Inform the user. ... Added a sock: red sock Added a sock: red sock Added a sock: red sock --snip-- Added a sock: red sock Traceback (most recent call last): File "<stdin>", line 3, in <module> KeyboardInterrupt وبإمكانك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة بزيارة الرابط. تكمن المشكلة في أنه عند إضافة العنصر 'red sock' إلى القائمة clothes، فسيصبح لدى هذه القائمة عنصر ثالث جديد، والذي يجب المرور عليه، وهو: ['skirt', 'red sock', 'red sock'] وبالتالي ستصل حلقة for إلى العنصر 'red sock' الثاني الجديد عند التكرار التالي مُضيفةً سلسلة 'red sock' أخرى إلى القائمة لتصبح على النحو: ['skirt', 'red sock', 'red sock', 'red sock'] مضيفةً سلسلةً نصيةً جديدةً إلى القائمة لتمر عليها بايثون. يستمر هذا الأمر بالحدوث كما هو موضح في الشكل 8-1 التالي، وهو سبب ظهور التدفق اللامتناهي من رسائل إضافة عنصر جوارب جديد 'Added a sock' إلى القائمة، وستستمر الحلقة بالتكرار حتى امتلاء ذاكرة الحاسب وبالتالي توقف برنامج بايثون عن العمل، أو حتى نضغط على مفتاحي "CTRL-C" في لوحة المفاتيح. الشكل 8-1: يُضاف عنصر 'red sock' جديد إلى القائمة من أجل كل تكرار للحلقة for، إذ تشير قيمة المتغير clothing إلى قيمة التكرار التالي، وتتكرر هذه الحلقة لانهائيًا. دورة تطوير التطبيقات باستخدام لغة Python احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة اشترك الآن يكون الحل بعدم إضافة عناصر إلى القائمة الحالية أثناء المرور عليها، وبالتالي سنستخدم قائمة منفصلة لمحتويات القائمة الجديدة المعدلة مثل القائمة newClothes في المثال التالي لتكون بمثابة حل بديل: >>> clothes = ['skirt', 'red sock', 'blue sock'] >>> newClothes = [] >>> for clothing in clothes: ... if 'sock' in clothing: ... print('Appending:', clothing) ... newClothes.append(clothing) # We change the newClothes list, not clothes. ... Appending: red sock Appending: blue sock >>> print(newClothes) ['red sock', 'blue sock'] >>> clothes.extend(newClothes) # Appends the items in newClothes to clothes. >>> print(clothes) ['skirt', 'red sock', 'blue sock', 'red sock', 'blue sock'] بإمكانك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة بزيارة الرابط. تمر الحلقة for في الشيفرة السابقة على عناصر القائمة clothes دون تعديلها، إذ تجري التعديلات ضمن قائمة مستقلة باسم newClothes، وبعد الانتهاء من المرور على عناصر القائمة clothes، نعدّلها بإضافة محتويات القائمة newClothes إليها، وبذلك نحصل على قائمة clothes تحتوي على عدد زوجي من أزواج الجوارب المتوافقة. على نحوٍ مشابه، لا ينبغي حذف عناصر من قائمة أثناء المرور على عناصرها، مثل شيفرة تحذف أي سلسلة نصية ليست hello من قائمة ما. تتمثل المنهجية الأكثر سذاجة في هذه الحالة بالمرور على عناصر القائمة وحذف العناصر غير المساوية للقيمة hello، على النحو التالي: >>> greetings = ['hello', 'hello', 'mello', 'yello', 'hello'] >>> for i, word in enumerate(greetings): ... if word != 'hello': # Remove everything that isn't 'hello'. ... del greetings[i] ... >>> print(greetings) ['hello', 'hello', 'yello', 'hello'] نلاحظ في نتيجة تنفيذ الشيفرة السابقة أن العنصر 'yello' بقي موجودًا في القائمة الناتجة، والسبب وراء ذلك هو أنه لدى وصول تكرار الحلقة for إلى العنصر ذو الفهرس (المؤشر) رقم 2، ستحذف العنصر المقابل وهو 'mello' من القائمة، ما سيؤدي إلى إزاحة فهرس كل من العناصر المتبقية بمقدار واحد نزولًا، وبالتالي يصبح فهرس العنصر 'yello' هو 2 بدلًا من 3. والتكرار التالي للحلقة سيختبر العنصر ذو الفهرس رقم 3، والذي أصبح الآن السلسلة النصية 'hello' الأخيرة، كما هو موضح في الشكل 8-2. إذ نلاحظ أن السلسلة النصية 'yello' لم تُختبر أصلًا، وبالتالي نؤكد مجددًا على ضرورة عدم حذف عناصر من قائمة أثناء المرور على عناصرها. الشكل 8-2: بعد مرور الحلقة على العنصر 'mello' واختباره وحذفه، تُزاح فهارس العناصر المتبقية نزولًا بمقدار واحد، ما يجعل المؤشر i يتجاوز العنصر 'yello'. يمكن بدلًا من ذلك إنشاء قائمة جديدة لننسخ فيها كافّة العناصر من القائمة الأصلية عدا تلك التي نريد حذفها، لنستبدل القائمة الأصلية بها كنتيجة. سنكتب الشيفرة التالية في الصدفة التفاعلية لتكون مكافئةً للمثال السابق لكن دون أخطاء: >>> greetings = ['hello', 'hello', 'mello', 'yello', 'hello'] >>> newGreetings = [] >>> for word in greetings: ... if word == 'hello': # Copy everything that is 'hello'. ... newGreetings.append(word) ... >>> greetings = newGreetings # Replace the original list. >>> print(greetings) ['hello', 'hello', 'hello'] يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة بزيارة الرابط. تجدر الملاحظة أنه وبما أن الشيفرة السابقة مجرد حلقة بسيطة تُنشِئ قائمة، فمن الممكن استبدالها ببنية اشتمال قوائم list comprehensions وحدها. لن تعمل هذه القوائم أسرع ولن تستخدم مساحةً أقل من الذاكرة، إلا أنها أسهل للكتابة وتجعل من مقروئية الشيفرة أفضل. لنكتب الشيفرة التالية ضمن الصدفة التفاعلية لتكون مكافئة للشيفرة السابقة: >>> greetings = ['hello', 'hello', 'mello', 'yello', 'hello'] >>> greetings = [word for word in greetings if word == 'hello'] >>> print(greetings) ['hello', 'hello', 'hello'] لا يُعد استخدام بنية اشتمال قوائم أكثر إيجازًا فحسب، بل ويجنبنا وقوع أخطاء من شيفرات تبدو صحيحة gotcha، والتي تحدث لدى تغيير عناصر قائمة أثناء المرور عليها. المراجع واستخدام الذاكرة والدالة ()sys.getsizeof قد يبدو إنشاء قائمة جديدة بدلًا من تعديل الأصلية نفسها هدرًا للذاكرة، لكن علينا تذكر أنه كما أن المتغيرات تحتوي من الناحية التقنية على مراجع للقيم وليس على القيم نفسها، كذلك الأمر بالنسبة للقوائم، إذ تحتوي على مراجع للقيم؛ فالسطر البرمجي (newGreetings.append(word المُستخدَم سابقًا لا يُنشئ نسخةً عن السلسلة النصية الموجودة في المتغير word، وإنما ينسخ فقط مرجع هذه السلسلة النصية وهو أصغر بكثير منها حجمًا. يمكن التأكد من ذلك باستخدام الدالة ()sys.getsizeof التي تعيد عدد البايتات التي يحجزها الكائن في الذاكرة. نلاحظ في مثالنا التالي في الصدفة التفاعلية أن السلسلة النصية القصيرة 'cat' تحجز 52 بايتًا، في حين أن سلسلة نصية أطول تحجز 85 بايتًا، على النحو التالي: >>> import sys >>> sys.getsizeof('cat') 52 >>> sys.getsizeof('a much longer string than just "cat"') 85 في إصدار بايثون الذي نستخدم، يُحجَز 49 بايتًا لحمل overhead كائن السلسلة النصية، في حين يُحجَز بايت واحد لكل محرف، أما القائمة وبغض النظر عن السلاسل النصية التي تتضمنها فإنها تحجز 72 بايت، مهما كان طول هذه السلاسل النصية، كما يلي: >>> sys.getsizeof(['cat']) 72 >>> sys.getsizeof(['a much longer string than just "cat"']) 72 السبب وراء ذلك هو أن القوائم من الناحية التقنية لا تحتوي على السلاسل النصية نفسها وإنما عناوينها فقط، وللمرجع الحجم نفسه بغض النظر عن البيانات التي يشير إليها؛ فتعليمة مثل (newGreetings.append(word لا تنسخ السلسلة النصية الموجودة في المتغير word وإنما تنسخ مرجعها فقط. إذا كنت ترغب بمعرفة الحجم الذي يحجزه كائن ما مع كل الكائنات التي يشير إليها، يمكنك استخدام الدالة التي أنشأها مطور نواة بايثون "ريمون هيتينجر Raymond Hettinger" لهذا الغرض والمتوفرة على الرابط. ينبغي ألا تعتقد بأنك تهدر ذاكرة بإنشائك لقائمة جديدة بدلًا من التعديل في نفس القائمة أثناء المرور على عناصرها، وحتى وإن بدت شيفرتك تعمل ظاهريًا، فقد تكون هي مصدر لأخطاء دقيقة تتطلب وقتًا طويلًا لاكتشافها وإصلاحها؛ فوقت المبرمج أثمن من الذاكرة، وهدره مكلف أكثر بكثير من هدرها. بالعودة الآن إلى موضوعنا، ورغم أنه لا يجب إضافة أو حذف عناصر من قائمة أو أي كائن تكراري آخر أثناء المرور على عناصرها، لكن من الممكن تعديل عناصر القائمة أثناء ذلك؛ فعلى سبيل المثال، ليكن لدينا قائمة من الأعداد في صورة سلاسل نصية بالشكل: ['5', '4', '3', '2', '1'] من الممكن تحويل قائمة السلاسل النصية هذه إلى قائمة من الأعداد الصحيحة [5, 4, 3, 2, 1] أثناء المرور على عناصرها، على النحو التالي: >>> numbers = ['1', '2', '3', '4', '5'] >>> for i, number in enumerate(numbers): ... numbers[i] = int(number) ... >>> numbers [1, 2, 3, 4, 5] يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة بزيارة الرابط، فتعديل العناصر في القائمة مقبول، لا سيما أنه قد يقلل عدد العناصر المعرضة للأخطاء فيها. إحدى الطرق الأخرى الممكنة لإضافة أو حذف عناصر من قائمة بأمان هي بالمرور على العناصر عكسًا من نهاية القائمة إلى بدايتها، ويمكن بهذه الطريقة حذف عناصر من القائمة أثناء المرور عليها، أو حتى إضافة عناصر جديدة إليها دون أن يكون لها أثرًا طالما أننا نضيفها إلى نهاية القائمة، فعلى سبيل المثال، لنكتب الشيفرة التالية التي تحذف الأعداد الزوجية من القائمة المسماة someInts: >>> someInts = [1, 7, 4, 5] >>> for i in range(len(someInts)): ... ... if someInts[i] % 2 == 0: ... del someInts[i] ... Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range >>> someInts = [1, 7, 4, 5] >>> for i in range(len(someInts) - 1, -1, -1): ... if someInts[i] % 2 == 0: ... del someInts[i] ... >>> someInts [1, 7, 5] ستعمل الشيفرة السابقة على نحوٍ سليم، لأن فهرس أي من العناصر التي ستمر عليها الحلقة لاحقًا لن يتغير أبدًا، إلا أن الإزاحة المتكررة للقيم الواقعة بعد تلك المحذوفة تجعل من هذه التقنية غير فعّالة لا سيما للقوائم الطويلة. يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة عبر الرابط. يوضح الشكل 8-3 التالي الفرق ما بين المرور المباشر (الأمامي) والعكسي على عناصر القائمة. الشكل 8-3: حذف الأعداد الزوجية من قائمة أثناء المرور على عناصرها عكسيًا (على اليسار) وأماميًا (على اليمين). يمكن بصورةٍ مشابهة للحالة السابقة إضافة عناصر إلى نهاية القائمة أثناء المرور عليها عكسيًا. لنكتب الشيفرة التالية في الصدفة التفاعلية، والتي تضيف نسخةً من أي عدد زوجي تصادفه إلى نهاية القائمة somwInts على النحو التالي: >>> someInts = [1, 7, 4, 5] >>> for i in range(len(someInts) - 1, -1, -1): ... if someInts[i] % 2 == 0: ... someInts.append(someInts[i]) ... >>> someInts [1, 7, 4, 5, 4] بإمكانك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة بزيارة الرابط. إذًا، يمكن بالمرور عكسًا على عناصر القائمة إضافة عناصر إلى القائمة أو حذفها منها، وقد يكون تنفيذ هذه التقنية على النحو الصحيح صعبًا، إذ قد يؤدي أدنى تغيير فيها بالنتيجة إلى وقوع الأخطاء، وبالتالي يبقى من الأسهل والأبسط إنشاء قائمة جديدة بدلًا من التعديل على القائمة الأصلية. وصّف مطور نواة بايثون ريمون هيتينجر: لا تنسخ القيم المتغيرة Mutable Values دون استخدام الدالتين ()copy.copy و ()copy.deepcopy لعله من الأفضل فهم المتغيرات على أنها لافتات أو أسماء وسوم تشير إلى الكائنات بدلًا من فهمها على أنها صناديق تحتوي على الكائنات نفسها، ويؤتي هذا الفهم ثماره لدى تعديل الكائنات المتغيرة، وهي كائنات مثل القوائم والقواميس والمجموعات، والتي قيمها قابلة للتغيير mutable؛ فإذا كان لدينا متغير يشير إلى كائن ما متغيّر، وأردنا نسخ هذا المتغير إلى متغير آخر، فإن اعتقادنا بأن الكائن نفسه سيُنسخ يمثل مصدرًا شائعًا لأخطاء gotcha. إذ أن تعليمات الإسناد في بايثون لا تنسخ الكائنات، وإنما تنسخ فقط المراجع المشيرة إليها. لنكتب مثلًا الشيفرة التالية في الصدفة التفاعلية، ونلاحظ منها أنه رغم تغيير المتغير spam وحده، فإن المتغير cheese قد تغير أيضًا: >>> spam = ['cat', 'dog', 'eel'] >>> cheese = spam >>> spam ['cat', 'dog', 'eel'] >>> cheese ['cat', 'dog', 'eel'] >>> spam[2] = 'MOOSE' >>> spam ['cat', 'dog', 'MOOSE'] >>> cheese ['cat', 'dog', 'MOOSE'] >>> id(cheese), id(spam) 2356896337288, 2356896337288 يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط. إذا كنت تعتقد بأن التعليمة cheese=spam تنسخ كائن القائمة نفسه، ستتفاجأ بأن المتغير cheese قد تغير رغم أننا عدلنا على المتغير spam وحده، ما يؤكّد أن تعليمة الإسناد في بايثون لا تنسخ الكائن نفسه وإنما المرجع إلى هذا الكائن؛ فتعليمة الإسناد cheese=spam تجعل من المتغير cheese يشير إلى نفس كائن القائمة الذي يشير إليه المتغير spam في ذاكرة الحاسب، وبالتالي لا تُستنسخ القائمة مرتين، وهذا ما يفسّر أن التعديل على المتغير spam سيطرأ أيضًا على المتغير cheese، إذ يشير كلاهما إلى نفس كائن القائمة. ينطبق المبدأ ذاته على الكائنات المتغيرة المُمررة إلى استدعاءات الدوال. لنكتب الشيفرة التالية في الصدفة التفاعلية ملاحظين أن كلًا من المتغير العام spam والمعامل المحلي theList يشيران إلى الكائن ذاته. تذكّر أن المعاملات parameters ما هي إلا متغيرات مُعرّفة ضمن تعليمة التصريح عن الدالة. >>> def printIdOfParam(theList): ... print(id(theList)) ... >>> eggs = ['cat', 'dog', 'eel'] >>> print(id(eggs)) 2356893256136 >>> printIdOfParam(eggs) 2356893256136 يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط. نلاحظ أن الهويتين identities المُعادتين من التابع ()id متساويتان لكل من eggs و theList، ما يعني أن هذان المتغيران يشيران إلى كائن القائمة ذاته، أي أن كائن القائمة الخاص بالمتغير eggs لم يُنسخ إلى المتغير theList، وإنما المرجع إلى هذا الكائن هو الذي نُسخ، وهذا ما يفسر كون كلا المتغيرين يشيران إلى القائمة ذاتها، فلا يشغل المرجع سوى بضعة بايتات في الذاكرة، ولكن ماذا لو كانت بايثون تنسخ القائمة كاملةً بدلًا من نسخ المرجع فقط، فلو كان المتغير يتضمّن مليار عنصر بدلًا من ثلاثة فقط‘ فإن تمريره إلى الدالة ()printIdOfParam سيتطلب نسخ هذه القائمة الضخمة كاملةً، ما سيستهلك حتى عدة جيجا بايت من الذاكرة من أجل هذا الاستدعاء البسيط للدالة، وهذا ما يفسر سبب أن تعليمة الإسناد في بايثون تنسخ المراجع فقط ولا تنسخ الكائنات. إحدى طرق تجنب خطأ gotcha هذا تكون بأخذ نسخة عن كائن القائمة -وليس مرجعه فقط- باستخدام الدالة ()copy.copy. لنكتب ما يلي في الصدفة التفاعلية: >>> import copy >>> fish = [2, 4, 8, 16] >>> chicken = copy.copy(fish) >>> id(fish), id(chicken) (2356896337352, 2356896337480) >>> fish[0] = 'CHANGED' >>> fish ['CHANGED', 4, 8, 16] >>> chicken [2, 4, 8, 16] >>> id(fish), id(chicken) (2356896337352, 2356896337480) يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط، وفيها يشير المتغير chicken إلى كائن قائمة مستقل منسوخ وليس على كائن القائمة الأصلي المشار إليه بالمتغير fish، وبذلك لن يحدث خطأ gotcha آنف الذكر. لكن وكما أن المتغيرات تمثّل عناوين أو أسماء وسوم تدل على الكائنات وليست صناديق محتوية على هذه الكائنات، فالقوائم تشابهها من حيث أنها تتضمن عناوين وأسماء وسوم دالة على الكائنات ولا تحتوي على الكائنات نفسها، فلو كان لدينا قائمة تحتوي على قوائم أخرى، فإن الدالة ()copy.copy تنسخ فقط مراجع تلك القوائم الداخلية. لنكتب الشيفرة التالية في الصدفة التفاعلية بغية الاطلاع على هذه المشكلة: >>> import copy >>> fish = [[1, 2], [3, 4]] >>> chicken = copy.copy(fish) >>> id(fish), id(chicken) (2356896466248, 2356896375368) >>> fish.append('APPENDED') >>> fish [[1, 2], [3, 4], 'APPENDED'] >>> chicken [[1, 2], [3, 4]] >>> fish[0][0] = 'CHANGED' >>> fish [['CHANGED', 2], [3, 4], 'APPENDED'] >>> chicken [['CHANGED', 2], [3, 4]] >>> id(fish[0]), id(chicken[0]) (2356896337480, 2356896337480) يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط. بالرغم من كون كل من fish و chicken كائنا قوائم مختلفين، إلا أنهما يشيران إلى ذات القائمتين الداخليتين [1,2] و [3,4]، وبالتالي فإن تغيير أي منهما سيؤثّر على كلا المتغيرين، رغم استخدامنا للدالة ()copy.copy. يكمن الحل باستخدام الدالة ()copy.deepcopy، التي ستنسخ أي كائنات قوائم موجودة ضمن كائن القائمة الأصلي المنسوخ، وأي كائنات قوائم فرعية ضمن كائنات القوائم الداخلية في كائن القائمة الأصلي، وهكذا. لنكتب ما يلي في الصدفة التفاعلية: >>> import copy >>> fish = [[1, 2], [3, 4]] >>> chicken = copy.deepcopy(fish) >>> id(fish[0]), id(chicken[0]) (2356896337352, 2356896466184) >>> fish[0][0] = 'CHANGED' >>> fish [['CHANGED', 2], [3, 4]] >>> chicken [[1, 2], [3, 4]] يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط. بالرغم من كون الدالة ()copy.deepcopy أبطأ قليلًا من الدالة ()copy.copy إلا أنها أأمن للاستخدام لحالات كونك غير متأكد من احتواء القائمة المنسوخة على قوائم داخلية أخرى، أو غيرها من الكائنات المتغيرة مثل القواميس والمجموعات. نصيحتنا العامة لك هي أن تستخدم الدالة ()copy.deepcopy دومًا، إذ أنها قد تجنبك وقوع أخطاء دقيقة، وغالبًا لن تلاحظ أصلًا البطء في تنفيذ الشيفرة الناتج عن استخدامها. لا تستخدم القيم المتغيرة من أجل وسطاء افتراضية تسمح بايثون بتعيين وسطاء افتراضية default arguments للمعاملات parameters لدى التصريح عن الدوال، فإذا لم يعين المستخدم معاملًا لدى استدعاء الدالة صراحةً، ستُنفّذ الدالة مُستخدمةً الوسيط الافتراضي مثل معامل، وهذا أمر مفيد خاصةُ لحالات كون معظم استدعاءات الدالة ستكون من أجل نفس الوسيط، إذ أن وجود الوسيط الافتراضي يجعل من تمرير معامل لدى استدعاء الدالة أمرًا اختياريًا؛ فعلى سبيل المثال، تمرير القيمة None إلى التابع ()split سيجعله يقتطع محارف المسافات البيضاء، ولكن ونظرًا لكون None هي أصلًا الوسيط الافتراضي لهذا التابع، إذ سيؤدي الاستدعاء ()cat dog'.split نفس الوظيفة كما لو كان بالشكل (cat dog'.split(None'، إذ تستخدم الدالة الوسيط الافتراضي معاملًا ما لم يُمرِّر المستخدم وسيطًا آخر. ينبغي عليك عدم تعيين أي من الكائنات المتغيرة مثل القائمة أو القاموس وسيطًا افتراضيًا. إذ سيؤدي ذلك إلى وقوع أخطاء كما هو موضح في المثال التالي، وفيه نصرح عن دالة باسم ()addIngredient تضيف سلسلةً نصيةً تمثّل مكونًا ما لقائمة تمثل شطيرة، وبما المكون الأول والأخير في الشطائر عادةً هو الخبز bread، استخدمنا القائمة المتغيرة ['bread', 'bread'] وسيطًا افتراضيًا: >>> def addIngredient(ingredient, sandwich=['bread', 'bread']): ... sandwich.insert(1, ingredient) ... return sandwich ... >>> mySandwich = addIngredient('avocado') >>> mySandwich ['bread', 'avocado', 'bread'] إلا أن استخدام كائنًا متغيّر مثل قائمة ['bread', 'bread'] وسيطًا افتراضيًا ينطوي على إشكال دقيق ألا وهو أن القائمة تُنشأ ولمرة واحدة لحظة تنفيذ التعليمة def المسؤولة عن التصريح عن الدالة، ولا تُنشأ من أجل كل استدعاء جديد للدالة، ما يعني أن كائن قائمة ['bread', 'bread'] وحيد سيُنشأ، كوننا لا نصرّح عن الدالة ()addIngredient سوى لمرة واحدة، إلا أن كل استدعاء جديد لهذه الدالة سيستخدم نفس القائمة، ما سيؤدي إلى سلوك خاطئ غير متوقع، كما يلي: >>> mySandwich = addIngredient('avocado') >>> mySandwich ['bread', 'avocado', 'bread'] >>> anotherSandwich = addIngredient('lettuce') >>> anotherSandwich ['bread', 'lettuce', 'avocado', 'bread'] في الشيفرة السابقة وبما أن الاستدعاء ('addIngredient('lettuce سيستخدم نفس القائمة المُعيّنة وسيطًا افتراضيًا كما هو الحال في الاستدعاءات السابقة، التي أُضيفت إليها هذه القائمة أصلًا على العنصر avocado، لذا وبدلًا من الحصول على قائمة بالشكل: ['bread', 'lettuce', 'bread']، ستعيد الدالة القائمة: ['bread', 'lettuce', 'avocado', 'bread']؛ إذ تظهر السلسلة النصية avocado مجددًا لأن القائمة الخاصة بالمعامل sandwich هي نفسها من الاستدعاء السابق للدالة، فلا تُنشأ سوى قائمة ['bread', 'bread'] واحدة، نظرًا لأن تعليمة def الخاصة بالتصريح عن الدالة لا تُنفّذ سوى مرة واحدة، ولا تُنفّذ من أجل كل استدعاء جديد للدالة. يمكنك الاطلاع على تمثيل مرئي لتنفيذ الشيفرة السابقة على الرابط. يكمن الحل البايثوني لدى حاجتك لتعيين قائمة أو قاموس وسيطًا افتراضيًا، في تعيين قيمة هذا الوسيط إلى None، ثم جعل شيفرتك تتحقق من إعادة هذه القيمة لتُنشئ عندها قائمة أو قاموس جديد من أجل كل استدعاء جديد للدالة، وهذا يضمن إنشاء الدالة لكائن مُتغير جديد من أجل كل استدعاء للدالة بدلًا من إنشائه لمرة واحدة عند التصريح عن الدالة، كما في المثال التالي: >>> def addIngredient(ingredient, sandwich=None): ... if sandwich is None: ... sandwich = ['bread', 'bread'] ... sandwich.insert(1, ingredient) ... return sandwich ... >>> firstSandwich = addIngredient('cranberries') >>> firstSandwich ['bread', 'cranberries', 'bread'] >>> secondSandwich = addIngredient('lettuce') >>> secondSandwich ['bread', 'lettuce', 'bread'] >>> id(firstSandwich) == id(secondSandwich) 1 False نلاحظ أن المعاملين firstSandwich وsecondSandwich لا يتشاركان مرجع القائمة ذاتها كما هو موضح في السطر رقم 1 من الشيفرة السابقة، ذلك لأن التعليمة ['sandwich = ['bread', 'bread تُنشئ كائن قائمة جديد في كل مرة تُستدعى فيها الدالة ()addIngredient، بدلًا من إنشائها لمرة واحدة لحظة التصريح عن الدالة. تتضمن أنماط البيانات المتغيرة كلًا من القوائم والقواميس والمجموعات وجميع الكائنات المُصرّح عنها باستخدام التعليمة class، فلا تستخدم كائنات من هذه الأنماط وسطاء افتراضية ضمن تعليمة def الخاصة بالتصريح عن الدوال. لا تبن السلاسل النصية باستخدام عمليات ربط السلاسل النصية String Concatenation تعد السلاسل النصية في بايثون كائناتٍ ثابتة immutable، ما يعني أنه لا يمكن تغيير قيم السلاسل النصية، وأي شيفرة تبدو وكأنها تُعدّل على سلسلة نصية، فإنها في الواقع تُنشئ كائن سلسلة نصية جديد؛ فعلى سبيل المثال، كل من العمليات التالية تُغير من محتوى المتغير spam، الأمر الذي يحدث باستبدال محتواه بسلسلة نصية جديدة ذات هوية جديدة وليس بالتعديل على قيمة السلسلة الموجودة أصلًا: >>> spam = 'Hello' >>> id(spam), spam (38330864, 'Hello') >>> spam = spam + ' world!' >>> id(spam), spam (38329712, 'Hello world!') >>> spam = spam.upper() >>> id(spam), spam (38329648, 'HELLO WORLD!') >>> spam = 'Hi' >>> id(spam), spam (38395568, 'Hi') >>> spam = f'{spam} world!' >>> id(spam), spam (38330864, 'Hi world!') من الجدير بالملاحظة أن كل استدعاء للدالة (id(spam يعيد هويةً جديدةً، والسبب وراء ذلك هو أن كائن السلسلة النصية الموجود في المتغير spam لا يُعدَّل، وإنما يُستبدل كاملًا بكائن سلسلة نصية جديد بهوية مختلفة، كما أن إنشاء سلاسل نصية جديدة باستخدام أي من بناء السلاسل النصية f-strings أو التابع ()format أو المحدد s% يُنشئ كائنات سلاسل نصية جديدة، كما هو الحال لدى ربط السلاسل النصية. قد لا تهمنا هذه التفاصيل التقنية في الأحوال العادية، إذ أن بايثون لغة برمجة عالية المستوى والتي تأخذ على عاتقها إنجاز العديد من التفاصيل المشابهة بدلًا عنك، سانحةً لك المجال لتصب تركيزك فقط على إنشاء برنامجك، إلا أن بناء سلسلة نصية باستخدام عدد كبير من عمليات ربط السلاسل النصية قد يتسبب بإبطاء برنامجك، فمع كل تكرار للحلقة سيُنشَئ كائن سلسلة نصية جديد ليُحذف سابقه، أي لدى استخدام عملية ربط سلاسل نصية ضمن حلقة for أو while كما في المثال التالي: >>> finalString = '' >>> for i in range(100000): ... finalString += 'spam ' ... >>> finalString spam spam spam spam spam spam spam spam spam spam spam spam --snip– بما أن العملية ' finalString += 'spam ستُنفّذ 100000 مرة ضمن الحلقة، ستجري بايثون 100000 عملية ربط سلاسل محرفية، إذ ستُنشئ وحدة المعالجة المركزية CPU قيم السلاسل النصية الوسيطة بربط القيمة الحالية للمتغير finalString عند كل تكرار مع السلسلة 'spam'، لتخزّن السلسلة الناتجة في الذاكرة، لتعود وتحذفها من الذاكرة مجددًا عند التكرار الجديد للحلقة، ما يمثل مقدارًا كبيرًا من الجهد الضائع، فكل ما يهمنا هو الحصول على السلسلة النصية النهائية بالنتيجة. تكمن الطريقة البايثونية في بناء السلاسل النصية بإضافة السلاسل الفرعية الأقصر إلى قائمة ومن ثم دمجها معًا وصولًا إلى سلسلة نصية واحدة. رغم كون هذه الطريقة تُنشئ أيضًا 100000 كائن سلسلة نصية، إلا أنها لا تجري سوى عملية ربط سلاسل نصية واحدة وذلك عند استدعاء الدالة ()join؛ فعلى سبيل المثال، تُنشئ الشيفرة التالية سلسلة نهائية في المتغير finalString مكافئة لتلك في المثال السابق ولكن دون استخدام عمليات ربط وسيطة للسلاسل النصية، على النحو التالي: >>> finalString = [] >>> for i in range(100000): ... finalString.append('spam ') ... >>> finalString = ''.join(finalString) >>> finalString spam spam spam spam spam spam spam spam spam spam spam spam --snip– لدى قياس زمن التنفيذ لكلا الشيفرتين السابقتين وعلى الحاسب نفسه، وجدنا أن المنهجية الثانية المتمثلة باستخدام قائمة لإضافة السلاسل النصية المراد ربطها إليها أسرع بعشر مرات من منهجية ربط السلاسل المحرفية بالعوامل، وسيغدو الفرق في السرعة هذا أكبر وأوضح مع زيادة عدد التكرارات المطلوبة، ولكن حتى لحالة التكرار 100 مرة عوضًا عن 100000، ورغم كون منهجية ربط السلاسل الأولى تبقى أبطأ من منهجية الإضافة إلى قائمة، إلا أن الفرق في هذه الحالة ضئيل ويمكن إهماله؛ ما يعني أنه لا يجب إلغاء استخدام ربط السلاسل النصية تمامًا سواء باستخدام f-strings أو التابع ()format أو المعرف s% لكل الحالات، فالسرعة تتحسن بصورة ملحوظة فقط من أجل عدد ضخم من عمليات الربط. تخفف عنك بايثون عبء التفكير والاهتمام بالكثير من التفاصيل التي تجري في الخلفية، سامحةً للمبرمجين بكتابة برامجهم بسرعة، وكما ذكرنا سابقًا فإن وقت المبرمج أثمن من وقت وحدة المعالجة المركزية، ومع ذلك من الجيد فهم التفاصيل في بعض الحالات، مثل الفرق ما بين السلاسل النصية الثابتة والقوائم المتغيرة، ما يجنبنا الوقوع في فخ أخطاء gotcha، كما في حالة بناء السلاسل النصية عبر ربطها. لا تتوقع من التابع ()sort أن يرتب القيم أبجديا لعل فهم خوارزميات الترتيب -الخوارزميات المسؤولة عن ترتيب القيم بطريقة ممنهجة وفقًا لترتيب أو فرز محدد- أمر أساسي ومهم في دراسة علم الحاسوب، إلا أن ما بين يديك الآن هو ليس كتاب في علم الحاسوب، ما قد يوحي بعدم أهمية معرفتك لهذه الخوارزميات، طالما أنه من الممكن استدعاء تابع الترتيب في بايثون ()sort ببساطة، ولكن ستلاحظ أن لهذا التابع بعض السلوكيات الغريبة أحيانًا في ترتيب البيانات، كأن يضع ترتيب حرف Z (الأخير في الأبجدية الإنجليزية) وهو في حالته الكبيرة قبل حرف a (الأول في الأبجدية الإنجليزية) وهو في حالته الصغيرة، كما في المثال: >>> letters = ['z', 'A', 'a', 'Z'] >>> letters.sort() >>> letters ['A', 'Z', 'a', 'z'] الترميز الأمريكي المعياري لتبادل المعلومات American Standard Code for Information Interchange -أو اختصارًا ASCII- ويُقرأ أسكي هو جدول يربط ما بين الترميزات الرقمية numeric codes (وتسمى بنقاط الترميز code points أو الأعداد الترتيبية ordinals) والمحارف النصية، ويرتب التابع ()sort القيم وفقًا لترتيب ترميز أسكي (ASCII-betical وهو مصطلح شائع ويعني الفرز وفق ترتيب عددي موافق لقيم ترميز الأسكي) وليس وفق ترتيب أبجدي. فوفقًا لترميز الأسكي نقطة الترميز الموافقة للحرف A هي 65، وللحرف B هي 66 وهكذا حتى الحرف Z الموافق لنقطة الترميز 90، أما الحرف a (الحرف A في حالته الصغيرة) فيوافق 97 والحرف b يوافق 98 وهكذا حتى الحرف z الموافق لنقطة الترميز 122، وبالتالي ولدى الفرز وفقًا لترميز الأسكي سيأتي الحرف Z (ذو نقطة الترميز 90) قبل الحرف a (ذو نقطة الترميز 97). رغم كون ترميز الأسكي هو الأشيع في مجال الحوسبة عند الغرب ما قبل وخلال التسعينيات، ولكن يبقى هذا الترميز أمريكيًا فقط، إذ يوجد نقطة ترميز لعلامة الدولار $ وهي 36، ولكن لا يوجد نقطة ترميز لعلامة الجنيه البريطاني £، وبالتالي استُبدل ترميز الأسكي على نطاقٍ واسع بترميز Unicode، إذ يتضمّن كافة نقاط ترميز الأسكي إضافةً إلى ما يزيد عن 100000 نقطة ترميز أخرى. يمكن معرفة نقطة الترميز الموافقة لمحرف ما بتمريره إلى الدالة ()ord. كما يمكن معرفة المحرف الموافق لنقطة ترميز ما بتمريره العدد الصحيح الموافق لنقطة الترميز إلى الدالة ()chr، التي تعيد سلسلةً نصيةً تتضمن المحرف الموافق، فعلى سبيل المثال، لنكتب الشيفرة التالية في الصدفة التفاعلية: >>> ord('a') 97 >>> chr(97) 'a' أما في حال الرغبة بالفرز وفق ترتيب أبجدي، نمرر التابع str.lower إلى المعامل key من التابع ()sort، وبالتالي تُفرز القيم كما لو أنها مُررت إلى الدالة ()lower قبل فرزها وترتيبها: >>> letters = ['z', 'A', 'a', 'Z'] >>> letters.sort(key=str.lower) >>> letters ['A', 'a', 'z', 'Z'] نلاحظ في الشيفرة السابقة أن السلاسل النصية ضمن القائمة لم تحوّل إلى حالة الأحرف الصغيرة، وإنما فقط فُرزت كما لو أنها كانت كذلك. يؤمن نيد باتشيلدرNed Batchelder مزيدًا من المعلومات حول يونيكود Unicode ونقاط الشيفرة في حديثه عن يونيكود البراغماتي أو كيف يمكنني إيقاف الألم من خلال الرابط. من الجدير بالذكر أن خوارزمية الفرز التي يستخدمها التابع ()sort هي Timsort،المصممة من قبل تيم بيترز Tim Peters مطوّر نواة بايثون ومؤلف مبادئ بايثون التوجيهية العشرون (Zen of Python)، وهي خوارزمية هجينة من الفرز بالدمج والفرز بالإدراج insertion. لا تفترض أن الأعداد ذات الفاصلة العشرية دقيقة تماما لا تستطيع الحواسيب تخزين الأعداد سوى في نظام العد الثنائي، المكون من الخانتين 1 و0 فقط. ولعرض الأرقام ذات الفاصلة العشرية بالشكل المألوف بالنسبة لنا، لا بد من ترجمة رقم مثل 3.14 إلى سلسلة من الأصفار والواحدات، وتجري الحواسيب عملية التحويل هذه وفقًا للمعيار IEEE 754، المنشور من قبل معهد مهندسي الكهرباء والإلكترونيات (IEEE والتي تُقرأ آي تربل إي). لتبسيط الأمور فإن هذه التفاصيل مخفية على المبرمجين، سامحةً لنا بكتابة الأعداد مع الفاصلة العشرية دون التفكير بعملية التحويل من النظام العشري إلى الثنائي: >>> 0.3 0.3 ورغم كون تفاصيل بعض الحالات هي خارج اهتمامات كتابنا هذا، لن يطابق تمثيل IEEE 754 للأعداد ذات الفاصلة العشرية تمامًا القيمة الموافقة في النظام العشري دومًا. أحد أشهر الأمثلة هو العدد 0.1، على النحو التالي: >>> 0.1 + 0.1 + 0.1 0.30000000000000004 >>> 0.3 == (0.1 + 0.1 + 0.1) False وهذا المجموع الغريب غير الدقيق ناتج عن أخطاء تقريب الناتجة عن كيفية تمثيل الحواسيب ومعالجتها للأعداد ذات الفاصلة العشرية، وهذا ليس خطأ gotcha خاص ببايثون، إذ أن المعيار IEEE 754 مطبق مباشرةً على دارات احتساب الفاصلة العشرية في وحدة المعالجة المركزية، وبالتالي سنحصل على نتيجة مشابهة لو استخدمنا لغات برمجة أخرى مثل ++C أو جافا سكريبت JavaScript أو أي لغة برمجة عاملة على وحدة معالجة مركزية تستخدم المعيار IEEE 754، وفي الواقع هو المعيار المطبق على كل وحدات المعالجة المركزية حول العالم، كما أن المعيار IEEE 754 ولأسبابٍ خارج اهتمامات كتابنا هذا غير قادر أيضًا على تمثيل كافة الأعداد الصحيحة التي تزيد عن 253. فمثلًا كلا القيمتين 253 و 253+1 مثل أعداد حقيقية (من النوع float) تقربان إلى القيمة 9007199254740992.0 ذاتها: >>> float(2**53) == float(2**53) + 1 True طالما أنك تستخدم نمط بيانات الفاصلة العشرية، فما من مفر من أخطاء التقريب هذه، ولكن لا تقلق، فطالما أنك لا تكتب برنامجًا لأحد المصارف أو لمفاعل نووي أو لمفاعل نووي خاص بأحد البنوك فإن أخطاء التقريب هذه ستكون صغيرة بما يكفي بحيث أنها لن تتسبب بمشاكل جوهرية في برامجك، ومن الممكن غالبًأ تجاوز هذه المشكلة بالاعتماد على الأعداد الصحيحة مع وحدات أصغر، كأن نستخدم 133 سنتًا بدلًا من 1.33 دولارًا، أو 200 ميللي ثانية بدلًا من 0.2 ثانية، وعلى هذا النحو تُجمع القيم 10+10+10 لتعطي 30 سنتًا أو ميللي ثانية بدلًا من جمع 0.1+0.1+0.1 لتعطي 0.30000000000000004 دولارًا أو ثانية. أما في حال الحاجة لدقة متناهية، مثلًا لحسابات علمية أو مالية، فمن الممكن استخدام وحدة decimal المبنية مُسبقًا في بايثون، ورغم كون كائنات هذه الوحدة أبطأ، إلا أنها بديل دقيق للقيم الحقيقية العشرية، فعلى سبيل المثال، التعليمة ('decimal.Decimal('0.1 تُنشئ كائنًا يمثل العدد 0.1 تمامًا بعيدًا عن خطأ التقريب الذي ينطوي عليه العدد ذاته كقيمة عشرية من النوع float. سيُنشئ تمرير القيمة الحقيقية 0.1 إلى ()decimal.Decimal كائن Decimal بنفس خطأ التقريب كما لو كان قيمة حقيقية عادية، وهذا ما يفسر كون القيمة الناتجة لا تساوي تمامًا القيمة ('Decimal('0.1. لذا يجب تمرير القيم الحقيقية مثل سلاسل نصية إلى ()decimal.Decimal، ولتوضيح هذه النقطة، لنكتب التالي في الصدفة التفاعلية: >>> import decimal >>> d = decimal.Decimal(0.1) >>> d Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> d = decimal.Decimal('0.1') >>> d Decimal('0.1') >>> d + d + d Decimal('0.3') ليس لدى الأعداد الصحيحة أخطاء تقريب، لذا من الآمن تمريرها كما هي إلى ()decimal.Decimal. والآن لنكتب التالي في الصدفة التفاعلية: >>> 10 + d Decimal('10.1') >>> d * 3 Decimal('0.3') >>> 1 - d Decimal('0.9') >>> d + 0.1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float' ولكن كائنات Decimal لا تمتلك دقة غير متناهية، إذ أنها ببساطة تمتلك مستوى دقة محدد ومعرّف. لنأخذ العمليات التالية كمثال: >>> import decimal >>> d = decimal.Decimal(1) / 3 >>> d Decimal('0.3333333333333333333333333333') >>> d * 3 Decimal('0.9999999999999999999999999999') >>> (d * 3) == 1 # d is not exactly 1/3 False يُقيّم في الشيفرة السابقة التعبير البرمجي decimal.Decimal(1) / 3 إلى قيمة لا تساوي تمامًا الثلث. وافتراضيًا يكون بمستوى 28 رقمًا بارزًا significant، ومن الممكن الاطلاع على عدد الأرقام البارزة التي تستخدمها الوحدة decimal باستخدام السمة decimal.getcontext().prec (إذ أن prec تقنيًا هي سمة للكائن المعاد من التابع ()getcontext، ولكن من المناسب استخدامها في نفس السطر مع التابع). يمكنك تغيير هذه السمة وبالتالي مستوى الدقة بتغيير عدد الأرقام البارزة إلى ذلك المحدد من قبلك، ففي المثال التالي في الصدفة التفاعلية، سنقلل عدد الأرقام البارزة من 28 إلى 2 على النحو التالي: >>> import decimal >>> decimal.getcontext().prec 28 >>> decimal.getcontext().prec = 2 >>> decimal.Decimal(1) / 3 Decimal('0.33') إذ توفر الوحدة decimal لنا القدرة على التحكم بكيفية تعاطي الأرقام مع بعضها بعضًا. لا تستخدم سلسلة من عوامل عدم التساوي =! يمثّل استخدام سلسلة من عوامل المقارنة مثل ‎18 < age < 35 أو سلسلة من عوامل الإسناد مثل six = halfDozen = 6 اختصارًا مفيدًا للتعابير: ( age > 18 ) and (age < 35) six = 6; halfDozen = 6 على التوالي، ولكن لا ينبغي استخدام سلسلة من عوامل عدم التساوي =!، فقد تظن أن الشيفرة التالية تتحقق من كون المتغيرات الثلاث تمتلك قيمًا مختلفة عن بعضها بعضًا، نظرًا لأن التعبير قد قُيّم على أنه صحيح True: >>> a = 'cat' >>> b = 'dog' >>> c = 'moose' >>> a != b != c True إلا أن السلسلة السابقة في الواقع تكافئ (a != b) and (b != c)، ما يعني أنه من الممكن كون المتغير a مساويًا للمتغير c وسيُقيم التعبير a != b != c أيضًا على أنه صحيح True، على النحو التالي: >>> a = 'cat' >>> b = 'dog' >>> c = 'cat' >>> a != b != c True وهو خطأ دقيق ومعه ستكون الشيفرة مُضلِلة، لذا من الأفضل تجنب استخدام سلسلة من عوامل عدم المساواة =! معًا. لا تنس استخدام الفاصلة في الصفوف وحيدة العنصر لدى استخدام الصفوف في الشيفرات، يجب أن نأخذ بالحسبان وضع فاصلة لاحقة زائدة حتى وإن كان الصف يحتوي على عنصر وحيد. ففي حين أن القيمة ( ,42) تُمثّل صفًا يحتوي على العدد الصحيح 42، فإن القيمة (42) تمثّل العدد الصحيح 42 نفسه؛ فالأقواس في (42) مشابهة لتلك في التعبير (20+1)*2 والذي يُقيّم إلى القيمة 42. فنسيان الفاصلة في الصف قد يؤدي لما يلي: >>> spam = ('cat', 'dog', 'moose') >>> spam[0] 'cat' >>> spam = ('cat') 1 >>> spam[0] 'c' 2 >>> spam = ('cat', ) >>> spam[0] 'cat' فبدون الفاصلة سيُقيّم ('cat') إلى قيمة سلسلة محارف، ما يفسر أن التعبير [spam[0 المشار إليه في السطر رقم 1 يُقيّم إلى المحرف الأول من السلسلة وهو c؛ فالفاصلة الزائدة مطلوبة ضمن القوسين حتى يجري التعرف عليها مثل صف كما في السطر رقم 2 من الشيفرة السابقة. يميز استخدام الفاصلة في بايثون الصف عن الأقواس المجردة. الخلاصة قد يحدث سوء التواصل في أي لغة بما في ذلك في لغات البرمجة، ولدى بايثون بضعة صيغ تبدو صحيحة إلا أنها قد تتسبب بحدوث أخطاء نسميها gotchas والتي يمكن أن يقع في فخها المبرمج غير المحترف، ورغم ندرتها إلا أنه من الضروري معرفتها بما يسمح بملاحظتها والتعرف عليها عند وقوعها وتنقيح المشاكل الناجمة عنها. بالرغم أنه من الممكن تقنيًا إضافة العناصر أو حذفها من قائمة ما أثناء المرور عليها، إلا أن هذه الآلية تُشكّل مصدرًا محتملًا للأخطاء، والحل الآمن هنا يكون بالتكرار على نسخة من القائمة، ثم إجراء التغييرات على القائمة الأصلية. من الجدير بالذكر أنه عند إنشاء نسخة عن قائمة (أو عن أي كائن قابل للتغيير)، فإن تعليمة الإسناد تنسخ فقط المرجع إلى هذا الكائن، وليس الكائن نفسه، وفي حال رغبتك بنسخ الكائن (مع أي كائنات فرعية يشير إليها) فيمكنك استخدام الدالة ()copy.deepcopy. ينبغي عليك عدم استخدام الكائنات المتغيرة مثل وسطاء افتراضية ضمن عبارة def الخاصة بالتصريح عن الدوال، إذ بذلك لن يُنشأ هذا الوسيط سوى مرة واحدة لحظة تنفيذ التعليمة def وليس في كل مرة تُستدعى فيها الدالة، والحل الأفضل يكون بتعيين الوسيط الافتراضي ليكون None، مع إضافة شيفرة لتتحقق من تمرير None بالفعل مثل وسيط لتُنشئ عندها الكائن المتغير المطلوب عند استدعاء الدالة. ينتج أحد أخطاء gotcha عن بناء سلسلة نصية عبر ربط العديد من السلاسل النصية الأصغر باستخدام العامل + ضمن حلقة. فمن أجل عدد صغير من التكرارات تعد الصياغة السابقة مقبولة. ولكن خلف الكواليس وباستخدام هذه الطريقة تضطر بايثون لإنشاء كائنات سلاسل نصية وإعادة حذفها على التوالي من أجل كل تكرار. الحل الأفضل يكون بإضافة السلاسل النصية الأصغر إلى قائمة ومن ثم المعامل ()join لإنشاء السلسلة النهائية المطلوبة. يفرز التابع ()sort القيم اعتمادًا على نقاط الترميز المختلفة عن الترتيب الأبجدي، إذ يُرتّب الحرف Z في حالته الكبيرة قبل الحرف a في حالته الصغيرة، ويمكن حل هذه المشكلة باستخدام الاستدعاء على النحو (sort(key=str.lower. تمتلك الأعداد العشرية خطأ تقريب بسيط ناتج عن طريقة تمثيل الأعداد، وهذا الأمر عديم الأهمية في معظم البرامج، وإذا كان الأمر ذو تأثير على برنامجك، فمن الممكن استخدام وحدة بايثون decimal. نهايةً، تجنّب تمامًا ربط عوامل عدم التساوي =!، لأن التعبير 'cat' != 'dog' != 'cat' سيُقيّم على نحوٍ غريب على أنه صحيح True. رغم أن هذا المقال يوصف أخطاء gotcha في بايثون التي قد تواجهها، إلا أن هذا النوع من الأخطاء قليل الحدوث في معظم الشيفرات الواقعية، وقد بذلت بايثون مجهودًا كبيرًا في تقليل المفاجآت التي قد تصادفك في برامجك. سنغطي في المقال التالي بعضًا من أخطاء gotcha الأندر والأغرب، ويكاد يكون من المستحيل أن تواجه هذه السلوكيات الغريبة ما لم تبحث عنها متعمدًا، ومع ذلك يبقى من الممتع الاطلاع عليها ومعرفة أسباب حدوثها. ترجمة -وبتصرف- للفصل الثامن "البنى الصحيحة المؤدية إلى الأخطاء Gotchas الشائعة في بايثون" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: مصطلحات شائعة مثيرة للالتباس في بايثون الطرق البايثونية في استخدام قواميس بايثون ومتغيراتها وعاملها الثلاثي تعلم لغة بايثون
  23. سنتعرف في هذا الفيديو على أحد أهم عناصر الشبكات، و أحد أكثر المفاهيم التي نتعامل معها بشكل يومي أثناء تصفحنا لمواقع الويب الموجودة عبر الانترنت وهو Domain Name System نظام تسمية النطاقات أو ما يعرف اختصارًا بالـ DNS. فما هو DNS؟ وما هي أهميته؟ وكيف يعمل؟ تعرف معنا على إجابات هذه الاسئلة خلال هذا الفيديو. إذا أردت التعرف أكثر على الخوادم والشبكات، فننصحك بالانضمام إلى دورة علوم الحاسوب، ولا تنسَ الاستعانة خلال رحلة تعلمك وعملك بتوثيقات موسوعة حسوب المجانية. وإذا أردت متابعة المعلومات البرمجية العلمية مكتوبة فيمكنك الاطلاع على قسم البرمجة في أكاديمية حسوب، كما يمكنك متابعة جديد الفيديوهات التقنية المتاحة على يوتيوب أكاديمية حسوب مجانًا.
  24. المصطلحات التقنية مربكة كفاية، فما بالك بمصطلحات متعلقة ببعضها وذات تعاريف مختلفة بنفس الوقت، وما يجعل الأمور تزداد سوءًا أن لغات البرمجة وأنظمة التشغيل ومجالات الحوسبة عمومًا قد تستخدم مصطلحات مختلفة لتوصيف الشيء ذاته أو تستخدم نفس المصطلحات لتوصيف أشياء مختلفة. لا بُد من التمييز ما بين المصطلحات التالية بما يضمن لك التواصل الواضح مع المبرمجين الآخرين. سنتعرّف في هذا المقال على مجموعة من المصطلحات التي قد تثير الالتباس في فهمها لنتجنّب الخلط فيما بينها. التعليمات البرمجية Statements والتعابير البرمجية Expressions التعابير البرمجية هي تعليمات مكونة من عوامل وقيمٍ والتي تُقيّم إلى قيمة وحيدة، ويمكن لهذه القيمة أن تكون متغيرًا (يحتوي على قيمة) أو استدعاء دالة (يعيد قيمة)؛ فمثلًا 2+2 هو تعبير برمجي إذ يُقيّم إلى القيمة الوحيدة 4، كما أن كلًا من len(myName) > 4 و ()myName.isupper أو 'myName == 'Zophie هي تعابير برمجية أيضًا. تُعد القيمة الوحيدة بحد ذاتها تعبيرًا برمجيًا أيضًا، إذ تُقيّم إلى نفسها. أما التعليمات البرمجية فهي كل ما تبقى من أوامر في بايثون (باستثناء التعابير البرمجية)، والتي تتضمّن تعليمات الجمل الشرطية if والحلقات التكرارية for وتعليمات التصريح عن الدوال def وتعليمات القيم المعادة return وغيرها، إذ لا تُقيّم التعليمات البرمجية إلى قيمة وحيدة، وقد تحتوي بعض التعليمات البرمجية على تعابير برمجية، كما في تعليمات الإسناد مثل spam = 2 + 2 أو تعليمات الشرط مثل 'myName == 'Zophie. يستخدم الإصدار الثالث من بايثون الدالة ()print، بينما يستخدم الإصدار الثاني منها التعليمة print بدلًا عنها، وقد يبدو أن الفرق بينهما هو فقط استخدام الأقواس أو عدمه، ولكن من المهم ملاحظة أن الدالة ()print في الإصدار الثالث من بايثون تتضمّن قيمة معادة (وهي تساوي دومًا None) كما من الممكن تمريرها وسيطًا للدوال الأخرى ويمكن إسنادها إلى المتغيرات، في حين أن كل من الإجراءات السابقة غير متاح لدى التعامل مع التعليمات. مع ذلك، يمكن استخدام الأقواس مع تعليمة print في الإصدار الثاني من بايثون كما في المثال التالي: >>> print 'Hello, world!' # run in Python 2 Hello, world! 1 >>> print('Hello, world!') # run in Python 2 Hello, world! يبدو السطر ذو الرقم 1 مثل استدعاء دالة، إلا أنه تعليمة print مع سلسلة نصية محصورة بين قوسين، وكذلك تعليمة الإسناد (spam = (2 + 2 تكافئ spam = 2 + 2. يمكن في كل من الإصدارين 2 و 3 من بايثون تمرير قيم متعددة إلى التعليمة print أو الدالة ()print على التوالي. سيبدو الأمر في الإصدار 3 من يابثون بالشكل التالي: >>> print('Hello', 'world') # run in Python 3 Hello world لو استخدمنا نفس الشيفرة السابقة ولكن في الإصدار الثاني من بايثون، ستفسّر التعليمة على أنها تمرير لصف مكون من سلسلتين نصيتين ضمن تعليمة print، لينتج الخرج التالي: >>> print('Hello', 'world') # run in Python 2 ('Hello', 'world') إذًا، للتعليمات البرمجية والتعابير البرمجية المكونة من استدعاء تابع اختلافات دقيقة جدًا إلا أنها حقيقية. الكتل Block والبنية Clause والمتن Body تُستخدم عادةً كل من مصطلحات الكتلة والبنية والمتن تبادليًا للإشارة إلى مجموعة من تعليمات بايثون. تبدأ الكتل بمسافة بادئة وتنتهي عندما تصبح المسافة البادئة بمستوى تلك السابقة، فعلى سبيل المثال، تدعى الشيفرات التالية لتعليمة if أو لتعليمة for بكتلة التعليمات، ولا بُد من وجود كتلة جديدة بعد التعليمات المنتهية بنقطتين رأسيتين :، مثل تعليمات if و else و for و while و def و class وغيرها. تسمح بايثون بكتل السطر الواحد، فهي تعمل رغم كونها غير منصوح بها في صيغة بايثون: if name == 'Zophie': print('Hello, kitty!') ومع استخدام الفاصلة المنقوطة، يصبح من الممكن استخدام عدة أوامر ضمن كتلة التعليمة if، على النحو التالي: if name == 'Zophie': print('Hello, kitty!'); print('Do you want a treat?') لكن من غير المسموح استخدام نفس السطر لكتابة تعليمات أخرى تتطلب بدورها كتلًا جديدة، فالشيفرة التالية مثلًا غير صالحة في بايثون: if name == 'Zophie': if age < 2: print('Hello, kitten!') وسبب رفض هذه الصيغة هو في حال وجود تعليمة else في السطر التالي، سيكون من الصعب معرفة كونها تابعة لأي من تعليمتي if. يفضّل توثيق بايثون الرسمي استخدام مصطلح البنية بدلًا من الكتلة، والشيفرة التالية تمثّل بنية: if name == 'Zophie': print('Hello, kitty!') print('Do you want a treat?') تمثّل التعليمة if ترويسة البنية، بينما يمثّل الاستدعائين المتداخلين للدالة ()print ضمن if متن البنية. يستخدم توثيق بايثون الرسمي مصطلح الكتلة للإشارة إلى جزء من شيفرات بايثون يُنفّذ مثل وحدة واحدة كما في حالات التعريف عن وحدة أو دالة أو صنف. المتغير Variable والسمة Attribute المتغيرات ببساطة هي أسماء تشير إلى الكائنات، أما السمات وفقًا لتوثيق بايثون الرسمي فهي أي اسم يتبع رمز النقطة، وترتبط السمات بالكائنات (وهي الأسماء التي تسبق النقطة). لنكتب على سبيل المثال الشيفرات التالية في الصدفة التفاعلية: >>> import datetime >>> spam = datetime.datetime.now() >>> spam.year 2018 >>> spam.month 1 يمثل spam في الشيفرة السابقة متغيرًا يتضمّن كائنًا من نوع datetime (والمُعاد من التعليمة ()datetime.datetime.now)، في حين أن كل من year و month هي سمات لهذا الكائن. حتى لو كتبنا على سبيل المثال التعليمة ()sys.exit، ففي هذه الحالة تمثّل الدالة ()exit سمةً لكائن الوحدة sys. تسمي بعض لغات البرمجة الأخرى السمات بالخاصيات properties أو صفات المتغيرات member variables. الدوال Function والتوابع Method الدالة هي مجموعة من الشيفرات التي تُشغَّل عند استدعائها؛ أما التابع فهو دالة (أو شيء قابل للاستدعاء callable، الأمر الذي سنشرحه في الفقرة التالية) مرتبطة بصنف ما، تمامًا مثل مبدأ كون السمات متغيرات مرتبطة بكائنات. تشمل الدوال تلك الموجودة أصلًا في المكتبة المبنية مسبقًا للغة built-in functions وتلك المخصصة للوحدات. لنكتب على سبيل المثال ما يلي في الصدفة التفاعلية: >>> len('Hello') 5 >>> 'Hello'.upper() 'HELLO' >>> import math >>> math.sqrt(25) 5.0 في الشيفرة أعلاه، ()len هي دالة، بينما ()upper هو تابع للسلاسل النصية. تُعد التوابع مثل سمات للكائنات المرتبطة بها. وتجدر الملاحظة أن وجود النقطة لا يعني بالضرورة أن ما يليها تابعًا وليس دالة، ففي مثالنا السابق الدالة ()sqrt مرتبطة بالوحدة math، إذ أن math ليست صنفًا. القابل للتكرار Iterable والمكرر Iterator حلقة for التكرارية في بايثون متعددة الاستعمالات، إذ ستشغّل التعليمة (for i in range(3 كتلة من الشيفرة لثلاث مرات، فالاستدعاء (3)range ليس مجرد طريقة في بايثون لإعلام حلقة for برغبتنا بتكرار جزء من الشيفرة لثلاث مرات، لأن استدعائها يعيد كائن نطاق، تمامًا كما يعيد الاستدعاء ('list('cat كائن قائمة. كل من الكائنين السابقين أمثلة عن الكائنات القابلة للتكرار. نستخدم الكائنات القابلة للتكرار في حلقات for التكرارية. لنكتب الشيفرات التالية في الصدفة التفاعلية بغية مشاهدة كيفية مرور الحلقة for على كائن نطاق وكائن قائمة: >>> for i in range(3): ... print(i) # body of the for loop ... 0 1 2 >>> for i in ['c', 'a', 't']: ... print(i) # body of the for loop ... c a t كما تتضمن الكائنات القابلة للتكرار كافة الأنماط المتسلسلة من قبيل كائنات النطاق والقائمة والصف والسلاسل النصية، وبعضًا من كائنات الحاويات مثل كائنات القاموس والمجموعة والملف. يحدث الكثير في كواليس أمثلة حلقات for هذه، إذ يستدعي بايثون كلًا من الدالتين ()iter و ()next المبنية مسبقًا لعمل حلقة for؛ فلدى استخدام حلقة for تُمرر الكائنات المُكرَّرة إلى الدالة المبنية مسبقًا ()iter، والتي تعيد كائناتٍ مكرِّرة. رغم كون الكائنات المكرَّرة تتضمن العناصر، إلا أن الكائن المكرِّر هو الذي يتتبع الكائن الواجب استخدامه في الخطوة التالية من الحلقة، فمن أجل كل تكرار للحلقة، يُمرَّر الكائن المكرِّر إلى الدالة ()next المبنية مسبقًا، لتعيد العنصر التالي من العناصر المكرَّرة. يمكن استخدام الدالتين ()iter و ()next يدويًا لمشاهدة كيفية عمل حلقات for التكرارية مباشرةً. لنكتب الشيفرات التالية في الصدفة التفاعلية لإنجاز نفس الأوامر كما في المثال السابق: >>> iterableObj = range(3) >>> iterableObj range(0, 3) >>> iteratorObj = iter(iterableObj) >>> i = next(iteratorObj) >>> print(i) # body of the for loop 0 >>> i = next(iteratorObj) >>> print(i) # body of the for loop 1 >>> i = next(iteratorObj) >>> print(i) # body of the for loop 2 >>> i = next(iteratorObj) Traceback (most recent call last): File "<stdin>", line 1, in <module> 1 StopIteration لو استدعينا الدالة ()next بعد العنصر الأخير من الكائن القابل للتكرار المعاد، سيعرض بايثون استثناء وقف التكرار StopIteration المشار إليه بالرقم 1؛ بينما في حلقات for التكرارية وبدلًا من جعل البرنامج يتوقف، تستخدم هذا الاستثناء لمعرفة متى يجب وقف التكرار. ويمكن للمكرِّر أن يمر على العناصر في الكائن القابل للتكرار مرةً واحدةً فقط، بما يشبه إمكانية استخدام الدالتين ()open و ()readlines لقراءة محتويات ملف ما لمرة واحدة فقط قبل أن تضطر إلى إعادة فتح الملف لقراءة محتوياته مجددًا، وفي حال رغبتك بالمرور على الكائن القابل للتكرار مجددًا، فلا بد من استدعاء الدالة ()iter مجددًا لتُنشئ كائنًا مكرِّرًا آخر. يمكنك إنشاء القدر الذي تريد من الكائنات المكرِّرة، إذ سيتتبع كل منها على حدى العنصر التالي الواجب إعادته. لنكتب ما يلي في الصدفة التفاعلية للاطلاع على كيفية العمل: >>> iterableObj = list('cat') >>> iterableObj ['c', 'a', 't'] >>> iteratorObj1 = iter(iterableObj) >>> iteratorObj2 = iter(iterableObj) >>> next(iteratorObj1) 'c' >>> next(iteratorObj1) 'a' >>> next(iteratorObj2) 'c' لا تنسَ أن الكائنات المكرَّرة تُمرَّر مثل وسيط إلى الدالة ()iter، إذ أن الكائن المُعاد عن استدعاءات الدالة ()iter هو كائن مكرِّر. لتمرر الكائنات المكرِّرة إلى الدالة ()next. وفي حال إنشاء أنماط بيانات خاصة باستخدام تعليمات الصنف class، فعندها من الممكن تطبيق التوابع الخاصة ()__iter__ و ()__next__ للتمكن من استخدام الكائنات الخاصة في حلقات for. أخطاء الصيغة وأخطاء زمن التنفيذ والأخطاء الدلالية توجد العديد من طرق تصنيف الأخطاء، إلا أن التصنيف عالي المستوى للأخطاء البرمجية يقسمها إلى ثلاثة أنواع: أخطاء الصياغة Syntax وأخطاء زمن التنفيذ Runtime والأخطاء الدلالية Semantic. يُقصد بالصياغة مجموعة القواعد اللازمة لكتابة أوامر صحيحة في لغة برمجة ما؛ إذ يتسبب خطأ الصياغة مثل إغفال أحد الأقواس أو استخدام نقطة بدلًا من الفاصلة أو غيرها من الأخطاء الإملائية بظهور خطأ صياغة SyntaxError في بايثون فورًا. تُعرف أخطاء الصياغة أيضًا باسم أخطاء التحليل parsing errors، والتي تحدث نتيجة عجز مفسر بايثون عن تحليل النص في الشيفرة المركزية إلى أوامر صحيحة. ولو شبهنا الأمر للغة العربية، فهذا الخطأ يكافئ حالات الأخطاء القواعدية أو حالة جملة من كلمات غير مترابطة لا معنى لها، فالحواسيب تتطلب وجود أوامر محددة ولا يمكنها قراءة أفكار المبرمج لمعرفة ما المتوقع من البرنامج فعله، وبالتالي لن يُشغّل البرنامج بوجود أخطاء صياغة. يحدث خطأ زمن التنفيذ عندما يعجز برنامج ما قيد التشغيل عن أداء مهمة ما، كأن يحاول فتح ملف غير موجود أصلًا، أو أن يقسّم عددًا على الصفر. لو شبهنا الأمر للغة العربية، فهذا الخطأ يكافئ إعطاء أمر مستحيل مثل "ارسم مربعًا باستخدام ثلاثة أضلاع"، فإذا لم يجري التعامل مع خطأ زمن التنفيذ، سيتوقف البرنامج عارضًا رسالة تتبع للخطأ. يمكن اكتشاف أخطاء زمن التنفيذ باستخدام تعليمات try-except التي تشغّل الشيفرة المسؤولة عن معالجة الخطأ. على سبيل المثال، لنكتب الشيفرات التالية في الصدفة التفاعلية: >>> slices = 8 >>> eaters = 0 >>> print('Each person eats', slices / eaters, 'slices.') ستعرض الشيفرة السابقة تتبع الخطأ التالي عند تشغيلها: Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> print('Each person eats', slices / eaters, 'slices.') ZeroDivisionError: division by zero من المهم تذكّر أن رقم السطر الذي يشير إليه متتبع الأخطاء هو السطر الذي اكتشف فيه مفسر بايثون وقوع الخطأ، في حين أن مُسبب الخطأ الحقيقي قد يكون في السطر السابق من الشيفرة أو قبله حتى. يكتشف مفسر بايثون أخطاء الصياغة في الشيفرة المصدرية قبل تشغيل البرنامج حتى، إلا أنها واردة الحدوث خلال التنفيذ، إذ يمكن على سبيل المثال، أن نمرر للدالة ()eval سلسلةً نصيةً مؤلفةً من شيفرات بايثون لتشغلها عند استدعائها، والتي قد تسبب خطأ في الصياغة أثناء التنفيذ. فمثلاً التعليمة ('(eval('print("Hello, world ينقصها علامة الاقتباس المزدوجة النهائية، الأمر الذي لن يكتشفه المفسر إلا عند استدعاء الدالة ()eval. الأخطاء الدلالية (والتي تدعى أيضاَ بالأخطاء المنطقية) فهي أخطاء أدق، فهي لن تتسبب بظهور رسائل أخطاء أو حتى توقف تنفيذ البرنامج، لكنها تجعل الحاسوب ينفذ الأوامر بطريقة مختلفة عن تلك المقصودة من المبرمج، ولو شبهنا الأمر للغة العربية، فهذا الخطأ يكافئ إعلام الحاسوب بشراء علبة حليب، وفي حال وجود بيض في المتجر، أن يشتري دزينة (اثنتا عشر علبة). سيشتري الحاسب في هذه الحالة ثلاثة عشر علبة حليب لأن المتجر لديه بيض فعلًا؛ فالحواسيب تنفذ ما نخبرها به حرفيًا شئنا أم أبينا. لنكتب المثال التالي في الصدفة التفاعلية: >>> print('The sum of 4 and 2 is', '4' + '2') فنحصل على الخرج التالي: The sum of 4 and 2 is 42 من الواضح أن القيمة 42 ليست الجواب الصحيح لعملية جمع العددين 4 و 2، ومع ذلك لم يتوقف البرنامج، إذ يجمع عامل الجمع + في بايثون الأعداد الصحيحة في حين أنه يربط قيم السلاسل النصية في سلسلة واحدة جديدة، وبالتالي حصلنا على جواب خاطئ بسبب استخدام كل من 4 و 2 مثل سلاسل نصية وليس أعداد صحيحة ما سبب الحصول على هذه النتيجة غير المرغوبة. المعاملات Parameters والوسطاء Arguments تُعرّف المعاملات بأنها أسماء المتغيرات الموجودة بين قوسين ضمن تعليمة التصريح عن الدوال def، أما الوسطاء فهي القيم الممررة لدى استدعاء الدوال، والتي تُسند لاحقًا إلى المعاملات. فعلى سبيل المثال، لنكتب ما يلي في الصدفة التفاعلية: 1 >>> def greeting(name, species): ... print(name + ' is a ' + species) ... 2 >>> greeting('Zophie', 'cat') Zophie is a cat كل من name و species في تعليمة def السابقة في السطر رقم 1 هي معاملات، أما 'Zophie' و 'cat' في استدعاء الدالة في السطر رقم 2 فهي وسطاء. يحدث خلط بين هذين المصطلحين عادةً، ولعل من الجيد تذكر أن المعاملات والوسطاء ما هي سوى أسماء أخرى للمتغيرات والقيم على التوالي عند استخدامها في هذا السياق آنف الذكر. تحويل نوع البيانات الضمني Type Coercion والصريح Type Casting يمكن تحويل نوع كائن ما ليصبح كائنًا من نوع آخر. فعلى سبيل المثال، تحوّل التعليمة ('int('42 السلسلة النصية '42' إلى الرقم الصحيح 42. في الواقع ليس الأمر تمامًا عملية تحويل، إذ تُنشئ الدالة ()int كائن عدد صحيح جديد اعتمادًا على الكائن الأصلي؛ فعندما نحوّل نوع البيانات صراحةً، يكون هذا تحويل نوع صريح، رغم أن المبرمجين في الواقع يشيرون إلى هذه العملية بمصطلح تحويل الكائن. تجري بايثون غالبًا تحويلًا ضمنيًا للنوع، كما في حالة تقييم التعبير البرمجي 3.0+2 إلى القيمة 5.0، إذ تُجبر القيمتان 2 و 5.0 على نوع شائع من أنواع البيانات والذي يتمكن المفسر من التعامل معه، ويُدعى هذا التحويل بالتحويل الضمني. قد يؤدي التحويل الضمني أحيانًا إلى نتائج غير متوقعة، إذ يمكن مثلًا أن تُحوّل القيم المنطقية True و False ضمنيًا إلى القيم العددية الصحيحة 1 و 0 على التوالي، رغم أنه من المستحيل أن نعبّر عن القيم المنطقية في الشيفرات الواقعية مستخدمين تلك القيم العددية؛ ما يعني أن التعبير True + False + True يكافئ التعبير 1 + 0 + 1 وسيُقيّم إلى 2. بعد معرفتك لهذا الأمر قد تعتقد أن تمرير قائمة من القيم المنطقية إلى التابع ()sum يمثّل طريقةً جيدةً في معرفة عدد القيم True في تلك القائمة، إلا أن استخدام التابع ()count لهذا الغرض أسرع. الخاصيات Properties والسمات Attributes يُستخدم مصطلحي الخاصيات والسمات في العديد من لغات البرمجة على أنهما كلمتان مترادفتان، لكن لهذين المصطلحين معانٍ مختلفة عن بعضهما في بايثون. فالسمة هي اسم مرتبط بكائن، وتتضمن المتغيرات والتوابع المرتبطة بالكائن. تمتلك لغات البرمجة الأخرى مثل جافا تمتلك توابع getter (الذي تعيد القيمة الموافقة لاسم متغير) و setter (الذي تأخذ معاملًا لتُسنده إلى اسم المتغير) للأصناف، فبدلًا من القدرة على إسناد قيمة إلى السمة مباشرةً، يجب على المبرمج استدعاء التابع setter لهذه السمة، بحيث تضمن الشيفرة الموجودة ضمن التابع setter أن قيمةً مناسبةً للمتغير الخاص بالكائن قد أُسندت إليه؛ بينما يقرأ التابع getter قيمة السمة. لو كانت السمة باسم accountBalance على سبيل المثال، فسيُسمى كلًا من التابعين getter و setter بالشكل ()setAccountBalance و ()getAccountBalance على التوالي. أما في بايثون، فتتيح الخاصيات للبرامج استخدام كل من getters و setters بصياغة أوضح. الشيفرة الثنائية Bytecode والشيفرة التنفيذية للتعليمات Machine Code تُترجم الشيفرة المصدرية إلى شكلٍ من التعليمات يسمى الشيفرة التنفيذية للتعليمات، والتي تتمكن وحدة المعالجة المركزية من تنفيذها مباشرةً، وتتكون هذه الشيفرة من تعليماتٍ مختارة من مجموعة تعليمات وحدة المعالجة المركزية وهي ومجموعة الأوامر المبنية مسبقًا في الحاسوب، ويسمى البرنامج المترجَم والمكون من شيفرة تنفيذية للتعليمات بالثنائي Binary. تمتلك لغة البرمجة سي C برنامج مترجم قادر على ترجمة شيفرات سي المصدرية إلى ثنائي لمعظم وحدات المعالجة المركزية المتوفرة، أما في حال تشغيل لغة برمجة مثل بايثون على نفس مجموعة وحدات المعالجة المركزية، فلا بُد من إنجاز كم هائل من العمل لكتابة مترجمات بايثون مخصصة لكل منها. توجد طريقة أخرى لتحويل الشيفرات المصدرية إلى شيفرات تنفيذية للتعليمات (قابلة للتنفيذ من قبل الحاسب)؛ فبدلًا من إنشاء شيفرة تنفيذية للتعليمات لتُنفّذ مباشرةً من قبل وحدة المعالجة المركزية، يمكن إنشاء شيفرة ثنائية والتي تسمى أيضًا بالشيفرة المحمولة أو p-code، والتي تُنفّذ من قبل مفسر بدلًا من وحدة المعالجة المركزية مباشرةً. تتكون هذه الشيفرة من تعليمات مختارة من مجموعة تعليمات، ورغم كون هذه التعليمات غير قابلة للتنفيذ من قبل وحدات المعالجة المركزية، إلا أن المفسر ينفذ هذه التعليمات. تُخزّن شيفرات بايثون الثنائية ضمن ملفات بالامتداد "pyc."، وقد تلاحظها جنبًا إلى جنب مع ملفاتك ذات الامتداد "py." المتضمنة للشيفرات المصدرية. المفسر CPython المكتوب بلغة سي قادر على ترجمة شيفرة بايثون المصدرية إلى شيفرة بايثون ثنائية لينفّذ لاحقًا التعليمات. ينطبق الأمر ذاته على برنامج Java Virtual Machine -أو اختصارًا JVM- في لغة جافا، والذي ينفذ شيفرات جافا الثنائية. بما أن المفسر CPython مكتوب بلغة سي، فإنه يمتلك مفسر بايثون، وهو قادر على ترجمة الشيفرات لتناسب أي وحدة معالجة مركزية ذات مفسر خاص بها بلغة سي. السكريبت Script والبرنامج Program، لغات كتابة السكريبت ولغات البرمجة الفرق بين السكريبت والبرنامج أو حتى ما بين لغات كتابة السكريبت ولغات البرمجة غامض، فمن المنطقي القول بأن كل سكريبت هو برنامج، وأن كل لغات كتابة السكريبت هي لغات برمجة، لكن تُعد لغات كتابة السكريبت أسهل أحيانًا، أو أنها ليست لغات برمجة حقيقية. إحدى طرق تمييز السكريبت عن البرنامج هي طريقة تنفيذ الشيفرة. إذ يُفسّر السكريبت المكتوب بلغة كتابة السكريبت مباشرةً من الشيفرة المصدرية، في حين تًترجم البرامج المكتوبة بلغات البرمجة إلى شيفرات ثنائية، لكن غالبًا ما يُنظر إلى بايثون على أنها لغة كتابة سكريبت رغم وجود خطوة الترجمة إلى شيفرة ثنائية لدى تشغيل برامج بايثون. لا تعد لغة جافا مثل لغة كتابة سكريبت رغم أنها تُنشئ شيفرات ثنائية تمامًا مثل بايثون. من الناحية التقنية: لن تُترجم اللغات أو تُفسّر ما لم يوجد مترجم أو مفسر لهذه اللغة، ومن الممكن إنشاء مترجم أو مفسر لأي لغة. يمكن في الواقع مناقشة الاختلافات، إلا أنها ليست بتلك الأهمية، فلغات كتابة السكريبت ليست بالضرورة أقل قدرة ولا لغات البرمجة بالضرورة أصعب للتعامل معها. المكتبة Library وإطار العمل Framework وحزمة أدوات تطوير البرمجيات SDK والمحرك Engine وواجهة برمجة التطبيقات API قد يوفر استخدام شيفرات الآخرين الكثير من الوقت، وغالبًا ما ستجد هذه الشيفرات ضمن مكتبات، أو أطر عمل، أو حزم أدوات تطوير، أو محركات، أو واجهات تطوير تطبيقات، والفرق ما بين الكيانات السابقة دقيق ومهم. المكتبة هي مصطلح عام يدل على مجموعة من الشيفرات المُعدّة من قبل جهة خارجية، وقد تتضمن دوالًا وأصنافًا وغيرها من أجزاء الشيفرات ليستخدمها المطور، وتكون عادةً مكتبات بايثون على هيئة حزم، أو مجرد وحدة واحدة. تكون المكتبات غالبًا مخصصةً للغة برمجة معينة، ولا يتعين على المبرمج معرفة آلية عمل شيفرة المكتبة، فكل ما عليه معرفته هو كيفية استدعاء والتخاطب مع الشيفرة الموجودة في المكتبة، أما المكتبة المعيارية، مثل مكتبة بايثون المعيارية، فهي مكتبة شيفرات يُفترض توفرها لكافة تطبيقات لغة البرمجة. إطار العمل هو مجموعة من الشيفرات عكسية التحكم inversion of control، بمعنى أن المطور يُنشئ دوالًا ليستدعيها إطار العمل عند الحاجة، على عكس استدعاء الدوال في شيفرات المطور. يُشبّه عادة مصطلح اللاتحكمية بعبارة "لا تتصل بنا، سنتصل نحن بك". على سبيل المثال، تتضمن كتابة شيفرة لإطار عمل تطبيق ويب إنشاء دوال لصفحات الويب التي سيستدعيها إطار العمل لدى وصول طلب ويب. تتضمن حزمة أدوات التطوير software development kit- أو اختصارًا SDK- مكتبات شيفرات وتوثيقات وأدوات برمجية للمساعدة في في إنشاء تطبيقات عاملة على أنظمة تشغيل أو منصات معينة. على سبيل المثال، تُستخدم كل من Android SDK و iOS SDK لإنشاء تطبيقات الهاتف المحمول لأنظمة أندرويد و iOS على التوالي، كما أن حزمة تطوير جافا Java Development Kit-أو اختصارًا JDK- ما هي إلا SDK مُعدّة لإنشاء التطبيقات العاملة على JVM. المحرك هو نظام ضخم مستقل يمكن التحكم به خارجيًا من قبل برامج المطور، ويستدعي المطورون عادةً الدوال الموجودة في محرك ما لإنجاز مهام كبيرة ومعقدة، ومن الأمثلة على المحركات: محركات الألعاب والمحركات الفيزيائية ومحركات التوصية ومحركات قواعد البيانات ومحركات الشطرنج ومحركات البحث. واجهة برمجة التطبيقات Application Programming Interface -أو اختصارًا API- هي الواجهة العمومية للمكتبة، أو حزمة أدوات التطوير، أو إطار العمل، أو المحرك؛ إذ أنها تحدد كيفية استدعاء الدوال أو إنشاء طلبات الوصول إلى موارد المكتبة. يقع على عاتق مُنشئي المكتبات إعداد التوثيقات حول واجهة برمجة التطبيقات المتوفرة، وتنشئ العديد من الشبكات الاجتماعية الشائعة والمواقع الإلكترونية واجهة برمجة التطبيقات من نوع HTTP API، التي تتيح للبرامج الوصول إلى خدماتها ذاتيًا، إذ يتيح لك استخدام واجهات برمجة التطبيقات هذه كتابة برامج قادرة مثلًا على النشر ذاتيًا على فيسبوك أو قراءة الأخبار عبر تويتر. الخلاصة من السهل أن تمارس البرمجة لسنوات مع بقاء بعض المصطلحات البرمجية تثير الالتباس بالنسبة لك، وبما أن معظم التطبيقات البرمجية تُنشئ من قبل فريق كامل من المطورين وليس من قبل أفراد، فلا بُد من فهم المصطلحات بدقة والتمييز بين المتقارب منها. لبعض المصطلحات مثل القيم والمتغيرات والدوال أسماء مختلفة باختلاف السياق، مثل العناصر والمعاملات والوسطاء والتوابع، ومن السهل الخلط بين العديد من المصطلحات، إذ لا يعد الخلط بين بعض المصطلحات في الحياة اليومية أمرًا كارثيًا، مثل الخلط بين الخاصية والسمة أو الكتلة والمتن أو الاستثناء والخطأ، أو حتى الاختلافات الدقيقة ما بين المكتبة وإطار العمل وحزمة أدوات التطوير والمحرك وواجهة برمجة التطبيقات، فلا يؤدي فهمها الخاطئ بالضرورة إلى عدم عمل شيفرتك، إلا أنك ستظهر بمظهر غير المحترف، فعلى سبيل المثال، يخلط المبتدئون ما بين مفاهيم من قبيل التعابير والتعليمات البرمجية أو الدوال والتوابع أو المعاملات والوسطاء، إلا أن مصطلحاتٍ أخرى مثل قابل للتكرار والمكرِّر أو خطأ الصياغة والخطأ الدلالي أو الشيفرة الثنائية والشيفرة التنفيذية للتعليمات، فلكل منها معناها الخاص الذي عليك فهمه بعمق وعدم الخلط بينه وبين مقابله مالم ترغب بإثارة الاستغراب لدى زملاء عملك. قدمنا في هذا المقال مجموعة من المصطلحات التي قد تخلط بينها، ومع ذلك ستجد دومًا أن استخدام المصطلحات يختلف من لغة لأخرى وحتى من مبرمج لآخر، وستغدو معتادًا أكثر على المصطلحات مع الخبرة والبحوثات المتكررة على الإنترنت. ترجمة -وبتصرف- لجزء من الفصل السابع "المصطلحات البرمجية" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: مصطلحات بايثون البرمجية المرجع الشامل إلى تعلم لغة بايثون التعامل مع الملفات والمسارات في بايثون اختيار أسماء برمجية مفهومة في بايثون النسخة العربية الكاملة لكتاب: البرمجة بلغة بايثون
  25. أنشأ راندال مونرو Randall Munroe في مدونة الكاريكاتير XKCD مخططًا تقنيًا لصاروخ "زحل الخامس" تحت عنوان "Up Goer Five" مُستخدمًا فقط الكلمات الإنجليزية الألف الأشهر، إذ بسّط المصطلحات التقنية إلى جمل يستطيع أي طفل فهمها، إلا أن هذا الأمر يسلط الضوء أيضًا على تفسير عدم إمكانية شرح أي شيء باستخدام مصطلحات بسيطة؛ فالشرح القائل "شيء يساعد الناس على الهرب بسرعة كبيرة في حال وجود مشكلة واشتعال كل شيء ما يجعلهم يقررون عدم الذهاب إلى الفضاء" أسهل فهمًا للمتلقي العادي من عبارة "بدء نظام الهروب Launch Escape System". الشرح الأول مبالغ بإطالته ليستخدمه مهندسو الإدارة الوطنية للملاحة الجوية والفضاء NASA في عملهم اليومي، بل أنهم حتى قد يفضلون استخدام الاختصار LES. قد تسبب مصطلحات الحوسبة الحيرة والقلق للمبرمجين المبتدئين، إلا أنها أمر لا بد من تعلمه، إذ تختلف العديد من المصطلحات في بايثون ومجال التطوير البرمجي عمومًا اختلافات دقيقة، لدرجة أن المطورين المتمرسين قد يخلطون بينها دون اكتراث. قد يختلف التعريف التقني لهذه المصطلحات من لغة برمجة لأخرى، إلا أن هذا المقال يغطي المصطلحات المتعلقة بلغة بايثون تحديدًا، إذ ستحصل فيه على فهم واسع لمفاهيم لغة البرمجة الكامنة في الكواليس حتى لو لم يكن عميقًا. تعاريف ما أن يبلغ عدد المبرمجين في نفس المكان اثنين، حتى تصبح احتمالية انطلاق نقاش حول دلالات اللغة 100%، فاللغة سهلة والبشر هم سادات الكلمات وليس العكس. قد يستخدم بعض المطورين المصطلحات بطريقة مختلفة قليلًا، ومع ذلك يبقى التعرف على هذه المصطلحات أمرًا مفيدًا. سنستعرض في هذا المقال قسمًا من هذه المصطلحات وكيفية مقارنتها ببعضها بعضًا، وفي حال رغبتك بالحصول على قائمة مرتبة أبجديًا بالمصطلحات، فيمكنك الاعتماد على تلك الرسمية الخاصة ببايثون وصولًا إلى التعريفات الأساسية. مما لا شك فيه أن بعض المبرمجين سيقرؤون التعريفات الواردة في هذا المقال طارحين حالاتٍ خاصة أو استثناءات وهي في الواقع غير محدودة، فلا يمكن عد هذا المقال دليلًا إرشاديًا كاملًا ونهائيًا، بل إنه يهدف إلى تزويدك بالتعريفات حتى لو لم تكن شاملة تمامًا، فكما هو الحال مع كل ميادين البرمجة، يوجد دائمًا المزيد لتعلمه. لغة بايثون ومفسر بايثون تنطوي كلمة "بايثون" على معانٍ عديدة، إذ سُميّت لغة البرمجة بايثون نسبةً إلى الفرقة الكوميدية البريطانية Monty Python، وليس نسبةً إلى الثعبان كما توحي الترجمة الحرفية، رغم كون مصادر بايثون التعليمية وتوثيقاتها تشير إلى كلًا من فرقة Monty Python والثعبان مثل مرجع لأصل التسمية، وكذلك الأمر من حيث الشق البرمجي، إذ تحمل كلمة بايثون معنيين أيضًا. يمكن أن نقول "تُشغّل بايثون برنامجًا" أو "ستعرض بايثون استثناءً"، والمقصود هنا هو مفسر بايثون -وهو البرنامج الفعلي المسؤول عن قراءة النصوص ضمن الملفات ذات اللاحقة "py." مُنفذًا التعليمات الواردة فيها؛ فعندما نقول "مفسر بايثون"، نقصد غالبًا "CPython"، وهو المفسر المُعتمد من قبل مؤسسة بايثون للبرمجيات Python Software Foundation. إذًا، CPython هو تنفيذ للغة بايثون، بمعنى أنه برمجية أُنشئت لتحقق مواصفات محددة، ولكن يوجد برمجيات غيرها؛ ففي حين أن المفسر CPython مكتوب بلغة البرمجة سي C، المفسر Jython مكتوبٌ بلغة جافا Java بغية تشغيل نصوص بايثون البرمجية القابلة للتشغيل المتبادل مع برامج جافا؛ أما PyPy فهو مترجم ديناميكي just-in-time compiler لبايثون والذي يترجم البرامج لدى تنفيذها، فهو مكتوبٌ بلغة بايثون. تُشغّل كل من هذه التنفيذات الشبفرات المصدرية المكتوبة بلغة البرمجة بايثون، وهي المقصودة لدى قولنا: "هذا برنامج بايثون" أو "أتعلم بايثون".ومن الناحية المثالية فإن أي مفسر بايثون قادر على تشغيل أي شيفرة مصدرية مكتوبة بلغة بايثون، أما في الواقع العملي فتوجد بعض حالات عدم التوافق البسيطة والاختلافات ما بين المفسرات، ولعل سبب تسمية CPython بالمفسر المرجعي للغة بايثون هو أنه في حال وجود اختلافات في كيفية تفسير شيفرة بايثون ما بين CPython وغيره من المفسرات، سيكون ما فسره CPython هو الصحيح والمُتفق عليه. كنس المهملات Garbage Collection كان على المبرمج سابقًا في لغات البرمجة القديمة توجيه البرنامج لتخصيص أو إلغاء تخصيص أو تحرير الذاكرة لبنى المعطيات حسب الحاجة، وقد كانت عملية تخصيص الذاكرة اليدوية هذه مصدرًا للعديد من الأخطاء، مثل تسريبات الذاكرة memory leaks (التي تحدث عند نسيان المبرمج لتحرير الذاكرة) أو أخطاء التحرير المزدوج للذاكرة double-free bugs (إذ يحرر المبرمج نفس الجزء من الذاكرة مرتين، متسببًا في تلف البيانات). تمنلك بايثون ميزة تجميع وكنس المهملات لتجنب هذه الأخطاء، والتي تعد أحد أشكال الإدارة التلقائية للذاكرة والتي تتعقّب التوقيت المناسب لتخصيص وتحرير الذاكرة نيابةً عن المبرمج. من الممكن النظر إلى تجميع المهملات مثل عملية إعادة تدوير للذاكرة، لكونها تتيح الذاكرة للبيانات الجديدة. فعلى سبيل المثال، لنكتب ما يلي في الصدفة التفاعلية: >>> def someFunction(): ... print('someFunction() called.') ... spam = ['cat', 'dog', 'moose'] ... >>> someFunction() someFunction() called. تخصص بايثون لدى استدعاء الدالة ()someFunction الذاكرة للقائمة ['cat', 'dog', 'moose']. وبالتالي ما من حاجة لمعرفة المبرمج عدد البايتات من الذاكرة المتوجب طلبها لأن بايثون تدير هذا الأمر نلقائيًا، إذ سيحرر مُجمّع المهملات المتغيرات المحلية بمجرد إعادة الدالة المُستدعاة، ما يجعل الذاكرة متاحةً لبيانات جديدة. إذًا، يجعل مفهوم تجميع المهملات من البرمجة أمرًا أسهل وأقل عرضة للأخطاء. القيم المجردة Literals القيمة المجردة هي نص ضمن الشيفرة المصدرية ذات قيمة ثابتة كما هي مكتوبة. ففي المثال التالي 42 هو قيمة صحيحة مجردة و 'Zophie' هي سلسلة نصية مجردة.: >>> age = 42 + len('Zophie') يمكن فهم القيم المجردة مثل قيم تظهر كما هي بحرفيتها مثل نص ضمن الشيفرة المصدرية، ولا يمكن إلا لأنواع البيانات المُعرّفة أصلًا في بايثون امتلاك قيم مجردة في الشيفرة المصدرية، وبالتالي ليس المتغير age قيمة مجردة. يبيّن الجدول 1 التالي بعض الأمثلة عن القيم المجردة في بايثون. القيمة المجردة نوع البيانات 42 عدد صحيح 3.14 عدد عشري 1.4886191506362924e+36 عدد عشري """!Howdy""" سلسلة نصية 'r'Green\Blue سلسلة نصية [] قائمة {'name': 'Zophie'} قاموس 'b'\x41 بايتات True بولياني None نوع فارغ NoneType قد يجادل بعض المعترضين في أن بعضًا من القيم لا يمثّل قيمًا مجردة استنادًا إلى التوثيق الرسمي للغة بايثون، فمن الناحية التقنية، 5- ليست قيمة مجردة في بايثون لأن اللغة تعرّف رمز السالب (-) مثل عامل على القيمة المجردة 5، كما تُعد القيم True و False و None كلمات مفتاحية في بايثون وليست قيمًا مجردة، في حين أن [] و{} تسمى مُخرجات أو ذرات atoms اعتمادًا على جزء التوثيق الرسمي الذي تبحث ضمنه. بغض النظر عن ذلك، القيمة المجردة هي مصطلح شائع يستخدمه محترفو البرمجيات للدلالة على كل الأمثلة السابقة. الكلمات المفتاحية Keywords تمتلك كل لغة برمجة الكلمات المفتاحية الخاصة بها؛ وكلمات بايثون المفتاحية هي مجموعة من الأسماء المحجوزة للاستخدام مثل جزء من بناء اللغة والتي لا يمكن استخدامها أسماءً للمتغيرات (أي المعرّفات). على سبيل المثال، لا يمكنك تسمية أحد المتغيرات بالاسم while لأنها كلمة محجوزة للاستخدام في حلقات while التكرارية، وفيما يلي الكلمات المفتاحية المحجوزة في بايثون وفقًا للإصدار 3.9 منها. and continue finally is raise as def for lambda return assert del from None True async elif global nonlocal try await else if not while break except import or with class False in pass yield نلاحظ أن كلمات بايثون المفتاحية هي دومًا باللغة الإنجليزية وغير متاحة بأي لغات أخرى. فعلى سبيل المثال، في الدالة التالية المعرفات مكتوبة باللغة الاسبانية، في حين بقيت الكلمتين def و return المفتاحيتين في بايثون بالإنجليزية: def agregarDosNúmeros(primerNúmero, segundoNúmero): return primerNúmero + segundoNúmero تهيمن اللغة الإنجليزية لسوء الحظ على مجال البرمجة بالنسبة للتعداد البالغ 6.5 مليار من غير الناطقين بها. الكائنات Objects والقيم Values والنسخ Instances والهويات Identities يمثّل الكائن جزءًا من البيانات سواء كانت عددًا أو بعضًا من النصوص أو هيكل بيانات أكثر تعقيدًا، مثل القائمة، أو القاموس. ويمكن تخزين الكائنات ضمن متغيرات وتمريرها مثل وسطاء عند استدعاء الدوال واستخدامها مثل قيمة معادة عن استدعاء الدوال. تمتلك جميع الكائنات قيمةً وهوية ونوع بيانات، والقيمة هي البيانات التي يمثلها الكائن، مثل العدد الصحيح 42، أو السلسلة النصية 'hello'. يستخدم بعض المبرمجين في الواقع مصطلح القيمة بمثابة مرادف لمصطلح الكائن، لا سيما لأنواع البيانات البسيطة مثل الأعداد الصحيحة أو السلاسل النصية، رغم أن هذا الأمر قد يسبب الإزعاج. يُنشَأ الكائن بهوية، وهي رقم صحيح فريد من الممكن الإطلاع عليه باستدعاء الدالة ()id. على سبيل المثال، لنكتب ما يلي في الصدفة التفاعلية: >>> spam = ['cat', 'dog', 'moose'] >>> id(spam) 33805656 يُخزّن المتغير spam كائنًا من نوع البيانات "قائمة"، قيمته تساوي ['cat', 'dog', 'moose'] أما هويته فهي 33805656، رغم كون العدد الصحيح ID هذا يتغير في كل مرة يُنفّذ فيها البرنامج، وبالتالي ستكون القيمة على حاسوبك مختلفة عن هذه المعروضة هنا، ولكن بمجرد إنشاء الكائن، لن تتغير قيمة هويته طالما أن البرنامج قيد التشغيل، ورغم أن كلًا من هوية ونوع بيانات الكائن لا يتغيران أثناء تشغيل البرنامج، إلا أن قيمته قد تتغير، كما في المثال التالي: >>> spam.append('snake') >>> spam ['cat', 'dog', 'moose', 'snake'] >>> id(spam) 33805656 وبذلك أصبحت القائمة تتضمّن أيضًا العنصر 'snake'، ولكن وكما هو مبين من نتيجة استدعاء الدالة (id(spam أن هويتها لم تتغير وبقيت نفسها، ولكن ماذا لو كتبنا الشيفرات التالية: >>> spam = [1, 2, 3] >>> id(spam) 33838544 استُبدلت القيمة في المتغير spam بكائن قائمة جديد وبهوية جديدة قيمتها 33838544 بدلًا عن 33805656، إذ يختلف المعرف spam عن مفهوم الهوية من حيث أن عدّة معرفات قد تتبع لكائن واحد، كما في المثال التالي الذي أسندنا فيه متغيرين لنفس القاموس: >>> spam = {'name': 'Zophie'} >>> id(spam) 33861824 >>> eggs = spam >>> id(eggs) 33861824 ومنه نجد أن هوية كل من المعرفين spam و eggs نفسها وتساوي 33861824، لأنهما يتبعان لنفس كائن القاموس. الآن وبتغيير قيمة spam في الصدفة التفاعلية: >>> spam = {'name': 'Zophie'} >>> eggs = spam 1 >>> spam['name'] = 'Al' >>> spam {'name': 'Al'} >>> eggs 2 {'name': 'Al'} ومنه نجد أن التغييرات على المتغير spam في السطر 1 قد ظهرت أيضًا على المتغير eggs في السطر 2، وسبب ذلك أن كلاهما يتبع لنفس الكائن. مفهوم المتغيرات المجرد: الصندوق BOX مقابل العنوان LABEL تستخدم الكثير من الكتب التمهيدية مصطلح "الصناديق" بمثابة مفهوم مجرد للمتغيرات، ويُعد الأمر بسيطًا جدًا، فمن السهل فهم المتغيرات على أنها صناديق تُخزَّن فيها القيم كما في الشكل 1، إلا أن هذا المفهوم يتهاوى لدى التفكير من وجهة النظر المرجعية، فمثلًا المتغيرين spam و eggs السابقين لا يخزنان قاموسين منفصلين (نسختين من نفس القاموس) بل يخزنان مرجعًا في ذاكرة الحاسوب لنفس القاموس. شكل 1: تفيد العديد من المراجع بأنه من الممكن فهم المتغيرات على أنها صناديق تحتوي على قيم المتغيرات في بايثون من الناحية التقنية هي مراجع وليست حاويات containers للقيم، وذلك بغض النظر عن نوع البيانات التي تحتويها. رغم بساطة مفهوم الصندوق إلا أنه منقوص، فبدلًا من فهم المتغيرات على أنها صناديق، يُفضّل فهمها مثل عناوين للكائنات في الذاكرة، والشكل 2 التالي يبين العناوين لأمثلة المتغيرين spam و eggs السابقين. شكل 2: من الممكن أيضًا فهم المتغيرات مثل عناوين للقيم قد تشير عدة متغيرات إلى نفس الكائن، لذلك قد يُخزَّن الكائن الواحد في عدة متغيرات، ولكن لا يمكن للصناديق المختلفة أن تخزّن نفس الكائن الوحيد، ما يجعل فهم المتغيرات مثل عناوين أسهل وأقرب للفهم. قد تكون معرضًا لوقوع أخطاء في شيفرتك ما لم تدرك أن عامل الإسناد = ينسخ دومًا مرجع الكائن وليس الكائن نفسه، وذلك بظنك أنك تُنشئ نسختين متطابقتين من الكائن ولكن في الواقع أنت تنسخ مرجع الكائن الأصلي. لحسن الحظ أن هذه المسألة لا تتعلق بالقيم الثابتة immutable من أعداد صحيحة وسلاسل نصية وصفوف، وذلك لسبب سنشرحه لاحقًا في هذا الكتاب ضمن فقرة "متغيرات وثوابت". يمكن استخدام العامل is لمقارنة فيما إذا كان لكائنين نفس الهوية، وبالمقابل يمكن استخدام العامل == للتحقق فقط من كون قيمتي الكائنين متطابقتين، وبالتالي التعبير x is y مكافئ لنظيره (id(x) == id(y. لنكتب ما يلي في الصدفة التفاعلية لنلاحظ الفرق: >>> spam = {'name': 'Zophie'} 1 >>> eggs = spam >>> spam is eggs True >>> spam == eggs True 2 >>> fish = {'name': 'Zophie'} >>> spam == fish True >>> spam is fish False إذ يشير المتغيران spam وeggs إلى نفس كائن القاموس (في السطر 1)، وبالتالي فهما متساويان بالقيمة والهوية.، بينما يشير المتغير fish إلى كائن قاموس آخر (في السطر 2)، رغم كونه يتضمن بيانات مطابقة لتلك الموجودة في المتغيرين spam و eggs. يعني تطابق البيانات أن للمتغير fish نفس القيمة كما في المتغيرين spam و eggs، إلا أنهما كائنان مختلفان بهويتين مختلفتين. العناصر Items ندعو في بايثون الكائن الموجود ضمن كائن حاوية مثل قائمة أو قاموس بالعنصر، فعلى سبيل المثال، السلسة النصية ضمن القائمة ['dog', 'cat', 'moose'] هي كائنات ولكننا ندعوها أيضًا بالعناصر. الثابت Immutable والمتغير Mutable كما لاحظنا سابقًا، تمتلك كل الكائنات في بايثون قيمة ونوع بيانات وهوية، ويمكن منها تغيير القيمة فقط، وبما أننا نستطيع تغيير قيمة الكائن فهو كائن متغيّر؛ أما إذا كانت قيمته غير متغيرة فندعوه بالكائن الثابت. يعرض الجدول 2 قائمةً ببعض أنواع البيانات الثابتة والمتغيرة في بايثون. جدول 2: بعض أنواع البيانات الثابتة والمتغيرة في بايثون أنواع البيانات الثابتة أنواع البيانات المتغيرة عدد صحيح قائمة عدد عشري قاموس قيمة بوليانية مجموعات سلسلة نصية مصفوفة بايت Bytearray مجموعة ثابتة Frozen set مصفوفة بايتات صف Tuple فلو أعدت الكتابة في متغير ما، قد يبدو لك أنك قد غيرت قيمة الكائن الخاص به، كما في المثال التالي في الصدفة التفاعلية: >>> spam = 'hello' >>> spam 'hello' >>> spam = 'goodbye' >>> spam 'goodbye' لكننا لم نغيّر هنا قيمة الكائن 'hello' من 'hello' إلى 'goodbye'، فهما كائنان منفصلان، وكل ما فعلناه في الشيفرة السابقة هو مجرّد جعل المتغير spam يشير إلى الكائن 'goodbye' بدلًا من 'hello'، ويمكن التحقق من صحة أنهما فعلًا كائنين مستقلين باستخدام الدالة ()id للحصول على هوية كل منهما: >>> spam = 'hello' >>> id(spam) 40718944 >>> spam = 'goodbye' >>> id(spam) 40719224 ومنه نجد أنّ لهذين الكائنين النصيين هويتان مختلفتان (40718944 و 40719224) نظرًا لكونهما كائنين مستقلين، بينما يمكن تغيير قيمة المتغيرات التي تشير إلى كائنات متغيرة، مثل القوائم والصفوف مع الحفاظ على نفس الهوية. دعنا نكتب على سبيل المثال الشيفرات التالية في الصدفة التفاعلية: >>> spam = ['cat', 'dog'] >>> id(spam) 33805576 1 >>> spam.append('moose') 2 >>> spam[0] = 'snake' >>> spam ['snake', 'dog', 'moose'] >>> id(spam) 33805576 تعمل كلًا من الدالة ()append (في السطر 1) وعملية الإسناد للعنصر (في السطر 2) على تعديل قيمة القائمة في مكانها (من أجل نفس الكائن بنفس الهوية)؛ فقد تغيرت قيمة القائمة، إلا أن هويتها قد بقيت كما هي 33805576؛ إما في حال استخدام العامل + لبناء تسلسل القائمة، فإننا ننشئ كائنًا جديدًا (ذو هوية جديدة) بديلًا عن ذلك السابق على النحو التالي: >>> spam = spam + ['rat'] >>> spam ['snake', 'dog', 'moose', 'rat'] >>> id(spam) 33840064 ومنه نجد أن بناء تسلسل القائمة وفق الطريق السابقة (باستخدام العامل +) قد أنشأ قائمة جديدة بهوية جديدة، وبمجرد حدوث ذلك يُحرّر مُجمّع المهملات القائمة القديمة من الذاكرة. لمعرفة التوابع والعمليات التي تعدل قيم الكائنات مع الحفاظ على هويتها وتلك التي تغيرها، عليك الرجوع إلى توثيق بايثون، ولكن يمكنك أخذ القاعدة التالية بالحسبان: إذا رأيت قيمةً مجردةً ضمن الشيفرة المصدرية، مثل ['rat'] في المثال السابق، فعندها سينشئ بايثون كائنًا جديدًا بهوية جديدة غالبًا، في حين أن التوابع المُستدعاة مع استخدام الكائن بمثابة وسيط لها، مثل التابع ()append، فغالبًا ما تعدّل قيمة الكائن مُحافظةً على هويته. في حال التعامل مع أنواع البيانات الثابتة مثل الأعداد الصحيحة والسلاسل النصية والصفوف، ستكون عمليات الإسناد هي الأبسط. لنكتب على سبيل المثال ما يلي في الصدفة التفاعلية: >>> fish = 'Goodbye' >>> id(fish) 33827584 1 >>> fish = 'Hello' >>> id(fish) 33863820 2 >>> fish = fish + ', world!' >>> fish 'Hello, world!' >>> id(fish) 33870056 3 >>> fish[0] = 'J' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment السلاسل النصية ثابتة، بمعني أنه من غير الممكن تغيير قيمة السلسلة نفسها، ففي حين يبدو أننا قد غيرنا قيمة السلسة النصية في المتغير fish من القيمة 'Goodbye' إلى القيمة 'Hello' (في السطر رقم 1)، إلا أنّه في الواقع قد غيّرنا قيمته إلى كائن سلسلة نصية جديدة ذو هوية جديدة؛ وعلى نحوٍ مشابه، ينشئ التعبير البرمجي باستخدام بناء تسلسل القائمة كائن قائمة جديد (كما في السطر 2) ذو هوية جديدة. لا يُسمَح في الإصدار 3 من بايثون تغيير السلاسل النصية باستخدام عمليات الإسناد دون تغيير هوية الكائن. تُعرّف قيمة الصف وفقًا للكائنات التي يحتويها وترتيب هذه الكائنات؛ فالصفوف هي تسلسلات من الكائنات الثابتة التي تحصر القيم في أقواس، ما يعني أنه من غير الممكن تغيير العناصر الموجودة في الصف، كما في المثال التالي: >>> eggs = ('cat', 'dog', [2, 4, 6]) >>> id(eggs) 39560896 >>> id(eggs[2]) 40654152 >>> eggs[2] = eggs[2] + [8, 10] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment أما في حال تواجد قائمة متغيرة ضمن الصف الثابت، فستبقى متغيّرة دون تغيير هويتها، كما في المثال التالي: >>> eggs[2].append(8) >>> eggs[2].append(10) >>> eggs ('cat', 'dog', [2, 4, 6, 8, 10]) >>> id(eggs) 39560896 >>> id(eggs[2]) 40654152 ورغم كون هذه الحالة خاصة ومبهمة قليلًا، إلا أنه من الضروري أخذها بالحسبان، فلا يزال الصف يشير إلى نفس الكائنات كما هو موضح في الشكل 3، ولكن إذا احتوى الصف على كائن متغيّر وفي حال تغيرت قيمة هذا الكائن -بمعنى أن هذا الكائن قد تغير- تتغيّر قيمة الصف أيضًا. يعد كل محترفي بايثون الصف ثابتًا، ويعتمد أمر تسمية بعض الصفوف بأنها متغيرة على تعريفك وفهمك لها. الشكل 3: رغم كون مجموعة الكائنات ضمن الصف ثابتة، إلا أن المتغيرات نفسها قادرة على التغير. الفهارس Indexes والمفاتيح Keys والقيم المعماة Hashes قوائم وقواميس بايثون هي قيم يمكن أن تحتوي على قيم أخرى متعددة، ويمكن للوصول إلى هذه القيم استخدام عامل الفهرس المكون من زوج من الأقواس المعقوفة وعدد صحيح يسمى الفهرس لتحديد القيمة المراد الوصول إليها، ولنبيّن كيفية استخدام الفهرس مع القائمة، سنكتب ما يلي في الصدفة التفاعلية: >>> spam = ['cat', 'dog', 'moose'] >>> spam[0] 'cat' >>> spam[-2] 'dog' في المثال السابق، الرقم 0 هو فهرس، إذ أن الفهرس الأول هو 0 وليس 1، إذ تستخدم بايثون ومعظم لغات البرمجة الصفر بدايةً للفهارس، أما اللغات التي تعتمد 1 بدايةً للفهارس فهي نادرة وأشهرها لغتي Lua و R، كما أن بايثون تدعم الفهارس السالبة، إذ يشير مثلًا "1-" إلى العنصر الأخير في القائمة، في حين يشير "2-" إلى العنصر ما قبل الأخير، وهكذا. يمكن فهم الفهرس السالب بالشكل [spam[–n مكافئًا لاستخدام [spam[len(spam) – n. ملاحظة: قال عالم الحاسوب والمغني وكاتب الأغاني ستان كيلي-بوتل Stan Kelly-Bootle مرة مازحًا: أيجب أن تبدأ فهارس المصفوفات من الصفر أو الواحد؟ كان اقتراحي بمثابة حلٍ وسطي أن تبدأ من 0.5 ولكنه رفض دون سبب وجيه على ما أعتقد. يمكن استخدام عامل الفهرس على السلسلة المجردة كما هي، رغم كون الأقواس المعقوصة الخاصة بالفهرس قد تبدو غريبة وغير ضرورية في هذه الحالة، كما في المثال: >>> ['cat', 'dog', 'moose'][2] 'moose' : كما يمكن استخدام الفهارس على قيم غير القوائم، مثل السلاسل النصية بغية الحصول على محارف معينة من السلسلة، على النحو التالي: >>> 'Hello, world'[0] 'H' أما قواميس بايثون، فتُرتّب على هيئة أزواج "مفتاح-قيمة"، على النحو التالي: >>> spam = {'name': 'Zophie'} >>> spam['name'] 'Zophie' تنحصر فهارس القوائم بكونها أعداد صحيحة، لكن فهارس قواميس بايثون هي مفاتيح، يمكن أن تكون أي قيمة قابلة للتعمية؛ فالقيمة المعماة هي عدد صحيح شبيه بالبصمة الخاصة بالقيمة، إذ أن القيمة المعماة الخاصة بكائن ما لا تتغير طوال فترة وجود الكائن، وللكائنات المتماثلة في قيمها، قيم معماة متماثلة أيضًا. في مثالنا السابق، السلسلة النصية 'name' هي مفتاح القيمة 'Zophie'. وتعيد الدالة ()hash القيمة المعماة للكائنات القابلة للتعمية. الكائنات الثابتة مثل الأعداد الصحيحة والأعداد العشرية والسلاسل النصية والصفوف قابلة للتعمية، في حين أن القوائم وغيرها من الكائنات المتغيرة غير قابلة للتعمية. لنكتب ما يلي في الصدفة التفاعلية: >>> hash('hello') -1734230105925061914 >>> hash(42) 42 >>> hash(3.14) 322818021289917443 >>> hash((1, 2, 3)) 2528502973977326415 >>> hash([1, 2, 3]) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' تُستخدم المفاتيح المعماة لإيجاد العناصر المخزنة في القواميس وغيرها من هياكل البيانات، ولهذا لا يمكن استخدام قائمة متغيرة مثل مفتاح لقاموس: >>> d = {} >>> d[[1, 2, 3]] = 'some value' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' تختلف القيمة المعماة عن الهوية، فلو كان لدينا كائنان مختلفان يتضمنان نفس القيمة، سيكون لكل منهما هويته الخاصة، أما القيمة المعماة فستكون نفسها لكليهما. لنكتب على سبيل المثال ما يلي في الصدفة التفاعلية: >>> a = ('cat', 'dog', 'moose') >>> b = ('cat', 'dog', 'moose') >>> id(a), id(b) (37111992, 37112136) 1 >>> id(a) == id(b) False >>> hash(a), hash(b) (-3478972040190420094, -3478972040190420094) 2 >>> hash(a) == hash(b) True يمتلك الصفان المُشار إليهما بواسطة a و b هويتين مختلفتين كما هو موضح في السطر 1 من الشيفرة السابقة، ولكن كونهما يحتويان على قيمتين متطابقتين فالقيم المعماة لهما متساوية كما هو موضح في السطر 2. يكون الصف قابلًا للتعمية في حال احتوائه على عناصر قابلة للتعمية، وبما أنه لا يمكن استخدام سوى عناصر قابلة للتعمية مثل مفاتيح للقواميس، فمن غير الممكن استخدام صفٍ يحتوي على قائمة غير قابلة للتعمية مثل مفتاح. لنكتب ما يلي ضمن الصدفة التفاعلية: >>> tuple1 = ('cat', 'dog') >>> tuple2 = ('cat', ['apple', 'orange']) >>> spam = {} 1 >>> spam[tuple1] = 'a value' 2 >>> spam[tuple2] = 'another value' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' نلاحظ أن الصف tuple1 قابل للتعمية، في حين أن الصف tuple2 يتضمن قائمة فهو غير قابل للتعمية مثلها. أنواع الحاويات Containers والسلاسل المفهرسة بأرقام Sequences والمفهرسة بمفاتيح Mapping والمجموعات set تمتلك كلًا من الكلمات: حاوية وسلسلة مفهرسة بأرقام وسلسلة مفهرسة بمفاتيح معانٍ في بايثون قد لا تنطبق بالضرورة على غيرها من لغات البرمجة؛ فالحاوية في بايثون هي كائن من أي نوع بيانات، والتي قد تتضمن العديد من الكائنات الأخرى. تمثّل كلًا من القوائم والقواميس أنواعًا شائعة من الحاويات المستخدمة في بايثون. السلسلة المفهرسة بأرقام هي كائن يحتوي على أي من أنواع بيانات الحاويات، ذو قيم مرتبة يمكن الوصول إليها اعتمادًا على فهارس من أعداد صحيحة. السلاسل النصية والصفوف والقوائم والمتغيرات من النوع byte هي أنواع بيانات من نوع السلاسل المفهرسة بأرقام، إذ يمكن الوصول إلى القيم في هذا النوع من الكائنات باستخدام عامل الفهرسة (القوس المعقوف [ و ])، كما يمكن تمرير الفهرس إلى الدالة ()len. يُقصد بكلمة "مرتبة" وجود ما يمكن تسميته القيمة الأولى والثانية وهكذا، فعلى سبيل المثال، لا يمكن أن تكون قيمتا القائمتين التاليتين متساويتين لأنهما مرتبتان على نحوٍ مختلف: >>> [1, 2, 3] == [3, 2, 1] False السلسلة المفهرسة بمفاتيح mapping هي كائن يحتوي على أي من أنواع بيانات الحاويات الذي يستخدم المفاتيح عوضًا عن الفهرس. يمكن لهذا النوع من السلاسل أن يكون مرتبًا أو غير مرتبًا؛ فالقواميس في الإصدار 3.4 من بايثون والإصدارات الأقدم غير مرتبة لعدم وجود ما يسمى أول أو آخر زوج "مفتاح-قيمة" في القاموس، كما في المثال التالي: >>> spam = {'a': 1, 'b': 2, 'c': 3, 'd': 4} # This is run from CPython 3.5. >>> list(spam.keys()) ['a', 'c', 'd', 'b'] >>> spam['e'] = 5 >>> list(spam.keys()) ['e', 'a', 'c', 'd', 'b'] لا توجد ضمانات للحصول على العناصر من القواميس بترتيب متسق في الإصدارات القديمة من بايثون، ونتيجةً للطبيعة غير الترتيبية للقواميس، سيكون لقاموسين مجردين يحتويان على نفس أزواج "مفتاح-قيمة" ولكن بترتيب مختلف قيمتان متساويتان: >>> {'a': 1, 'b': 2, 'c': 3} == {'c': 3, 'a': 1, 'b': 2} True ولكن بدءًا من الإصدار 3.6 من المفسر CPython أصبحت القواميس تحتفظ بترتيب إدخال أزواج "مفتاح قيمة"، كما في المثال التالي: >>> spam = {'a': 1, 'b': 2, 'c': 3, 'd': 4} # This is run from CPython 3.6. >>> list(spam) ['a', 'b', 'c', 'd'] >>> spam['e'] = 5 >>> list(spam) ['a', 'b', 'c', 'd', 'e'] وتعد هذه إحدى الميزات الموجودة في الإصدار 3.6 من المفسّر CPython وليست موجودة في كافة مفسرات الإصدار 3.6 من بايثون، بينما تدعم كافة مفسرات الإصدار 3.7 من بايثون القواميس الترتيبية، والتي أصبحت معيارية في لغة بايثون في الإصدار 3.7. إلا أن حقيقة كون قاموس ما ترتيبي لا يعني أنه من الممكن الوصول إلى عناصره باستخدام فهارس من أعداد صحيحة، فمثلًا لا يُقيّم التعبير [spam[0 على أنه العنصر الأول من القاموس الترتيبي (فيما عدا حالة كون مفتاح العنصر الأول من القاموس هو فعلًا 0). تعد القواميس الترتيبية أيضًا متطابقة إذا كانت تحتوي على نفس أزواج "مفتاح-قيمة"، حتى ولو كانت هذه الأزواج بترتيب مختلف في كل قاموس. تتضمّن الوحدة collections العديد من السلاسل المفهرسة بمفاتيح الأخرى بما يتضمن OrderedDict (والتي تمثّل القواميس التي تحافظ على الترتيب دائمًا) و ChainMap (المُستخددمة لتجميع عدة قواميس معًا) و Counter (التي تُخزّن العناصر مثل مفاتيح للقواميس وعددها على أنها قيم فيه) و UserDict، وجميعها موصوفة ضمن توثيق بايثون. التوابع السحرية التوابع السحرية والتي تسمّى Dunder methods أو magic methods هي توابع خاصة في بايثون تبدأ أسماء كل منها وتنتهي بشرطتين سفليتين، وتُستخدم هذه التوابع لزيادة تحميل العامل. يأتي مصطلح Dunder اختصارًا للعبارة double underscore أي "شرطتين سفليتين". لعل أشهر التوابع السحرية هو ()__int__ والتي تقرأ بالشكل "dunder init dunder" أي "شرطتان سفليتان int شرطتان سفليتان" أو فقط "int"، والذي يعمل على تهيئة الكائنات. تتضمن بايثون عشراتٍ من التوابع السحرية. الوحدات والحزم الوحدة module هي برنامج بايثون تكون برامج بايثون الأخرى قادرةً على استيراده وبالتالي استخدام شيفرته. تسمّى مجموعة الوحدات الموجودة افتراضيًا في بايثون متجمعةً باسم مكتبة بايثون المعيارية، كما من الممكن إنشاء وحدات خاصة بك. إذا حفظت برنامجًا ما باسم "spam.py" على سبيل المثال، فمن الممكن تشغيل أمر الاستيراد import spam.py للوصول إلى الدوال والأصناف والمتغيرات عالية المستوى للبرنامج spam.py. الحزمة Package هي مجموعة من الوحدات التي نُشكّلها عبر إنشاء ملف باسم "init.py" ضمن المجلد المطلوب، إذ نستخدم اسم هذا المجلد اسمًا للحزمة، ويمكن أن تحتوي الحزم على وحدات متعددة (وهي ملفات ذات امتداد "py.") أو حتى حزم أخرى (أي مجلدات أخرى تحتوي على ملفات "init.py"). الكائنات من الدرجة الأولى First-Class Objects والقابلة للاستدعاء Callables لا تقتصر الأشياء القابلة للاستدعاء في بايثون على الدوال والتوابع، بل أي كائن يتضمّن عامل الاستدعاء -أي رمز القوسين الهلاليين ()- هو كائن قابل للاستدعاء. على سبيل المثال، لو كان لدينا التعبير ()def hello، يمكن فهم هذه الشيفرة على أنها متغير يدعى hello مُتضمنًا كائن دالة، إذ سيستدعي استخدام عامل الاستدعاء لهذا المتغير الدالة في المتغير بالشكل: ()hello. الأصناف classes هي من مفاهيم لغات البرمجة كائنية التوجه، ويمثّل الصنف مثالًا على كائن قابل للاستدعاء دون أن يكون دالة أو تابع، فعلى سبيل المثال، يمكن استدعاء صنف التاريخ date الموجود في الوحدة datatime باستخدام عامل الاستدعاء كما في الشيفرة (datetime.date(2020, 1, 1، وبمجرد استدعاء كائن الصنف، تعمل الشيفرة الموجودة ضمن التابع ()__int__ من الصنف. تعد الدوال من كائنات الدرجة الأولى في بايثون، ما يعني أنه يمكن تخزينها ضمن متغيرات وتمريرها مثل وسطاء عند استدعاء الدوال الأخرى واستخدامها مثل قيم مُعادة للدوال الأخرى أو تنفيذ كل ما يمكن تنفيذه مع أي كائن آخر، وبالتالي يمكن فهم التعليمة def الخاصة بتعريف الدوال على أنها عملية إسناد لكائن الدالة إلى متغير ما، إذ يمكن مثلًا إنشاء دالة ()spam لتُستدعى لاحقًا على النحو التالي: >>> def spam(): ... print('Spam! Spam! Spam!') ... >>> spam() Spam! Spam! Spam! كما يمكن إسناد كائن الدالة ()spam إلى متغيراتٍ أخرى، وبمجرد استدعاء المتغير الذي أسندنا الدالة إليه، ينفذ بايثون هذه الدالة: >>> eggs = spam >>> eggs() Spam! Spam! Spam! ويُدعى هذا بالأسماء المستعارة aliases، وهي أسماء جديدة مختلفة لدوال موجودة أصلًا، ونلجأ إلى هذه الطريقة عند الحاجة إلى إعادة تسمية دالة ما مع موجود أجزاء كبيرة من الشيفرة تستخدم الاسم القديم، ما يتطلب جهدًا كبيرًا لتعديل الاسم في كامل الشيفرة. ولعل الاستخدام الأكثر شيوعًا للدوال من الدرجة الأولى هو إمكانية تمرير الدوال مثل وسطاء لدوال أخرى، إذ يمكن مثلًا تعريف دالة باسم ()callTwicw لنمرر لها أي دالة نريد استدعائها مرتين، على النحو التالي: >>> def callTwice(func): ... func() ... func() ... >>> callTwice(spam) Spam! Spam! Spam! Spam! Spam! Spam! كما يمكن الاكتفاء باستدعاء الدالة ()spam مرتين بكتابتها مرتين في الشيفرة المصدرية، إلا أنه يمكن تمرير التابع المراد استدعائه مرتين إلى الدالة ()callTwicw عوضًا عن كتابة اسم الدالة مرتين في الشيفرة المصدرية. الخلاصة من السهل أن تمارس البرمجة لسنوات مع بقاء بعض المصطلحات البرمجية غير مألوفة بالنسبة لك، إلا أن معظم التطبيقات البرمجية قد أنشئت من قبل فريق كامل من المطورين وليس من قبل أفراد، لذا تُعد القدرة على التواصل الواضح أمرًا أساسيًا عند العمل ضمن فريق. وضحنا في هذا المقال أن برامج بايثون مكونة من معرفات ومتغيرات وسلاسل مجردة وكلمات مفتاحية وكائنات، وأن كل كائنات بايثون تمتلك قيمةً ونوع بيانات وهوية، ورغم كون كل كائن يمتلك نوع بيانات، إلا أنه يوجد مجموعة واسعة من فئات الأنواع، مثل الحاويات والسلاسل المفهرسة بأرقام وتلك المفهرسة بمفاتيح والمجموعات والمُعرف منها مسبقًا أو من قبل المستخدم. ستجد دومًا أن استخدام المصطلحات يختلف من لغة لأخرى وحتى من مبرمج لآخر، وستغدو معتادًا أكثر على المصطلحات مع الخبرة والبحوثات المتكررة على الإنترنت. ترجمة -وبتصرف- لجزء من الفصل السابع "المصطلحات البرمجية" من كتاب Beyond the Basic Stuff with Python لصاحبه Al Sweigart. اقرأ أيضًا المقال السابق: الطرق البايثونية في استخدام قواميس بايثون ومتغيراتها وعاملها الثلاثي مفهوم الدوال Functions في البرمجة كيف تتعلم البرمجة: نصائح وأدوات لرحلتك في عالم البرمجة تعلم البرمجة
×
×
  • أضف...