CodeGym /Kurslar /SQL SELF /Daxili dövrlər və rekursiya

Daxili dövrlər və rekursiya

SQL SELF
Səviyyə, Dərs
Mövcuddur

Daxili dövrlər — başqa dövrlərin içində işləyən dövrlərdir. Təsəvvür elə, müsahibədə sual-cavab bölməsi var və bir sual yeni dəqiqləşdirici suallara gətirib çıxarır. Daxili dövrlərin məntiqi belədir: xarici dövr iterasiya edir, daxili dövr isə hər xarici dövr üçün keçidlər edir.

Nümunə: çarpma cədvəli

Gəlin 1-dən 5-ə qədər ədədlər üçün çarpma cədvəli yaradan funksiya yazaq. Bu klassik nümunədir, burada daxili dövrlərdən istifadə olunur:

CREATE OR REPLACE FUNCTION generate_multiplication_table()
RETURNS VOID AS $$
BEGIN
  FOR i IN 1..5 LOOP -- Xarici dövr
    FOR j IN 1..5 LOOP -- Daxili dövr
      RAISE NOTICE '% x % = %', i, j, i * j; -- Nəticəni loglayırıq
    END LOOP;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Funksiyanı çağırmaq:
SELECT generate_multiplication_table();

İş prinsipi:

  1. Xarici dövr i üçün 1-dən 5-ə qədər olan dəyərləri tutur.
  2. Hər i üçün daxili dövr j üçün 1-dən 5-ə qədər olan dəyərləri tutur.
  3. Hər addımda i və j dəyərləri birləşdirilir və vurma nəticəsi hesablanır.
  4. Çıxış mini-cədvəl kimi görünür:
   1 x 1 = 1
   1 x 2 = 2
   ...
   5 x 5 = 25

Praktika: iki cədvəldə kəsişmələri tapmaq

İndi isə daha "real" bir nümunə yaradaq. Təsəvvür elə ki, bizdə iki cədvəl var:

  • students (tələbələr, onların adları),
  • courses (onların yazıldığı kurslar).

Biz istəyirik ki, bir neçə kursa yazılmış tələbələri tapaq. Daxili dövrlərdən istifadə edirik:

CREATE OR REPLACE FUNCTION find_students_with_multiple_courses()
RETURNS TABLE(student_name TEXT, course_name TEXT) AS $$
BEGIN
  FOR student IN SELECT DISTINCT student_name FROM students LOOP
    FOR course IN SELECT DISTINCT course_name FROM courses WHERE student_id = student.student_id LOOP
      RETURN QUERY SELECT student.student_name, course.course_name;
    END LOOP;
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- Funksiyanı çağırmaq:
SELECT * FROM find_students_with_multiple_courses();

Rekursiya

Rekursiya — bu, funksiya özünü çağıranda baş verir. Elə bil dostundan SQL izah etməyi xahiş edirsən, o isə sənə bu dərsə istinad edən sənədi oxumağı məsləhət görür... Rekursiyanı sonsuz dövrlə qarışdırmaq lazım deyil. Rekursiyada həmişə "dayanma şərti" olur (funksiya özünü çağırmağı dayandırdığı nöqtə).

Nümunə: ədədin faktorialını hesablamaq

n ədədinin faktorialı — 1-dən n-ə qədər bütün ədədlərin hasilidir. Məsələn, 5-in faktorialı (5!) 5 * 4 * 3 * 2 * 1 = 120-yə bərabərdir. Bunu rekursiya ilə belə yazmaq olar:

CREATE OR REPLACE FUNCTION calculate_factorial(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
  -- Dayanma şərti: 0 və ya 1-in faktorialı 1-dir
  IF n = 0 OR n = 1 THEN
    RETURN 1;
  END IF;

  -- Funksiyanın rekursiv çağırılması
  RETURN n * calculate_factorial(n - 1);
END;
$$ LANGUAGE plpgsql;

-- Funksiyanı çağırmaq:
SELECT calculate_factorial(5); -- Nəticə: 120

İş prinsipi:

  1. Əgər n 0 və ya 1-ə bərabərdirsə, 1 qaytarırıq.
  2. Əgər n > 1 isə, funksiya özünü n - 1 arqumenti ilə çağırır və bu dəyəri n-ə vurur.
  3. Beləliklə, çağırışlar "toplanır", sonra bir istiqamətdə açılır.

Praktik nümunə: Fibonacci ədədləri

Fibonacci ədədləri — bu, hər bir ədədin əvvəlki iki ədədin cəmi olduğu ardıcıllıqdır. Ardıcıllıq belə başlayır: 0, 1, 1, 2, 3, 5, 8....

Gəlin ardıcıllıqda n-ci ədədi hesablamaq üçün funksiya yazaq:

CREATE OR REPLACE FUNCTION fibonacci(n INTEGER)
RETURNS INTEGER AS $$
BEGIN
  -- Dayanma şərti: ilk iki ədəd məlumdur
  IF n = 0 THEN
    RETURN 0;
  ELSIF n = 1 THEN
    RETURN 1;
  END IF;

  -- Funksiyanın rekursiv çağırılması
  RETURN fibonacci(n - 1) + fibonacci(n - 2);
END;
$$ LANGUAGE plpgsql;

-- Funksiyanı çağırmaq:
SELECT fibonacci(6); -- Nəticə: 8

Daxili dövrlər və rekursiyanı nə vaxt istifadə etməli?

  1. Daxili dövrlər cədvəllərlə işləmək üçün əladır:

    • İki cədvəl arasında dəyərləri müqayisə etmək.
    • Mürəkkəb məlumat kombinasiyaları qurmaq.
  2. Rekursiya daha yaxşıdır:

    • Ardıcıllıqları hesablamaq üçün (məsələn, faktorial, Fibonacci).
    • İyerarxik strukturlarla işləmək üçün (məsələn, məhsul kateqoriyaları ağacı).

Tipik səhvlər

Daxili dövrlər bəzən performans baxımından "bahalı" olur, xüsusilə böyük cədvəllərlə işləyəndə. Onlardan yalnız adi SQL vasitələri ilə nəticə əldə etmək mümkün olmadıqda istifadə et.

Rekursiyadan istifadə edəndə mütləq aydın "dayanma şərtin" olsun. Onsuz sonsuz funksiya çağırışı və çox güman ki, stack overflow xətası alacaqsan.

Mürəkkəb daxili konstruksiyalar debug-u çətinləşdirə bilər. Aralıq nəticələri çıxarmaq üçün özünə RAISE NOTICE ilə kömək et.

2
Tapşırıq
SQL SELF, səviyyə, dərs
Bağlanıb
Sadə vurma cədvəli iç-içə dövrlərlə
Sadə vurma cədvəli iç-içə dövrlərlə
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION