MySQL და მონაცემთა ბაზებთან პროგრამირება

ეს არის სრული ქართულენოვანი სასწავლო წიგნი მონაცემთა ბაზებზე და MySQL-ზე — ნულიდან რეალურ პროექტამდე. თითოეული თავი მოიცავს თეორიას, ცოცხალ SQL და PHP მაგალითებს, სავარჯიშოებსა და თვითშეფასების კითხვებს.

კოდის ნებისმიერი ბლოკი შეგიძლია დააკოპირო 📋 ღილაკით და გაუშვა შენს გარემოში (PHP 8+, MySQL 8 / MariaDB 10+).

18 თავი 85 თემა 18 სასწავლო თავი

1 🗄️ შესავალი მონაცემთა ბაზებში

რა არის მონაცემთა ბაზა, რატომ გვჭირდება DBMS და რომელია დღეს პოპულარული სისტემები.

რა არის მონაცემთა ბაზა

მონაცემთა ბაზა (Database) არის ორგანიზებულად შენახული მონაცემების ერთობლიობა, რომელშიც მარტივად ხდება ინფორმაციის ჩაწერა, წაკითხვა, ძებნა და განახლება. მონაცემები ინახება სტრუქტურირებულად — ცხრილებში, რომლებიც ერთმანეთთან კავშირშია.

მაგალითად, ონლაინ მაღაზიის ბაზაში არსებობს ცხრილები: მომხმარებლები, პროდუქტები, შეკვეთები. ეს ცხრილები კავშირშია ერთმანეთთან — ერთ მომხმარებელს შეიძლება ჰქონდეს მრავალი შეკვეთა.

💡 რჩევა: მონაცემთა ბაზა არ არის თვითონ პროგრამა — ის მონაცემების ორგანიზებული შენახვის ფორმაა. პროგრამას, რომელიც ამ ბაზას მართავს, DBMS ჰქვია.

მონაცემთა ბაზების ისტორია

მონაცემთა ბაზების განვითარების ეტაპები:

მნიშვნელობები

მნიშვნელობააღწერა
1960-იანი პირველი იერარქიული და ქსელური ბაზები (IBM IMS).
1970 Edgar Codd-მა შემოგვთავაზა რელაციური მოდელი — ცხრილები და კავშირები.
1979 Oracle — პირველი კომერციული რელაციური DBMS.
1995 გამოვიდა MySQL — სწრაფი, უფასო, ღია კოდის ბაზა.
2009+ NoSQL ბაზების ეპოქა (MongoDB, Redis) — დიდი მონაცემებისთვის.

🌍 რეალური გამოყენება

დღემდე რელაციური მოდელი (SQL) ყველაზე გავრცელებულია ბიზნეს-აპლიკაციებში, რადგან მონაცემთა მთლიანობას (integrity) და ზუსტ კავშირებს უზრუნველყოფს.

ფაილურ სისტემასა და DBMS-ს შორის განსხვავება

მონაცემების ფაილებში (მაგ. .txt, .csv) შენახვა მარტივ შემთხვევებში მუშაობს, მაგრამ პროექტის ზრდასთან ერთად პრობლემები ჩნდება. DBMS ამ პრობლემებს წყვეტს:

მნიშვნელობები

მნიშვნელობააღწერა
ერთდროული წვდომა ფაილი: კონფლიქტები ხდება. DBMS: ბევრი მომხმარებელი ერთდროულად მუშაობს უსაფრთხოდ.
ძებნის სიჩქარე ფაილი: სტრიქონ-სტრიქონ კითხვა. DBMS: ინდექსები და ოპტიმიზებული ძებნა.
მთლიანობა ფაილი: ადვილია გაფუჭება. DBMS: შეზღუდვები (constraints), ტრანზაქციები.
უსაფრთხოება ფაილი: ფაილის დონის უფლებები. DBMS: მომხმარებლები, როლები, უფლებები ცხრილის დონეზე.
დუბლირება ფაილი: მონაცემები მეორდება. DBMS: ნორმალიზაცია ამცირებს გამეორებას.
💡 რჩევა: პატარა კონფიგურაციისთვის ფაილი ან JSON კარგია; ბიზნეს-მონაცემებისთვის — DBMS.

რა არის DBMS და MySQL-ის დანიშნულება

DBMS (Database Management System) — მონაცემთა ბაზის მართვის სისტემა. ეს არის სერვერული პროგრამა, რომელიც ინახავს მონაცემებს და ამუშავებს მათზე ბრძანებებს (queries).

MySQL არის რელაციური DBMS — სერვერული პროგრამა, რომელიც განკუთვნილია რელაციური მონაცემების ერთდროულად დასამუშავებლად. ერთ MySQL სერვერზე შეიძლება იყოს რამდენიმე მონაცემთა ბაზა. კლიენტი (მაგ. PHP) უგზავნის SQL ბრძანებებს, სერვერი ასრულებს და აბრუნებს შედეგს.

სინტაქსი

Syntax
კლიენტი (PHP / phpMyAdmin / CLI)
        │  SQL query
        ▼
MySQL სერვერი  ──►  მონაცემთა ბაზა (ცხრილები)
        │  შედეგი (rows)
        ▼
     კლიენტი

🌍 რეალური გამოყენება

MySQL-ს იყენებენ WordPress, Facebook-ის ნაწილები, YouTube, Booking.com და მილიონობით ვებ-აპლიკაცია. ის სწრაფი, საიმედო და უფასოა (ღია კოდი).

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. ჩამოწერე 3 რეალური მაგალითი, სად გამოიყენებ მონაცემთა ბაზას (მაგ. სკოლა, მაღაზია, ბიბლიოთეკა). თითოეულზე დაასახელე 2-3 ცხრილი.
  2. აღწერე, რა პრობლემები შეგექმნებოდა, თუ 10 000 მომხმარებლის მონაცემს მხოლოდ ერთ .txt ფაილში შეინახავდი.
  3. შეადარე MySQL და PostgreSQL — იპოვე 3 მსგავსება და 2 განსხვავება.

თვითშეფასების კითხვები

  1. რა განსხვავებაა „მონაცემთა ბაზასა" და „DBMS"-ს შორის?
  2. დაასახელე DBMS-ის 3 უპირატესობა ჩვეულებრივ ფაილთან შედარებით.
  3. რა არის რელაციური მოდელი და ვინ შემოგვთავაზა?
  4. რატომ არის MySQL პოპულარული ვებ-პროექტებში?

2 ⚙️ ინსტალაცია და გარემოს მომზადება

როგორ დავაყენოთ MySQL, phpMyAdmin და მზა გარემოები (XAMPP, Laragon, Docker).

MySQL Server

MySQL Server — ეს არის თვითონ ბაზის სერვერი, რომელიც ფონურად მუშაობს და ელოდება კლიენტების ბრძანებებს (ჩვეულებრივ პორტი 3306). დაყენების შემდეგ მას ვუკავშირდებით ბრძანებათა ხაზიდან ან გრაფიკული ხელსაწყოებიდან.

მაგალითები

სერვერთან დაკავშირება ბრძანებათა ხაზიდან

root მომხმარებლით შესვლა (პაროლს მოგთხოვს):

Shell
mysql -u root -p
ვერსიის და ბაზების შემოწმება
SQL
SELECT VERSION();
SHOW DATABASES;
💡 რჩევა: პროდაქშენში არასდროს გამოიყენო root მომხმარებელი აპლიკაციისთვის — შექმენი ცალკე მომხმარებელი შეზღუდული უფლებებით.

phpMyAdmin

phpMyAdmin — ვებ-ინტერფეისი MySQL-ის გრაფიკული მართვისთვის. ბრაუზერიდან შეგიძლია შექმნა ბაზები, ცხრილები, ჩაწერა მონაცემები, გაუშვა SQL ბრძანებები და ნახო შედეგი — ბრძანებათა ხაზის გარეშე. იდეალურია დამწყებებისთვის.

🌍 რეალური გამოყენება

ჩვეულებრივ ხელმისაწვდომია მისამართზე http://localhost/phpmyadmin. XAMPP და Laragon-ში წინასწარ დაყენებულია.

XAMPP და Laragon

დამწყებისთვის ცალ-ცალკე Apache, PHP და MySQL-ის დაყენება რთულია. მზა გარემოები ერთ პაკეტში აერთიანებენ ყველაფერს:

მნიშვნელობები

მნიშვნელობააღწერა
XAMPP Apache + MySQL/MariaDB + PHP + phpMyAdmin. მუშაობს Windows/Linux/macOS-ზე.
Laragon მსუბუქი, სწრაფი Windows-ისთვის. ავტომატური ვირტუალური ჰოსტები, მარტივი ვერსიების გადართვა.
💡 რჩევა: Windows-ზე Laragon ხშირად უფრო მოსახერხებელია; XAMPP — კლასიკური და კარგად დოკუმენტირებული არჩევანია.

Docker (მოკლე მიმოხილვა)

Docker საშუალებას გაძლევს MySQL გაუშვა იზოლირებულ კონტეინერში — სისტემაში არაფრის „ჭუჭყიანად" დაყენების გარეშე. ეს მოსახერხებელია გუნდური მუშაობისთვის: ყველას ერთნაირი გარემო აქვს.

მაგალითები

MySQL კონტეინერის გაშვება ერთი ბრძანებით
Shell
docker run --name my-mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=shop \
  -p 3306:3306 \
  -d mysql:8

🌍 რეალური გამოყენება

კონტეინერთან დაკავშირება: docker exec -it my-mysql mysql -u root -p. მონაცემების შესანახად (კონტეინერის წაშლის შემდეგაც) გამოიყენე volume.

