استيراد CSV في SQLite يتم من سطر الأوامر، لا من داخل SQL
لا يوجد في لهجة SQL الخاصة بـ SQLite أي أمر اسمه IMPORT. عملية استيراد CSV إلى SQLite ليست جزءاً من اللغة نفسها، بل ميزة موجودة في صدفة sqlite3 على سطر الأوامر، عبر أمر النقطة .import. وهذه نقطة تحول ذهنية مهمة إذا كنت قادماً من MySQL وأمر LOAD DATA INFILE، أو من Postgres وأمر COPY؛ فتلك الأوامر تُنفَّذ على الخادم نفسه، أما .import فهو شيء تقوم به الأداة العميلة نيابةً عنك: تقرأ ملف csv file وتُصدر جمل INSERT خلف الكواليس.
لذلك كل ما سيرد في هذه الصفحة يفترض أنك داخل صدفة sqlite3:
sqlite3 mydata.db
إذا كنت تحتاج للاستيراد من داخل تطبيقك بدلًا من سطر الأوامر — سواء بـ Python أو Node أو Go — فالطريقة هي أن تقرأ ملف الـ CSV عبر لغتك ثم تستخدم جمل INSERT مع المعاملات (parameterized). سنتناول هذا الأسلوب في فصل دمج التطبيقات. أما هنا فتركيزنا على واجهة سطر الأوامر.
استخدام أمر .import الأساسي
أقصر طريق لاستيراد csv الى sqlite: أخبر SQLite أن الملف بصيغة CSV، ثم وجّه أمر .import إلى مسار الملف واسم الجدول.
.mode csv
.import people.csv people
يحصل أحد أمرين حسب ما إذا كان الجدول people موجوداً مسبقاً أم لا:
- الجدول غير موجود — يقوم SQLite بإنشائه، ويستخدم السطر الأول من ملف CSV كأسماء للأعمدة، ويعطي كل عمود نوع
TEXT. - الجدول موجود — يُدرج SQLite كل أسطر الملف كبيانات، بما في ذلك سطر العناوين إن وُجد، فيتحوّل إلى صف عادي ضمن البيانات.
الحالة الثانية هي الفخّ الذي يقع فيه معظم الناس في أول محاولة. لو كان ملف CSV يحتوي على سطر عناوين والجدول موجود مسبقاً، فلا بدّ من تخطّي هذا السطر يدوياً.
تخطّي سطر العناوين عند الاستيراد إلى جدول موجود في SQLite
استخدم --skip 1 لإخبار الأمر .import بتجاهل أول N من الأسطر:
CREATE TABLE people (
name TEXT,
age INTEGER,
city TEXT
);
.import --csv --skip 1 people.csv people
--csv اختصار لـ .mode csv لكن مفعوله محصور في هذا الأمر فقط، يعني ما تحتاج تضبط الوضع بشكل منفصل. أما --skip 1 فيتجاهل سطر العناوين (الـ header)، وباقي الأسطر تنضاف إلى جدول people حسب ترتيب الأعمدة.
تحقق سريع بعد الاستيراد:
SELECT count(*) FROM people;
SELECT * FROM people LIMIT 5;
ترتيب الأعمدة في الملف يجب أن يطابق ترتيب الأعمدة في الجدول. ما في ربط بناءً على أسماء العناوين — أمر .import ببساطة يحاذي الحقل رقم N مع العمود رقم N.
ترك SQLite ينشئ الجدول تلقائياً
لو شغلك استكشافي، أسهل طريقة هي تتجاوز CREATE TABLE كلياً وتخلي .import ينشئ الجدول اعتماداً على سطر العناوين:
.mode csv
.import sales.csv sales
.schema sales
.schema sales ستعرض شيئًا مثل:
CREATE TABLE sales(
"order_id" TEXT,
"amount" TEXT,
"ordered_at" TEXT
);
لاحظ أن كل الأعمدة جاءت من نوع TEXT، وهذا أمر مقصود — فأمر .import لا يحاول استنتاج الأنواع. إذا أردت أن يكون amount رقمًا فعليًا وordered_at طابعًا زمنيًا حقيقيًا، فأنشئ الجدول بنفسك أولًا بالأنواع الصحيحة، ثم استورد البيانات مع --skip 1. وسيتكفّل نظام type affinity في SQLite بتحويل السلاسل الرقمية إلى أعداد صحيحة وأعداد عشرية عند الإدراج.
فواصل مخصّصة: TSV وPipe وSemicolon
الوضع .mode csv يستخدم الفاصلة. أما إذا كان ملفك مفصولًا بعلامات Tab، فبدّل الوضع:
.mode tabs
.import data.tsv events
للفواصل الأخرى، استخدم .separator بعد اختيار الوضع المناسب:
.mode csv
.separator "|"
.import pipe_data.txt events
معلومة مهمة: الوضع .mode csv يلتزم بقواعد الاقتباس في RFC 4180، يعني الحقول التي فيها فواصل أو أسطر جديدة بداخلها تشتغل تمام طول ما هي محاطة بعلامات اقتباس " بشكل سليم. أما .mode tabs فهو وضع أبسط يعتمد على التقسيم بحرف فاصل فقط دون أي دعم للاقتباس. لو ملفك يحتوي على حقول مقتبسة بداخلها فواصل، فالأفضل تبقى على .mode csv وتغيّر الفاصل.
مثال عملي خطوة بخطوة
لنفترض أن ملف orders.csv يبدو هكذا:
order_id,customer,amount,ordered_at
1001,Ada,49.99,2026-01-12
1002,Boris,12.50,2026-01-13
1003,"Chen, Wei",199.00,2026-01-14
لاحظ أنّ السطر الثالث يحتوي على فاصلة داخل حقل محاط بعلامتي اقتباس. وإليك الجلسة كاملة:
في الصدفة الحقيقية، يُستبدل بلوك INSERT بسطر واحد فقط: .import --csv --skip 1 orders.csv orders. لاحظ أن الحقل "Chen, Wei" يبقى كما هو دون أن يتقطّع، لأن وضع CSV يحترم علامات الاقتباس. كذلك يُخزَّن amount كرقم فعلي وorder_id كعدد صحيح، وذلك بفضل أنواع الأعمدة المُعرَّفة في الجدول.
تغليف عملية الاستيراد داخل معاملة (Transaction)
الأمر .import ينفّذ INSERT منفصلًا لكل صف. لو كان عندك بضعة آلاف من الصفوف، فالأمر مقبول. لكن حين تتعامل مع مليون صف، تصبح العملية بطيئة جدًا ما لم تُغلِّفها كلها داخل معاملة واحدة، حتى لا تقوم SQLite بعمل commit بعد كل صف على حدة:
BEGIN;
.import --csv --skip 1 big_file.csv events
COMMIT;
هذا التعديل البسيط يقدر يحوّل عملية استيراد تستغرق دقائق إلى ثوانٍ معدودة. ولو حصل أي خطأ في منتصف العملية، فإن ROLLBACK يتراجع عن التحميل الجزئي، وهذا مفيد كذلك عند إعادة المحاولة.
ولزيادة السرعة أكثر، يمكنك حذف الفهارس (indexes) قبل الاستيراد وإعادة إنشائها بعده، لأن صيانة الفهرس مع كل صف تتراكم وتؤثر على الأداء.
الأخطاء الشائعة وطريقة حلّها
Error: expected N columns but found M — عدد الحقول في الصف لا يطابق عدد أعمدة الجدول. الأسباب المعتادة:
- فاصلة شاردة داخل حقل غير محاط بعلامات اقتباس. أعد تصدير الـ
csv fileمع اقتباس صحيح، أو استخدم.mode csv(وفق RFC 4180) بدلاً من.mode tabs. - سطر فارغ زائد في نهاية الملف. عدّل الملف أو استخدم
--skipبشكل ذكي. - عدد أعمدة الجدول أكبر من عدد أعمدة الـ CSV. إما تضيف الأعمدة الناقصة للملف، أو تستورد إلى جدول وسيط (staging table) بنفس بنية الملف ثم تنسخ منه إلى الجدول الفعلي.
سطر العناوين يظهر كبيانات — نسيت --skip 1 عند الاستيراد إلى جدول موجود. احذف هذا الصف (DELETE FROM t WHERE rowid = 1) ثم أعد التشغيل مع الراية.
الأرقام مخزّنة كنصوص — تركت .import ينشئ الجدول، فصارت كل الأعمدة من نوع TEXT. احذف الجدول، وأعد إنشاءه يدويًا بأنواع INTEGER/REAL، ثم أعد عملية استيراد csv إلى sqlite.
Error: no such file — المسار يُحسب نسبةً إلى المجلد الذي شغّلت منه sqlite3، لا نسبةً إلى ملف قاعدة البيانات. استخدم مسارًا مطلقًا، أو نفّذ cd للمجلد الصحيح قبل فتح الـ shell.
سطر الأوامر يطبع رقم السطر عند حدوث الخطأ، وهذه أسرع طريقة لتحديد الصف المُسبّب للمشكلة داخل ملف ضخم.
ملخّص سريع
.importأمر نقطة (dot-command) خاص بسطر الأوامر، وليس SQL. شغّله داخل صدفةsqlite3.- استخدم
--csvللتعامل الصحيح مع الاقتباس، و--skip 1لتخطي سطر العناوين في csv. - إن لم يكن الجدول موجودًا، فإن
.importينشئه من سطر العناوين، لكن كل الأعمدة ستكونTEXT. أنشئ الجدول بنفسك للحصول على الأنواع الصحيحة. - لفّ عمليات الاستيراد الكبيرة بين
BEGINوCOMMITحتى لا تُنفَّذ معاملة لكل صف. - ترتيب الأعمدة في الملف يجب أن يطابق ترتيب الأعمدة في الجدول.
التالي: تصدير البيانات
الاستيراد نصف الحكاية فقط. نفس الصدفة تقدر تُفرِّغ نتائج الاستعلامات أو جداول كاملة إلى CSV أو JSON أو SQL، وهو أمر مفيد للنسخ الاحتياطي وخطوط معالجة البيانات وتسليمها لأدوات أخرى. سنتناول هذا في درس تصدير البيانات.
الأسئلة الشائعة
كيف أستورد ملف CSV إلى قاعدة بيانات SQLite؟
افتح قاعدة البيانات عبر سطر الأوامر sqlite3، ثم فعّل وضع CSV بالأمر .mode csv، وبعدها نفّذ .import data.csv table_name. إذا لم يكن الجدول موجودًا، فسينشئه SQLite تلقائيًا ويأخذ أسماء الأعمدة من السطر الأول في الملف. أما إذا كان الجدول موجودًا فسيتم إدراج جميع الأسطر كبيانات، ولذلك تحتاج عادةً إلى .import --skip 1 لتخطي سطر العناوين.
كيف أستورد ملف CSV يحتوي على سطر عناوين إلى جدول SQLite موجود مسبقًا؟
استخدم الأمر .import --csv --skip 1 data.csv table_name. الخيار --skip 1 يخبر SQLite بتجاهل السطر الأول حتى لا يدخل سطر العناوين كصف بيانات. بدون هذا الخيار ستجد في جدولك صفًا يحتوي على أسماء الأعمدة نفسها كقيم نصية.
لماذا تظهر لي رسالة 'expected N columns but found M' أثناء استيراد CSV في SQLite؟
السبب أن الملف يحتوي على أسطر بعدد أعمدة مختلف عن الجدول، وغالبًا يكون ذلك بسبب فواصل مدمجة داخل النصوص، أو علامات اقتباس غير مغلقة، أو سطر فارغ في نهاية الملف. استخدم .mode csv (أو الخيار --csv) بدلًا من .mode tabs ليتعامل SQLite مع الاقتباسات وفق معيار RFC 4180، وراجع الملف في محرر نصوص للبحث عن فواصل شاذة. سطر الأوامر يطبع رقم السطر المسبّب للخطأ، وهي أسرع طريقة لتحديد الصف الخاطئ.