آوند — داده: SQL، مدل‌سازی و تحلیل برای تصمیم

فصل ۲ از ۸

پیشرفت ترم
۰٪

ترم ۱ · داده را بخوان

اولین `SELECT`

فصل ۲پیش‌نمایش رایگان
۱۱ دقیقه مطالعه فصل ۲

در این فصل چه یاد می‌گیری#

فصلِ قبل دو پرسش را اجرا کردیم بدونِ اینکه بگوییم چرا این‌طور نوشته شده‌اند. این فصل همان بدهی را می‌پردازد و در راه، اولین عددهای واقعی را از این جدول بیرون می‌کشد: ۹۴٬۱۹۱ امانت روی ۱٬۲۰۰ کتاب.

و به عددی می‌رسیم که کلِ ترم را شکل می‌دهد. از همین یک ستونِ rating دو میانگین بیرون می‌آید — ۳٫۷۸۶۸۹۳۷۰۴۸۵۰۳۶۱۳ و ۳٫۰۵۷۹۱۶۶۶۶۶۶۶۶۷ — و هیچ‌کدام غلط محاسبه نشده‌اند. تفاوتشان در چیزی است که هنوز اسمش را نبرده‌ایم.

یک ترازوی دو کفه‌ای که یک کفه‌اش سوراخ دارد و بخشی از وزنه‌ها از آن بیرون ریخته‌اند

آخر این فصل می‌توانی:

  • یک پرسشِ SELECT بنویسی که فقط ستون‌های لازم را برمی‌گرداند
  • با LIMIT قبل از هر کارِ دیگری چند سطر نمونه ببینی
  • با AS نامِ خروجی را خودت انتخاب کنی و با -- توضیح بگذاری
  • سه عددِ کلی از یک جدول بگیری: COUNT(*) و SUM و AVG
  • بگویی AVG روی چند سطر حساب کرده — نه چند سطر در جدول هست

قبل از شروع#

از فصلِ ۱: جدول و سطر و ستون، اتصال، و تابعِ q(...). یک سطر از books یعنی یک عنوانِ کتاب که در یکی از شعبه‌ها ثبت شده — این جمله در هر فصلِ این ترم تکرار می‌شود چون هر عددی که می‌سازیم رویش سوار است.

📓 نوت‌بوک: نوت‌بوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و به‌ترتیب داخلش هست.

جدول تعدادِ سطر زمانِ ساخت
books ۱٬۲۰۰ کمتر از یک ثانیه
branches ۶ آنی

۱. SELECT و FROM: کدام ستون‌ها، از کدام جدول#

کوچک‌ترین پرسشِ ممکن دو تکه دارد: چه چیزی می‌خواهی و از کجا.

SELECT فهرستِ ستون‌هایی است که می‌خواهی. FROM نامِ جدول است. همین.

یک قاعدهٔ نگارشی که از همین اولین پرسش رعایتش کن: کلیدواژه‌ها با حروفِ بزرگ، نامِ جدول و ستون با حروفِ کوچک. در پرسشِ سه‌کلمه‌ای فرقی نمی‌کند؛ در پرسشِ ده‌خطیِ ترمِ ۳ تنها چیزی است که می‌گذارد با یک نگاه ساختار را از داده جدا کنی.

print(q("SELECT title, author, borrowed FROM books"))
                title        author  borrowed
0           روزهای پل    مهدی سلیمی        33
1         مه در باران    رویا رحیمی        20
2          روزهای خاک   سعید کاشانی         1
3          مه پشت آتش   سیما نیکزاد        86
4     بازگشت به باران   سعید کاشانی        53
...               ...           ...       ...
1195     خاک پشت جنگل  فرهاد نیکزاد       135
1196        دریا و مه    سعید توکلی         0
1197       برف پشت مه  شیرین فرهنگی         2
1198     روزهای باران  الهام کاشانی         0
1199   بازگشت به کویر     بابک بیات       138