კავშირის შემოწმება

ინსტალაციის შემდეგ პირველი ნაბიჯი — დარწმუნდე, რომ სერვერი მუშაობს და კავშირი ხდება.

მაგალითები

სტატუსის შემოწმება SQL-ით
SQL
SELECT 'კავშირი წარმატებულია' AS status, NOW() AS server_time;
💡 რჩევა: თუ ვერ უკავშირდები: შეამოწმე სერვისი გაშვებულია თუ არა, სწორია თუ არა პორტი (3306), მომხმარებელი/პაროლი და host.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. დააყენე XAMPP ან Laragon, გახსენი phpMyAdmin და შექმენი ბაზა სახელად school.
  2. გაუშვი SELECT VERSION(); და ჩაიწერე შენი MySQL/MariaDB-ის ვერსია.
  3. (არჩევითი) გაუშვი MySQL Docker-ში და დაუკავშირდი მას.

თვითშეფასების კითხვები

  1. რა განსხვავებაა MySQL Server-სა და phpMyAdmin-ს შორის?
  2. რა სტანდარტულ პორტზე მუშაობს MySQL?
  3. რატომ არ არის კარგი იდეა აპლიკაციისთვის root მომხმარებლის გამოყენება?

3 📐 ცხრილები და მონაცემთა მოდელირება

ცხრილების სტრუქტურა, მონაცემთა ტიპები, Primary/Foreign Key და ცხრილებს შორის ურთიერთობები.

Tables, Columns, Rows

რელაციური ბაზა მონაცემებს ინახავს ცხრილებში (Tables) — ისევე როგორც Excel-ის ფურცელი.

  • Table (ცხრილი) — წინასწარ განსაზღვრული სტრუქტურის მონაცემთა გაერთიანება (მაგ. students).
  • Column (სვეტი) — ცხრილის ველი, აქვს სახელი და ტიპი (მაგ. first_name VARCHAR(50)).
  • Row (სტრიქონი) — ერთი ჩანაწერი: ერთი კონკრეტული სტუდენტის მონაცემები.

მაგალითები

მარტივი ცხრილის შექმნა
SQL
CREATE TABLE students (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(50) NOT NULL,
  last_name  VARCHAR(50) NOT NULL,
  email      VARCHAR(120) UNIQUE,
  birth_date DATE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
💡 რჩევა: სვეტების რაოდენობა შეიძლება ბევრი იყოს, მაგრამ რეკომენდებულია 1000-ზე ნაკლები — ხშირად ეს ცუდი დიზაინის ნიშანია.

Data Types — მონაცემთა ტიპები

თითოეულ სვეტს აქვს ტიპი, რომელიც განსაზღვრავს რა მონაცემი შეიძლება მასში ჩაიწეროს. სწორად შერჩეული ტიპი ზოგავს ადგილს და აჩქარებს ბაზას.

მნიშვნელობები

მნიშვნელობააღწერა
INT მთელი რიცხვი (მაგ. ასაკი, რაოდენობა, id).
BIGINT დიდი მთელი რიცხვი (მაგ. ფულის თეთრებში, ID-ები მსხვილ სისტემებში).
DECIMAL(10,2) ზუსტი ათწილადი — ფულისთვის (FLOAT-ი ფულისთვის არ გამოიყენო!).
VARCHAR(n) ცვალებადი სიგრძის ტექსტი n სიმბოლომდე (სახელი, email).
TEXT გრძელი ტექსტი (აღწერა, კომენტარი).
DATE თარიღი (YYYY-MM-DD).
DATETIME თარიღი + დრო.
TIMESTAMP დროის ნიშნული — ხშირად created_at/updated_at-ისთვის.
BOOLEAN ლოგიკური (TRUE/FALSE; რეალურად TINYINT(1)).
ENUM(...) წინასწარ განსაზღვრული მნიშვნელობების სია (მაგ. status).
💡 რჩევა: ფულისთვის ყოველთვის DECIMAL, არასდროს FLOAT/DOUBLE — ისინი ამრგვალებენ და შეცდომებს იძლევიან.

Primary Key და Auto Increment

Primary Key (პირველადი გასაღები) — სვეტი, რომელიც უნიკალურად ცნობს თითოეულ სტრიქონს. ის არ შეიძლება იყოს NULL და არ მეორდება. ჩვეულებრივ ეს არის id.

AUTO_INCREMENT — ბაზა ავტომატურად ანიჭებს ყოველ ახალ სტრიქონს მომდევნო რიცხვს (1, 2, 3...). აღარ გჭირდება id-ის ხელით მითითება.

სინტაქსი

Syntax
id INT AUTO_INCREMENT PRIMARY KEY

მაგალითები

PRIMARY KEY ცალკე ხაზზე
SQL
CREATE TABLE courses (
  id    INT AUTO_INCREMENT,
  title VARCHAR(120) NOT NULL,
  PRIMARY KEY (id)
);

✗ ხშირი შეცდომები

  • Primary Key-ად ისეთი ველის გამოყენება, რომელიც შეიძლება შეიცვალოს (მაგ. email) — სჯობს ცალკე id.
  • ერთ ცხრილში ორი Primary Key — დაუშვებელია (მაგრამ შეიძლება შედგენილი — composite key).

Foreign Key

Foreign Key (გარე გასაღები) — სვეტი, რომელიც მიუთითებს სხვა ცხრილის Primary Key-ზე და ქმნის კავშირს. ის უზრუნველყოფს მონაცემთა მთლიანობას: ვერ ჩაწერ შეკვეთას არარსებული მომხმარებლისთვის.

მნიშვნელობები

მნიშვნელობააღწერა
ON DELETE CASCADE მშობელი სტრიქონის წაშლისას შვილებიც წაიშლება.
ON DELETE SET NULL მშობლის წაშლისას გარე გასაღები NULL ხდება.
ON DELETE RESTRICT კრძალავს მშობლის წაშლას, თუ შვილები არსებობს (default).

მაგალითები

enrollments ცხრილი ორი Foreign Key-ით
SQL
CREATE TABLE enrollments (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT NOT NULL,
  course_id  INT NOT NULL,
  FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
  FOREIGN KEY (course_id)  REFERENCES courses(id)  ON DELETE CASCADE
);
💡 რჩევა: Foreign Key მუშაობს მხოლოდ InnoDB ძრავაზე (MySQL-ში default). MyISAM-ი მას უგულებელყოფს.

ურთიერთობები (Relations)

ცხრილებს შორის სამი ძირითადი ტიპის კავშირი არსებობს:

  • One-to-One (1:1) — ერთ სტრიქონს ერთი შესაბამისი (მაგ. user ↔ user_profile).
  • One-to-Many (1:N) — ერთ მომხმარებელს მრავალი შეკვეთა.
  • Many-to-Many (N:M) — სტუდენტი მრავალ კურსს ესწრება, კურსზე მრავალი სტუდენტია; ასეთ კავშირს შუალედური (junction) ცხრილი სჭირდება.

ქვემოთ — სტუდენტი/კურსი/ჩარიცხვა მოდელის ER დიაგრამა (Many-to-Many):

erDiagram STUDENTS ||--o{ ENROLLMENTS : "ერიცხება" COURSES ||--o{ ENROLLMENTS : "მოიცავს" STUDENTS { int id PK string first_name string email } COURSES { int id PK string title int credits } ENROLLMENTS { int id PK int student_id FK int course_id FK date enrolled_at }
💡 რჩევა: Many-to-Many ყოველთვის იშლება ორ One-to-Many კავშირად შუალედური ცხრილის გავლით.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. დააპროექტე ბიბლიოთეკის ბაზა: ცხრილები books, members, loans (წიგნის გატანა). განსაზღვრე Primary და Foreign Key-ები.
  2. ააგე ER დიაგრამა (ქაღალდზე ან Mermaid-ით) author ↔ book ↔ category მოდელისთვის.
  3. ჩამოწერე, რა მონაცემთა ტიპი მიანიჭებდი ველებს: ფასი, სათაური, გამოცემის თარიღი, ხელმისაწვდომია თუ არა.

თვითშეფასების კითხვები

  1. რა განსხვავებაა Primary Key-სა და Foreign Key-ს შორის?
  2. რატომ არ უნდა გამოვიყენოთ FLOAT ფულისთვის?
  3. როგორ ხდება Many-to-Many კავშირის რეალიზება ბაზაში?
  4. რას აკეთებს ON DELETE CASCADE?

4 🔌 PHP-დან MySQL-თან დაკავშირება

mysqli (პროცედურული და OOP) და PDO; კავშირის პარამეტრები და შემოწმება.

mysqli_connect() — პროცედურული PHP 8+

კავშირისთვის გვჭირდება 4 პარამეტრი: Host (სერვერის მისამართი), Username, Password და Database Name.

მნიშვნელობები

მნიშვნელობააღწერა
mysqli_connect() ბაზასთან დაკავშირება.
mysqli_connect_error() დაკავშირების შეცდომის ტექსტი.
mysqli_connect_errno() დაკავშირების შეცდომის კოდი.
mysqli_close() კავშირის დახურვა.
mysqli_set_charset() კოდირების დაყენება (utf8mb4 — ქართულისთვის).

მაგალითები

დაკავშირება და შემოწმება
PHP
<?php
$servername = "localhost";  // სერვერის IP ან ჰოსტი
$username   = "root";       // მომხმარებლის სახელი
$password   = "";           // პაროლი
$dbname     = "school";      // ბაზის სახელი

// დაკავშირება
$conn = mysqli_connect($servername, $username, $password, $dbname);

// კავშირის გადამოწმება
if (!$conn) {
    die("კავშირი ვერ დამყარდა: " . mysqli_connect_error());
}
mysqli_set_charset($conn, "utf8mb4"); // ქართულისთვის სავალდებულო
echo "კავშირი წარმატებულია";
💡 რჩევა: ქართული ტექსტისთვის აუცილებლად დააყენე utf8mb4 — სხვა შემთხვევაში სიმბოლოები „გაიფუჭება".

new mysqli() — ობიექტზე ორიენტირებული PHP 8+

იგივე mysqli, ოღონდ ობიექტური სტილით. თანამედროვე კოდში უპირატესობა ენიჭება OOP-ს ან PDO-ს.

მაგალითები

OOP კავშირი exception-ით
PHP
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // შეცდომა → exception

try {
    $conn = new mysqli("localhost", "root", "", "school");
    $conn->set_charset("utf8mb4");
    echo "კავშირი წარმატებულია";
} catch (mysqli_sql_exception $e) {
    die("კავშირი ვერ დამყარდა: " . $e->getMessage());
}
💡 რჩევა: PHP 8+-ში mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) ჩართულია default-ად — შეცდომები exception-ად ისვრება, რაც კარგია.

PDO PHP 8+

PDO (PHP Data Objects) — უნივერსალური ინტერფეისი მრავალი ბაზისთვის (MySQL, PostgreSQL, SQLite...). თუ ოდესმე ბაზას შეცვლი, კოდი თითქმის უცვლელი დარჩება. prepared statements-ისთვის PDO ძალიან მოსახერხებელია.

მაგალითები

PDO კავშირი
PHP
<?php
$dsn = "mysql:host=localhost;dbname=school;charset=utf8mb4";
$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, "root", "", $options);
    echo "PDO კავშირი წარმატებულია";
} catch (PDOException $e) {
    die("შეცდომა: " . $e->getMessage());
}
💡 რჩევა: ახალ პროექტებში PDO ან mysqli OOP გამოიყენე — პროცედურული mysqli ძველი სტილია.

