perf: 제품 검색 최적화 - 사용약품만 옵션 추가
문제: 전체 CD_GOODS 검색 시 178,232건 스캔 + OUTER APPLY → 6-14초 소요 해결: - '사용약품만' 체크박스 추가 (기본 활성화) - IM_total INNER JOIN으로 재고 있는 2,810건만 검색 - OUTER APPLY 제거로 쿼리 단순화 성능: 6.5초 → 1.4초 (4.6배 향상)
This commit is contained in:
parent
5dd3489385
commit
a0cbb984e5
@ -3306,10 +3306,12 @@ def api_products():
|
|||||||
제품 검색 API
|
제품 검색 API
|
||||||
- 상품명, 상품코드, 바코드로 검색
|
- 상품명, 상품코드, 바코드로 검색
|
||||||
- 세트상품 바코드도 CD_ITEM_UNIT_MEMBER에서 조회
|
- 세트상품 바코드도 CD_ITEM_UNIT_MEMBER에서 조회
|
||||||
|
- in_stock_only: 사용약품만 (IM_total에 재고 있는 제품)
|
||||||
"""
|
"""
|
||||||
search = request.args.get('search', '').strip()
|
search = request.args.get('search', '').strip()
|
||||||
limit = int(request.args.get('limit', 100))
|
limit = int(request.args.get('limit', 100))
|
||||||
animal_only = request.args.get('animal_only', '0') == '1'
|
animal_only = request.args.get('animal_only', '0') == '1'
|
||||||
|
in_stock_only = request.args.get('in_stock_only', '0') == '1'
|
||||||
|
|
||||||
if not search or len(search) < 2:
|
if not search or len(search) < 2:
|
||||||
return jsonify({'success': False, 'error': '검색어는 2글자 이상 입력하세요'})
|
return jsonify({'success': False, 'error': '검색어는 2글자 이상 입력하세요'})
|
||||||
@ -3317,9 +3319,32 @@ def api_products():
|
|||||||
try:
|
try:
|
||||||
drug_session = db_manager.get_session('PM_DRUG')
|
drug_session = db_manager.get_session('PM_DRUG')
|
||||||
|
|
||||||
# 제품 검색 쿼리
|
# 제품 검색 쿼리 - 사용약품만 옵션에 따라 JOIN 방식 변경
|
||||||
# CD_GOODS.BARCODE가 없으면 CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE 사용 (세트상품)
|
# in_stock_only: INNER JOIN으로 재고 있는 제품만 (빠름)
|
||||||
# 세트상품 여부 확인: CD_item_set에 SetCode로 존재하면 세트상품
|
# 그렇지 않으면: LEFT JOIN으로 모든 제품 (느림)
|
||||||
|
if in_stock_only:
|
||||||
|
# 최적화된 쿼리: 재고 있는 제품만 (IM_total INNER JOIN)
|
||||||
|
products_query = text(f"""
|
||||||
|
SELECT TOP {limit}
|
||||||
|
G.DrugCode as drug_code,
|
||||||
|
G.GoodsName as product_name,
|
||||||
|
COALESCE(NULLIF(G.BARCODE, ''), '') as barcode,
|
||||||
|
G.Saleprice as sale_price,
|
||||||
|
G.Price as cost_price,
|
||||||
|
ISNULL(G.SplName, '') as supplier,
|
||||||
|
0 as is_set,
|
||||||
|
G.POS_BOON as pos_boon,
|
||||||
|
IT.IM_QT_sale_debit as stock
|
||||||
|
FROM CD_GOODS G
|
||||||
|
INNER JOIN IM_total IT ON G.DrugCode = IT.DrugCode AND IT.IM_QT_sale_debit > 0
|
||||||
|
WHERE
|
||||||
|
G.GoodsName LIKE :search_like
|
||||||
|
OR G.DrugCode LIKE :search_like
|
||||||
|
OR G.BARCODE LIKE :search_like
|
||||||
|
ORDER BY G.GoodsName
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
# 전체 쿼리 (OUTER APPLY 포함, 느림)
|
||||||
products_query = text(f"""
|
products_query = text(f"""
|
||||||
SELECT TOP {limit}
|
SELECT TOP {limit}
|
||||||
G.DrugCode as drug_code,
|
G.DrugCode as drug_code,
|
||||||
|
|||||||
@ -581,14 +581,22 @@
|
|||||||
<div class="search-hint">
|
<div class="search-hint">
|
||||||
<span>예시</span> 타이레놀, 벤포파워, 8806418067510, LB000001423
|
<span>예시</span> 타이레놀, 벤포파워, 8806418067510, LB000001423
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: flex; gap: 20px;">
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #475569;">
|
||||||
|
<input type="checkbox" id="inStockOnly" checked style="width: 18px; height: 18px; accent-color: #8b5cf6; cursor: pointer;">
|
||||||
|
<span style="display: flex; align-items: center; gap: 4px;">
|
||||||
|
📦 <strong style="color: #8b5cf6;">사용약품만</strong>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #475569;">
|
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #475569;">
|
||||||
<input type="checkbox" id="animalOnly" style="width: 18px; height: 18px; accent-color: #10b981; cursor: pointer;">
|
<input type="checkbox" id="animalOnly" style="width: 18px; height: 18px; accent-color: #10b981; cursor: pointer;">
|
||||||
<span style="display: flex; align-items: center; gap: 4px;">
|
<span style="display: flex; align-items: center; gap: 4px;">
|
||||||
🐾 <strong style="color: #10b981;">동물약만</strong> 보기
|
🐾 <strong style="color: #10b981;">동물약만</strong>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 결과 -->
|
<!-- 결과 -->
|
||||||
<div class="result-count" id="resultCount" style="display:none;">
|
<div class="result-count" id="resultCount" style="display:none;">
|
||||||
@ -700,7 +708,11 @@
|
|||||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><p>검색 중...</p></td></tr>';
|
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><p>검색 중...</p></td></tr>';
|
||||||
|
|
||||||
const animalOnly = document.getElementById('animalOnly').checked;
|
const animalOnly = document.getElementById('animalOnly').checked;
|
||||||
fetch(`/api/products?search=${encodeURIComponent(search)}${animalOnly ? '&animal_only=1' : ''}`)
|
const inStockOnly = document.getElementById('inStockOnly').checked;
|
||||||
|
let url = `/api/products?search=${encodeURIComponent(search)}`;
|
||||||
|
if (animalOnly) url += '&animal_only=1';
|
||||||
|
if (inStockOnly) url += '&in_stock_only=1';
|
||||||
|
fetch(url)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user