[1200 rows x 3 columns]

آن سه نقطهٔ وسط را pandas گذاشته، نه پایگاه داده. هر ۱٬۲۰۰ سطر واقعاً آمد؛ pandas فقط برای نمایش خلاصه‌شان کرد. این دقیقاً همان اشتباهی است که فصلِ ۱ اندازه‌اش را گرفت — هزینه پرداخت شد و بعد دور ریخته شد.

۲. LIMIT: اول نگاه کن، بعد بپرس#

LIMIT n به پایگاه داده می‌گوید بعد از n سطر بس کند.

print(q("SELECT title, author, borrowed FROM books LIMIT 5"))
             title       author  borrowed
0        روزهای پل   مهدی سلیمی        33
1      مه در باران   رویا رحیمی        20
2       روزهای خاک  سعید کاشانی         1
3       مه پشت آتش  سیما نیکزاد        86
4  بازگشت به باران  سعید کاشانی        53

این اولین کاری است که روی هر جدولِ ناآشنایی باید بکنی. قبل از هر عددی، قبل از هر گزارشی، پنج سطر نمونه ببین. نصفِ اشتباه‌هایی که در این دوره جلوی‌شان را می‌گیریم با همین یک نگاه پیدا می‌شوند.

⚠️ مواظب باش: LIMIT 5 پنج سطرِ اول را نمی‌دهد، پنج سطر می‌دهد. «اول» تا وقتی ترتیبی نگفته باشی معنایی ندارد؛ اینجا اتفاقاً همان ترتیبِ ثبت درآمده، ولی این یک تصادف است نه یک تضمین. فصلِ ۴ کلِ ماجرا را باز می‌کند.

۳. ستون‌ها را اسم ببر، * ننویس#

* یعنی «همهٔ ستون‌ها». برای یک نگاهِ اولیه خوب است و در فصلِ ۱ هم از همان استفاده کردیم. برای هر چیزِ دیگری عادتِ بدی است، و دلیلش قابلِ اندازه‌گیری است:

star = q("SELECT * FROM books LIMIT 5")
three = q("SELECT title, author, borrowed FROM books LIMIT 5")

print("SELECT *          →", star.shape[0], "سطر ×", star.shape[1], "ستون =",
      star.shape[0] * star.shape[1], "خانه")
print("SELECT سه ستون    →", three.shape[0], "سطر ×", three.shape[1], "ستون =",
      three.shape[0] * three.shape[1], "خانه")
SELECT *          → 5 سطر × 13 ستون = 65 خانه
SELECT سه ستون    → 5 سطر × 3 ستون = 15 خانه

روی پنج سطر، ۶۵ در برابرِ ۱۵. روی هزار سطر همان نسبت است و عددها هزار برابر.

و دلیلِ دومی هم دارد که از اولی مهم‌تر است: * یعنی پرسشِ تو به شکلِ امروزیِ جدول وابسته است. اگر فردا کسی ستونی اضافه کند، خروجیِ پرسشِ تو بی‌خبر عوض می‌شود. وقتی ستون‌ها را اسم برده باشی، هیچ اتفاقی نمی‌افتد.

۴. AS و کامنتِ --#

AS نامِ ستون را در خروجی عوض می‌کند، نه در جدول. و هر چیزی بعد از -- تا آخرِ همان خط، توضیح است و اجرا نمی‌شود.

print(q("""
    -- ستون borrowed اسم گویایی ندارد؛ در خروجی اسم بهتری برایش می‌گذاریم
    SELECT title            AS name,
           borrowed         AS times_borrowed
    FROM books
    LIMIT 3
"""))
          name  times_borrowed
0    روزهای پل              33
1  مه در باران              20
2   روزهای خاک               1

AS وقتی ضروری می‌شود که ستونِ خروجی اصلاً در جدول وجود نداشته باشد — مثلِ نتیجهٔ یک شمارش. بخشِ بعد را ببین.