კავშირის პარამეტრები

კავშირის 4 მთავარი პარამეტრი:

მნიშვნელობები

მნიშვნელობააღწერა
Host სერვერის მისამართი — localhost, IP, ან Docker-ის სერვისის სახელი.
Username მონაცემთა ბაზის მომხმარებლის სახელი.
Password მომხმარებლის პაროლი.
Database Name ბაზის სახელი, რომელსაც ვუკავშირდებით.

✓ საუკეთესო პრაქტიკა

  • პაროლები არ ჩაწერო კოდში — გამოიყენე .env ფაილი ან გარემოს ცვლადები.
  • წარმოებაში (production) გამოიყენე შეზღუდული უფლებების მქონე ცალკე მომხმარებელი.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. დაწერე PHP სკრიპტი, რომელიც უკავშირდება შენს ბაზას სამივე გზით (mysqli პროცედურული, mysqli OOP, PDO) და ბეჭდავს „კავშირი წარმატებულია".
  2. განზრახ შეცვალე პაროლი არასწორად და ნახე, რა შეცდომას აბრუნებს თითოეული მეთოდი.
  3. გადაიტანე კავშირის პარამეტრები ცალკე config.php ფაილში.

თვითშეფასების კითხვები

  1. რა 4 პარამეტრი სჭირდება ბაზასთან დასაკავშირებლად?
  2. რატომ ვაყენებთ utf8mb4 კოდირებას?
  3. რა უპირატესობა აქვს PDO-ს mysqli-სთან შედარებით?

5 🔎 SQL ენის საფუძვლები

SELECT, WHERE, ORDER BY, LIMIT და DISTINCT — მონაცემების წაკითხვის ბირთვი.

SELECT

SELECT კითხულობს მონაცემებს ცხრილიდან. შეგიძლია წამოიღო ყველა სვეტი (*), კონკრეტული სვეტები, ან მიანიჭო მათ მეტსახელი (Alias) AS-ით.

სინტაქსი

Syntax
SELECT [ALL | DISTINCT] select_expr [, ...]
  FROM table_name
  [WHERE condition]
  [GROUP BY ...] [HAVING ...]
  [ORDER BY ... ASC|DESC]
  [LIMIT offset, count];

მაგალითები

ყველა სვეტი
SQL
SELECT * FROM students;
კონკრეტული სვეტები + Alias
SQL
SELECT first_name AS `სახელი`,
       last_name  AS `გვარი`
FROM students;

✗ ხშირი შეცდომები

  • პროდაქშენში SELECT *-ის ბოროტად გამოყენება — წამოიღე მხოლოდ ის სვეტები, რაც გჭირდება.

WHERE — პირობითი ფილტრი

WHERE ფილტრავს სტრიქონებს პირობის მიხედვით. მხოლოდ ის სტრიქონები წამოვა, რომლებიც პირობას აკმაყოფილებენ.

მნიშვნელობები

მნიშვნელობააღწერა
= ტოლობა: age = 18.
!= ან <> არ უდრის.
&gt; &lt; მეტობა / ნაკლებობა; ასევე >=, <=.
BETWEEN a AND b a-სა და b-ს შორის (ჩათვლით).
IN (...) სიაში მოხვედრა: id IN (1,2,3).
LIKE შაბლონით ძებნა: % = ნებისმიერი სიმბოლოები, _ = ერთი სიმბოლო.
AND / OR / NOT პირობების გაერთიანება.
IS NULL ცარიელი მნიშვნელობის შემოწმება (არა = NULL!).

მაგალითები

რამდენიმე პირობა
SQL
SELECT * FROM students
WHERE birth_date BETWEEN '2000-01-01' AND '2005-12-31'
  AND email LIKE '%@gmail.com'
  AND last_name IN ('ბერიძე', 'მაისურაძე');

✗ ხშირი შეცდომები

  • NULL-ის შემოწმება = NULL-ით — სწორია IS NULL / IS NOT NULL.
  • LIKE-ში %-ის თავში დაწერა (LIKE '%x') ანელებს ძებნას — ვერ იყენებს ინდექსს.

ORDER BY

ORDER BY ალაგებს შედეგს. ASC — ზრდადობით (default), DESC — კლებადობით. შეიძლება რამდენიმე სვეტით დალაგება.

მაგალითები

ორი სვეტით დალაგება
SQL
SELECT * FROM students
ORDER BY last_name ASC, first_name ASC;

LIMIT

LIMIT ზღუდავს დასაბრუნებელი სტრიქონების რაოდენობას. გამოიყენება pagination-ისთვის (გვერდებად გადანაწილება).

სინტაქსი

Syntax
LIMIT offset, count
-- ან
LIMIT count OFFSET offset

მაგალითები

პირველი 10 ჩანაწერი
SQL
SELECT * FROM students ORDER BY id LIMIT 10;
მე-2 გვერდი (11-20)
SQL
SELECT * FROM students ORDER BY id LIMIT 10 OFFSET 10;

DISTINCT

DISTINCT აბრუნებს მხოლოდ უნიკალურ (განმეორების გარეშე) მნიშვნელობებს.

მაგალითები

უნიკალური ქალაქები
SQL
SELECT DISTINCT city FROM students;
💡 რჩევა: DISTINCT მთელ სტრიქონს ადარებს — თუ რამდენიმე სვეტს ჩამოწერ, უნიკალურობა მათი კომბინაციით ფასდება.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. წამოიღე ყველა სტუდენტი, რომლის გვარი იწყება „მ"-ით, დალაგებული ასაკის კლებადობით.
  2. დაწერე query, რომელიც აბრუნებს მე-3 გვერდის 20 ჩანაწერს (pagination).
  3. იპოვე უნიკალური email დომენების სია (მინიშნება: SUBSTRING + DISTINCT).

თვითშეფასების კითხვები

  1. რა განსხვავებაა WHERE x = NULL-სა და WHERE x IS NULL-ს შორის?
  2. როგორ წამოვიღოთ მე-2 გვერდის მონაცემები LIMIT-ით?
  3. რას აკეთებს Alias (AS)?

6 🔗 JOIN ოპერაციები

ცხრილების შეერთება: INNER, LEFT, RIGHT და SELF JOIN — პრაქტიკული მაგალითებით.

INNER JOIN

INNER JOIN აბრუნებს მხოლოდ იმ სტრიქონებს, რომლებსაც ორივე ცხრილში აქვთ შესაბამისობა. თუ შესაბამისი არ მოიძებნა — სტრიქონი ამოვარდება.

სინტაქსი

Syntax
SELECT ...
FROM a
INNER JOIN b ON a.id = b.a_id;

მაგალითები

სტუდენტები და მათი ჩარიცხვები
SQL
SELECT s.first_name, s.last_name, c.title
FROM students AS s
INNER JOIN enrollments AS e ON e.student_id = s.id
INNER JOIN courses     AS c ON c.id = e.course_id;
💡 რჩევა: ALIAS-ები (s, e, c) აადვილებენ რთული JOIN-ების წაკითხვას.

LEFT JOIN

LEFT JOIN აბრუნებს მარცხენა ცხრილის ყველა სტრიქონს და მათ შესაბამის მარჯვენა სტრიქონებს. თუ მარჯვნივ შესაბამისი არ არის — სვეტები NULL-ით ივსება.

მაგალითები

ყველა სტუდენტი — ჩარიცხვის გარეშეც
SQL
SELECT s.first_name, c.title
FROM students AS s
LEFT JOIN enrollments AS e ON e.student_id = s.id
LEFT JOIN courses     AS c ON c.id = e.course_id;
სტუდენტები, რომლებსაც არცერთი კურსი არ აქვთ
SQL
SELECT s.*
FROM students AS s
LEFT JOIN enrollments AS e ON e.student_id = s.id
WHERE e.id IS NULL;
💡 რჩევა: „იპოვე ისინი, ვისაც კავშირი არ აქვს" — კლასიკური ხერხია LEFT JOIN ... WHERE b.id IS NULL.

