SQL چیست؟ آموزش SQL از صفر با MySQL، Python، FastAPI و پروژه هوش مصنوعی
در این آموزش SQL را از صفر و با مثالهای عملی MySQL یاد میگیرید؛ از ساخت جدول و CRUD تا JOIN، Index و Transaction. در پایان نیز یک API واقعی برای ذخیره مکالمات هوش مصنوعی با FastAPI و درواره میسازیم.
SQL یکی از مهمترین مهارتها برای برنامهنویسان، تحلیلگران داده، توسعهدهندگان Backend و متخصصان هوش مصنوعی است. تقریباً هر برنامهای که اطلاعات کاربران، سفارشها، پیامها، تراکنشها یا تاریخچه مکالمات را نگهداری میکند، به یک پایگاه داده نیاز دارد.
در این آموزش SQL را فقط در حد تعریف مفاهیم بررسی نمیکنیم. ابتدا دستورات اصلی SQL را با MySQL یاد میگیریم و سپس یک پروژه واقعی میسازیم که مکالمات کاربران با مدل هوش مصنوعی را در پایگاه داده ذخیره میکند.
پروژه نهایی شامل اجزای زیر است:
- MySQL برای ذخیره اطلاعات
- SQL و SQLModel برای کار با دادهها
- Python و FastAPI برای ساخت API
- API درواره برای دسترسی به مدل هوش مصنوعی
- Docker Compose برای اجرای سرویسها
- ذخیره Conversation و Message
- بازیابی تاریخچه گفتگو
- مدیریت Transaction
- اعتبارسنجی ورودی
- محافظت از Endpoint با Token
- جلوگیری از SQL Injection
- Health Check و مدیریت خطا
SQL چیست؟
SQL مخفف Structured Query Language است و برای تعریف، خواندن، تغییر و مدیریت دادههای پایگاههای داده رابطهای استفاده میشود.
با SQL میتوان عملیات زیر را انجام داد:
- ساخت پایگاه داده
- ساخت جدول
- افزودن اطلاعات
- جستوجو و فیلتر داده
- ویرایش اطلاعات
- حذف اطلاعات
- اتصال چند جدول
- گروهبندی و محاسبه آمار
- ساخت Index
- مدیریت Transaction
- تعریف محدودیتهای داده
- مدیریت ارتباط بین رکوردها
یک Query ساده SQL:
SELECT id, name, email
FROM users
WHERE is_active = TRUE
ORDER BY created_at DESC;
این Query کاربران فعال را انتخاب و آنها را براساس زمان ایجاد از جدید به قدیم مرتب میکند.
آیا SQL یک زبان برنامهنویسی است؟
SQL یک زبان تخصصی برای کار با پایگاه داده رابطهای است. برخلاف Python یا JavaScript، معمولاً برای ساخت کل برنامه استفاده نمیشود.
در یک برنامه واقعی، زبانها در کنار هم قرار میگیرند:
Frontend
|
v
Python / FastAPI
|
v
SQL
|
v
MySQL
Python منطق برنامه را اجرا میکند، SQL عملیات مربوط به داده را توضیح میدهد و MySQL دادهها را ذخیره و پردازش میکند.
پایگاه داده رابطهای چیست؟
در پایگاه داده رابطهای، اطلاعات داخل Table یا جدول ذخیره میشوند. هر جدول دارای ستون و سطر است.
جدول کاربران:
| id | name | |
|---|---|---|
| 1 | امیر | amir@example.com |
| 2 | سارا | sara@example.com |
هر سطر یک Record و هر ستون یک ویژگی از آن Record است.
میتوان میان جدولها ارتباط ایجاد کرد. برای مثال:
users
|
| یک کاربر چند مکالمه دارد
v
conversations
|
| یک مکالمه چند پیام دارد
v
messages
این ارتباطها با Primary Key و Foreign Key مدیریت میشوند.
تفاوت SQL و MySQL چیست؟
SQL یک زبان است، اما MySQL یک سیستم مدیریت پایگاه داده است.
| مفهوم | توضیح |
|---|---|
| SQL | زبان کار با پایگاه داده رابطهای |
| MySQL | نرمافزار مدیریت پایگاه داده |
| PostgreSQL | یک سیستم مدیریت پایگاه داده دیگر |
| SQLite | پایگاه داده سبک و فایلمحور |
| Microsoft SQL Server | محصول پایگاه داده مایکروسافت |
| Oracle Database | پایگاه داده سازمانی Oracle |
بخش زیادی از SQL میان این پایگاههای داده مشترک است، اما هرکدام قابلیتها و تفاوتهای خاص خود را دارند.
SQL در پروژههای هوش مصنوعی چه کاربردی دارد؟
SQL فقط برای فروشگاه اینترنتی یا نرمافزار حسابداری نیست. در برنامههای هوش مصنوعی نیز کاربردهای فراوانی دارد:
- ذخیره کاربران
- ذخیره مکالمات
- مدیریت دستیارهای هوش مصنوعی
- ذخیره تنظیمات پرامپت
- ثبت مدل استفادهشده
- ثبت مصرف توکن
- ثبت هزینه درخواست
- مدیریت بازخورد کاربران
- ذخیره نتیجه ارزیابی مدل
- نگهداری Jobهای پردازشی
- ذخیره اسناد و Metadata
- ثبت Tool Callها
- گزارشگیری از میزان استفاده
برای دادههای ساختاریافته مانند کاربران، درخواستها، هزینهها و ارتباط میان موجودیتها، پایگاه داده SQL انتخاب مناسبی است.
دستهبندی دستورات SQL
دستورات SQL معمولاً در چند گروه قرار میگیرند.
DDL
Data Definition Language برای تعریف ساختار پایگاه داده:
CREATE
ALTER
DROP
TRUNCATE
DML
Data Manipulation Language برای تغییر داده:
INSERT
UPDATE
DELETE
DQL
Data Query Language برای خواندن داده:
SELECT
TCL
Transaction Control Language برای مدیریت تراکنش:
START TRANSACTION
COMMIT
ROLLBACK
DCL
Data Control Language برای مدیریت دسترسی:
GRANT
REVOKE
نصب MySQL با Docker
سادهترین راه برای اجرای محیط آزمایشی MySQL استفاده از Docker است.
فایل compose.mysql.yaml را ایجاد کنید:
services:
mysql:
image: mysql:8.4
container_name: sql-tutorial-mysql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: ai_chat
MYSQL_USER: ai_user
MYSQL_PASSWORD: ai_password
ports:
- "3306:3306"
volumes:
- mysql_tutorial_data:/var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
healthcheck:
test:
[
"CMD",
"mysqladmin",
"ping",
"-h",
"localhost",
"-uroot",
"-proot_password"
]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
volumes:
mysql_tutorial_data:
MySQL را اجرا کنید:
docker compose \
-f compose.mysql.yaml \
up -d
وضعیت Container:
docker compose \
-f compose.mysql.yaml \
ps
ورود به محیط MySQL:
docker exec -it \
sql-tutorial-mysql \
mysql -uai_user -pai_password ai_chat
در محیط Production باید رمزهای قوی و مستقل انتخاب کنید و آنها را مستقیماً داخل فایل عمومی قرار ندهید.
ساخت پایگاه داده
اگر پایگاه داده از قبل ساخته نشده است:
CREATE DATABASE ai_chat
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
انتخاب پایگاه داده:
USE ai_chat;
مشاهده پایگاههای داده:
SHOW DATABASES;
ساخت اولین جدول
جدول کاربران:
CREATE TABLE users (
id BIGINT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
name VARCHAR(100)
NOT NULL,
email VARCHAR(255)
NOT NULL
UNIQUE,
is_active BOOLEAN
NOT NULL
DEFAULT TRUE,
created_at DATETIME
NOT NULL
DEFAULT CURRENT_TIMESTAMP
);
اجزای مهم:
| عبارت | کاربرد |
|---|---|
BIGINT | عدد صحیح بزرگ |
UNSIGNED | جلوگیری از مقدار منفی |
AUTO_INCREMENT | تولید خودکار شناسه |
PRIMARY KEY | شناسه منحصربهفرد |
VARCHAR | رشته با حداکثر طول مشخص |
NOT NULL | مقدار اجباری |
UNIQUE | جلوگیری از مقدار تکراری |
DEFAULT | مقدار پیشفرض |
مشاهده ساختار جدول:
DESCRIBE users;
مشاهده جدولها:
SHOW TABLES;
Primary Key چیست؟
Primary Key ستونی است که هر رکورد را بهصورت منحصربهفرد مشخص میکند:
id BIGINT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY
ویژگیهای Primary Key:
- تکراری نیست.
- مقدار
NULLنمیپذیرد. - برای ارتباط میان جدولها استفاده میشود.
- بهتر است در طول عمر رکورد ثابت بماند.
Foreign Key چیست؟
Foreign Key ارتباط میان دو جدول را مشخص میکند.
ابتدا جدول مکالمات را میسازیم:
CREATE TABLE conversations (
id BIGINT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
user_id BIGINT UNSIGNED
NOT NULL,
title VARCHAR(200)
NOT NULL,
created_at DATETIME
NOT NULL
DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_conversations_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
);
هر Conversation به یک User تعلق دارد.
جدول پیامها:
CREATE TABLE messages (
id BIGINT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
conversation_id BIGINT UNSIGNED
NOT NULL,
role ENUM(
'user',
'assistant',
'system'
) NOT NULL,
content TEXT
NOT NULL,
model_id VARCHAR(255)
NULL,
input_tokens INT UNSIGNED
NULL,
output_tokens INT UNSIGNED
NULL,
created_at DATETIME
NOT NULL
DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_messages_conversation
FOREIGN KEY (conversation_id)
REFERENCES conversations(id)
ON DELETE CASCADE
);
با ON DELETE CASCADE اگر یک مکالمه حذف شود، پیامهای وابسته به آن نیز حذف میشوند. استفاده از این رفتار باید آگاهانه و متناسب با نیاز محصول باشد.
افزودن اطلاعات با INSERT
افزودن یک کاربر:
INSERT INTO users (
name,
email
)
VALUES (
'امیر افشار',
'amir@example.com'
);
افزودن چند کاربر:
INSERT INTO users (
name,
email
)
VALUES
(
'سارا محمدی',
'sara@example.com'
),
(
'علی رضایی',
'ali@example.com'
);
افزودن مکالمه:
INSERT INTO conversations (
user_id,
title
)
VALUES (
1,
'آموزش SQL'
);
افزودن پیام:
INSERT INTO messages (
conversation_id,
role,
content
)
VALUES (
1,
'user',
'SQL چیست؟'
);
خواندن اطلاعات با SELECT
دریافت تمام کاربران:
SELECT *
FROM users;
دریافت ستونهای مشخص:
SELECT
id,
name,
email
FROM users;
بهتر است در Queryهای برنامهای فقط ستونهای موردنیاز را انتخاب کنید و بدون دلیل از SELECT * استفاده نکنید.
ساختار کلی SELECT در MySQL شامل بخشهایی مانند FROM، WHERE، ORDER BY و LIMIT است. ترتیب Clauseها در Query اهمیت دارد. مستندات SELECT در MySQL
فیلتر کردن با WHERE
کاربر با شناسه مشخص:
SELECT
id,
name,
email
FROM users
WHERE id = 1;
کاربران فعال:
SELECT
id,
name
FROM users
WHERE is_active = TRUE;
استفاده از چند شرط:
SELECT
id,
name,
email
FROM users
WHERE is_active = TRUE
AND created_at >= '2026-08-01';
شرط جایگزین:
SELECT
id,
name
FROM users
WHERE id = 1
OR id = 2;
جستوجو با LIKE
یافتن کاربرانی که نام آنها با «امیر» شروع میشود:
SELECT
id,
name
FROM users
WHERE name LIKE 'امیر%';
الگوهای مهم:
| الگو | معنی |
|---|---|
'امیر%' | شروع با امیر |
'%امیر' | پایان با امیر |
'%امیر%' | شامل امیر |
'A_' | A و دقیقاً یک کاراکتر دیگر |
در جدولهای بزرگ، جستوجوی LIKE '%word%' ممکن است از Index معمولی بهخوبی استفاده نکند.
مرتبسازی با ORDER BY
مرتبسازی از جدید به قدیم:
SELECT
id,
title,
created_at
FROM conversations
ORDER BY created_at DESC;
مرتبسازی صعودی:
SELECT
id,
name
FROM users
ORDER BY name ASC;
مرتبسازی براساس چند ستون:
SELECT
conversation_id,
role,
created_at
FROM messages
ORDER BY
conversation_id ASC,
created_at ASC;
محدود کردن نتیجه با LIMIT
دریافت ۱۰ مکالمه جدید:
SELECT
id,
title,
created_at
FROM conversations
ORDER BY created_at DESC
LIMIT 10;
صفحه دوم با ۱۰ نتیجه در هر صفحه:
SELECT
id,
title
FROM conversations
ORDER BY id DESC
LIMIT 10
OFFSET 10;
در دادههای بسیار بزرگ، Pagination مبتنی بر Cursor معمولاً از OFFSETهای بزرگ کارآمدتر است.
ویرایش اطلاعات با UPDATE
غیرفعال کردن یک کاربر:
UPDATE users
SET is_active = FALSE
WHERE id = 1;
تغییر عنوان مکالمه:
UPDATE conversations
SET title = 'آموزش پیشرفته SQL'
WHERE id = 1;
اگر WHERE را حذف کنید، همه رکوردهای جدول تغییر میکنند:
UPDATE users
SET is_active = FALSE;
به همین دلیل بهتر است پیش از اجرای UPDATE ابتدا شرط را با SELECT بررسی کنید:
SELECT *
FROM users
WHERE id = 1;
حذف اطلاعات با DELETE
حذف یک پیام:
DELETE FROM messages
WHERE id = 10;
حذف مکالمه:
DELETE FROM conversations
WHERE id = 1;
بدون WHERE تمام رکوردهای جدول حذف میشوند:
DELETE FROM messages;
برای عملیات حساس، Backup، Transaction و محدود کردن سطح دسترسی کاربر پایگاه داده اهمیت زیادی دارد.
مقدار NULL در SQL
NULL به معنی نبود مقدار است و با رشته خالی یا عدد صفر تفاوت دارد.
پیامهایی که Model ID ندارند:
SELECT
id,
content
FROM messages
WHERE model_id IS NULL;
استفاده از این شرط نادرست است:
WHERE model_id = NULL
شرط درست:
WHERE model_id IS NULL
و برای مقدار غیرخالی:
WHERE model_id IS NOT NULL
توابع آماری SQL
تعداد کل پیامها:
SELECT COUNT(*) AS message_count
FROM messages;
مجموع توکن خروجی:
SELECT
SUM(output_tokens)
AS total_output_tokens
FROM messages;
میانگین توکن خروجی:
SELECT
AVG(output_tokens)
AS average_output_tokens
FROM messages
WHERE output_tokens IS NOT NULL;
کمترین و بیشترین مقدار:
SELECT
MIN(output_tokens)
AS minimum_tokens,
MAX(output_tokens)
AS maximum_tokens
FROM messages;
GROUP BY چیست؟
تعداد پیامها براساس Role:
SELECT
role,
COUNT(*) AS message_count
FROM messages
GROUP BY role;
تعداد پیامهای هر مکالمه:
SELECT
conversation_id,
COUNT(*) AS message_count
FROM messages
GROUP BY conversation_id
ORDER BY message_count DESC;
فیلتر نتیجه گروهبندی با HAVING:
SELECT
conversation_id,
COUNT(*) AS message_count
FROM messages
GROUP BY conversation_id
HAVING COUNT(*) >= 5;
تفاوت WHERE و HAVING:
WHEREرکوردها را قبل از گروهبندی فیلتر میکند.HAVINGنتیجه گروهبندی را فیلتر میکند.
JOIN چیست؟
JOIN اطلاعات چند جدول مرتبط را در یک Query ترکیب میکند.
INNER JOIN
نمایش مکالمه همراه نام کاربر:
SELECT
conversations.id,
conversations.title,
users.name
FROM conversations
INNER JOIN users
ON users.id =
conversations.user_id;
فقط رکوردهایی برگردانده میشوند که در هر دو جدول ارتباط معتبر دارند.
LEFT JOIN
نمایش همه کاربران حتی اگر مکالمهای نداشته باشند:
SELECT
users.id,
users.name,
conversations.title
FROM users
LEFT JOIN conversations
ON conversations.user_id =
users.id;
JOIN سه جدول
نمایش پیامها همراه عنوان مکالمه و نام کاربر:
SELECT
messages.id,
users.name AS user_name,
conversations.title,
messages.role,
messages.content,
messages.created_at
FROM messages
INNER JOIN conversations
ON conversations.id =
messages.conversation_id
INNER JOIN users
ON users.id =
conversations.user_id
ORDER BY messages.created_at ASC;
ساختار و انواع JOIN در مستندات رسمی MySQL توضیح داده شده است.
Alias در SQL
برای کوتاهتر شدن Query میتوان از Alias استفاده کرد:
SELECT
m.id,
u.name,
c.title,
m.role,
m.content
FROM messages AS m
INNER JOIN conversations AS c
ON c.id = m.conversation_id
INNER JOIN users AS u
ON u.id = c.user_id;
Alias فقط در همان Query معتبر است و نام اصلی جدول را تغییر نمیدهد.
Index چیست؟
Index ساختاری برای سریعتر شدن بعضی جستوجوهاست.
برای بازیابی پیامهای یک مکالمه:
CREATE INDEX
idx_messages_conversation_created
ON messages (
conversation_id,
created_at
);
برای جستوجوی Model ID:
CREATE INDEX
idx_messages_model_id
ON messages (
model_id
);
Index همیشه مفید نیست. هر Index:
- فضای ذخیرهسازی مصرف میکند.
- عملیات
INSERTرا کمی سنگینتر میکند. - عملیات
UPDATEوDELETEرا تحت تأثیر قرار میدهد. - باید براساس Queryهای واقعی ساخته شود.
ترتیب ستونها در Composite Index اهمیت دارد:
INDEX (
conversation_id,
created_at
)
این Index برای Query زیر مناسب است:
SELECT *
FROM messages
WHERE conversation_id = 10
ORDER BY created_at DESC;
بررسی Query با EXPLAIN
برای مشاهده برنامه اجرای Query:
EXPLAIN
SELECT
id,
role,
content,
created_at
FROM messages
WHERE conversation_id = 10
ORDER BY created_at DESC
LIMIT 20;
EXPLAIN کمک میکند متوجه شوید:
- چه جدولی خوانده میشود.
- چه Indexی انتخاب شده است.
- تقریباً چند رکورد بررسی میشود.
- آیا Full Table Scan رخ میدهد.
- ترتیب JOIN چگونه است.
نباید فقط با دیدن یک Query کند، بهصورت تصادفی Index اضافه کنید. ابتدا الگوی Query و خروجی EXPLAIN را بررسی کنید.
Transaction چیست؟
Transaction چند عملیات وابسته را بهعنوان یک واحد منطقی اجرا میکند.
فرض کنید میخواهیم پیام کاربر و پاسخ دستیار را با هم ذخیره کنیم:
START TRANSACTION;
INSERT INTO messages (
conversation_id,
role,
content
)
VALUES (
1,
'user',
'SQL چیست؟'
);
INSERT INTO messages (
conversation_id,
role,
content,
model_id
)
VALUES (
1,
'assistant',
'SQL زبان کار با پایگاه داده است.',
'YOUR_MODEL_ID'
);
COMMIT;
اگر عملیات دوم شکست بخورد:
ROLLBACK;
Transaction مانع باقی ماندن وضعیت ناقص میشود.
ویژگیهای شناختهشده Transaction با عبارت ACID بیان میشوند:
| ویژگی | مفهوم |
|---|---|
| Atomicity | همه عملیات انجام شوند یا هیچکدام |
| Consistency | داده از حالت معتبر به حالت معتبر برود |
| Isolation | تراکنشهای همزمان کمترین تداخل نامطلوب را داشته باشند |
| Durability | پس از Commit داده پایدار بماند |
SQL Injection چیست؟
SQL Injection زمانی رخ میدهد که ورودی کاربر بدون جداسازی مناسب داخل Query قرار بگیرد.
نمونه نادرست:
email = request.query_params["email"]
query = (
"SELECT * FROM users "
f"WHERE email = '{email}'"
)
کاربر میتواند بخشی از Query را تغییر دهد.
روش مناسب استفاده از Parameter است:
cursor.execute(
"""
SELECT id, name, email
FROM users
WHERE email = %s
""",
(email,),
)
در SQLAlchemy یا SQLModel نیز شرطها بهصورت جداگانه ساخته میشوند:
statement = (
select(User)
.where(User.email == email)
)
ورودی کاربر را برای نام جدول، نام ستون یا قطعه SQL آزاد استفاده نکنید. Parameterها معمولاً برای Valueها هستند، نه ساختار Query.
ORM چیست؟
ORM مخفف Object-Relational Mapping است و جدولها را به Classهای زبان برنامهنویسی تبدیل میکند.
جدول SQL:
CREATE TABLE conversations (
id BIGINT PRIMARY KEY,
title VARCHAR(200)
);
مدل Python:
class Conversation(
SQLModel,
table=True
):
id: int | None = Field(
default=None,
primary_key=True
)
title: str
ORM باعث حذف نیاز به یادگیری SQL نمیشود. برای طراحی Schema، بررسی Performance، ساخت Index و تحلیل Query همچنان دانش SQL ضروری است.
FastAPI شما را به یک پایگاه داده یا ORM خاص محدود نمیکند. ابزارهایی مانند SQLModel و SQLAlchemy میتوانند با پایگاههای داده مختلف کار کنند. راهنمای پایگاه داده SQL در FastAPI
پروژه عملی: ذخیره مکالمات هوش مصنوعی
اکنون یک API میسازیم که:
- مکالمه جدید ایجاد میکند.
- پیام کاربر را دریافت میکند.
- تاریخچه مکالمه را از MySQL میخواند.
- درخواست را به API درواره میفرستد.
- پاسخ مدل را دریافت میکند.
- پیام کاربر و پاسخ مدل را داخل یک Transaction ذخیره میکند.
- تاریخچه مکالمه را برمیگرداند.
معماری:
Client
|
v
FastAPI
|
+----> MySQL
|
+----> API درواره
|
v
مدل هوش مصنوعی
ساختار پروژه
sql-ai-chat/
├── app/
│ ├── __init__.py
│ ├── database.py
│ ├── main.py
│ ├── models.py
│ └── schemas.py
├── .dockerignore
├── .env
├── .env.example
├── .gitignore
├── compose.yaml
├── Dockerfile
└── requirements.txt
ساخت پوشهها:
mkdir -p sql-ai-chat/app
cd sql-ai-chat
touch app/__init__.py
در Windows میتوانید پوشه و فایلها را از طریق File Explorer یا VS Code ایجاد کنید.
تعریف وابستگیها
فایل requirements.txt:
fastapi>=0.115,<1.0
uvicorn[standard]>=0.34,<1.0
sqlmodel>=0.0.24,<1.0
sqlalchemy>=2.0,<3.0
pymysql>=1.1,<2.0
cryptography>=44,<46
openai>=1.68,<3.0
python-dotenv>=1.1,<2.0
نصب در محیط مجازی:
python -m venv .venv
فعالسازی در Linux یا macOS:
source .venv/bin/activate
فعالسازی در Windows PowerShell:
.venv\Scripts\Activate.ps1
نصب Packageها:
python -m pip install \
--upgrade pip
python -m pip install \
-r requirements.txt
تنظیم متغیرهای محیطی
فایل .env:
DATABASE_URL=mysql+pymysql://ai_user:ai_password@localhost:3306/ai_chat?charset=utf8mb4
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
APP_TOKEN=YOUR_LONG_RANDOM_APP_TOKEN
فایل .env.example:
DATABASE_URL=mysql+pymysql://USERNAME:PASSWORD@HOST:3306/DATABASE?charset=utf8mb4
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
APP_TOKEN=YOUR_LONG_RANDOM_APP_TOKEN
فایل .gitignore:
.env
.venv/
__pycache__/
*.pyc
.pytest_cache/
.idea/
.vscode/
برای دریافت کلید API در درواره ثبتنام کنید. شناسه مدل را نیز از صفحه مدلها و قیمتها بردارید.
اتصال FastAPI به MySQL
فایل app/database.py:
import os
from collections.abc import Generator
from dotenv import load_dotenv
from sqlmodel import Session, create_engine
load_dotenv()
DATABASE_URL = os.environ["DATABASE_URL"]
engine = create_engine(
DATABASE_URL,
echo=False,
pool_pre_ping=True,
pool_recycle=1800,
)
def get_session() -> Generator[
Session,
None,
None,
]:
with Session(engine) as session:
yield session
pool_pre_ping
پیش از استفاده از Connection، معتبر بودن آن را بررسی میکند:
pool_pre_ping=True
این تنظیم میتواند خطاهای ناشی از Connectionهای قدیمی یا بستهشده را کاهش دهد.
pool_recycle
Connectionهای قدیمی را پس از زمان مشخص بازیابی میکند:
pool_recycle=1800
مقدار مناسب باید براساس تنظیمات MySQL و زیرساخت انتخاب شود.
ساخت مدلهای پایگاه داده
فایل app/models.py:
from datetime import UTC, datetime
from sqlalchemy import Index
from sqlmodel import Field, SQLModel
def utc_now() -> datetime:
return (
datetime
.now(UTC)
.replace(tzinfo=None)
)
class Conversation(
SQLModel,
table=True,
):
__tablename__ = "conversations"
id: int | None = Field(
default=None,
primary_key=True,
)
title: str = Field(
max_length=200,
)
created_at: datetime = Field(
default_factory=utc_now,
nullable=False,
)
class Message(
SQLModel,
table=True,
):
__tablename__ = "messages"
__table_args__ = (
Index(
"idx_messages_conversation_created",
"conversation_id",
"created_at",
),
)
id: int | None = Field(
default=None,
primary_key=True,
)
conversation_id: int = Field(
foreign_key="conversations.id",
index=True,
)
role: str = Field(
max_length=20,
)
content: str
model_id: str | None = Field(
default=None,
max_length=255,
)
input_tokens: int | None = Field(
default=None,
)
output_tokens: int | None = Field(
default=None,
)
created_at: datetime = Field(
default_factory=utc_now,
nullable=False,
)
در این مدل، Conversation و Message دو جدول مستقل هستند. هر Message با conversation_id به یک مکالمه متصل میشود.
ساخت Schemaهای ورودی و خروجی
فایل app/schemas.py:
from datetime import datetime
from pydantic import BaseModel, Field
class ConversationCreate(
BaseModel,
):
title: str = Field(
min_length=1,
max_length=200,
)
class ConversationRead(
BaseModel,
):
id: int
title: str
created_at: datetime
class ChatRequest(
BaseModel,
):
message: str = Field(
min_length=1,
max_length=8000,
)
temperature: float = Field(
default=0.3,
ge=0,
le=2,
)
max_tokens: int = Field(
default=800,
ge=1,
le=2000,
)
class MessageRead(
BaseModel,
):
id: int
conversation_id: int
role: str
content: str
model_id: str | None
input_tokens: int | None
output_tokens: int | None
created_at: datetime
class ChatResponse(
BaseModel,
):
conversation_id: int
user_message: MessageRead
assistant_message: MessageRead
Schema ورودی از مدل Database جدا شده است تا Client نتواند فیلدهایی مانند id، role یا model_id را آزادانه تعیین کند.
ساخت API اصلی
فایل app/main.py:
import os
import secrets
from contextlib import asynccontextmanager
from typing import Annotated
from dotenv import load_dotenv
from fastapi import (
Depends,
FastAPI,
HTTPException,
Query,
status,
)
from fastapi.security import (
HTTPAuthorizationCredentials,
HTTPBearer,
)
from openai import OpenAI
from sqlmodel import Session, SQLModel, select
from app.database import engine, get_session
from app.models import Conversation, Message
from app.schemas import (
ChatRequest,
ChatResponse,
ConversationCreate,
ConversationRead,
MessageRead,
)
load_dotenv()
DARVAREH_API_KEY = os.environ[
"DARVAREH_API_KEY"
]
DARVAREH_MODEL_ID = os.environ[
"DARVAREH_MODEL_ID"
]
APP_TOKEN = os.environ[
"APP_TOKEN"
]
security = HTTPBearer(
auto_error=False,
)
ai_client = OpenAI(
api_key=DARVAREH_API_KEY,
base_url="https://api.darvareh.ir/v1",
timeout=90.0,
max_retries=2,
)
SessionDep = Annotated[
Session,
Depends(get_session),
]
def require_app_token(
credentials: Annotated[
HTTPAuthorizationCredentials | None,
Depends(security),
],
) -> None:
if credentials is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authorization header is required",
)
valid_scheme = (
credentials.scheme.lower()
== "bearer"
)
valid_token = (
secrets.compare_digest(
credentials.credentials,
APP_TOKEN,
)
)
if not valid_scheme or not valid_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid application token",
)
AuthDep = Annotated[
None,
Depends(require_app_token),
]
@asynccontextmanager
async def lifespan(
app: FastAPI,
):
SQLModel.metadata.create_all(
engine
)
yield
ai_client.close()
app = FastAPI(
title="SQL AI Chat API",
version="1.0.0",
lifespan=lifespan,
)
@app.get("/health")
def health():
return {
"status": "ok",
"service": "sql-ai-chat",
}
@app.post(
"/conversations",
response_model=ConversationRead,
status_code=status.HTTP_201_CREATED,
)
def create_conversation(
payload: ConversationCreate,
session: SessionDep,
_: AuthDep,
):
conversation = Conversation(
title=payload.title.strip(),
)
session.add(conversation)
session.commit()
session.refresh(conversation)
return conversation
@app.get(
"/conversations",
response_model=list[ConversationRead],
)
def list_conversations(
session: SessionDep,
_: AuthDep,
offset: Annotated[
int,
Query(ge=0),
] = 0,
limit: Annotated[
int,
Query(ge=1, le=100),
] = 20,
):
statement = (
select(Conversation)
.order_by(
Conversation.id.desc()
)
.offset(offset)
.limit(limit)
)
return list(
session.exec(statement).all()
)
@app.get(
"/conversations/{conversation_id}/messages",
response_model=list[MessageRead],
)
def list_messages(
conversation_id: int,
session: SessionDep,
_: AuthDep,
limit: Annotated[
int,
Query(ge=1, le=100),
] = 50,
):
conversation = session.get(
Conversation,
conversation_id,
)
if conversation is None:
raise HTTPException(
status_code=404,
detail="Conversation not found",
)
statement = (
select(Message)
.where(
Message.conversation_id
== conversation_id
)
.order_by(
Message.id.desc()
)
.limit(limit)
)
messages = list(
session.exec(statement).all()
)
messages.reverse()
return messages
@app.post(
"/conversations/{conversation_id}/messages",
response_model=ChatResponse,
)
def send_message(
conversation_id: int,
payload: ChatRequest,
session: SessionDep,
_: AuthDep,
):
conversation = session.get(
Conversation,
conversation_id,
)
if conversation is None:
raise HTTPException(
status_code=404,
detail="Conversation not found",
)
history_statement = (
select(Message)
.where(
Message.conversation_id
== conversation_id
)
.order_by(
Message.id.desc()
)
.limit(20)
)
history = list(
session
.exec(history_statement)
.all()
)
history.reverse()
model_messages = [
{
"role": "system",
"content": (
"شما یک دستیار فارسی دقیق و "
"کاربردی هستید. پاسخ را روشن "
"و بدون اطلاعات ساختگی ارائه کنید."
),
}
]
for message in history:
if message.role not in {
"user",
"assistant",
}:
continue
model_messages.append(
{
"role": message.role,
"content": message.content,
}
)
model_messages.append(
{
"role": "user",
"content": payload.message,
}
)
try:
ai_response = (
ai_client
.chat
.completions
.create(
model=DARVAREH_MODEL_ID,
messages=model_messages,
temperature=(
payload.temperature
),
max_tokens=(
payload.max_tokens
),
)
)
except Exception as exc:
raise HTTPException(
status_code=502,
detail=(
"AI service request failed"
),
) from exc
answer = (
ai_response
.choices[0]
.message
.content
or ""
)
usage = ai_response.usage
user_message = Message(
conversation_id=conversation_id,
role="user",
content=payload.message,
)
assistant_message = Message(
conversation_id=conversation_id,
role="assistant",
content=answer,
model_id=DARVAREH_MODEL_ID,
input_tokens=(
usage.prompt_tokens
if usage
else None
),
output_tokens=(
usage.completion_tokens
if usage
else None
),
)
try:
session.add(user_message)
session.add(assistant_message)
session.commit()
session.refresh(
user_message
)
session.refresh(
assistant_message
)
except Exception:
session.rollback()
raise
return ChatResponse(
conversation_id=conversation_id,
user_message=user_message,
assistant_message=(
assistant_message
),
)
چرا پیامها بعد از پاسخ مدل ذخیره میشوند؟
در این پیادهسازی ابتدا پاسخ مدل دریافت و سپس پیام کاربر و پاسخ دستیار داخل یک Transaction ذخیره میشوند.
اگر درخواست مدل شکست بخورد، پیام ناقص داخل تاریخچه ثبت نمیشود.
اگر بخواهید پیام کاربر حتی در صورت شکست مدل ذخیره شود، باید وضعیت پردازش را نیز نگهداری کنید:
pending
completed
failed
این تصمیم به طراحی محصول بستگی دارد.
چرا فقط ۲۰ پیام آخر ارسال میشود؟
در کد:
.limit(20)
ارسال تاریخچه نامحدود مشکلات زیر را ایجاد میکند:
- افزایش Token ورودی
- افزایش هزینه
- افزایش زمان پاسخ
- عبور از Context Window
- ورود اطلاعات قدیمی و نامرتبط
- بزرگ شدن Request
در محصول واقعی میتوان پیامهای قدیمی را خلاصه یا براساس ارتباط انتخاب کرد.
اجرای MySQL پروژه
برای اجرای محلی MySQL میتوانید از فایل Docker قبلی استفاده کنید یا کل پروژه را با Compose اجرا کنید.
اگر MySQL روی Host اجرا میشود، مقدار زیر درست است:
DATABASE_URL=mysql+pymysql://ai_user:ai_password@localhost:3306/ai_chat?charset=utf8mb4
اجرای API:
uvicorn app.main:app \
--reload \
--host 0.0.0.0 \
--port 8000
مستندات Swagger:
http://localhost:8000/docs
Health Check:
curl \
http://localhost:8000/health
ایجاد Conversation
curl -X POST \
http://localhost:8000/conversations \
-H "Authorization: Bearer YOUR_LONG_RANDOM_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "آموزش SQL"
}'
پاسخ:
{
"id": 1,
"title": "آموزش SQL",
"created_at": "2026-08-09T12:00:00"
}
ارسال پیام به مدل
curl -X POST \
http://localhost:8000/conversations/1/messages \
-H "Authorization: Bearer YOUR_LONG_RANDOM_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "تفاوت INNER JOIN و LEFT JOIN چیست؟",
"temperature": 0.2,
"max_tokens": 700
}'
پاسخ شامل پیام کاربر و دستیار است:
{
"conversation_id": 1,
"user_message": {
"id": 1,
"conversation_id": 1,
"role": "user",
"content": "تفاوت INNER JOIN و LEFT JOIN چیست؟",
"model_id": null,
"input_tokens": null,
"output_tokens": null,
"created_at": "2026-08-09T12:01:00"
},
"assistant_message": {
"id": 2,
"conversation_id": 1,
"role": "assistant",
"content": "INNER JOIN فقط رکوردهای مشترک را...",
"model_id": "YOUR_MODEL_ID",
"input_tokens": 120,
"output_tokens": 250,
"created_at": "2026-08-09T12:01:02"
}
}
دریافت تاریخچه مکالمه
curl \
http://localhost:8000/conversations/1/messages \
-H "Authorization: Bearer YOUR_LONG_RANDOM_APP_TOKEN"
محدود کردن تعداد پیامها:
curl \
"http://localhost:8000/conversations/1/messages?limit=20" \
-H "Authorization: Bearer YOUR_LONG_RANDOM_APP_TOKEN"
بررسی مستقیم داده در MySQL
ورود به MySQL:
docker exec -it \
sql-tutorial-mysql \
mysql -uai_user -pai_password ai_chat
مشاهده مکالمات:
SELECT
id,
title,
created_at
FROM conversations
ORDER BY id DESC;
مشاهده پیامها:
SELECT
id,
conversation_id,
role,
LEFT(content, 100)
AS content_preview,
model_id,
input_tokens,
output_tokens,
created_at
FROM messages
ORDER BY id DESC;
گزارش مصرف هر مکالمه:
SELECT
conversation_id,
COUNT(*) AS message_count,
SUM(input_tokens)
AS total_input_tokens,
SUM(output_tokens)
AS total_output_tokens
FROM messages
WHERE role = 'assistant'
GROUP BY conversation_id
ORDER BY conversation_id DESC;
ساخت Dockerfile
فایل Dockerfile:
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
--upgrade \
-r requirements.txt
COPY app ./app
RUN useradd \
--create-home \
--uid 10001 \
appuser
USER appuser
EXPOSE 8000
CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]
ساخت .dockerignore
.env
.venv
__pycache__
*.pyc
.git
.gitignore
.idea
.vscode
اجرای کامل FastAPI و MySQL با Docker Compose
فایل compose.yaml:
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: ai_chat
MYSQL_USER: ai_user
MYSQL_PASSWORD: ai_password
volumes:
- mysql_data:/var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
healthcheck:
test:
[
"CMD",
"mysqladmin",
"ping",
"-h",
"localhost",
"-uroot",
"-proot_password"
]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
restart: unless-stopped
api:
build:
context: .
environment:
DATABASE_URL: mysql+pymysql://ai_user:ai_password@db:3306/ai_chat?charset=utf8mb4
DARVAREH_API_KEY: ${DARVAREH_API_KEY}
DARVAREH_MODEL_ID: ${DARVAREH_MODEL_ID}
APP_TOKEN: ${APP_TOKEN}
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
]
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
restart: unless-stopped
volumes:
mysql_data:
در .env فقط Secretهای موردنیاز Compose را قرار دهید:
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
APP_TOKEN=YOUR_LONG_RANDOM_APP_TOKEN
اجرا:
docker compose up \
--build \
--detach
مشاهده وضعیت:
docker compose ps
مشاهده لاگ API:
docker compose logs \
-f api
مشاهده لاگ MySQL:
docker compose logs \
-f db
توقف:
docker compose down
حذف Containerها همراه داده MySQL:
docker compose down -v
گزینه -v دادههای Volume را حذف میکند؛ بنابراین فقط زمانی از آن استفاده کنید که واقعاً قصد حذف داده آزمایشی را دارید.
آیا create_all برای Production مناسب است؟
در پروژه آموزشی از دستور زیر استفاده کردیم:
SQLModel.metadata.create_all(
engine
)
این دستور جدولهای موجود را ایجاد میکند، اما جایگزین کامل Database Migration نیست.
برای تغییرات واقعی Schema بهتر است از Migration استفاده شود:
نسخه ۱:
ساخت conversations
نسخه ۲:
افزودن model_id
نسخه ۳:
افزودن token usage
نسخه ۴:
ساخت index جدید
ابزاری مانند Alembic میتواند این تغییرات را نسخهبندی و اجرا کند. مستندات FastAPI نیز برای محیط Production استفاده از Migration را توصیه میکند.
بهینهسازی Schema مکالمات هوش مصنوعی
برای بزرگتر شدن پروژه میتوانید ستونهای زیر را اضافه کنید.
جدول Conversation:
ALTER TABLE conversations
ADD COLUMN user_id BIGINT UNSIGNED NULL,
ADD COLUMN updated_at DATETIME NULL,
ADD COLUMN archived_at DATETIME NULL;
جدول Message:
ALTER TABLE messages
ADD COLUMN request_id VARCHAR(100) NULL,
ADD COLUMN finish_reason VARCHAR(50) NULL,
ADD COLUMN latency_ms INT UNSIGNED NULL,
ADD COLUMN error_code VARCHAR(100) NULL;
Indexهای احتمالی:
CREATE INDEX
idx_conversations_created
ON conversations (
created_at
);
CREATE INDEX
idx_messages_model_created
ON messages (
model_id,
created_at
);
Index را فقط براساس Query و گزارش واقعی اضافه کنید.
مدیریت همزمانی مکالمات
اگر دو درخواست همزمان برای یک Conversation ارسال شوند، ممکن است هر دو یک تاریخچه یکسان بخوانند و پاسخها با ترتیب غیرمنتظره ذخیره شوند.
راهکارها براساس نیاز پروژه:
- جلوگیری از چند درخواست همزمان برای یک Conversation
- استفاده از Queue
- ثبت وضعیت پردازش
- استفاده از Version Number
- قفلگذاری کنترلشده
- ساخت Request ID منحصربهفرد
- مرتبسازی براساس Sequence Number
برای چت ساده ممکن است محدود کردن یک درخواست فعال برای هر مکالمه کافی باشد.
ذخیره پرامپت سیستم
در پروژه آموزشی System Prompt داخل کد قرار دارد:
{
"role": "system",
"content": "شما یک دستیار فارسی..."
}
در محصول بزرگتر میتوان نسخه پرامپت را ذخیره کرد:
CREATE TABLE prompt_versions (
id BIGINT UNSIGNED
AUTO_INCREMENT
PRIMARY KEY,
name VARCHAR(100)
NOT NULL,
version INT UNSIGNED
NOT NULL,
content TEXT
NOT NULL,
is_active BOOLEAN
NOT NULL
DEFAULT TRUE,
created_at DATETIME
NOT NULL
DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY
uq_prompt_name_version (
name,
version
)
);
این ساختار کمک میکند بدانید هر پاسخ با کدام نسخه پرامپت تولید شده است.
آیا متن کامل مکالمه را ذخیره کنیم؟
این تصمیم به کاربرد محصول، رضایت کاربر و سیاست نگهداری داده بستگی دارد.
پیش از ذخیرهسازی باید موارد زیر مشخص شوند:
- هدف ذخیره اطلاعات
- مدت نگهداری
- امکان حذف توسط کاربر
- سطح دسترسی کارکنان
- دادههای حساس
- Backup
- Logging
- محیط توسعه و Production
- سیاست حریم خصوصی محصول
از ذخیره اطلاعات حساس غیرضروری خودداری کنید و دسترسی پایگاه داده را محدود نگه دارید.
MySQL یا PostgreSQL؟
هر دو برای بسیاری از برنامهها مناسب هستند.
| معیار | MySQL | PostgreSQL |
|---|---|---|
| پایگاه داده رابطهای | بله | بله |
| پشتیبانی از Transaction | بله | بله |
| JSON | بله | بسیار قدرتمند |
| یادگیری اولیه | نسبتاً ساده | نسبتاً ساده |
| اکوسیستم میزبانی | گسترده | گسترده |
| قابلیتهای پیشرفته SQL | مناسب | بسیار گسترده |
| پروژه چت و API | مناسب | مناسب |
انتخاب باید براساس تجربه تیم، زیرساخت، نیازهای Query، امکانات میزبانی و مقیاس پروژه انجام شود.
SQL یا NoSQL؟
هیچکدام همیشه بهتر نیستند.
SQL برای این موارد مناسب است:
- دادههای دارای ارتباط مشخص
- Transaction
- گزارشگیری ساختاریافته
- محدودیتهای دقیق
- کاربران، سفارشها و پرداختها
- مدیریت مصرف و صورتحساب
- مکالمات دارای Metadata
NoSQL میتواند برای این موارد مناسب باشد:
- Schema بسیار متغیر
- Documentهای مستقل
- بعضی بارهای کاری توزیعشده
- دادههایی با ساختار غیرثابت
بسیاری از سیستمهای واقعی از ترکیب چند نوع ذخیرهساز استفاده میکنند:
MySQL یا PostgreSQL:
داده اصلی و تراکنشها
Redis:
Cache و داده موقت
Vector Database:
Embedding و جستوجوی معنایی
Object Storage:
فایل و سند
خطاهای رایج MySQL و FastAPI
خطای Connection refused
موارد زیر را بررسی کنید:
- MySQL اجرا شده باشد.
- Host درست باشد.
- پورت
3306درست باشد. - Containerها در شبکه مشترک باشند.
- نام سرویس Docker بهجای
localhostاستفاده شود.
داخل Docker Compose:
db
روی سیستم Host:
localhost
خطای Access denied
نام کاربری و رمز را بررسی کنید:
DATABASE_URL=mysql+pymysql://ai_user:ai_password@db:3306/ai_chat
اگر Volume قبلاً با رمز دیگری ساخته شده باشد، تغییر Environment Variable الزاماً رمز Database موجود را تغییر نمیدهد.
خطای Unknown database
پایگاه داده ساخته نشده یا نام آن اشتباه است:
SHOW DATABASES;
نمایش نادرست متن فارسی
از utf8mb4 استفاده کنید:
?charset=utf8mb4
و در MySQL:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
خطای Table does not exist
بررسی کنید:
- Lifespan اجرا شده باشد.
- برنامه به Database درست متصل باشد.
- کاربر اجازه ساخت جدول داشته باشد.
- Migration اجرا شده باشد.
کند شدن Query پیامها
ابتدا Query را با EXPLAIN بررسی کنید:
EXPLAIN
SELECT *
FROM messages
WHERE conversation_id = 100
ORDER BY created_at DESC
LIMIT 20;
سپس وجود Index مناسب را بررسی کنید:
SHOW INDEX
FROM messages;
خطای 502 از Endpoint چت
موارد احتمالی:
- کلید درواره اشتباه است.
- Model ID معتبر نیست.
- ارتباط شبکه برقرار نشده است.
- پاسخ مدل Timeout شده است.
- موجودی یا محدودیت حساب نیاز به بررسی دارد.
چکلیست یادگیری SQL
- تفاوت SQL و MySQL را میدانم.
- میتوانم Database و Table بسازم.
- Primary Key را میشناسم.
- Foreign Key را میشناسم.
- با
INSERTداده اضافه میکنم. - با
SELECTداده میخوانم. - با
WHEREفیلتر میکنم. - با
ORDER BYمرتب میکنم. - با
LIMITنتیجه را محدود میکنم. - با
UPDATEداده را تغییر میدهم. - با
DELETEرکورد حذف میکنم. - تفاوت
NULLو رشته خالی را میدانم. - از
GROUP BYاستفاده میکنم. - تفاوت
WHEREوHAVINGرا میدانم. INNER JOINوLEFT JOINرا میشناسم.- مفهوم Index را میدانم.
- Query را با
EXPLAINبررسی میکنم. - Transaction و Rollback را میشناسم.
- از Query پارامتری استفاده میکنم.
- Secret را داخل Source Code قرار نمیدهم.
- برای تغییر Schema از Migration استفاده میکنم.
پرسشهای متداول
SQL چیست؟
SQL زبان استاندارد کار با پایگاههای داده رابطهای است. با آن میتوان جدول ساخت، اطلاعات را خواند، تغییر داد، حذف کرد و میان جدولها ارتباط ایجاد کرد.
تفاوت SQL و MySQL چیست؟
SQL یک زبان است، اما MySQL نرمافزاری است که Queryهای SQL را اجرا و دادهها را مدیریت میکند.
آیا یادگیری SQL سخت است؟
دستورات پایه مانند SELECT، INSERT، UPDATE و DELETE سادهاند. طراحی Schema، بهینهسازی Query، Transaction و مدیریت همزمانی به تمرین بیشتری نیاز دارند.
برای یادگیری SQL از MySQL استفاده کنیم یا SQLite؟
SQLite برای شروع سریع و پروژههای کوچک مناسب است، زیرا به سرور جداگانه نیاز ندارد. MySQL تجربه نزدیکتری به بسیاری از برنامههای Backend و محیطهای عملیاتی ارائه میدهد.
آیا برای هوش مصنوعی باید SQL بلد باشیم؟
برای استفاده ساده از ابزارهای هوش مصنوعی الزامی نیست، اما برای ساخت محصولات واقعی، ذخیره داده، مدیریت کاربران، تحلیل مصرف و نگهداری تاریخچه مکالمات بسیار مفید است.
آیا SQL میتواند متن فارسی ذخیره کند؟
بله. در MySQL بهتر است از Character Set مناسب مانند utf8mb4 استفاده شود.
آیا ORM جای SQL را میگیرد؟
خیر. ORM نوشتن بخشی از کد را ساده میکند، اما برای طراحی جدول، ساخت Index، تحلیل Performance و رفع خطا همچنان دانش SQL لازم است.
آیا SQLModel فقط با SQLite کار میکند؟
خیر. SQLModel روی SQLAlchemy ساخته شده و میتواند با پایگاههای دادهای که Driver سازگار دارند کار کند. در این آموزش از MySQL و PyMySQL استفاده کردیم.
چطور تاریخچه چت را ذخیره کنیم؟
معمولاً یک جدول Conversation و یک جدول Message ساخته میشود. هر Message با Foreign Key به Conversation متصل است و فیلدهایی مانند Role، Content، Model ID و Token Usage دارد.
آیا باید همه پیامهای قبلی را برای مدل ارسال کنیم؟
خیر. ارسال تاریخچه نامحدود باعث افزایش هزینه و مصرف Context میشود. میتوان تعداد مشخصی از پیامهای اخیر را ارسال یا پیامهای قدیمی را خلاصه کرد.
هزینه استفاده از مدل چگونه محاسبه میشود؟
هزینه به مدل، تعداد Token ورودی و خروجی و نوع درخواست بستگی دارد. برای مشاهده اطلاعات بهروز، صفحه مدلها و قیمتهای درواره را بررسی کنید.
جمعبندی
SQL یکی از بنیادیترین مهارتها در برنامهنویسی و توسعه محصولات هوش مصنوعی است. با SQL میتوانید دادههای ساختاریافته را ذخیره، جستوجو، ویرایش، حذف و گزارشگیری کنید.
در این مقاله موارد زیر را یاد گرفتیم:
- SQL و پایگاه داده رابطهای چیست.
- تفاوت SQL و MySQL چیست.
- چگونه Database و Table بسازیم.
- Primary Key و Foreign Key چه هستند.
- چگونه از CRUD استفاده کنیم.
WHERE،LIKE،ORDER BYوLIMITچگونه کار میکنند.GROUP BYو توابع آماری چه کاربردی دارند.INNER JOINوLEFT JOINچه تفاوتی دارند.- Index و
EXPLAINچگونه به Performance کمک میکنند. - Transaction و Rollback چه هستند.
- چگونه از SQL Injection جلوگیری کنیم.
- چگونه FastAPI را به MySQL متصل کنیم.
- چگونه تاریخچه مکالمات هوش مصنوعی را ذخیره کنیم.
- چگونه Python، SQLModel و API درواره را در یک پروژه واقعی ترکیب کنیم.
- چگونه پروژه را با Docker Compose اجرا کنیم.
برای دریافت کلید API و ساخت برنامههای متصل به مدلهای هوش مصنوعی، وارد درواره شوید. برای انتخاب مدل مناسب و مشاهده قیمتهای فعلی نیز صفحه مدلها و قیمتهای درواره را ببینید.
منابع
- مستندات رسمی MySQL 8.4
- مستندات SELECT در MySQL
- مستندات JOIN در MySQL
- راهنمای پایگاه داده SQL در FastAPI
- مستندات SQLAlchemy
- مستندات SQLModel
مقالات مرتبط
- PostgreSQL چیست؟ آموزش SQL و JSONB با Python و FastAPI
- تبدیل متن به SQL با هوش مصنوعی
- آموزش Migration پایگاه داده با PostgreSQL، SQLAlchemy و Alembic
- MongoDB چیست؟ آموزش NoSQL با Python و FastAPI
- Redis چیست؟ آموزش Cache و Queue با Python و FastAPI
- ساخت API آماده Production برای هوش مصنوعی
- آموزش اتصال API هوش مصنوعی به اپلیکیشن
- آموزش استفاده از API درواره با cURL
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.