JSON الثابت مقابل الديناميكي
- الأنواع البدائية - إذا كانت قيمة المفتاح من الأنواع البدائية، سواء كانت جزءًا من كائن فرعي أو موجودة في الجذر، فتأكد من اختيار نوعها وفقًا لـ أفضل ممارسات تصميم المخطط وقواعد تحسين الأنواع. ويمكن تمثيل مصفوفات الأنواع البدائية، مثل
phone_numbersأدناه، على أنهاArray(<type>)، مثلArray(String). - الثابت مقابل الديناميكي - إذا كانت قيمة المفتاح كائنًا معقدًا، أي كائنًا أو مصفوفة من الكائنات، فحدّد ما إذا كانت قابلة للتغيّر. يمكن اعتبار الكائنات التي نادرًا ما تُضاف إليها مفاتيح جديدة، والتي يمكن توقّع إضافة مفتاح جديد إليها والتعامل معه عبر تعديل المخطط باستخدام
ALTER TABLE ADD COLUMN، كائنات ثابتة. ويشمل ذلك الكائنات التي قد لا يظهر فيها سوى جزء من المفاتيح في بعض مستندات JSON. أما الكائنات التي تُضاف إليها مفاتيح جديدة بشكل متكرر و/أو يتعذر التنبؤ بها، فينبغي اعتبارها ديناميكية. والاستثناء هنا هو البُنى التي تحتوي على مئات أو آلاف المفاتيح الفرعية، إذ يمكن اعتبارها ديناميكية لأغراض التيسير.
- يمكن تمثيل المفاتيح الجذرية
nameوusernameوemailوwebsiteبالنوعString. أما العمودphone_numbersفهو قيمة أولية من النوعArray(String)، بينما يكون نوعاdobوidهماDateوUInt32على التوالي. - لن تُضاف مفاتيح جديدة إلى الكائن
address(بل كائنات address جديدة فقط)، ولذلك يمكن اعتباره ثابتًا. وإذا تابعنا بشكل تكراري، فيمكن اعتبار جميع الأعمدة الفرعية قيمًا أولية (ومن النوعString) باستثناءgeo. وهذا أيضًا هيكل ثابت يحتوي على عمودين من النوعFloat32، هماlatوlon. - العمود
tagsديناميكي. ونفترض أنه يمكن إضافة وسوم عشوائية جديدة إلى هذا الكائن بأي نوع أو بنية. - الكائن
companyثابت وسيحتوي دائمًا على المفاتيح الثلاثة المحددة كحد أقصى. المفتاحان الفرعيانnameوcatchPhraseمن النوعString. أما المفتاحlabelsفهو ديناميكي. ونفترض أنه يمكن إضافة وسوم عشوائية جديدة إلى هذا الكائن. وستكون القيم دائمًا أزواج مفتاح-قيمة من النوع string.
يمكن اعتبار البنى التي تحتوي على مئات أو آلاف المفاتيح الثابتة ديناميكية، إذ نادرًا ما يكون من العملي تعريف الأعمدة الخاصة بها تعريفًا ثابتًا. ومع ذلك، حيثما أمكن، تخطَّ المسارات غير المطلوبة لتقليل كلٍّ من مساحة التخزين والعبء الإضافي للاستدلال.
التعامل مع البُنى الثابتة
Tuple مُسمّاة. ويمكن تمثيل مصفوفات الكائنات باستخدام مصفوفات من Tuple، أي Array(Tuple). وداخل قيم Tuple نفسها، ينبغي تعريف الأعمدة وأنواعها المقابلة وفق القواعد نفسها. وقد ينتج عن ذلك استخدام Tuple متداخلة لتمثيل الكائنات المتداخلة، كما هو موضح أدناه.
ولتوضيح ذلك، نستخدم مثال شخص JSON السابق، مع حذف الكائنات الديناميكية:
company على أنه Tuple(catchPhrase String, name String). ويستخدم المفتاح address النوع Array(Tuple)، مع Tuple متداخلة لتمثيل العمود geo.
يمكن إدراج JSON في هذا الجدول ببنيته الحالية:
address.street على شكل Array. وللاستعلام عن كائن معيّن داخل مصفوفة حسب موضعه، يجب تحديد فهرس المصفوفة بعد اسم العمود. على سبيل المثال، للوصول إلى الشارع من العنوان الأول:
24.12:
التعامل مع القيم الافتراضية
Tuple وجود جميع الأعمدة في حمولة JSON. وإذا لم تُوفَّر، فستُستخدَم القيم الافتراضية.
لننظر إلى جدول people الذي استخدمناه سابقًا، وإلى JSON المتناثر التالي الذي يفتقد المفاتيح suite وgeo وphone_numbers وcatchPhrase.
التمييز بين القيمة الفارغة وNULLإذا كنت بحاجة إلى التمييز بين كون القيمة فارغة وبين عدم توفيرها، فيمكن استخدام النوع Nullable. ويُنصح بتجنّب ذلك ما لم تكن هناك حاجة ماسّة إليه، لأنه سيؤثر سلبًا في التخزين وأداء الاستعلامات لهذه الأعمدة.
التعامل مع الأعمدة الجديدة
nickname:
nickname:
ALTER TABLE ADD COLUMN. ويمكن تحديد قيمة افتراضية عبر عبارة DEFAULT، وتُستخدم هذه القيمة إذا لم تُحدَّد أثناء عمليات الإدراج اللاحقة. كما أن الصفوف التي لا تتضمن هذه القيمة (لأنها أُدرجت قبل إنشائها) ستُرجِع أيضًا هذه القيمة الافتراضية. وإذا لم يتم تحديد قيمة DEFAULT، فستُستخدم القيمة الافتراضية للنوع.
على سبيل المثال:
التعامل مع البنى شبه المنظَّمة/الديناميكية
JSON هو الخيار الموصى به.
وبشكل أكثر تحديدًا، استخدم النوع JSON عندما تكون بياناتك:
- تحتوي على مفاتيح غير متوقعة يمكن أن تتغير بمرور الوقت.
- تتضمن قيمًا ذات أنواع متفاوتة (على سبيل المثال، قد يحتوي مسار ما أحيانًا على سلسلة نصية، وأحيانًا أخرى على رقم).
- تتطلب مرونة في المخطط حيث لا يكون الالتزام الصارم بالأنواع عمليًا.
- لديك مئات أو حتى آلاف المسارات الثابتة، لكن التصريح بها صراحةً ليس عمليًا. ويكون هذا نادرًا في العادة.
company.labels ديناميكي.
لنفترض أن company.labels يحتوي على مفاتيح عشوائية. بالإضافة إلى ذلك، قد لا يكون نوع أي مفتاح في هذه البنية متسقًا بين الصفوف. على سبيل المثال:
company.labels عبر الكائنات من حيث المفاتيح والأنواع، لدينا عدة خيارات لنمذجة هذه البيانات:
- عمود JSON واحد - يمثّل المخطط بالكامل على هيئة عمود
JSONواحد، مما يتيح أن تكون جميع البُنى تحته ديناميكية. - عمود JSON موجَّه - استخدام النوع
JSONفقط للعمودcompany.labels، مع الإبقاء على المخطط المنظَّم المستخدم أعلاه لجميع الأعمدة الأخرى.
- التحقق من صحة البيانات – إن فرض مخطط صارم يجنّب خطر تضخّم الأعمدة خارج البُنى المحددة.
- تجنّب خطر تضخّم الأعمدة - رغم أن نوع JSON يمكنه التوسّع ليشمل آلاف الأعمدة المحتملة، إذ تُخزَّن الأعمدة الفرعية كأعمدة مخصّصة، فقد يؤدي ذلك إلى تضخّم ملفات الأعمدة، أي إنشاء عدد مفرط من ملفات الأعمدة بما يؤثر في الأداء. وللحد من ذلك، يوفّر النوع Dynamic الأساسي الذي يستخدمه JSON المعلَمة
max_dynamic_paths، التي تقيّد عدد المسارات الفريدة المخزّنة كملفات أعمدة منفصلة. وما إن يُبلَغ هذا الحد، تُخزَّن المسارات الإضافية في ملف عمود مشترك باستخدام تنسيق مرمّز مضغوط، بما يحافظ على الأداء وكفاءة التخزين مع دعم استيعاب البيانات بمرونة. إلا أن الوصول إلى ملف العمود المشترك هذا يكون أقل أداءً. ومع ذلك، تجدر الإشارة إلى أنه يمكن استخدام عمود JSON مع تلميحات الأنواع. وستوفّر الأعمدة “المزوّدة بتلميحات” الأداء نفسه الذي توفّره الأعمدة المخصّصة. - استبطان أبسط للمسارات والأنواع - رغم أن نوع JSON يدعم دوال الاستبطان لتحديد الأنواع والمسارات التي جرى استنتاجها، فإن البُنى الثابتة قد تكون أسهل في الاستكشاف، مثلًا باستخدام
DESCRIBE.
عمود JSON واحد
JSON فقط للبُنى الفرعية الديناميكية عند الحاجة.
اعتبارات الأداءيمكن تحسين عمود JSON واحد من خلال تخطي مسارات JSON غير المطلوبة (أي عدم تخزينها)، ومن خلال استخدام تلميحات الأنواع. تتيح تلميحات الأنواع للمستخدم تحديد النوع صراحةً لعمود فرعي، وبذلك يُستغنى عن الاستدلال والمعالجة غير المباشرة وقت تنفيذ الاستعلام. ويمكن استخدام ذلك لتحقيق مستوى الأداء نفسه كما لو استُخدم مخطط صريح. راجع “استخدام تلميحات الأنواع وتخطي المسارات” لمزيد من التفاصيل.
نقدّم تلميحًا للنوع للعمود
username في تعريف JSON لأننا نستخدمه ضمن مفتاح الترتيب/المفتاح الأساسي. يساعد ذلك ClickHouse على معرفة أن هذا العمود لن تكون قيمته NULL، ويضمن أيضًا تحديد أي عمود فرعي لـ username ينبغي استخدامه (إذ قد يوجد أكثر من عمود فرعي واحد لكل نوع، وإلا فسيكون الأمر ملتبسًا).JSONAsObject:
.، مثل:
NULL.
بالإضافة إلى ذلك، يُنشأ عمود فرعي منفصل للمسارات التي لها النوع نفسه. على سبيل المثال، يوجد عمود فرعي للمسار company.labels.type بكلٍّ من النوعين String وArray(Nullable(String)). ومع أنه ستتم إعادة كليهما متى أمكن، يمكننا استهداف أعمدة فرعية محددة باستخدام الصياغة .::
^. وهذا خيار تصميمي لتجنّب قراءة عدد كبير من الأعمدة ما لم يُطلب ذلك صراحةً. وستُرجِع الكائنات التي يتم الوصول إليها من دون ^ القيمة NULL، كما هو موضح أدناه:
عمود JSON المستهدف
JSON واحد لعمود company.labels.
JSONEachRow:
company.labels.
استخدام تلميحات الأنواع وتخطّي المسارات
dissolved وemployees وfounded داخل عمود JSON company.labels
SKIP وSKIP REGEXP لتقليل مساحة التخزين وتجنب الاستدلال غير الضروري على المسارات غير المطلوبة. على سبيل المثال، لنفترض أننا نستخدم عمود JSON واحدًا للبيانات أعلاه. يمكننا تخطي المسارين address وcompany:
تحسين الأداء باستخدام تلميحات الأنواع
تهيئة المسارات الديناميكية
max_dynamic_paths في تعريف نوع JSON.
SKIP لتقييد ما يُخزَّن.
للمستخدمين المهتمين بكيفية تنفيذ هذا النوع الجديد من الأعمدة، نوصي بقراءة تدوينتنا التفصيلية “نوع بيانات JSON جديد وقوي لـ ClickHouse”.