RIGHT JOIN

RIGHT JOIN LEFT JOIN-ის სარკისებური ანარეკლია: აბრუნებს მარჯვენა ცხრილის ყველა სტრიქონს. პრაქტიკაში იშვიათად გამოიყენება — ჩვეულებრივ ცხრილების ადგილს ვცვლით და LEFT JOIN-ს ვწერთ.

მაგალითები

ყველა კურსი — ჩარიცხული სტუდენტებით
SQL
SELECT c.title, s.first_name
FROM enrollments AS e
RIGHT JOIN courses AS c ON c.id = e.course_id
LEFT  JOIN students AS s ON s.id = e.student_id;

SELF JOIN

SELF JOIN — ცხრილის შეერთება თავის თავთან. გამოიყენება იერარქიული მონაცემებისთვის (მაგ. თანამშრომელი → მენეჯერი, რომელიც იმავე ცხრილშია).

მაგალითები

თანამშრომელი და მისი მენეჯერი
SQL
SELECT e.name AS `თანამშრომელი`,
       m.name AS `მენეჯერი`
FROM employees AS e
LEFT JOIN employees AS m ON e.manager_id = m.id;
💡 რჩევა: SELF JOIN-ში ALIAS-ი სავალდებულოა — ისე ცხრილს ორ „ეგზემპლარად" ვერ მივმართავთ.

CROSS JOIN (ჯვარედინი)

CROSS JOIN აბრუნებს ორი ცხრილის ყველა შესაძლო კომბინაციას (კარტეზიული ნამრავლი). ფრთხილად — შედეგი შეიძლება უზარმაზარი იყოს.

მაგალითები

ყველა ზომა × ყველა ფერი
SQL
SELECT sz.label, cl.name
FROM sizes AS sz
CROSS JOIN colors AS cl;

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. წამოიღე თითოეული კურსის სახელი და ჩარიცხული სტუდენტების რაოდენობა (JOIN + GROUP BY).
  2. იპოვე კურსები, რომლებზეც არცერთი სტუდენტი არ ჩარიცხულა.
  3. დახაზე INNER და LEFT JOIN-ის სხვაობა Venn დიაგრამით.

თვითშეფასების კითხვები

  1. რა განსხვავებაა INNER JOIN-სა და LEFT JOIN-ს შორის?
  2. როდის გამოვიყენებთ SELF JOIN-ს?
  3. როგორ ვიპოვოთ ის სტრიქონები, რომლებსაც მეორე ცხრილში შესაბამისი არ აქვთ?

7 ➕ მონაცემების დამატება (INSERT)

ერთი და მრავალი ჩანაწერის ჩაწერა, SELECT-დან ჩაწერა და PHP-ით უსაფრთხო INSERT.

INSERT — ერთი ჩანაწერი

INSERT ამატებს ახალ სტრიქონს ცხრილში.

სინტაქსი

Syntax
INSERT INTO table_name (col1, col2, ...)
VALUES (val1, val2, ...);

მაგალითები

ახალი სტუდენტის დამატება
SQL
INSERT INTO students (first_name, last_name, email, birth_date)
VALUES ('ნინო', 'ბერიძე', 'nino@example.com', '2003-05-14');
💡 რჩევა: id-ს არ ვწერთ — AUTO_INCREMENT თავად მიანიჭებს.

INSERT — მრავალი ჩანაწერი

ერთ ბრძანებაში რამდენიმე სტრიქონის ჩაწერა უფრო სწრაფია, ვიდრე ცალ-ცალკე INSERT-ები.

მაგალითები

ერთდროულად სამი სტუდენტი
SQL
INSERT INTO students (first_name, last_name, email) VALUES
  ('გიორგი', 'მაისურაძე', 'giorgi@example.com'),
  ('ანა',    'კაპანაძე',  'ana@example.com'),
  ('ლუკა',   'ჩხეიძე',    'luka@example.com');

INSERT ... SELECT და ON DUPLICATE KEY

მონაცემების კოპირება სხვა ცხრილიდან ან დუბლიკატის დამუშავება:

მაგალითები

არქივში გადატანა SELECT-ით
SQL
INSERT INTO students_archive (id, first_name, last_name)
SELECT id, first_name, last_name
FROM students
WHERE created_at < '2020-01-01';
დუბლიკატის განახლება (upsert)
SQL
INSERT INTO settings (k, v) VALUES ('theme', 'dark')
ON DUPLICATE KEY UPDATE v = VALUES(v);

INSERT PHP-დან (უსაფრთხო) PHP 8+

მომხმარებლისგან მიღებული მონაცემები არასდროს ჩასვა პირდაპირ query-ში — გამოიყენე prepared statement (იხ. უსაფრთხოება).

მაგალითები

mysqli prepared INSERT
PHP
<?php
$stmt = $conn->prepare(
    "INSERT INTO students (first_name, last_name, email) VALUES (?, ?, ?)"
);
$stmt->bind_param("sss", $firstName, $lastName, $email);
$stmt->execute();

echo "ახალი ID: " . $conn->insert_id;
💡 რჩევა: $conn->insert_id (mysqli) ან $pdo->lastInsertId() (PDO) აბრუნებს ახლად ჩაწერილი სტრიქონის id-ს.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. ჩაწერე 5 კურსი ერთი INSERT ბრძანებით.
  2. დაწერე PHP ფუნქცია addStudent($conn, $first, $last, $email) prepared statement-ით.
  3. გადაიტანე ძველი ჩანაწერები არქივის ცხრილში INSERT ... SELECT-ით.

თვითშეფასების კითხვები

  1. რატომ არ ვწერთ id-ს INSERT-ში, როცა AUTO_INCREMENT გვაქვს?
  2. რა უპირატესობა აქვს მრავალსტრიქონიან INSERT-ს?
  3. როგორ მივიღოთ ახლად ჩაწერილი ჩანაწერის id PHP-ში?

8 ✏️ მონაცემების განახლება (UPDATE)

UPDATE-ის უსაფრთხო გამოყენება და WHERE-ის კრიტიკული მნიშვნელობა.

UPDATE

UPDATE ცვლის არსებულ სტრიქონებს. SET განსაზღვრავს რა შეიცვალოს, WHERE — რომელ სტრიქონებში.

სინტაქსი

Syntax
UPDATE table_name
SET col1 = val1, col2 = val2
WHERE condition;

მაგალითები

ერთი სტუდენტის email-ის განახლება
SQL
UPDATE students
SET email = 'nino.beridze@example.com'
WHERE id = 1;

WHERE-ის მნიშვნელობა

⚠️ UPDATE-ში WHERE-ის დავიწყება კატასტროფაა — ცხრილის ყველა სტრიქონი შეიცვლება!

მაგალითები

❌ საშიში — ცვლის ყველას
SQL
UPDATE students SET email = 'x@x.com';  -- ყველა email-ი ერთნაირი გახდა!
✅ უსაფრთხო — კონკრეტული სტრიქონი
SQL
UPDATE students SET email = 'x@x.com' WHERE id = 5;
💡 რჩევა: ჩართე safe mode: SET SQL_SAFE_UPDATES = 1; — დაგიცავს WHERE-ის გარეშე UPDATE/DELETE-ისგან.

✓ საუკეთესო პრაქტიკა

  • ჯერ გაუშვი SELECT იმავე WHERE-ით — ნახე რამდენ სტრიქონს შეეხება.
  • ფრთხილ რეჟიმში გაუშვი MySQL safe-updates-ით — PRIMARY KEY-ის გარეშე UPDATE აიკრძალება.

UPDATE PHP-დან PHP 8+

prepared statement-ით უსაფრთხო განახლება და შეცვლილი სტრიქონების რაოდენობის შემოწმება.

მაგალითები

PDO UPDATE + affected rows
PHP
<?php
$stmt = $pdo->prepare("UPDATE students SET email = ? WHERE id = ?");
$stmt->execute([$newEmail, $id]);

echo "შეიცვალა {$stmt->rowCount()} სტრიქონი";

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. გაზარდე ყველა კურსის კრედიტი 1-ით მხოლოდ იმ კურსებისთვის, რომელთა კრედიტიც < 6.
  2. დაწერე PHP ფუნქცია, რომელიც ანახლებს სტუდენტის email-ს და აბრუნებს true-ს, თუ მართლა შეიცვალა სტრიქონი.
  3. ჩართე SQL_SAFE_UPDATES და სცადე WHERE-ის გარეშე UPDATE — ნახე შეცდომა.

თვითშეფასების კითხვები

  1. რა მოხდება, თუ UPDATE-ში WHERE-ს დაგვავიწყდება?
  2. როგორ შევამოწმოთ წინასწარ, რამდენ სტრიქონს შეეხება UPDATE?
  3. როგორ მივიღოთ შეცვლილი სტრიქონების რაოდენობა PHP-ში?

9 🗑️ მონაცემების წაშლა (DELETE)

უსაფრთხო წაშლა, Soft Delete კონცეფცია და DELETE vs TRUNCATE.

DELETE

DELETE შლის სტრიქონებს. UPDATE-ის მსგავსად, WHERE-ის გარეშე მთელ ცხრილს ასუფთავებს.

სინტაქსი

Syntax
DELETE FROM table_name WHERE condition;

მაგალითები

კონკრეტული ჩანაწერის წაშლა
SQL
DELETE FROM students WHERE id = 42;

