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
|
||||
- 상품명, 상품코드, 바코드로 검색
|
||||
- 세트상품 바코드도 CD_ITEM_UNIT_MEMBER에서 조회
|
||||
- in_stock_only: 사용약품만 (IM_total에 재고 있는 제품)
|
||||
"""
|
||||
search = request.args.get('search', '').strip()
|
||||
limit = int(request.args.get('limit', 100))
|
||||
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:
|
||||
return jsonify({'success': False, 'error': '검색어는 2글자 이상 입력하세요'})
|
||||
@ -3317,43 +3319,66 @@ def api_products():
|
||||
try:
|
||||
drug_session = db_manager.get_session('PM_DRUG')
|
||||
|
||||
# 제품 검색 쿼리
|
||||
# CD_GOODS.BARCODE가 없으면 CD_ITEM_UNIT_MEMBER.CD_CD_BARCODE 사용 (세트상품)
|
||||
# 세트상품 여부 확인: CD_item_set에 SetCode로 존재하면 세트상품
|
||||
products_query = text(f"""
|
||||
SELECT TOP {limit}
|
||||
G.DrugCode as drug_code,
|
||||
G.GoodsName as product_name,
|
||||
COALESCE(NULLIF(G.BARCODE, ''), U.CD_CD_BARCODE, '') as barcode,
|
||||
G.Saleprice as sale_price,
|
||||
G.Price as cost_price,
|
||||
CASE
|
||||
WHEN G.SplName IS NOT NULL AND G.SplName != '' THEN G.SplName
|
||||
WHEN SET_CHK.is_set = 1 THEN N'세트상품'
|
||||
ELSE ''
|
||||
END as supplier,
|
||||
CASE WHEN SET_CHK.is_set = 1 THEN 1 ELSE 0 END as is_set,
|
||||
G.POS_BOON as pos_boon,
|
||||
ISNULL(IT.IM_QT_sale_debit, 0) as stock
|
||||
FROM CD_GOODS G
|
||||
LEFT JOIN IM_total IT ON G.DrugCode = IT.DrugCode
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 CD_CD_BARCODE
|
||||
FROM CD_ITEM_UNIT_MEMBER
|
||||
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
|
||||
) U
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 1 as is_set
|
||||
FROM CD_item_set
|
||||
WHERE SetCode = G.DrugCode AND DrugCode = 'SET0000'
|
||||
) SET_CHK
|
||||
WHERE
|
||||
G.GoodsName LIKE :search_like
|
||||
OR G.DrugCode LIKE :search_like
|
||||
OR G.BARCODE LIKE :search_like
|
||||
OR U.CD_CD_BARCODE LIKE :search_like
|
||||
ORDER BY G.GoodsName
|
||||
""")
|
||||
# 제품 검색 쿼리 - 사용약품만 옵션에 따라 JOIN 방식 변경
|
||||
# in_stock_only: INNER JOIN으로 재고 있는 제품만 (빠름)
|
||||
# 그렇지 않으면: 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"""
|
||||
SELECT TOP {limit}
|
||||
G.DrugCode as drug_code,
|
||||
G.GoodsName as product_name,
|
||||
COALESCE(NULLIF(G.BARCODE, ''), U.CD_CD_BARCODE, '') as barcode,
|
||||
G.Saleprice as sale_price,
|
||||
G.Price as cost_price,
|
||||
CASE
|
||||
WHEN G.SplName IS NOT NULL AND G.SplName != '' THEN G.SplName
|
||||
WHEN SET_CHK.is_set = 1 THEN N'세트상품'
|
||||
ELSE ''
|
||||
END as supplier,
|
||||
CASE WHEN SET_CHK.is_set = 1 THEN 1 ELSE 0 END as is_set,
|
||||
G.POS_BOON as pos_boon,
|
||||
ISNULL(IT.IM_QT_sale_debit, 0) as stock
|
||||
FROM CD_GOODS G
|
||||
LEFT JOIN IM_total IT ON G.DrugCode = IT.DrugCode
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 CD_CD_BARCODE
|
||||
FROM CD_ITEM_UNIT_MEMBER
|
||||
WHERE DRUGCODE = G.DrugCode AND CD_CD_BARCODE IS NOT NULL AND CD_CD_BARCODE != ''
|
||||
) U
|
||||
OUTER APPLY (
|
||||
SELECT TOP 1 1 as is_set
|
||||
FROM CD_item_set
|
||||
WHERE SetCode = G.DrugCode AND DrugCode = 'SET0000'
|
||||
) SET_CHK
|
||||
WHERE
|
||||
G.GoodsName LIKE :search_like
|
||||
OR G.DrugCode LIKE :search_like
|
||||
OR G.BARCODE LIKE :search_like
|
||||
OR U.CD_CD_BARCODE LIKE :search_like
|
||||
ORDER BY G.GoodsName
|
||||
""")
|
||||
|
||||
search_like = f'%{search}%'
|
||||
rows = drug_session.execute(products_query, {
|
||||
|
||||
@ -581,12 +581,20 @@
|
||||
<div class="search-hint">
|
||||
<span>예시</span> 타이레놀, 벤포파워, 8806418067510, LB000001423
|
||||
</div>
|
||||
<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;">
|
||||
<span style="display: flex; align-items: center; gap: 4px;">
|
||||
🐾 <strong style="color: #10b981;">동물약만</strong> 보기
|
||||
</span>
|
||||
</label>
|
||||
<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;">
|
||||
<input type="checkbox" id="animalOnly" style="width: 18px; height: 18px; accent-color: #10b981; cursor: pointer;">
|
||||
<span style="display: flex; align-items: center; gap: 4px;">
|
||||
🐾 <strong style="color: #10b981;">동물약만</strong>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -700,7 +708,11 @@
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-state"><p>검색 중...</p></td></tr>';
|
||||
|
||||
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(data => {
|
||||
if (data.success) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user