sqlServerConnector 0.1.8__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.8
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
@@ -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
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "sqlServerConnector"
9
- version = "0.1.8"
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"
@@ -119,18 +119,7 @@ class SQLServerConnector:
119
119
  conflict_strategy: Literal['last', 'skip'] = 'last',
120
120
  auto_evolve_schema: bool = False):
121
121
  """
122
- Hàm Upsert mạnh mẽ.
123
-
124
- Args:
125
- df: DataFrame cần upload.
126
- target_table: Tên bảng đích.
127
- match_columns: Danh sách cột dùng làm Key so khớp (Primary Key).
128
- conflict_strategy:
129
- - 'last': Update ghi đè dữ liệu mới vào dòng cũ (Default).
130
- - 'skip': Nếu trùng key thì bỏ qua, không update.
131
- auto_evolve_schema:
132
- - True: Tự động thêm cột vào DB nếu DF có cột mới.
133
- - 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'.
134
123
  """
135
124
  if df.empty:
136
125
  logger.warning(f"DataFrame for {target_table} is empty. Skip.")
@@ -139,17 +128,19 @@ class SQLServerConnector:
139
128
  # 1. Map Unicode Types
140
129
  dtype_mapping = self._generate_dtype_mapping(df)
141
130
 
142
- # 2. Staging Table Name
143
- 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]}"
144
135
 
145
136
  try:
146
137
  with self.engine.begin() as conn:
147
- # --- A. Kiểm tra Schema & Table ---
138
+ # --- A. Kiểm tra & Tạo bảng đích ---
148
139
  inspector = inspect(conn)
149
140
  if not inspector.has_table(target_table):
150
141
  logger.info(f"Table {target_table} not found. Creating new...")
151
142
  df.to_sql(target_table, conn, index=False, dtype=dtype_mapping)
152
- # Tạo Primary Key nếu cần
143
+
153
144
  if match_columns:
154
145
  pk_str = ", ".join([f"[{c}]" for c in match_columns])
155
146
  try:
@@ -158,25 +149,22 @@ class SQLServerConnector:
158
149
  logger.warning(f"Could not create PK: {e}")
159
150
  return
160
151
 
161
- # --- B. Xử lý Schema Evolution ---
152
+ # --- B. Schema Evolution ---
162
153
  db_cols = self._get_table_columns(target_table, conn)
163
154
  df_cols = list(df.columns)
164
-
165
- # Tìm cột có trong DF mà không có trong DB
166
155
  new_cols = [c for c in df_cols if c not in db_cols]
167
156
 
168
157
  if new_cols:
169
158
  if auto_evolve_schema:
170
159
  self._add_missing_columns(target_table, new_cols, dtype_mapping, conn)
171
- db_cols.extend(new_cols) # Update danh sách cột DB
160
+ db_cols.extend(new_cols)
172
161
  else:
173
- # Nếu không auto evolve, chỉ giữ lại các cột khớp với DB
174
162
  valid_cols = [c for c in df_cols if c in db_cols]
175
163
  if len(valid_cols) < len(df_cols):
176
- logger.warning(f"Schema strict: Dropping columns {new_cols} because they are not in DB.")
177
164
  df = df[valid_cols]
178
-
179
- # --- 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
180
168
  df.to_sql(
181
169
  name=staging_table,
182
170
  con=conn,
@@ -185,60 +173,52 @@ class SQLServerConnector:
185
173
  dtype=dtype_mapping
186
174
  )
187
175
 
188
- # --- D. Thực hiện MERGE ---
189
- # 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 ---
190
177
  common_cols = [c for c in df.columns if c in db_cols]
191
-
192
178
  on_clause = " AND ".join([f"Target.[{col}] = Source.[{col}]" for col in match_columns])
193
179
 
194
- # Logic Insert
195
180
  insert_cols = ", ".join([f"[{col}]" for col in common_cols])
196
181
  insert_vals = ", ".join([f"Source.[{col}]" for col in common_cols])
197
182
 
198
- # Logic Update
199
183
  merge_sql = ""
200
-
201
- # Trường hợp 1: Update ('last')
202
184
  if conflict_strategy == 'last':
203
185
  update_cols = [c for c in common_cols if c not in match_columns]
204
186
  if update_cols:
205
187
  update_set = ", ".join([f"Target.[{col}] = Source.[{col}]" for col in update_cols])
206
188
  merge_sql = f"""
207
- MERGE [{target_table}] AS Target
208
- USING {staging_table} AS Source
189
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
209
190
  ON {on_clause}
210
- WHEN MATCHED THEN
211
- UPDATE SET {update_set}
212
- WHEN NOT MATCHED BY TARGET THEN
213
- 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});
214
193
  """
215
194
  else:
216
- # Nếu chỉ có cột PK, không có gì để update -> Chỉ Insert if not exists
217
195
  merge_sql = f"""
218
- MERGE [{target_table}] AS Target
219
- USING {staging_table} AS Source
196
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
220
197
  ON {on_clause}
221
- WHEN NOT MATCHED BY TARGET THEN
222
- INSERT ({insert_cols}) VALUES ({insert_vals});
198
+ WHEN NOT MATCHED BY TARGET THEN INSERT ({insert_cols}) VALUES ({insert_vals});
223
199
  """
224
200
 
225
- # Trường hợp 2: Skip (Chỉ Insert, không Update)
226
201
  elif conflict_strategy == 'skip':
227
202
  merge_sql = f"""
228
- MERGE [{target_table}] AS Target
229
- USING {staging_table} AS Source
203
+ MERGE [{target_table}] AS Target USING [{staging_table}] AS Source
230
204
  ON {on_clause}
231
- WHEN NOT MATCHED BY TARGET THEN
232
- INSERT ({insert_cols}) VALUES ({insert_vals});
205
+ WHEN NOT MATCHED BY TARGET THEN INSERT ({insert_cols}) VALUES ({insert_vals});
233
206
  """
234
207
 
235
208
  conn.execute(text(merge_sql))
236
- conn.execute(text(f"DROP TABLE IF EXISTS {staging_table}"))
237
- logger.info(f"Upserted {len(df)} rows to {target_table} (Strategy: {conflict_strategy})")
209
+ logger.info(f"Upserted {len(df)} rows to {target_table}")
238
210
 
239
211
  except Exception as e:
240
212
  logger.error(f"Upsert failed for {target_table}: {e}")
241
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}")
242
222
 
243
223
  def dispose(self):
244
224
  self.engine.dispose()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlServerConnector
3
- Version: 0.1.8
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