Merge getBySku into single SQL query with FOR JSON sub-queries
Replaces 6 round-trips (1 sequential resolve + 5 parallel detail) with one query. Auth resolution, article detail, categories, photos, eans, and prices are all fetched in a single round-trip.
This commit is contained in:
@@ -159,3 +159,152 @@ SELECT id
|
|||||||
FROM brand
|
FROM brand
|
||||||
WHERE id = @brandId AND company_id = @companyId
|
WHERE id = @brandId AND company_id = @companyId
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-query replacement for resolveArticleBySku + queryBySku.
|
||||||
|
* Resolves SKU → article + company + auth, then loads full article detail
|
||||||
|
* including categories, photos, eans, and prices via FOR JSON sub-queries.
|
||||||
|
* Returns one row with articleJson, or empty if not found/unauthorized.
|
||||||
|
*/
|
||||||
|
export const GET_ARTICLE_BY_SKU_FULL = `
|
||||||
|
;WITH article_cat AS (
|
||||||
|
SELECT TOP 1 a.id AS articleId, ca.category_id AS categoryId
|
||||||
|
FROM article a
|
||||||
|
INNER JOIN categoryarticle ca ON ca.article_id = a.id
|
||||||
|
WHERE a.sku = @sku
|
||||||
|
),
|
||||||
|
ancestors AS (
|
||||||
|
SELECT c.id, c.parent_id
|
||||||
|
FROM category c
|
||||||
|
INNER JOIN article_cat ac ON c.id = ac.categoryId
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT c.id, c.parent_id
|
||||||
|
FROM category c
|
||||||
|
INNER JOIN ancestors a ON c.id = a.parent_id
|
||||||
|
),
|
||||||
|
root AS (
|
||||||
|
SELECT TOP 1 id AS rootCategoryId
|
||||||
|
FROM ancestors
|
||||||
|
WHERE parent_id IS NULL
|
||||||
|
),
|
||||||
|
auth AS (
|
||||||
|
SELECT
|
||||||
|
ac.articleId,
|
||||||
|
ac.categoryId,
|
||||||
|
r.rootCategoryId,
|
||||||
|
co.id AS companyId,
|
||||||
|
co.default_language_id AS languageId,
|
||||||
|
co.poscustomergroup_id AS posCustomerGroupId,
|
||||||
|
cu.id AS companyuserId
|
||||||
|
FROM article_cat ac
|
||||||
|
LEFT JOIN root r ON 1 = 1
|
||||||
|
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
|
||||||
|
LEFT JOIN companyuser cu
|
||||||
|
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
auth.articleId,
|
||||||
|
auth.categoryId,
|
||||||
|
auth.rootCategoryId,
|
||||||
|
auth.companyId,
|
||||||
|
auth.languageId,
|
||||||
|
auth.posCustomerGroupId,
|
||||||
|
auth.companyuserId,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
a.id,
|
||||||
|
a.sku,
|
||||||
|
at.name,
|
||||||
|
at.slug,
|
||||||
|
a.unitamount,
|
||||||
|
a.weight,
|
||||||
|
u.si AS unitSi,
|
||||||
|
u.basesi AS unitBasesi,
|
||||||
|
u.basesifactor AS unitBasesifactor,
|
||||||
|
ut.symbol AS unitSymbol,
|
||||||
|
ut.name AS unitName,
|
||||||
|
b.name AS brandName,
|
||||||
|
bp.thumbw AS brandThumbW,
|
||||||
|
bp.thumbh AS brandThumbH,
|
||||||
|
bp.thumbhash AS brandThumbHash,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM optionslistarticle ola
|
||||||
|
WHERE ola.optionslist_id = a.optionslist_id
|
||||||
|
) AS optionsCount,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM articlelistarticle ala
|
||||||
|
WHERE ala.articlelist_id = a.articlelist_id
|
||||||
|
) AS articleListCount,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM article child
|
||||||
|
WHERE child.fatherarticle_id = a.id
|
||||||
|
) AS childCount,
|
||||||
|
a.fatherarticle_id AS fatherArticleId,
|
||||||
|
fa.sku AS fatherSku,
|
||||||
|
(
|
||||||
|
SELECT ca2.category_id
|
||||||
|
FROM categoryarticle ca2
|
||||||
|
WHERE ca2.article_id = a.id
|
||||||
|
ORDER BY ca2.category_id
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS categoryIdsJson,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
ap.sort,
|
||||||
|
p.hash AS fullHash,
|
||||||
|
p.w AS fullW,
|
||||||
|
p.h AS fullH,
|
||||||
|
p.size AS fullSize,
|
||||||
|
p.thumbw AS thumbW,
|
||||||
|
p.thumbh AS thumbH,
|
||||||
|
p.thumbhash AS thumbHash
|
||||||
|
FROM articlepic ap
|
||||||
|
INNER JOIN pic p ON p.id = ap.pic_id
|
||||||
|
WHERE ap.article_id = a.id
|
||||||
|
ORDER BY ap.sort ASC, ap.id ASC
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS photosJson,
|
||||||
|
(
|
||||||
|
SELECT ae.ean
|
||||||
|
FROM articleean ae
|
||||||
|
WHERE ae.article_id = a.id
|
||||||
|
ORDER BY ae.ean
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS eansJson,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
pr.minmultiplier,
|
||||||
|
pr.net,
|
||||||
|
t.rate AS taxRate,
|
||||||
|
pr.customergroup_id AS customergroupId
|
||||||
|
FROM price pr
|
||||||
|
INNER JOIN tax t ON t.id = pr.tax_id
|
||||||
|
WHERE pr.article_id = a.id
|
||||||
|
ORDER BY pr.customergroup_id, pr.minmultiplier
|
||||||
|
FOR JSON PATH
|
||||||
|
) AS pricesJson
|
||||||
|
FROM article a
|
||||||
|
LEFT JOIN articletext at
|
||||||
|
ON at.article_id = a.id AND at.language_id = auth.languageId
|
||||||
|
LEFT JOIN unit u ON u.id = a.unit_id
|
||||||
|
LEFT JOIN unittext ut
|
||||||
|
ON ut.unit_id = u.id AND ut.language_id = auth.languageId
|
||||||
|
LEFT JOIN brand b ON b.id = a.brand_id
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 bp2.pic_id
|
||||||
|
FROM brandpic bp2
|
||||||
|
WHERE bp2.brand_id = b.id
|
||||||
|
ORDER BY bp2.sort ASC, bp2.id ASC
|
||||||
|
) first_brand_pic
|
||||||
|
LEFT JOIN pic bp ON bp.id = first_brand_pic.pic_id
|
||||||
|
LEFT JOIN article fa ON fa.id = a.fatherarticle_id
|
||||||
|
WHERE a.id = auth.articleId
|
||||||
|
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
|
||||||
|
) AS articleJson
|
||||||
|
FROM auth
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,22 +1,70 @@
|
|||||||
import resolveArticleBySku from './resolveArticleBySku.js';
|
import { GET_ARTICLE_BY_SKU_FULL } from '../../sql/article.js';
|
||||||
import queryBySku from './queryBySku.js';
|
|
||||||
|
function parseJsonArray(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
const arr = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
return Array.isArray(arr) ? arr : [];
|
||||||
|
}
|
||||||
|
|
||||||
export default function getBySku(pool) {
|
export default function getBySku(pool) {
|
||||||
const resolve = resolveArticleBySku(pool);
|
|
||||||
const load = queryBySku(pool);
|
|
||||||
|
|
||||||
return async (googleAuthId, sku) => {
|
return async (googleAuthId, sku) => {
|
||||||
let t0 = performance.now();
|
const t0 = performance.now();
|
||||||
const { articleId, languageId } = await resolve(googleAuthId, sku);
|
const result = await pool
|
||||||
console.log(`[getBySku] resolveArticleBySku ${(performance.now() - t0).toFixed(1)}ms`);
|
.request()
|
||||||
|
.input('sku', sku)
|
||||||
|
.input('googleAuthId', googleAuthId)
|
||||||
|
.query(GET_ARTICLE_BY_SKU_FULL);
|
||||||
|
console.log(`[getBySku] single query ${(performance.now() - t0).toFixed(1)}ms`);
|
||||||
|
|
||||||
t0 = performance.now();
|
const row = result.recordset[0];
|
||||||
const article = await load(articleId, languageId);
|
if (!row) {
|
||||||
console.log(`[getBySku] queryBySku (parallel) ${(performance.now() - t0).toFixed(1)}ms`);
|
|
||||||
if (!article) {
|
|
||||||
throw new Error('Article not found');
|
throw new Error('Article not found');
|
||||||
}
|
}
|
||||||
|
if (!row.articleId || !row.categoryId) {
|
||||||
|
throw new Error('Article not found');
|
||||||
|
}
|
||||||
|
if (!row.companyId || !row.rootCategoryId || !row.languageId) {
|
||||||
|
throw new Error('Parent not found');
|
||||||
|
}
|
||||||
|
if (!row.companyuserId) {
|
||||||
|
throw new Error('Not authorized');
|
||||||
|
}
|
||||||
|
|
||||||
return article;
|
const a = typeof row.articleJson === 'string' ? JSON.parse(row.articleJson) : row.articleJson;
|
||||||
|
|
||||||
|
return {
|
||||||
|
article: {
|
||||||
|
id: a.id,
|
||||||
|
sku: a.sku,
|
||||||
|
name: a.name,
|
||||||
|
slug: a.slug,
|
||||||
|
unitamount: a.unitamount,
|
||||||
|
weight: a.weight,
|
||||||
|
unit: {
|
||||||
|
si: a.unitSi,
|
||||||
|
basesi: a.unitBasesi,
|
||||||
|
basesifactor: a.unitBasesifactor,
|
||||||
|
symbol: a.unitSymbol ?? null,
|
||||||
|
name: a.unitName ?? null,
|
||||||
|
},
|
||||||
|
brand: a.brandName
|
||||||
|
? {
|
||||||
|
name: a.brandName,
|
||||||
|
thumbW: a.brandThumbW,
|
||||||
|
thumbH: a.brandThumbH,
|
||||||
|
thumbHash: a.brandThumbHash,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
categoryIds: parseJsonArray(a.categoryIdsJson).map((r) => r.category_id),
|
||||||
|
photos: parseJsonArray(a.photosJson),
|
||||||
|
eans: parseJsonArray(a.eansJson).map((r) => r.ean),
|
||||||
|
prices: parseJsonArray(a.pricesJson),
|
||||||
|
optionsCount: a.optionsCount,
|
||||||
|
articleListCount: a.articleListCount,
|
||||||
|
childCount: a.childCount,
|
||||||
|
fatherArticleId: a.fatherArticleId,
|
||||||
|
fatherSku: a.fatherSku,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user