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.
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/PKG-INFO +37 -18
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/README.md +36 -17
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/pyproject.toml +1 -1
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/connector.py +44 -52
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/PKG-INFO +37 -18
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/setup.cfg +0 -0
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/__init__.py +0 -0
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/SOURCES.txt +0 -0
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/dependency_links.txt +0 -0
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/requires.txt +0 -0
- {sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlServerConnector
|
|
3
|
-
Version: 0.1.
|
|
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
|
-
|
|
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
|
|
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 >=
|
|
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.
|
|
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
|
-
#
|
|
115
|
+
# Gia lap du lieu
|
|
107
116
|
data = {
|
|
108
117
|
'TransactionID': [101, 102],
|
|
109
|
-
'Product': ['Laptop Dell', '
|
|
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
|
-
#
|
|
123
|
+
# Day vao DB
|
|
115
124
|
db.upsert_data(
|
|
116
125
|
df=df_new,
|
|
117
126
|
target_table="Fact_Sales",
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
143
|
+
6. Dong ket noi
|
|
125
144
|
```python
|
|
126
|
-
#
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 >=
|
|
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.
|
|
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
|
-
#
|
|
96
|
+
# Gia lap du lieu
|
|
88
97
|
data = {
|
|
89
98
|
'TransactionID': [101, 102],
|
|
90
|
-
'Product': ['Laptop Dell', '
|
|
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
|
-
#
|
|
104
|
+
# Day vao DB
|
|
96
105
|
db.upsert_data(
|
|
97
106
|
df=df_new,
|
|
98
107
|
target_table="Fact_Sales",
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
124
|
+
6. Dong ket noi
|
|
106
125
|
```python
|
|
107
|
-
#
|
|
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,
|
|
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
|
|
|
@@ -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/
|
|
18
|
-
- Schema Evolution
|
|
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
|
|
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
|
-
#
|
|
131
|
-
|
|
131
|
+
# [FIX] Dùng tên bảng thường (Staging_UUID) thay vì ##Staging
|
|
132
|
+
# Lý 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
|
|
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
|
-
|
|
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.
|
|
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)
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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()
|
{sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlServerConnector
|
|
3
|
-
Version: 0.1.
|
|
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
|
-
|
|
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
|
|
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 >=
|
|
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.
|
|
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
|
-
#
|
|
115
|
+
# Gia lap du lieu
|
|
107
116
|
data = {
|
|
108
117
|
'TransactionID': [101, 102],
|
|
109
|
-
'Product': ['Laptop Dell', '
|
|
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
|
-
#
|
|
123
|
+
# Day vao DB
|
|
115
124
|
db.upsert_data(
|
|
116
125
|
df=df_new,
|
|
117
126
|
target_table="Fact_Sales",
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
143
|
+
6. Dong ket noi
|
|
125
144
|
```python
|
|
126
|
-
#
|
|
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,
|
|
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
|
|
|
File without changes
|
|
File without changes
|
{sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/requires.txt
RENAMED
|
File without changes
|
{sqlserverconnector-0.1.7 → sqlserverconnector-0.1.9}/src/sqlServerConnector.egg-info/top_level.txt
RENAMED
|
File without changes
|