Merge remote-tracking branch 'origin/master'
This commit is contained in:
106
svc/queries/tbstok_exists_bulk.go
Normal file
106
svc/queries/tbstok_exists_bulk.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LookupTbStokExistsByCodes checks if tbStok contains records matching the given codes.
|
||||
// Match rules are aligned with costing usage:
|
||||
// - exact sKodu (ignoring spaces)
|
||||
// - exact sModel
|
||||
//
|
||||
// Returns a map[code]exists.
|
||||
func LookupTbStokExistsByCodes(ctx context.Context, mssqlDB *sql.DB, codes []string) (map[string]bool, error) {
|
||||
if mssqlDB == nil {
|
||||
return nil, fmt.Errorf("mssql db is nil")
|
||||
}
|
||||
norm := make([]string, 0, len(codes))
|
||||
seen := map[string]struct{}{}
|
||||
for _, c := range codes {
|
||||
c = strings.TrimSpace(c)
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[c]; ok {
|
||||
continue
|
||||
}
|
||||
seen[c] = struct{}{}
|
||||
norm = append(norm, c)
|
||||
}
|
||||
out := map[string]bool{}
|
||||
for _, c := range norm {
|
||||
out[c] = false
|
||||
}
|
||||
if len(norm) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
valParts := make([]string, 0, len(norm))
|
||||
args := make([]any, 0, len(norm))
|
||||
for i, code := range norm {
|
||||
valParts = append(valParts, fmt.Sprintf("(@p%d)", i+1))
|
||||
args = append(args, code)
|
||||
}
|
||||
|
||||
// NOTE: This endpoint is a UX validation helper and must be fast.
|
||||
// Keep predicates sargable: no function wrapping on tbStok columns.
|
||||
//
|
||||
// For "ignore spaces" behavior, we send both the original code and its no-space variant from input,
|
||||
// and match via exact equality against tbStok.sKodu.
|
||||
//
|
||||
// IMPORTANT: We intentionally do not filter by IsBlocked here because IsBlocked is nullable and
|
||||
// filtering requires OR logic, which can prevent index seeks and cause timeouts. This is a best-effort
|
||||
// existence check for UX highlighting.
|
||||
sqlText := fmt.Sprintf(`
|
||||
WITH C AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(V.code)) AS code,
|
||||
REPLACE(LTRIM(RTRIM(V.code)), ' ', '') AS code_nospace
|
||||
FROM (VALUES %s) AS V(code)
|
||||
),
|
||||
HIT AS (
|
||||
SELECT DISTINCT C.code
|
||||
FROM C
|
||||
JOIN dbo.tbStok S WITH (NOLOCK)
|
||||
ON S.sKodu = C.code
|
||||
OR S.sKodu = C.code_nospace
|
||||
UNION
|
||||
SELECT DISTINCT C.code
|
||||
FROM C
|
||||
JOIN dbo.tbStok S WITH (NOLOCK)
|
||||
ON S.sModel = C.code
|
||||
)
|
||||
SELECT
|
||||
C.code,
|
||||
CASE WHEN H.code IS NULL THEN 0 ELSE 1 END AS existsFlag
|
||||
FROM C
|
||||
LEFT JOIN HIT H
|
||||
ON H.code = C.code
|
||||
`, strings.Join(valParts, ","))
|
||||
|
||||
rows, err := mssqlDB.QueryContext(ctx, sqlText, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var code string
|
||||
var existsFlag int
|
||||
if err := rows.Scan(&code, &existsFlag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
out[code] = existsFlag == 1
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user