sqlServerConnector 0.1.7__tar.gz → 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlServerConnector
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: A custom SQL Server Connector for ETL processes with Pandas
5
5
  Author-email: Nguyen Minh Son <nguyen.minhson1511@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/johnnyb1509/sqlServerConnector
@@ -20,8 +20,10 @@ Requires-Dist: jupyterlab
20
20
  # SQL Server Connector
21
21
 
22
22
  Thư viện kết nối SQL Server chuyên dụng cho các tác vụ ETL, được tối ưu hóa cho **Pandas**, hỗ trợ **Tiếng Việt (Unicode)** và **Upsert (Merge)** hiệu năng cao.
23
- ## Update 0.1.7
23
+
24
+ ### **Update 0.1.9**
24
25
  > Sửa lỗi nhỏ liên quan đến việc upsert với các bảng có cột chứa Tiếng Việt
26
+ > Thay vì dùng bảng tạm (##Staging), sẽ dùng bảng vật lý tạm thời (Physical Staging Table) có tên chứa UUID (để đảm bảo duy nhất, không trùng lặp giữa các luồng chạy). Sau khi Upsert xong, ta sẽ DROP bảng này ngay lập tức. Cách này tương thích 100% với Pandas và SQLAlchemy.
25
27
 
26
28
 
27
29
  ## 🚀 Tính năng nổi bật
@@ -69,9 +71,10 @@ db_info:
69
71
  ## 📝 Hướng dẫn sử dụng nhanh
70
72
 
71
73
  1. Khởi tạo kết nối
72
- ```python
74
+ ```python
73
75
  import yaml
74
76
  from connector import SQLServerConnector
77
+
75
78
  # Load config
76
79
  with open('config/db_config.yaml', 'r') as f:
77
80
  conf = yaml.safe_load(f)['db_info']
@@ -88,47 +91,63 @@ db = SQLServerConnector(
88
91
  2. Lấy dữ liệu (Read)
89
92
  ```python
90
93
  # Cách 1: Lấy toàn bộ bảng
91
- df = db.get_data("DM_KhachHang")
94
+ df = db.get_data("SELECT * FROM DM_KhachHang")
92
95
 
93
- # Cách 2: Dùng câu lệnh SQL tùy ý
96
+ # Cách 2: Dùng câu lệnh SQL tuy bien
94
97
  query = """
95
- SELECT TOP 100 * FROM Sales_Transaction
96
- WHERE created_date >= '2023-01-01'
98
+ SELECT TOP 100 * FROM Sales_Transaction
99
+ WHERE created_date >= :from_date
97
100
  """
98
- df_sales = db.get_data(query)
101
+ df_sales = db.get_data(query, params={"from_date": "2023-01-01"})
99
102
  print(df_sales.head())
100
103
  ```
101
104
 
102
- 3. Ghi dữ liệu (Upsert)
105
+ 3. Kiểm tra bảng tồn tại
106
+ ```python
107
+ if not db.check_table_exists("Fact_Sales"):
108
+ print("Bang Fact_Sales chua ton tai")
109
+ ```
110
+
111
+ 4. Ghi du lieu (Upsert)
103
112
  ```python
104
113
  import pandas as pd
105
114
 
106
- # Giả lập dữ liệu
115
+ # Gia lap du lieu
107
116
  data = {
108
117
  'TransactionID': [101, 102],
109
- 'Product': ['Laptop Dell', 'Chuột Logitech'], # Hỗ trợ tiếng Việt
118
+ 'Product': ['Laptop Dell', 'Chuot Logitech'], # Ho tro tieng Viet
110
119
  'Amount': [15000000, 250000]
111
120
  }
112
121
  df_new = pd.DataFrame(data)
113
122
 
114
- # Đẩy vào DB
123
+ # Day vao DB
115
124
  db.upsert_data(
116
125
  df=df_new,
117
126
  target_table="Fact_Sales",
118
- primary_key="TransactionID", # Cột dùng để định danh (tránh trùng lặp)
119
- auto_evolve_schema=True # Tự động thêm cột nếu thiếu
127
+ match_columns=["TransactionID"], # Khoa so khop (Primary Key)
128
+ conflict_strategy="last", # "last" hoac "skip"
129
+ auto_evolve_schema=True # Tu dong them cot neu thieu
130
+ )
131
+ print("Du lieu da duoc upsert thanh cong!")
132
+ ```
133
+
134
+ 5. Thuc thi cau lenh khong tra ve du lieu
135
+ ```python
136
+ # Vi du: xoa du lieu cu
137
+ db.execute_query(
138
+ "DELETE FROM Fact_Sales WHERE created_date < :cutoff",
139
+ params={"cutoff": "2023-01-01"}
120
140
  )
121
- print("Dữ liệu đã được upsert thành công!")
122
141
  ```
123
142
 
124
- 4. Đóng kết nối
143
+ 6. Dong ket noi
125
144
  ```python
126
- # Luôn đóng kết nối khi hoàn tất để giải phóng tài nguyên
145
+ # Luon dong ket noi khi hoan tat de giai phong tai nguyen
127
146
  db.dispose()
128
147
  ```
129
148
 
130
149
  ## ⚠️ Lưu ý quan trọng
131
- 1. **Primary Key:** Khi dùng upsert_data, bắt buộc phải cung cấp primary_key. Nếu bảng chưa Primary Key, thư viện sẽ tự set cột đó làm khóa chính khi tạo bảng mới.
150
+ 1. **Primary Key:** Khi dùng upsert_data, bat buoc phai cung cap `match_columns`. Neu bang chua co Primary Key, thu vien se co gang set cac cot nay lam khoa chinh khi tao bang moi.
132
151
 
133
152
  2. **Date Time:** Các cột ngày tháng nên được convert sang datetime64[ns] trong Pandas trước khi đẩy vào để đảm bảo tính chính xác.
134
153
 
@@ -1,8 +1,10 @@
1
1
  # SQL Server Connector
2
2
 
3
3
  Thư viện kết nối SQL Server chuyên dụng cho các tác vụ ETL, được tối ưu hóa cho **Pandas**, hỗ trợ **Tiếng Việt (Unicode)** và **Upsert (Merge)** hiệu năng cao.
4
- ## Update 0.1.7
4
+
5
+ ### **Update 0.1.9**
5
6
  > Sửa lỗi nhỏ liên quan đến việc upsert với các bảng có cột chứa Tiếng Việt
7
+ > Thay vì dùng bảng tạm (##Staging), sẽ dùng bảng vật lý tạm thời (Physical Staging Table) có tên chứa UUID (để đảm bảo duy nhất, không trùng lặp giữa các luồng chạy). Sau khi Upsert xong, ta sẽ DROP bảng này ngay lập tức. Cách này tương thích 100% với Pandas và SQLAlchemy.
6
8
 
7
9
 
8
10
  ## 🚀 Tính năng nổi bật
@@ -50,9 +52,10 @@ db_info:
50
52
  ## 📝 Hướng dẫn sử dụng nhanh
51
53
 
52
54
  1. Khởi tạo kết nối
53
- ```python
55
+ ```python
54
56
  import yaml
55
57
  from connector import SQLServerConnector
58
+
56
59
  # Load config
57
60
  with open('config/db_config.yaml', 'r') as f:
58
61
  conf = yaml.safe_load(f)['db_info']
@@ -69,47 +72,63 @@ db = SQLServerConnector(
69
72
  2. Lấy dữ liệu (Read)
70
73
  ```python
71
74
  # Cách 1: Lấy toàn bộ bảng
72
- df = db.get_data("DM_KhachHang")
75
+ df = db.get_data("SELECT * FROM DM_KhachHang")
73
76
 
74
- # Cách 2: Dùng câu lệnh SQL tùy ý
77
+ # Cách 2: Dùng câu lệnh SQL tuy bien
75
78
  query = """
76
- SELECT TOP 100 * FROM Sales_Transaction
77
- WHERE created_date >= '2023-01-01'
79
+ SELECT TOP 100 * FROM Sales_Transaction
80
+ WHERE created_date >= :from_date
78
81
  """
79
- df_sales = db.get_data(query)
82
+ df_sales = db.get_data(query, params={"from_date": "2023-01-01"})
80
83
  print(df_sales.head())
81
84
  ```
82
85
 
83
- 3. Ghi dữ liệu (Upsert)
86
+ 3. Kiểm tra bảng tồn tại
87
+ ```python
88
+ if not db.check_table_exists("Fact_Sales"):
89
+ print("Bang Fact_Sales chua ton tai")
90
+ ```
91
+
92
+ 4. Ghi du lieu (Upsert)
84
93
  ```python
85
94
  import pandas as pd
86
95
 
87
- # Giả lập dữ liệu
96
+ # Gia lap du lieu
88
97
  data = {
89
98
  'TransactionID': [101, 102],
90
- 'Product': ['Laptop Dell', 'Chuột Logitech'], # Hỗ trợ tiếng Việt
99
+ 'Product': ['Laptop Dell', 'Chuot Logitech'], # Ho tro tieng Viet
91
100
  'Amount': [15000000, 250000]
92
101
  }
93
102
  df_new = pd.DataFrame(data)
94
103
 
95
- # Đẩy vào DB
104
+ # Day vao DB
96
105
  db.upsert_data(
97
106
  df=df_new,
98
107
  target_table="Fact_Sales",
99
- primary_key="TransactionID", # Cột dùng để định danh (tránh trùng lặp)
100
- auto_evolve_schema=True # Tự động thêm cột nếu thiếu
108
+ match_columns=["TransactionID"], # Khoa so khop (Primary Key)
109
+ conflict_strategy="last", # "last" hoac "skip"
110
+ auto_evolve_schema=True # Tu dong them cot neu thieu
111
+ )
112
+ print("Du lieu da duoc upsert thanh cong!")
113
+ ```
114
+
115
+ 5. Thuc thi cau lenh khong tra ve du lieu
116
+ ```python
117
+ # Vi du: xoa du lieu cu
118
+ db.execute_query(
119
+ "DELETE FROM Fact_Sales WHERE created_date < :cutoff",
120
+ params={"cutoff": "2023-01-01"}
101
121
  )
102
- print("Dữ liệu đã được upsert thành công!")
103
122
  ```
104
123
 
105
- 4. Đóng kết nối
124
+ 6. Dong ket noi
106
125
  ```python
107
- # Luôn đóng kết nối khi hoàn tất để giải phóng tài nguyên
126
+ # Luon dong ket noi khi hoan tat de giai phong tai nguyen
108
127
  db.dispose()
109
128
  ```
110
129
 
111
130
  ## ⚠️ Lưu ý quan trọng
112
- 1. **Primary Key:** Khi dùng upsert_data, bắt buộc phải cung cấp primary_key. Nếu bảng chưa Primary Key, thư viện sẽ tự set cột đó làm khóa chính khi tạo bảng mới.
131
+ 1. **Primary Key:** Khi dùng upsert_data, bat buoc phai cung cap `match_columns`. Neu bang chua co Primary Key, thu vien se co gang set cac cot nay lam khoa chinh khi tao bang moi.
113
132
 
114
133
  2. **Date Time:** Các cột ngày tháng nên được convert sang datetime64[ns] trong Pandas trước khi đẩy vào để đảm bảo tính chính xác.
115
134
 
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "sqlServerConnector"
9
- version = "0.1.7"
9
+ version = "0.1.9"
10
10
  description = "A custom SQL Server Connector for ETL processes with Pandas"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.8"
@@ -10,12 +10,13 @@ from sqlalchemy.types import NVARCHAR, FLOAT, INTEGER, DATE, DATETIME, BIGINT
10
10
 
11
11
  class SQLServerConnector:
12
12
  """
13
- Trình kết nối SQL Server chuẩn hóa (Full Features).
13
+ Trình kết nối SQL Server chuẩn hóa (Full Features - Fixed Missing Attribute).
14
14
  Tích hợp:
15
15
  - Fast Executemany (Tốc độ cao).
16
16
  - Unicode Support (NVARCHAR).
17
- - Upsert Strategy (Last/Sum/Skip).
18
- - Schema Evolution (Tự động thêm cột).
17
+ - Upsert Strategy (Last/Skip).
18
+ - Schema Evolution.
19
+ - Helper methods: check_table_exists.
19
20
  """
20
21
 
21
22
  def __init__(self, server: str, database: str, username: str, password: str, driver: str = 'ODBC Driver 17 for SQL Server', **kwargs):
@@ -65,6 +66,17 @@ class SQLServerConnector:
65
66
  logger.error(f"Execute query error: {e}")
66
67
  raise e
67
68
 
69
+ # --- [ĐÃ BỔ SUNG LẠI HÀM NÀY] ---
70
+ def check_table_exists(self, table_name: str) -> bool:
71
+ """Kiểm tra bảng có tồn tại trong database không"""
72
+ try:
73
+ inspector = inspect(self.engine)
74
+ return inspector.has_table(table_name)
75
+ except Exception as e:
76
+ logger.error(f"Check table exists failed: {e}")
77
+ return False
78
+ # --------------------------------
79
+
68
80
  def _generate_dtype_mapping(self, df: pd.DataFrame) -> Dict:
69
81
  """Tự động map kiểu dữ liệu (NVARCHAR cho string)"""
70
82
  dtype_map = {}
@@ -107,18 +119,7 @@ class SQLServerConnector:
107
119
  conflict_strategy: Literal['last', 'skip'] = 'last',
108
120
  auto_evolve_schema: bool = False):
109
121
  """
110
- Hàm Upsert mạnh mẽ (Khôi phục đầy đủ tính năng cũ).
111
-
112
- Args:
113
- df: DataFrame cần upload.
114
- target_table: Tên bảng đích.
115
- match_columns: Danh sách cột dùng làm Key so khớp (Primary Key).
116
- conflict_strategy:
117
- - 'last': Update ghi đè dữ liệu mới vào dòng cũ (Default).
118
- - 'skip': Nếu trùng key thì bỏ qua, không update.
119
- auto_evolve_schema:
120
- - True: Tự động thêm cột vào DB nếu DF có cột mới.
121
- - False: Bỏ qua các cột trong DF mà DB không có (Strict Schema).
122
+ Hàm Upsert sửa lỗi 'Table not found'.
122
123
  """
123
124
  if df.empty:
124
125
  logger.warning(f"DataFrame for {target_table} is empty. Skip.")
@@ -127,17 +128,19 @@ class SQLServerConnector:
127
128
  # 1. Map Unicode Types
128
129
  dtype_mapping = self._generate_dtype_mapping(df)
129
130
 
130
- # 2. Staging Table Name
131
- staging_table = f"##Staging_{uuid.uuid4().hex[:8]}"
131
+ # [FIX] Dùng tên bảng thường (Staging_UUID) thay vì ##Staging
132
+ # do: Pandas to_sql gặp lỗi khi check sự tồn tại của bảng ## trong transaction.
133
+ # Bảng này sẽ được drop thủ công ở khối finally.
134
+ staging_table = f"Staging_{uuid.uuid4().hex[:8]}"
132
135
 
133
136
  try:
134
137
  with self.engine.begin() as conn:
135
- # --- A. Kiểm tra Schema & Table ---
138
+ # --- A. Kiểm tra & Tạo bảng đích ---
136
139
  inspector = inspect(conn)
137
140
  if not inspector.has_table(target_table):
138
141
  logger.info(f"Table {target_table} not found. Creating new...")
139
142
  df.to_sql(target_table, conn, index=False, dtype=dtype_mapping)
140
- # Tạo Primary Key nếu cần
143
+
141
144
  if match_columns:
142
145
  pk_str = ", ".join([f"[{c}]" for c in match_columns])
143
146
  try:
@@ -146,25 +149,22 @@ class SQLServerConnector:
146
149
  logger.warning(f"Could not create PK: {e}")
147
150
  return
148
151
 
149
- # --- B. Xử lý Schema Evolution ---
152
+ # --- B. Schema Evolution ---
150
153
  db_cols = self._get_table_columns(target_table, conn)
151
154
  df_cols = list(df.columns)
152
-
153
- # Tìm cột có trong DF mà không có trong DB
154
155
  new_cols = [c for c in df_cols if c not in db_cols]
155
156
 
156
157
  if new_cols:
157
158
  if auto_evolve_schema:
158
159
  self._add_missing_columns(target_table, new_cols, dtype_mapping, conn)
159
- db_cols.extend(new_cols) # Update danh sách cột DB
160
+ db_cols.extend(new_cols)
160
161
  else:
161
- # Nếu không auto evolve, chỉ giữ lại các cột khớp với DB
162
162
  valid_cols = [c for c in df_cols if c in db_cols]
163
163
  if len(valid_cols) < len(df_cols):
164
- logger.warning(f"Schema strict: Dropping columns {new_cols} because they are not in DB.")
165
164
  df = df[valid_cols]
166
-
167
- # --- C. Đẩy vào Staging (Fast Executemany) ---
165
+
166
+ # --- C. Đẩy vào Staging ---
167
+ # to_sql hoạt động tốt với bảng thường
168
168
  df.to_sql(
169
169
  name=staging_table,
170
170
  con=conn,
@@ -173,60 +173,52 @@ class SQLServerConnector:
173
173
  dtype=dtype_mapping
174
174
  )
175
175
 
176
- # --- D. Thực hiện MERGE ---
177
- # Chỉ lấy các cột chung giữa DF và DB để Merge (tránh lỗi cột không tồn tại)
176
+ # --- D. MERGE ---
178
177
  common_cols = [c for c in df.columns if c in db_cols]
179
-
180
178
  on_clause = " AND ".join([f"Target.[{col}] = Source.[{col}]" for col in match_columns])
181
179
 
182
- # Logic Insert
183
180
  insert_cols = ", ".join([f"[{col}]" for col in common_cols])
184
181
  insert_vals = ", ".join([f"Source.[{col}]" for col in common_cols])
185
182
 
186
- # Logic Update
187
183
  merge_sql = ""
188
-
189
- # Trường hợp 1: Update ('last')
190
184
  if conflict_strategy == 'last':
191
185
  update_cols = [c for c in common_cols if c not in match_columns]
192
186
  if update_cols:
193
187
  update_set = ", ".join([f"Target.[{col}] = Source.[{col}]" for col in update_cols])
194
188
  merge_sql = f"""
195
- MERGE [{target_table}] AS Target
196
- USING {staging_table} AS Source
189
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
197
190
  ON {on_clause}
198
- WHEN MATCHED THEN
199
- UPDATE SET {update_set}
200
- WHEN NOT MATCHED BY TARGET THEN
201
- INSERT ({insert_cols}) VALUES ({insert_vals});
191
+ WHEN MATCHED THEN UPDATE SET {update_set}
192
+ WHEN NOT MATCHED BY TARGET THEN INSERT ({insert_cols}) VALUES ({insert_vals});
202
193
  """
203
194
  else:
204
- # Nếu chỉ có cột PK, không có gì để update -> Chỉ Insert if not exists
205
195
  merge_sql = f"""
206
- MERGE [{target_table}] AS Target
207
- USING {staging_table} AS Source
196
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
208
197
  ON {on_clause}
209
- WHEN NOT MATCHED BY TARGET THEN
210
- INSERT ({insert_cols}) VALUES ({insert_vals});
198
+ WHEN NOT MATCHED BY TARGET THEN INSERT ({insert_cols}) VALUES ({insert_vals});
211
199
  """
212
200
 
213
- # Trường hợp 2: Skip (Chỉ Insert, không Update)
214
201
  elif conflict_strategy == 'skip':
215
202
  merge_sql = f"""
216
- MERGE [{target_table}] AS Target
217
- USING {staging_table} AS Source
203
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
218
204
  ON {on_clause}
219
- WHEN NOT MATCHED BY TARGET THEN
220
- INSERT ({insert_cols}) VALUES ({insert_vals});
205
+ WHEN NOT MATCHED BY TARGET THEN INSERT ({insert_cols}) VALUES ({insert_vals});
221
206
  """
222
207
 
223
208
  conn.execute(text(merge_sql))
224
- conn.execute(text(f"DROP TABLE IF EXISTS {staging_table}"))
225
- logger.info(f"Upserted {len(df)} rows to {target_table} (Strategy: {conflict_strategy})")
209
+ logger.info(f"Upserted {len(df)} rows to {target_table}")
226
210
 
227
211
  except Exception as e:
228
212
  logger.error(f"Upsert failed for {target_table}: {e}")
229
213
  raise e
214
+ finally:
215
+ # [QUAN TRỌNG] Luôn luôn xóa bảng Staging rác dù thành công hay thất bại
216
+ # Sử dụng kết nối mới để đảm bảo lệnh Drop được thực thi
217
+ try:
218
+ with self.engine.begin() as conn:
219
+ conn.execute(text(f"IF OBJECT_ID('{staging_table}', 'U') IS NOT NULL DROP TABLE [{staging_table}]"))
220
+ except Exception as e:
221
+ logger.warning(f"Could not clean up staging table {staging_table}: {e}")
230
222
 
231
223
  def dispose(self):
232
224
  self.engine.dispose()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlServerConnector
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: A custom SQL Server Connector for ETL processes with Pandas
5
5
  Author-email: Nguyen Minh Son <nguyen.minhson1511@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/johnnyb1509/sqlServerConnector
@@ -20,8 +20,10 @@ Requires-Dist: jupyterlab
20
20
  # SQL Server Connector
21
21
 
22
22
  Thư viện kết nối SQL Server chuyên dụng cho các tác vụ ETL, được tối ưu hóa cho **Pandas**, hỗ trợ **Tiếng Việt (Unicode)** và **Upsert (Merge)** hiệu năng cao.
23
- ## Update 0.1.7
23
+
24
+ ### **Update 0.1.9**
24
25
  > Sửa lỗi nhỏ liên quan đến việc upsert với các bảng có cột chứa Tiếng Việt
26
+ > Thay vì dùng bảng tạm (##Staging), sẽ dùng bảng vật lý tạm thời (Physical Staging Table) có tên chứa UUID (để đảm bảo duy nhất, không trùng lặp giữa các luồng chạy). Sau khi Upsert xong, ta sẽ DROP bảng này ngay lập tức. Cách này tương thích 100% với Pandas và SQLAlchemy.
25
27
 
26
28
 
27
29
  ## 🚀 Tính năng nổi bật
@@ -69,9 +71,10 @@ db_info:
69
71
  ## 📝 Hướng dẫn sử dụng nhanh
70
72
 
71
73
  1. Khởi tạo kết nối
72
- ```python
74
+ ```python
73
75
  import yaml
74
76
  from connector import SQLServerConnector
77
+
75
78
  # Load config
76
79
  with open('config/db_config.yaml', 'r') as f:
77
80
  conf = yaml.safe_load(f)['db_info']
@@ -88,47 +91,63 @@ db = SQLServerConnector(
88
91
  2. Lấy dữ liệu (Read)
89
92
  ```python
90
93
  # Cách 1: Lấy toàn bộ bảng
91
- df = db.get_data("DM_KhachHang")
94
+ df = db.get_data("SELECT * FROM DM_KhachHang")
92
95
 
93
- # Cách 2: Dùng câu lệnh SQL tùy ý
96
+ # Cách 2: Dùng câu lệnh SQL tuy bien
94
97
  query = """
95
- SELECT TOP 100 * FROM Sales_Transaction
96
- WHERE created_date >= '2023-01-01'
98
+ SELECT TOP 100 * FROM Sales_Transaction
99
+ WHERE created_date >= :from_date
97
100
  """
98
- df_sales = db.get_data(query)
101
+ df_sales = db.get_data(query, params={"from_date": "2023-01-01"})
99
102
  print(df_sales.head())
100
103
  ```
101
104
 
102
- 3. Ghi dữ liệu (Upsert)
105
+ 3. Kiểm tra bảng tồn tại
106
+ ```python
107
+ if not db.check_table_exists("Fact_Sales"):
108
+ print("Bang Fact_Sales chua ton tai")
109
+ ```
110
+
111
+ 4. Ghi du lieu (Upsert)
103
112
  ```python
104
113
  import pandas as pd
105
114
 
106
- # Giả lập dữ liệu
115
+ # Gia lap du lieu
107
116
  data = {
108
117
  'TransactionID': [101, 102],
109
- 'Product': ['Laptop Dell', 'Chuột Logitech'], # Hỗ trợ tiếng Việt
118
+ 'Product': ['Laptop Dell', 'Chuot Logitech'], # Ho tro tieng Viet
110
119
  'Amount': [15000000, 250000]
111
120
  }
112
121
  df_new = pd.DataFrame(data)
113
122
 
114
- # Đẩy vào DB
123
+ # Day vao DB
115
124
  db.upsert_data(
116
125
  df=df_new,
117
126
  target_table="Fact_Sales",
118
- primary_key="TransactionID", # Cột dùng để định danh (tránh trùng lặp)
119
- auto_evolve_schema=True # Tự động thêm cột nếu thiếu
127
+ match_columns=["TransactionID"], # Khoa so khop (Primary Key)
128
+ conflict_strategy="last", # "last" hoac "skip"
129
+ auto_evolve_schema=True # Tu dong them cot neu thieu
130
+ )
131
+ print("Du lieu da duoc upsert thanh cong!")
132
+ ```
133
+
134
+ 5. Thuc thi cau lenh khong tra ve du lieu
135
+ ```python
136
+ # Vi du: xoa du lieu cu
137
+ db.execute_query(
138
+ "DELETE FROM Fact_Sales WHERE created_date < :cutoff",
139
+ params={"cutoff": "2023-01-01"}
120
140
  )
121
- print("Dữ liệu đã được upsert thành công!")
122
141
  ```
123
142
 
124
- 4. Đóng kết nối
143
+ 6. Dong ket noi
125
144
  ```python
126
- # Luôn đóng kết nối khi hoàn tất để giải phóng tài nguyên
145
+ # Luon dong ket noi khi hoan tat de giai phong tai nguyen
127
146
  db.dispose()
128
147
  ```
129
148
 
130
149
  ## ⚠️ Lưu ý quan trọng
131
- 1. **Primary Key:** Khi dùng upsert_data, bắt buộc phải cung cấp primary_key. Nếu bảng chưa Primary Key, thư viện sẽ tự set cột đó làm khóa chính khi tạo bảng mới.
150
+ 1. **Primary Key:** Khi dùng upsert_data, bat buoc phai cung cap `match_columns`. Neu bang chua co Primary Key, thu vien se co gang set cac cot nay lam khoa chinh khi tao bang moi.
132
151
 
133
152
  2. **Date Time:** Các cột ngày tháng nên được convert sang datetime64[ns] trong Pandas trước khi đẩy vào để đảm bảo tính chính xác.
134
153