✓ საუკეთესო პრაქტიკა

  • ყოველთვის ჯერ SELECT-ით შეამოწმე, რას შლი.
  • მნიშვნელოვან მონაცემებზე გამოიყენე Soft Delete.

Soft Delete კონცეფცია

Soft Delete — ფიზიკურად არ ვშლით ჩანაწერს, არამედ ვნიშნავთ წაშლილად (მაგ. deleted_at ან is_deleted ველით). მონაცემი რჩება — შეიძლება აღდგენა, აუდიტი, ანალიტიკა.

მაგალითები

ცხრილი deleted_at ველით
SQL
ALTER TABLE students ADD COLUMN deleted_at DATETIME NULL DEFAULT NULL;
„წაშლა" = დროის ჩაწერა
SQL
UPDATE students SET deleted_at = NOW() WHERE id = 42;
მხოლოდ აქტიური ჩანაწერების კითხვა
SQL
SELECT * FROM students WHERE deleted_at IS NULL;
💡 რჩევა: Laravel-ის და სხვა framework-ების SoftDeletes სწორედ ამ პრინციპზე დგას.

DELETE vs TRUNCATE

TRUNCATE ასუფთავებს მთელ ცხრილს ბევრად სწრაფად, ვიდრე DELETE, და აბრუნებს AUTO_INCREMENT-ს 1-ზე. მაგრამ ის ვერ იყენებს WHERE-ს და ვერ ჩამოვა ტრანზაქციით.

მნიშვნელობები

მნიშვნელობააღწერა
DELETE სტრიქონ-სტრიქონ; WHERE შესაძლებელია; ტრანზაქციით უკან დაბრუნებადი.
TRUNCATE მთელი ცხრილი მყისიერად; WHERE არ აქვს; AUTO_INCREMENT ნულდება.

✗ ხშირი შეცდომები

  • TRUNCATE-ის გამოყენება Foreign Key-ით დაკავშირებულ ცხრილზე — შეცდომას მოგცემს.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. დაამატე students ცხრილს deleted_at და დაამუშავე Soft Delete.
  2. დაწერე query, რომელიც აბრუნებს მხოლოდ აქტიურ სტუდენტებს.
  3. აღწერე, როდის ჯობია TRUNCATE და როდის DELETE.

თვითშეფასების კითხვები

  1. რა განსხვავებაა Hard Delete-სა და Soft Delete-ს შორის?
  2. რატომ არის TRUNCATE უფრო სწრაფი ვიდრე DELETE?
  3. როგორ ავარიდოთ თავი მთელი ცხრილის შემთხვევით წაშლას?

10 📊 აგრეგაციული ფუნქციები

COUNT, SUM, AVG, MAX, MIN და დაჯგუფება GROUP BY / HAVING-ით.

COUNT, SUM, AVG, MAX, MIN

აგრეგაციული ფუნქციები ბევრ სტრიქონს ერთ მნიშვნელობად კრებენ.

მნიშვნელობები

მნიშვნელობააღწერა
COUNT() სტრიქონების რაოდენობა.
SUM() ჯამი.
AVG() საშუალო.
MAX() მაქსიმუმი.
MIN() მინიმუმი.

მაგალითები

ერთ query-ში ყველა
SQL
SELECT
  COUNT(*)        AS students_count,
  AVG(credits)    AS avg_credits,
  MAX(credits)    AS max_credits
FROM courses;
💡 რჩევა: COUNT(*) ითვლის ყველა სტრიქონს; COUNT(col) — მხოლოდ NULL-ის გარეშე.

GROUP BY

GROUP BY აჯგუფებს სტრიქონებს სვეტის მიხედვით და თითო ჯგუფზე ითვლის აგრეგაციას (მაგ. რამდენი სტუდენტია თითო ქალაქში).

მაგალითები

სტუდენტების რაოდენობა ქალაქების მიხედვით
SQL
SELECT city, COUNT(*) AS cnt
FROM students
GROUP BY city
ORDER BY cnt DESC;
სტუდენტების რაოდენობა თითო კურსზე
SQL
SELECT c.title, COUNT(e.id) AS students
FROM courses AS c
LEFT JOIN enrollments AS e ON e.course_id = c.id
GROUP BY c.id, c.title;

✗ ხშირი შეცდომები

  • SELECT-ში ისეთი სვეტის ჩაწერა, რომელიც არც GROUP BY-შია და არც აგრეგაციაშია — გაურკვეველი შედეგი (MySQL strict mode-ში შეცდომა).

HAVING

HAVING ფილტრავს დაჯგუფების შემდეგ — აგრეგაციის შედეგზე. (WHERE მუშაობს დაჯგუფებამდე, ცალკეულ სტრიქონებზე.)

მაგალითები

კურსები, სადაც 5+ სტუდენტია
SQL
SELECT c.title, COUNT(e.id) AS students
FROM courses AS c
JOIN enrollments AS e ON e.course_id = c.id
GROUP BY c.id, c.title
HAVING students >= 5;
💡 რჩევა: დაიმახსოვრე რიგი: WHEREGROUP BYHAVINGORDER BY.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. იპოვე საშუალო კრედიტი ყველა კურსზე.
  2. დააჯგუფე სტუდენტები დაბადების წლის მიხედვით და დათვალე თითო წელს რამდენი დაიბადა.
  3. იპოვე კურსები, რომლებზეც საშუალოზე მეტი სტუდენტია ჩარიცხული.

თვითშეფასების კითხვები

  1. რა განსხვავებაა WHERE-სა და HAVING-ს შორის?
  2. რა განსხვავებაა COUNT(*)-სა და COUNT(col)-ს შორის?
  3. რა თანმიმდევრობით სრულდება GROUP BY, WHERE, HAVING, ORDER BY?

11 💳 ტრანზაქციები

START TRANSACTION / COMMIT / ROLLBACK, ACID პრინციპები და ბანკინგის მაგალითი.

START TRANSACTION / COMMIT / ROLLBACK

ტრანზაქცია არის რამდენიმე SQL ოპერაციის ერთობლიობა, რომელიც სრულდება „ყველა ან არცერთი" პრინციპით. თუ ერთი ოპერაცია ჩავარდა — ROLLBACK ყველაფერს უკან აბრუნებს; თუ ყველაფერი წესრიგშია — COMMIT ინახავს ცვლილებებს.

სინტაქსი

Syntax
START TRANSACTION;
  -- SQL ოპერაციები
COMMIT;    -- დამახსოვრება
-- ან
ROLLBACK;  -- გაუქმება

მაგალითები

მარტივი ტრანზაქცია
SQL
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
💡 რჩევა: ტრანზაქცია მუშაობს მხოლოდ ტრანზაქციული ძრავით — MySQL-ში ეს არის InnoDB (default). MyISAM ტრანზაქციებს არ უჭერს მხარს.

ACID პრინციპები

საიმედო ტრანზაქცია 4 თვისებას აკმაყოფილებს:

მნიშვნელობები

მნიშვნელობააღწერა
Atomicity (ატომურობა) ყველა ოპერაცია ერთ მთლიანობად — ან ყველა შესრულდება, ან არცერთი.
Consistency (თანმიმდევრობა) ბაზა ერთი ვალიდური მდგომარეობიდან მეორეში გადადის (შეზღუდვები ინახება).
Isolation (იზოლაცია) პარალელური ტრანზაქციები ერთმანეთს ხელს არ უშლიან.
Durability (მდგრადობა) COMMIT-ის შემდეგ მონაცემი შენახულია სერვერის გათიშვის შემთხვევაშიც.

ბანკინგის მაგალითი (PHP) PHP 8+

თანხის გადარიცხვა ერთი ანგარიშიდან მეორეზე — კლასიკური მაგალითი. ფული არ უნდა „გაქრეს": თუ ჩარიცხვა ჩავარდა, ჩამოჭრაც უნდა გაუქმდეს.

მაგალითები

ატომური გადარიცხვა try/catch-ით
PHP
<?php
try {
    $pdo->beginTransaction();

    // 1. ჩამოჭრა გამგზავნისგან
    $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
        ->execute([$amount, $fromId]);

    // 2. ბალანსის შემოწმება
    $balance = $pdo->query("SELECT balance FROM accounts WHERE id = $fromId")
                   ->fetchColumn();
    if ($balance < 0) {
        throw new Exception("არასაკმარისი თანხა");
    }

    // 3. ჩარიცხვა მიმღებზე
    $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
        ->execute([$amount, $toId]);

    $pdo->commit();          // ყველაფერი წესრიგშია — ვინახავთ
    echo "გადარიცხვა შესრულდა";
} catch (Exception $e) {
    $pdo->rollBack();        // შეცდომა — ყველაფერი უკან ბრუნდება
    echo "შეცდომა: " . $e->getMessage();
}
💡 რჩევა: შეცდომის შემთხვევაში დაბრუნება (rollBack) ტრანზაქციის მთავარი ღირებულებაა — მონაცემები არასდროს რჩება „ნახევრად შესრულებულ" მდგომარეობაში.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. დაწერე ტრანზაქცია, რომელიც ერთდროულად ჩაწერს შეკვეთას და შეამცირებს პროდუქტის მარაგს; თუ მარაგი არ ჰყოფნის — ROLLBACK.
  2. ახსენი ACID-ის თითოეული ასო ბანკის გადარიცხვის მაგალითზე.
  3. გამოცადე: გათიშე ROLLBACK და ნახე, რა ხდება ჩავარდნის შემთხვევაში.

თვითშეფასების კითხვები

  1. რას ნიშნავს „ყველა ან არცერთი"?
  2. რა განსხვავებაა COMMIT-სა და ROLLBACK-ს შორის?
  3. რომელი ძრავი უჭერს მხარს ტრანზაქციებს MySQL-ში?