۵. اولین عددها: COUNT(*)، SUM، AVG#

تا اینجا هر پرسشی سطر برمی‌گرداند. این سه تابع کارِ متفاوتی می‌کنند: از کلِ جدول یک عدد می‌سازند.

  • COUNT(*) سطرها را می‌شمارد.
  • SUM(col) مقدارهای یک ستون را جمع می‌زند.
  • AVG(col) میانگینشان را می‌گیرد.
print(q("""
    SELECT COUNT(*)      AS books,
           SUM(borrowed) AS all_borrows,
           AVG(borrowed) AS borrows_per_book,
           AVG(pages)    AS pages_per_book
    FROM books
"""))
   books  all_borrows  borrows_per_book  pages_per_book
0   1200        94191           78.4925      503.798333

یک سطر، چهار عدد. و اینجا دانه‌بندیِ نتیجه عوض شد: ورودی ۱٬۲۰۰ سطر بود که هرکدام یک کتاب بودند، خروجی یک سطر است که یعنی «کلِ فهرست». هر بار که یک تابعِ تجمیعی می‌نویسی همین اتفاق می‌افتد، و ترمِ ۲ تمامش دربارهٔ کنترل کردنِ همین است.

چک کن: 78.4925 باید دقیقاً 94191 / 1200 باشد. حساب کن. اگر نشد، ستونی را جمع زده‌ای که با ستونی که شمرده‌ای یکی نیست — و این شایع‌ترین شکلِ عددِ غلط در گزارش‌های واقعی است.

۶. دو میانگین از یک ستون، و هیچ‌کدام غلط نیست#

حالا همان AVG را روی ستونِ rating بگیریم و کنارش، همان میانگین را از راهِ دستی حساب کنیم: جمعِ ستون تقسیم بر تعدادِ سطرهای جدول.

row = q("""
    SELECT COUNT(*)    AS n,
           SUM(rating) AS rating_sum,
           AVG(rating) AS rating_avg
    FROM books
""")
print(row)
print()
print("AVG(rating) چه می‌گوید       :", float(row["rating_avg"][0]))
print("SUM(rating) تقسیم بر COUNT(*):", float(row["rating_sum"][0]) / int(row["n"][0]))
      n  rating_sum  rating_avg
0  1200      3669.5    3.786894

AVG(rating) چه می‌گوید       : 3.7868937048503613
SUM(rating) تقسیم بر COUNT(*): 3.057916666666667

دو عدد، از یک ستون، در یک پرسش. اختلافشان بیش از هفت‌دهم است.

هیچ‌کدام اشتباه حساب نشده‌اند. SUM و COUNT(*) هر دو درست‌اند، و AVG هم درست است. تفاوت در مخرج است: AVG بر ۱٬۲۰۰ تقسیم نکرده.

بر چه تقسیم کرده؟ عددش را می‌شود از همین دو خط درآورد: 3669.5 / 3.7868937048503613 می‌شود ۹۶۹. یعنی AVG دویست‌وسی‌ویک سطر را اصلاً به حساب نیاورده.

چرا؟ و آیا آن ۲۳۱ سطر تصادفی انتخاب شده‌اند؟ جوابِ دوم «نه» است و همان چیزی است که فصلِ ۶ را به یک فصلِ ⚠️ تبدیل می‌کند. فعلاً همین‌قدر بدان که ستونِ rating در بعضی سطرها خالی است، و AVG سطرهای خالی را نمی‌شمارد. فصلِ ۶ کاملاً بازش می‌کند و نشان می‌دهد آن ۲۳۱ کتاب چه ویژگیِ مشترکی دارند.

پس دو عدد داری و باید انتخاب کنی، و انتخاب به پرسشت بستگی دارد: «میانگینِ امتیازِ کتاب‌هایی که امتیاز خورده‌اند» یعنی ۳٫۷۹. «میانگینِ امتیاز اگر کتابِ بی‌امتیاز را صفر بگیریم» یعنی ۳٫۰۶. اولی معمولاً چیزی است که می‌خواهی؛ دومی معمولاً چیزی است که به‌اشتباه گزارش می‌شود.

