Menjana dan Mengesahkan Pertanyaan SQL
Corak gesaan untuk SQL yang selamat: mod SELECT sahaja dan pertanyaan berparameter.
Menjana dan Mengesahkan Pertanyaan SQL ialah pelajaran Ejen AI percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Ejen AI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Ejen AI merangkumi sejumlah 4 pelajaran.
Matlamat Penjanaan SQL
Menjana pertanyaan SQL hanyalah separuh daripada tugas. Sebelum melaksanakannya terhadap pangkalan data sebenar, anda perlu mengesahkan bahawa pertanyaan itu selamat, betul dari segi sintaks, dan melakukan tepat seperti yang dimaksudkan pengguna.
Pelajaran ini merangkumi penguatkuasaan SELECT sahaja, penghuraian, pelaksanaan selamat dan pengesahan pelan EXPLAIN.
Penguatkuasaan Mod SELECT Sahaja
Perkara paling berbahaya yang boleh dilakukan oleh ejen NL-ke-SQL ialah melaksanakan pernyataan yang merosakkan. Sentiasa kuatkuasakan mod SELECT sahaja tanpa mengira perkara yang dikembalikan oleh LLM.
Pemeriksaan rentetan naif tidak mencukupi — gunakan penghuraik SQL yang betul.
import sqlparse
def is_select_only(sql):
parsed = sqlparse.parse(sql)
if not parsed:
return False
for statement in parsed:
stmt_type = statement.get_type()
if stmt_type != 'SELECT':
print(f'Blocked statement type: {stmt_type}')
return False
return True
# Test
print(is_select_only('SELECT * FROM users')) # True
print(is_select_only('DROP TABLE users')) # False — BlockedSenarai Sekatan Kata Kunci sebagai Pertahanan Berlapis
Walaupun menggunakan sqlparse, tambahkan senarai sekatan kata kunci sebagai pertahanan sekunder. Sesetengah suntikan SQL boleh memperdaya penghuraik. Memeriksa kata kunci berbahaya sebelum pelaksanaan menambah lapisan keselamatan tambahan.
DANGEROUS_KEYWORDS = [
'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE',
'ALTER', 'TRUNCATE', 'GRANT', 'REVOKE', 'EXEC',
'EXECUTE', 'CALL', 'MERGE'
]
def passes_blocklist(sql):
sql_upper = sql.upper()
for keyword in DANGEROUS_KEYWORDS:
# Check as whole word to avoid false positives like 'CREATED_AT'
import re
if re.search(r'\b' + keyword + r'\b', sql_upper):
raise ValueError(f'Blocked keyword detected: {keyword}')
return True
def validate_sql(sql):
if not is_select_only(sql):
raise ValueError('Only SELECT statements are allowed')
passes_blocklist(sql)
return TrueMenghuraikan SQL dengan sqlparse
sqlparse memecahkan rentetan SQL kepada token dan menghuraikannya tanpa melaksanakannya. Anda boleh memeriksa struktur pertanyaan, mengekstrak nama jadual dan menyemak isu sintaks.
Pasang dengan pip install sqlparse.
import sqlparse
from sqlparse.sql import IdentifierList, Identifier
from sqlparse.tokens import Keyword, DML
def extract_table_names(sql):
parsed = sqlparse.parse(sql)[0]
tables = []
from_seen = False
for token in parsed.tokens:
if token.ttype is DML and token.value.upper() == 'SELECT':
continue
if token.ttype is Keyword and token.value.upper() in ('FROM', 'JOIN'):
from_seen = True
continue
if from_seen:
if isinstance(token, Identifier):
tables.append(token.get_name())
elif isinstance(token, IdentifierList):
for item in token.get_identifiers():
tables.append(item.get_name())
from_seen = False
return tables
print(extract_table_names('SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id'))
# ['users', 'orders']Mengesahkan Kewujudan Jadual dalam Skema
Selepas mengekstrak nama jadual daripada SQL yang dijana, semak silang nama tersebut dengan skema yang diketahui. Jika LLM mereka-reka nama jadual, tolak pertanyaan itu sebelum pelaksanaan dan bukannya menerima ralat pangkalan data yang sukar difahami.
def validate_tables_exist(sql, known_tables):
used_tables = extract_table_names(sql)
invalid = [t for t in used_tables if t and t not in known_tables]
if invalid:
raise ValueError(
f'Query references non-existent tables: {invalid}. '
f'Available tables: {list(known_tables)[:10]}...'
)
return True
# Usage
known = set(build_schema_dict(conn).keys())
try:
validate_tables_exist(generated_sql, known)
except ValueError as e:
# Send error back to LLM for correction
corrected_sql = llm_fix_sql(generated_sql, str(e))
print('Corrected SQL:', corrected_sql)Pelaksanaan Berparameter
Jangan gunakan pemformatan rentetan untuk menyisipkan nilai yang diberikan pengguna ke dalam SQL. Walaupun LLM menjana pertanyaan tersebut, sebarang nilai penapis yang diberikan pengguna hendaklah dihantar sebagai parameter untuk mencegah suntikan SQL.
import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE orders (status TEXT, user_id INTEGER)')
conn.execute("INSERT INTO orders VALUES ('pending', 42)")
def safe_execute(conn, sql_template, params=()):
"""Execute with parameterized values."""
cur = conn.cursor()
cur.execute(sql_template, params) # driver handles escaping
columns = [d[0] for d in cur.description]
rows = cur.fetchmany(200)
return {'columns': columns, 'rows': rows}
sql = 'SELECT * FROM orders WHERE status = ? AND user_id = ?'
result = safe_execute(conn, sql, params=('pending', 42))
print(result)Pelan EXPLAIN Sebelum Pelaksanaan
Untuk pertanyaan yang mahal terhadap jadual besar, jalankan EXPLAIN sebelum pertanyaan sebenar. Jika perancang menunjukkan imbasan seluruh jadual yang mengandungi sejuta baris, beri amaran kepada pengguna atau tolak pertanyaan tersebut.
def check_explain_plan(conn, sql):
explain_sql = f'EXPLAIN {sql}'
with conn.cursor() as cur:
cur.execute(explain_sql)
plan = '\n'.join(row[0] for row in cur.fetchall())
# Check for sequential scans on large tables
if 'Seq Scan' in plan:
print('WARNING: Query involves a sequential scan')
print(plan)
return {'safe': False, 'plan': plan, 'warning': 'Sequential scan detected'}
return {'safe': True, 'plan': plan}
# Use before executing
plan_result = check_explain_plan(conn, generated_sql)
if not plan_result['safe']:
print(f'Optimization hint: {plan_result["warning"]}')Penguatkuasaan Had Baris
LLM mungkin menjana SELECT * FROM logs tanpa LIMIT, yang berpotensi mengembalikan berjuta-juta baris. Sentiasa kuatkuasakan bilangan baris maksimum — sama ada dengan menambahkan LIMIT pada pertanyaan atau mendapatkan set hasil yang terhad.
import re
MAX_ROWS = 500
def enforce_row_limit(sql, max_rows=MAX_ROWS):
sql_upper = sql.upper().rstrip().rstrip(';')
# Check if LIMIT already present
if re.search(r'\bLIMIT\b', sql_upper):
# Extract current limit and enforce maximum
match = re.search(r'LIMIT\s+(\d+)', sql_upper)
if match:
current = int(match.group(1))
if current > max_rows:
sql = re.sub(r'LIMIT\s+\d+', f'LIMIT {max_rows}', sql, flags=re.IGNORECASE)
else:
sql = sql.rstrip(';') + f' LIMIT {max_rows}'
return sql
print(enforce_row_limit('SELECT * FROM users'))
# SELECT * FROM users LIMIT 500Mengekstrak SQL Bersih daripada Keluaran LLM
LLM sering memulangkan SQL yang dibungkus dalam blok kod markdown (```sql ... ```) atau bersama teks penerangan. Anda perlu mengekstrak SQL mentah sebelum menghuraikan atau melaksanakannya.
import re
CODE_FENCE = chr(96) * 3 # three backticks, built at runtime to avoid template issues
def extract_sql(llm_response):
# Remove markdown code blocks ('''sql ... ''' or ''' ... ''')
pattern = CODE_FENCE + r'(?:sql)?\s*([\s\S]+?)' + CODE_FENCE
match = re.search(pattern, llm_response, re.IGNORECASE)
if match:
return match.group(1).strip()
# If no code block, look for SELECT statement
match = re.search(r'(SELECT\s+[\s\S]+?;)', llm_response, re.IGNORECASE)
if match:
return match.group(1).strip()
# Fallback: strip common preamble phrases
cleaned = re.sub(r'^(Here is|The SQL query is|Query:)[^\n]*\n', '',
llm_response, flags=re.IGNORECASE).strip()
return cleaned
if __name__ == '__main__':
demo_response = 'Here is the SQL query:\n' + CODE_FENCE + 'sql\nSELECT * FROM users;\n' + CODE_FENCE
print(extract_sql(demo_response))
Aliran Pengesahan Lengkap
Rantaikan semua langkah pengesahan ke dalam satu fungsi yang menerima keluaran mentah LLM dan mengembalikan rentetan SQL yang selamat serta boleh dilaksanakan, atau mencetuskan ralat dengan mesej yang jelas untuk pemulihan.
def validate_and_prepare_sql(llm_output, known_tables, max_rows=500):
# Step 1: extract raw SQL
sql = extract_sql(llm_output)
if not sql:
raise ValueError('No SQL found in LLM response')
# Step 2: type check
if not is_select_only(sql):
raise ValueError('Only SELECT queries allowed')
# Step 3: keyword blocklist
passes_blocklist(sql)
# Step 4: table existence check
validate_tables_exist(sql, known_tables)
# Step 5: row limit
sql = enforce_row_limit(sql, max_rows)
return sql
# Full flow
try:
safe_sql = validate_and_prepare_sql(llm_output, known_tables)
result = safe_execute(conn, safe_sql)
except ValueError as e:
corrected = llm_fix_sql(llm_output, str(e))
safe_sql = validate_and_prepare_sql(corrected, known_tables)
result = safe_execute(conn, safe_sql)Pengguna Pangkalan Data Baca Sahaja
Pengesahan pada peringkat kod penting tetapi tidak mencukupi. Sebagai lapisan pertahanan terakhir, sambung ke pangkalan data menggunakan akaun pengguna baca sahaja yang hanya mempunyai keistimewaan SELECT. Walaupun pertanyaan berniat jahat berjaya melepasi semua pemeriksaan, pangkalan data akan menolaknya.
# Create read-only user in PostgreSQL:
# CREATE USER nl_to_sql_reader WITH PASSWORD 'secure_password';
# GRANT CONNECT ON DATABASE yourdb TO nl_to_sql_reader;
# GRANT USAGE ON SCHEMA public TO nl_to_sql_reader;
# GRANT SELECT ON ALL TABLES IN SCHEMA public TO nl_to_sql_reader;
import os
import psycopg2
def get_readonly_connection():
return psycopg2.connect(
host=os.getenv('DB_HOST'),
database=os.getenv('DB_NAME'),
user='nl_to_sql_reader', # read-only account
password=os.getenv('DB_READER_PASS')
)Semakan Pengetahuan
Apakah pendekatan pertahanan berlapis yang betul untuk pengesahan SQL dalam ejen NL-ke-SQL?
Imbas Kembali: Menjana dan Mengesahkan SQL
Penjanaan SQL yang selamat memerlukan aliran pengesahan lengkap: ekstrak SQL bersih daripada keluaran LLM, kuatkuasakan SELECT sahaja menggunakan sqlparse, gunakan senarai sekatan kata kunci, sahkan nama jadual dengan skema sebenar, kuatkuasakan had baris, dan gunakan pengguna pangkalan data baca sahaja sebagai perlindungan terakhir.
Pertanyaan berparameter melindungi daripada suntikan apabila nilai yang diberikan pengguna terlibat. Pemeriksaan pelan EXPLAIN menghalang pertanyaan yang tidak dijangka mahal daripada dijalankan terhadap data pengeluaran.
Pelajari Ejen AI dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 60
- Pelajaran
- 239
Soalan Lazim
Adakah pelajaran “Menjana dan Mengesahkan Pertanyaan SQL” percuma?
Ya — teks penuh “Menjana dan Mengesahkan Pertanyaan SQL” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Ejen AI, tingkat taraf kepada CoddyKit PRO. Kursus Ejen AI merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Menjana dan Mengesahkan Pertanyaan SQL”?
Corak gesaan untuk SQL yang selamat: mod SELECT sahaja dan pertanyaan berparameter. Anda berlatih Ejen AI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Ejen AI?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Ejen AI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.
Berapa lamakah pelajaran “Menjana dan Mengesahkan Pertanyaan SQL” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Ejen AI ini?
Ya. Setiap pelajaran Ejen AI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Cara Ejen NL-ke-SQL Berfungsi
- Memahami dan Menyuntik Skema
- Menjana dan Mengesahkan Pertanyaan SQL
- Mengendalikan Soalan Pangkalan Data yang Kabur