12 ⚡ Stored Procedures

პროცედურის შექმნა, პარამეტრები (IN/OUT/INOUT) და გამოძახება CALL-ით.

პროცედურის შექმნა

Stored Procedure — ბაზაში შენახული, სახელდებული SQL კოდის ბლოკი. ერთხელ იწერება, შემდეგ ბევრჯერ გამოიძახება CALL-ით. პროცედურა მნიშვნელობას არ აბრუნებს (ფუნქციისგან განსხვავებით).

DELIMITER დროებით ცვლის ბრძანების დასასრულის სიმბოლოს, რათა პროცედურის შიგნით ;-მა ნაადრევად არ დაასრულოს განსაზღვრება.

სინტაქსი

Syntax
DELIMITER //
CREATE PROCEDURE proc_name(params)
BEGIN
  -- SQL კოდი
END //
DELIMITER ;

მაგალითები

უმარტივესი პროცედურა
SQL
DELIMITER //
CREATE PROCEDURE get_all_students()
BEGIN
  SELECT * FROM students ORDER BY last_name;
END //
DELIMITER ;

პარამეტრები (IN / OUT / INOUT)

პროცედურას შეუძლია მიიღოს და დააბრუნოს მნიშვნელობები პარამეტრების მეშვეობით:

მნიშვნელობები

მნიშვნელობააღწერა
IN მხოლოდ შემავალი — გადაეცემა პროცედურას (default).
OUT მხოლოდ გამავალი — პროცედურა აბრუნებს მნიშვნელობას.
INOUT ორმხრივი — გადაეცემა და უკან ბრუნდება.

მაგალითები

IN + OUT პარამეტრები
SQL
DELIMITER //
CREATE PROCEDURE count_by_city(IN p_city VARCHAR(50), OUT p_count INT)
BEGIN
  SELECT COUNT(*) INTO p_count
  FROM students
  WHERE city = p_city;
END //
DELIMITER ;

გამოძახება (CALL) და PHP-დან PHP 8+

პროცედურა გამოიძახება CALL ბრძანებით. OUT პარამეტრს ვკითხულობთ ცვლადით.

მაგალითები

გამოძახება SQL-ში
SQL
CALL count_by_city('თბილისი', @cnt);
SELECT @cnt AS students_in_tbilisi;
პროცედურის გამოძახება PHP-დან
PHP
<?php
$stmt = $pdo->prepare("CALL count_by_city(?, @cnt)");
$stmt->execute(['თბილისი']);
$count = $pdo->query("SELECT @cnt")->fetchColumn();
echo "თბილისში: $count სტუდენტი";
💡 რჩევა: პროცედურის შესაცვლელად — წაშალე და ხელახლა შექმენი (DROP PROCEDURE + CREATE), ან გამოიყენე ALTER PROCEDURE მახასიათებლებისთვის.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. შექმენი პროცედურა enroll_student(IN sid INT, IN cid INT), რომელიც ამატებს ჩარიცხვას.
  2. შექმენი პროცედურა OUT პარამეტრით, რომელიც აბრუნებს კონკრეტული კურსის სტუდენტთა რაოდენობას.
  3. გამოიძახე ორივე პროცედურა PHP-დან.

თვითშეფასების კითხვები

  1. რა განსხვავებაა IN, OUT და INOUT პარამეტრებს შორის?
  2. რატომ გვჭირდება DELIMITER?
  3. რა განსხვავებაა პროცედურასა და ფუნქციას შორის?

13 🧮 Functions

მომხმარებლის ფუნქციები, დაბრუნებული მნიშვნელობები და ჩაშენებული ფუნქციები.

მომხმარებლის ფუნქცია (CREATE FUNCTION)

ფუნქცია პროცედურის მსგავსია, ოღონდ აბრუნებს ერთ მნიშვნელობას (RETURNS) და გამოიძახება უშუალოდ SQL-ის შიგნით (მაგ. SELECT-ში).

სინტაქსი

Syntax
DELIMITER //
CREATE FUNCTION fn_name(params) RETURNS type
[DETERMINISTIC]
BEGIN
  RETURN value;
END //
DELIMITER ;

მაგალითები

სრული სახელის დაბრუნება
SQL
DELIMITER //
CREATE FUNCTION full_name(p_first VARCHAR(50), p_last VARCHAR(50))
RETURNS VARCHAR(101)
DETERMINISTIC
BEGIN
  RETURN CONCAT(p_first, ' ', p_last);
END //
DELIMITER ;
ფუნქციის გამოყენება SELECT-ში
SQL
SELECT id, full_name(first_name, last_name) AS name
FROM students;
💡 რჩევა: DETERMINISTIC ნიშნავს, რომ ერთი და იმავე შესასვლელზე ფუნქცია ყოველთვის ერთ შედეგს აბრუნებს — ეს ბაზას ოპტიმიზაციის საშუალებას აძლევს.

ჩაშენებული ფუნქციები

MySQL-ს უამრავი მზა ფუნქცია აქვს ტექსტთან, რიცხვებთან და თარიღებთან სამუშაოდ:

მნიშვნელობები

მნიშვნელობააღწერა
UPPER() / LOWER() რეგისტრის შეცვლა (ძველი: UCASE/LCASE).
SUBSTRING() ქვესტრიქონის ამოჭრა: SUBSTRING('123456',5,2)56.
LENGTH() / CHAR_LENGTH() ბაიტების / სიმბოლოების რაოდენობა (ქართულისთვის CHAR_LENGTH).
CONCAT() ტექსტების შეერთება.
ROUND() დამრგვალება.
NOW() / CURDATE() მიმდინარე დრო+თარიღი / მხოლოდ თარიღი.
DATE_FORMAT() თარიღის ფორმატირება: DATE_FORMAT(NOW(), '%Y-%m-%d').
COALESCE() პირველი არა-NULL მნიშვნელობა სიიდან.

მაგალითები

პრაქტიკული მაგალითი
SQL
SELECT
  UPPER(last_name)                       AS gvari,
  DATE_FORMAT(birth_date, '%d.%m.%Y')    AS daibada,
  COALESCE(email, 'არ აქვს')             AS mail
FROM students;

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. შექმენი ფუნქცია age(birth_date DATE), რომელიც ასაკს აბრუნებს წლებში.
  2. გამოიყენე ეს ფუნქცია SELECT-ში ყველა სტუდენტის ასაკის გამოსატანად.
  3. დაწერე query, რომელიც COALESCE-ით აჩვენებს „არ აქვს", თუ email ცარიელია.

თვითშეფასების კითხვები

  1. რა განსხვავებაა ფუნქციასა და პროცედურას შორის?
  2. რას ნიშნავს DETERMINISTIC?
  3. ქართული ტექსტის სიგრძისთვის LENGTH თუ CHAR_LENGTH უნდა გამოვიყენოთ და რატომ?

14 👁️ Views (ხედები)

რა არის View, როდის გამოვიყენოთ და რა უპირატესობა აქვს.

რა არის View

View (ხედი) — შენახული SELECT query, რომელსაც ვირტუალურ ცხრილად ვმართავთ. ის არ ინახავს მონაცემებს — ყოველ მიმართვაზე ხელახლა ითვლება ფაქტობრივი ცხრილებიდან.

სინტაქსი

Syntax
CREATE VIEW view_name AS
SELECT ... FROM ... WHERE ...;

მაგალითები

აქტიური სტუდენტების ხედი
SQL
CREATE VIEW active_students AS
SELECT id, first_name, last_name, email
FROM students
WHERE deleted_at IS NULL;
ხედის გამოყენება ჩვეულებრივი ცხრილივით
SQL
SELECT * FROM active_students WHERE last_name LIKE 'ბ%';

როდის გამოვიყენოთ და უპირატესობები

View გამოსადეგია, როცა რთული query ხშირად მეორდება ან გვინდა მონაცემების ნაწილის „დამალვა".

მაგალითები

რთული JOIN-ის ხედად შენახვა
SQL
CREATE VIEW student_courses AS
SELECT s.first_name, s.last_name, c.title, e.enrolled_at
FROM enrollments AS e
JOIN students AS s ON s.id = e.student_id
JOIN courses  AS c ON c.id = e.course_id;
💡 რჩევა: ხედის წასაშლელად: DROP VIEW view_name;. შესაცვლელად: CREATE OR REPLACE VIEW ....