۷. شمارشِ دوم#

قانونِ دومِ این دوره: هر عدد را با یک شمارشِ دوم بسنج. نه به این خاطر که به SQLite بی‌اعتمادیم — به این خاطر که به پرسشِ خودمان بی‌اعتمادیم.

in_sql = int(q("SELECT SUM(borrowed) FROM books").iloc[0, 0])
in_python = sum(row[0] for row in con.execute("SELECT borrowed FROM books"))

print("SUM در SQL    :", in_sql)
print("جمع در پایتون :", in_python)
print("دو شمارش یکی‌اند؟", in_sql == in_python)
SUM در SQL    : 94191
جمع در پایتون : 94191
دو شمارش یکی‌اند؟ True

📏 اندازه بگیر: یک سطر یعنی چه؟ نتیجهٔ بخشِ ۵ یک سطر دارد و آن سطر یعنی «کلِ فهرست» — نه یک کتاب، نه یک شعبه. با کدام شمارشِ دوم سنجیدی؟ جمعِ ستونِ borrowed را دو بار از دو مسیرِ مستقل گرفتیم: یک بار داخلِ پایگاه داده با SUM و یک بار در پایتون روی تک‌تکِ سطرها. هر دو ۹۴٬۱۹۱. چه کسی از قلم افتاد؟ دو گروه: ۲۴۰ سطرِ arrivals.csv که هنوز وارد نشده‌اند، و — تازه در همین فصل کشفش کردیم — ۲۳۱ سطری که AVG(rating) بی‌صدا کنار گذاشت.

🔧 اگر کار نکرد: نامِ ستون را غلط تایپ کن تا ببینی چه می‌شود. این خطا را در کارِ واقعی روزی چند بار می‌گیری:

try:
    q("SELECT titel FROM books LIMIT 3")
except Exception as error:
    print(type(error).__module__ + "." + type(error).__name__)
    print(error)
pandas.errors.DatabaseError
Execution failed on sql 'SELECT titel FROM books LIMIT 3': no such column: titel

دو لایه در این پیام هست و هر دو را باید بخوانی. بیرونی‌اش pandas.errors.DatabaseError است، چون q از pandas رد می‌شود. جملهٔ آخر — no such column: titel — از خودِ SQLite است و همان است که به دردت می‌خورد. عادت کن پیام را از آخر بخوانی.

🤖 از دستیارت بپرس: «فرقِ COUNT(*) با COUNT(1) در SQLite چیست؟» بعد این را بپرس: «اگر یک ستون در بعضی سطرها خالی باشد، AVG آن سطرها را چطور حساب می‌کند: صفر می‌گیرد یا نادیده می‌گیرد؟» — جوابِ درست «نادیده می‌گیرد» است، و اگر دستیارت «صفر می‌گیرد» گفت، خودت همین حالا با عددهای بخشِ ۶ ردش کن.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
SELECT سلکت بندی که می‌گوید چه ستون‌هایی می‌خواهی
FROM فرام بندی که می‌گوید از کدام جدول
LIMIT لیمیت سقفِ تعدادِ سطرهای برگشتی
alias الیاس نامِ تازه‌ای که با AS برای ستونِ خروجی می‌گذاری
aggregate اگریگیت تابعی که از چند سطر یک عدد می‌سازد

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

تا اینجا هر پرسشی دربارهٔ همهٔ ۱٬۲۰۰ سطر بود. فصلِ بعد WHERE را می‌آورد — بندی که می‌گوید کدام سطرها. و با یک تلهٔ واقعی شروع می‌کند: دو پرسش که تنها تفاوتشان یک جفت پرانتز است، یکی ۱۸۱ سطر می‌دهد و دیگری ۳۵ سطر.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.