✓ საუკეთესო პრაქტიკა

  • სიმარტივე — რთული JOIN-ები ერთ მარტივ სახელში ჩაატევ.
  • უსაფრთხოება — მომხმარებელს ხედზე აძლევ წვდომას, ფაქტობრივი ცხრილის ნაცვლად (მგრძნობიარე სვეტები იმალება).
  • თანმიმდევრობა — ბიზნეს-ლოგიკა (მაგ. „აქტიური სტუდენტი") ერთ ადგილას იწერება.

✗ ხშირი შეცდომები

  • View-ში ძალიან მძიმე query — ყოველ მიმართვაზე თავიდან ითვლება; დიდ მონაცემებზე გამოიყენე ინდექსები ან materialized მიდგომა.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. შექმენი ხედი, რომელიც აჩვენებს თითო კურსზე ჩარიცხულ სტუდენტთა რაოდენობას.
  2. შექმენი ხედი მხოლოდ საჯარო სვეტებით (id, სახელი) email-ის გარეშე.
  3. წაშალე და თავიდან შექმენი ხედი CREATE OR REPLACE-ით.

თვითშეფასების კითხვები

  1. ინახავს თუ არა View მონაცემებს?
  2. დაასახელე View-ს 2 უპირატესობა.
  3. როდის შეიძლება View ანელებდეს სისტემას?

15 🚀 ინდექსები და ოპტიმიზაცია

INDEX, UNIQUE INDEX, EXPLAIN და query-ების ოპტიმიზაცია.

INDEX

ინდექსი — სვეტის სპეციალური „საძიებო ცნობარი", რომელიც მკვეთრად აჩქარებს ძებნას (WHERE, JOIN, ORDER BY). ინდექსის გარეშე ბაზა ცხრილს სტრიქონ-სტრიქონ ათვალიერებს (full scan); ინდექსით — პირდაპირ პოულობს.

თუმცა ინდექსი იკავებს ადგილს და ანელებს INSERT/UPDATE-ს, ამიტომ ყველა სვეტზე დადება არ ღირს.

სინტაქსი

Syntax
CREATE INDEX idx_name ON table_name (column);

მაგალითები

ინდექსი ხშირად საძიებო სვეტზე
SQL
CREATE INDEX idx_students_city ON students (city);
CREATE INDEX idx_enroll_student ON enrollments (student_id);
💡 რჩევა: დაასკვნი ინდექსის საჭიროება ანალიზით: დააინდექსე ის სვეტები, რომლებითაც ხშირად ეძებ ან აერთებ ცხრილებს (Foreign Key-ები თითქმის ყოველთვის).

UNIQUE INDEX

UNIQUE INDEX აჩქარებს ძებნას და კრძალავს დუბლიკატებს. იდეალურია email-ისთვის, მომხმარებლის სახელისთვის და სხვა უნიკალური ველებისთვის.

მაგალითები

უნიკალური email
SQL
CREATE UNIQUE INDEX idx_students_email ON students (email);
შედგენილი უნიკალური ინდექსი (ერთი წყვილი ერთხელ)
SQL
CREATE UNIQUE INDEX idx_enroll_unique
ON enrollments (student_id, course_id);
💡 რჩევა: შედგენილი UNIQUE ინდექსი (student_id, course_id) უზრუნველყოფს, რომ ერთი სტუდენტი ერთ კურსზე ორჯერ ვერ ჩაირიცხოს.

EXPLAIN და Query Optimization

EXPLAIN აჩვენებs, როგორ აპირებს MySQL query-ის შესრულებას — იყენებს თუ არა ინდექსს და რამდენ სტრიქონს ათვალიერებს. ეს ოპტიმიზაციის მთავარი ხელსაწყოა.

მნიშვნელობები

მნიშვნელობააღწერა
type = ALL სრული სკანირება — ცუდია (ინდექსი არ გამოიყენა).
type = ref/range ინდექსი გამოიყენა — კარგია.
key რომელი ინდექსი გამოიყენა (NULL = ვერცერთი).
rows სავარაუდოდ რამდენ სტრიქონს გადახედავს (რაც ნაკლები, მით უკეთესი).

მაგალითები

query-ის გეგმის ნახვა
SQL
EXPLAIN SELECT * FROM students WHERE city = 'თბილისი';

✓ საუკეთესო პრაქტიკა

  • დააინდექსე WHERE/JOIN/ORDER BY-ში გამოყენებული სვეტები.
  • მოერიდე SELECT *-ს — წამოიღე მხოლოდ საჭირო სვეტები.
  • LIKE-ში თავიდან % არ დაიწყო — ინდექსი არ იმუშავებს.
  • პერიოდულად გაუშვი EXPLAIN ნელ query-ებზე.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. გაუშვი EXPLAIN query-ზე ინდექსის გარეშე, შემდეგ დააინდექსე სვეტი და თავიდან გაუშვი — შეადარე rows.
  2. დაამატე UNIQUE ინდექსი enrollments-ზე (student_id, course_id) და სცადე დუბლიკატის ჩაწერა.
  3. ჩამოწერე 3 სვეტი შენი ბაზიდან, რომელსაც აუცილებლად დააინდექსებდი და ახსენი რატომ.

თვითშეფასების კითხვები

  1. რატომ აჩქარებს ინდექსი ძებნას?
  2. რატომ არ ღირს ყველა სვეტზე ინდექსის დადება?
  3. რას გვიჩვენებს EXPLAIN-ის key და rows სვეტები?

16 🔒 უსაფრთხოება

SQL Injection, Prepared Statements, Parameter Binding და პაროლების ჰეშირება.

SQL Injection

SQL Injection — ერთ-ერთი ყველაზე საშიში დაუცველობა. ხდება მაშინ, როცა მომხმარებლის შეყვანილ მონაცემს პირდაპირ ვწებავთ SQL query-ში. თავდამსხმელს შეუძლია ჩაამატოს საკუთარი SQL და წაიკითხოს/წაშალოს მთელი ბაზა.

მაგალითები

❌ მოწყვლადი კოდი — არასდროს ასე!
PHP (ცუდი)
<?php
// მომხმარებელი შეიყვანს: 1 OR 1=1
$id = $_GET['id'];
$sql = "SELECT * FROM students WHERE id = $id";  // 💀 ღია კარი
$result = $conn->query($sql);
მაგალითი, რა შეიძლება შემოაგზავნოს
SQL
-- შეყვანა: 0; DROP TABLE students; --
-- შედეგი: ცხრილი წაიშალა!
💡 რჩევა: წესი #1: მომხმარებლის მონაცემი არასდროს ჩასვა query-ში სტრიქონის შეერთებით. ყოველთვის prepared statements.

Prepared Statements PHP 8+

Prepared Statement — query-ს ცალკე ვამზადებთ პლეისჰოლდერებით (? ან :name), მონაცემებს კი ცალკე ვაკავშირებთ. ბაზა მათ მონაცემად აღიქვამს, არასოდეს — კოდად. ეს SQL Injection-ის უმთავრესი დაცვაა.

მაგალითები

✅ mysqli prepared statement
PHP (mysqli)
<?php
$stmt = $conn->prepare("SELECT * FROM students WHERE id = ?");
$stmt->bind_param("i", $id);   // i = integer
$stmt->execute();
$result = $stmt->get_result();
$student = $result->fetch_assoc();
✅ PDO named parameters
PHP (PDO)
<?php
$stmt = $pdo->prepare("SELECT * FROM students WHERE email = :email");
$stmt->execute(['email' => $email]);
$student = $stmt->fetch();

Parameter Binding PHP 8+

mysqli-ში bind_param()-ს ვაწვდით ტიპების სტრიქონს, რომელიც აღწერს თითო პარამეტრს:

მნიშვნელობები

მნიშვნელობააღწერა
i integer — მთელი რიცხვი.
d double — ათწილადი.
s string — ტექსტი.
b blob — ბინარული მონაცემი.

მაგალითები

რამდენიმე პარამეტრის მიბმა
PHP
<?php
$stmt = $conn->prepare(
  "INSERT INTO students (first_name, last_name, birth_date) VALUES (?, ?, ?)"
);
$stmt->bind_param("sss", $first, $last, $birth);
$stmt->execute();
💡 რჩევა: ტიპების სტრიქონის სიგრძე უნდა ემთხვეოდეს პარამეტრების რაოდენობას: 3 პარამეტრი → "sss".

Password Hashing PHP 8+

პაროლები არასდროს ინახება ღია ტექსტად. გამოიყენე PHP-ის password_hash() (bcrypt/argon2) — ერთმხრივი ჰეშირება, რომელსაც ვერ „გაშიფრავ".

მაგალითები

რეგისტრაცია — ჰეშის შენახვა
PHP
<?php
$hash = password_hash($password, PASSWORD_DEFAULT);

$stmt = $pdo->prepare("INSERT INTO users (email, password_hash) VALUES (?, ?)");
$stmt->execute([$email, $hash]);
ავტორიზაცია — ჰეშის შემოწმება
PHP
<?php
$stmt = $pdo->prepare("SELECT password_hash FROM users WHERE email = ?");
$stmt->execute([$email]);
$row = $stmt->fetch();

if ($row && password_verify($password, $row['password_hash'])) {
    echo "შესვლა წარმატებულია";
} else {
    echo "არასწორი მონაცემები";
}
💡 რჩევა: password_hash() თვითონ აგენერირებს „მარილს" (salt) — შენ არაფერი გჭირდება დამატებით.

✗ ხშირი შეცდომები

  • პაროლის MD5/SHA1-ით ჰეშირება — დღეს უსაფრთხო აღარაა, გამოიყენე password_hash().
  • პაროლის ღია ტექსტად შენახვა — გაუტოლდება უსაფრთხოების კატასტროფას.

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. გადააკეთე მოწყვლადი query prepared statement-ად (mysqli და PDO ვერსიები).
  2. დაწერე რეგისტრაცია/ავტორიზაცია password_hash / password_verify-ით.
  3. აღწერე, რას იზამდა თავდამსხმელი 1 OR 1=1 შეყვანით და როგორ აგვარებს prepared statement ამ პრობლემას.

თვითშეფასების კითხვები

  1. რა არის SQL Injection და როგორ ხდება?
  2. როგორ იცავს prepared statement injection-ისგან?
  3. რატომ არ ვინახავთ პაროლს ღია ტექსტად ან MD5-ით?

17 🎓 რეალური პროექტის მაგალითი

სტუდენტების სისტემა: students / courses / enrollments — შექმნა, კავშირები, CRUD და ანგარიშგება.

ბაზის სქემა (students / courses / enrollments)

ავაგოთ მარტივი სასწავლო სისტემა. სამი ცხრილი, Many-to-Many კავშირი შუალედური enrollments ცხრილით:

erDiagram STUDENTS ||--o{ ENROLLMENTS : "ერიცხება" COURSES ||--o{ ENROLLMENTS : "მოიცავს" STUDENTS { int id PK string first_name string last_name string email } COURSES { int id PK string title int credits } ENROLLMENTS { int id PK int student_id FK int course_id FK decimal grade date enrolled_at }

მაგალითები

სამივე ცხრილის შექმნა
SQL
CREATE TABLE students (
  id         INT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(50)  NOT NULL,
  last_name  VARCHAR(50)  NOT NULL,
  email      VARCHAR(120) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

CREATE TABLE courses (
  id      INT AUTO_INCREMENT PRIMARY KEY,
  title   VARCHAR(120) NOT NULL,
  credits INT NOT NULL DEFAULT 5
) ENGINE=InnoDB;

CREATE TABLE enrollments (
  id          INT AUTO_INCREMENT PRIMARY KEY,
  student_id  INT NOT NULL,
  course_id   INT NOT NULL,
  grade       DECIMAL(4,1) NULL,
  enrolled_at DATE NOT NULL DEFAULT (CURRENT_DATE),
  UNIQUE KEY uq_enroll (student_id, course_id),
  FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
  FOREIGN KEY (course_id)  REFERENCES courses(id)  ON DELETE CASCADE
) ENGINE=InnoDB;

CRUD ოპერაციები PHP 8+

CRUD = Create, Read, Update, Delete. ქვემოთ — სრული ციკლი PDO-ით და prepared statements-ით.

მაგალითები

CREATE — ახალი სტუდენტი + ჩარიცხვა
PHP
<?php
// ახალი სტუდენტი
$stmt = $pdo->prepare("INSERT INTO students (first_name, last_name, email) VALUES (?, ?, ?)");
$stmt->execute(['ნინო', 'ბერიძე', 'nino@example.com']);
$studentId = $pdo->lastInsertId();

// კურსზე ჩარიცხვა
$stmt = $pdo->prepare("INSERT INTO enrollments (student_id, course_id) VALUES (?, ?)");
$stmt->execute([$studentId, 1]);
READ — სტუდენტის კურსები
PHP
<?php
$stmt = $pdo->prepare(
  "SELECT c.title, e.grade
   FROM enrollments e
   JOIN courses c ON c.id = e.course_id
   WHERE e.student_id = ?"
);
$stmt->execute([$studentId]);
foreach ($stmt as $row) {
    echo "{$row['title']}: {$row['grade']}\n";
}
UPDATE / DELETE
PHP
<?php
// ნიშნის დაყენება
$pdo->prepare("UPDATE enrollments SET grade = ? WHERE student_id = ? AND course_id = ?")
    ->execute([9.5, $studentId, 1]);

// ჩარიცხვის გაუქმება
$pdo->prepare("DELETE FROM enrollments WHERE student_id = ? AND course_id = ?")
    ->execute([$studentId, 1]);

ანგარიშგება (Reporting)

JOIN + GROUP BY + აგრეგაცია — ანგარიშების ბირთვი.

მაგალითები

თითო კურსზე სტუდენტთა რაოდენობა და საშუალო ნიშანი
SQL
SELECT
  c.title,
  COUNT(e.id)          AS students,
  ROUND(AVG(e.grade),2) AS avg_grade
FROM courses AS c
LEFT JOIN enrollments AS e ON e.course_id = c.id
GROUP BY c.id, c.title
ORDER BY students DESC;
საუკეთესო სტუდენტები (საშუალო ნიშნით)
SQL
SELECT
  s.first_name, s.last_name,
  ROUND(AVG(e.grade),2) AS gpa
FROM students AS s
JOIN enrollments AS e ON e.student_id = s.id
WHERE e.grade IS NOT NULL
GROUP BY s.id, s.first_name, s.last_name
HAVING gpa >= 9
ORDER BY gpa DESC;
💡 რჩევა: ანგარიშებში ხშირად გამოგადგება LEFT JOIN — რომ ცარიელი კურსებიც გამოჩნდეს (0 სტუდენტით).

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. შექმენი სამივე ცხრილი, ჩაყარე 5 სტუდენტი, 3 კურსი და რამდენიმე ჩარიცხვა.
  2. დაწერე ანგარიში: თითო სტუდენტის სრული სახელი და ჩარიცხული კურსების რაოდენობა.
  3. გააფართოვე სქემა teachers ცხრილით და დააკავშირე კურსებთან.

თვითშეფასების კითხვები

  1. რა არის CRUD?
  2. რატომ გვჭირდება შუალედური enrollments ცხრილი?
  3. რატომ LEFT JOIN ანგარიშებში INNER JOIN-ის ნაცვლად?

18 ✅ საუკეთესო პრაქტიკები

დასახელების წესები, დიზაინი, ნორმალიზაცია, Backup და SQL-ის ვერსიათა კონტროლი.

Naming Conventions

თანმიმდევრული დასახელება კოდს კითხვადს ხდის. ფართოდ გავრცელებული წესები:

✓ საუკეთესო პრაქტიკა

  • ცხრილების სახელები მრავლობითში და პატარა ასოებით: students, courses.
  • სვეტები snake_case-ით: first_name, created_at.
  • Primary Key — id; Foreign Key — <ცხრილი>_id (მაგ. student_id).
  • ინდექსები პრეფიქსით: idx_, უნიკალური — uq_.
  • მოერიდე დარეზერვებულ სიტყვებს (order, group) სვეტის სახელად.

Database Design

კარგი დიზაინი პრობლემებს თავიდანვე ააცილებს:

✓ საუკეთესო პრაქტიკა

  • ყოველ ცხრილს — ხელოვნური Primary Key (id).
  • გამოიყენე სწორი მონაცემთა ტიპები (ფულისთვის DECIMAL, თარიღისთვის DATE/DATETIME).
  • დააწესე შეზღუდვები: NOT NULL, UNIQUE, FOREIGN KEY.
  • ჩაამატე created_at / updated_at აუდიტისთვის.
  • გამოიყენე InnoDB ძრავი — Foreign Key-ებისა და ტრანზაქციებისთვის.

Normalization (ნორმალიზაცია)

ნორმალიზაცია — მონაცემთა ისე ორგანიზება, რომ მინიმუმამდე დაიყვანოს გამეორება (redundancy). ძირითადი ნორმალური ფორმები:

მნიშვნელობები

მნიშვნელობააღწერა
1NF თითო უჯრაში — ერთი ატომური მნიშვნელობა (არა სია „მათემატიკა, ფიზიკა" ერთ ველში).
2NF 1NF + ყველა არა-გასაღები სვეტი დამოკიდებულია მთლიან Primary Key-ზე.
3NF 2NF + არა-გასაღები სვეტები ერთმანეთზე არ არიან დამოკიდებული (გადატანითი დამოკიდებულება არ არის).
💡 რჩევა: პრაქტიკაში 3NF ჩვეულებრივ საკმარისია. ზოგჯერ წარმადობისთვის შეგნებულად ვახდენთ დენორმალიზაციას (გამეორების დაშვებას).

✗ ხშირი შეცდომები

  • რამდენიმე მნიშვნელობის ერთ ველში ჩაწერა მძიმეებით — არღვევს 1NF-ს.
  • ერთი და იმავე მონაცემის ბევრ ცხრილში დუბლირება — განახლებისას აირევა.

Backup (სარეზერვო ასლი)

რეგულარული ბექაფი მონაცემთა დაკარგვისგან გიცავს. MySQL-ში მთავარი ხელსაწყოა mysqldump.

მაგალითები

ბაზის ექსპორტი ფაილში
Shell
mysqldump -u root -p school > school_backup.sql
ბაზის აღდგენა ფაილიდან
Shell
mysql -u root -p school < school_backup.sql

✓ საუკეთესო პრაქტიკა

  • ბექაფი ავტომატიზებულად, განრიგით (cron) აიღე.
  • ასლი შეინახე სხვა ფიზიკურ ადგილას/ღრუბელში.
  • პერიოდულად შეამოწმე, რომ აღდგენა მართლა მუშაობს.

Version Control for SQL Scripts

ბაზის სტრუქტურის ცვლილებები (სქემა) უნდა ინახებოდეს მიგრაციების სახით Git-ში — ისევე როგორც კოდი. ასე გუნდის ყველა წევრს ერთნაირი სქემა აქვს და ცვლილებების ისტორია თვალსაჩინოა.

💡 რჩევა: „როგორ მივიდა ბაზა ამ მდგომარეობამდე?" — ამ კითხვაზე პასუხი მიგრაციების ისტორიამ უნდა მოგცეს.

✓ საუკეთესო პრაქტიკა

  • შეინახე .sql მიგრაციის ფაილები რეპოზიტორიაში (მაგ. migrations/2026_01_create_students.sql).
  • ერთი მიგრაცია — ერთი ლოგიკური ცვლილება.
  • მონაცემები (პაროლები, ტოკენები) არ ჩააგდო Git-ში — გამოიყენე .env.
  • გამოიყენე მიგრაციის ხელსაწყოები (Laravel Migrations, Flyway, Liquibase).

📝 სავარჯიშოები და თვითშეფასება

სავარჯიშოები

  1. გადახედე შენი ბიბლიოთეკის სქემას და დაამთხვიე დასახელების წესებს.
  2. აიღე ბაზის ბექაფი mysqldump-ით და აღადგინე ახალ ბაზაში.
  3. ააგე migrations/ საქაღალდე და ჩაწერე პირველი მიგრაცია Git-ში.

თვითშეფასების კითხვები

  1. რას ნიშნავს ცხრილების მრავლობითში დასახელება და რატომ?
  2. ახსენი 1NF, 2NF და 3NF საკუთარი სიტყვებით.
  3. რატომ ვინახავთ SQL მიგრაციებს ვერსიათა კონტროლში?