0

This is my database schema:

Database schema

I am trying to query products to display on my website's front page. Although, I need every product row to come with information about it's availability. Also, I need to ability to order these rows so that products that are not in stock come last.

This is what I am looking to recieve as json in my front end website, if the product is available, list the sizes in the "inventory" property, if not, inventory should be null:

[
    {
        id: 1,
        name: "product 1",
        sku: "dslfgsd",
        brand_slug: "some-brand",
        inventory: [
            { "price": 400, "size": "41½" },
            { "price": 500, "size": "42" }
        ]
    },
    {
        id: 2,
        name: "product 2",
        sku: "djsfkj",
        brand_slug: "some-other-brand",
        inventory: null
    }
]

My first attempt was to run two different queries then matching the available sizes to the products. So, I run the first query to get products, get the product ids, then search for them in the junction table. Then I match them to the products, sort them so that the products that are available rank first. I don't like the following attempt because it uses two queries and won't work well with pagination since I am only ordering the 12 products received from the first query.

const productsQuery = `
    SELECT products.id,
        products.name,
        products.sku,
        brands.slug as brand_slug
    FROM products
    INNER JOIN brands
        ON products.brand = brands.id
        AND products.public IS true
    LIMIT 12
`
 
const productsResult = await database.query(productsQuery)

let products = productsResult.rows.map(p => ({ ...p, inventory: [] }))

if (products.length === 0) return { props: { initialProducts: products } }

const productIds = products.map(r => r.id)

const inventoryQuery = `
    SELECT products_sizes_suppliers.product_id,
        products_sizes_suppliers.price,
        sizes.display_text as size
    FROM products_sizes_suppliers
    INNER JOIN sizes
        ON products_sizes_suppliers.size_id = sizes.id
        AND products_sizes_suppliers.product_id IN (${productIds.join(",")})
        AND cost IS NOT NULL
        AND price IS NOT NULL
    ORDER BY sizes.numerical_value
`

const inventoryResult = await database.query(inventoryQuery)

for (let inventory of inventoryResult.rows) {
    for (let index = 0; index < products.length; index++) {
            if (products[index].id === inventory.product_id) products[index].inventory = [...products[index].inventory, inventory]
    }
}

products = products
    .sort((a, b) => {
        if (a.inventory.length === 0 && b.inventory.length > 0) return 1
        if (a.inventory.length > 0 && b.inventory.length === 0) return -1
        if ((a.inventory.length === 0 && b.inventory.length === 0)) return 0
    })
    .map(product => {
        const newProduct = { ...product }
        if (newProduct.inventory.length === 0) newProduct.inventory = null
        return newProduct
    })

My second attempt was to do everything in one query. I used array_to_json, array_agg and json_build_object. But I am unable to order this query.

SELECT products_a.id,
    products_a.name,
    products_a.sku,
    brands_a.slug as brand_slug,
    (
        SELECT array_to_json(array_agg(json_build_object('price', products_sizes_suppliers.price, 'size', sizes.display_text))) AS inventory
        FROM products_sizes_suppliers
        INNER JOIN sizes
        ON products_sizes_suppliers.size_id = sizes.id
            AND products_sizes_suppliers.product_id = products_a.id
            AND products_sizes_suppliers.cost IS NOT NULL
            AND products_sizes_suppliers.price IS NOT NULL
    )
FROM products as products_a
INNER JOIN brands as brands_a
    ON brands_a.id = products_a.brand
    AND products_a.public IS true

Query result

I am unable to order this query using ORDER BY inventory DESC NULLS LAST which results in the error: "could not identify an ordering operator for type json". Other attempts I have made result in the error: "column "inventory" does not exist".

I feel as though I am missing a core concept here, and that there is a proper way of doing this as I know SQL is a very powerful language.

Thanks in advance.

2 Answers 2

1

To determine the order in which rows are aggreated, use

jsonb_agg(element ORDER BY expression)
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for you answer. But how would this be integrated into the query?
Instead of the array_to_json(array_agg(...)).
0

@Lauren Albe Pointed me in the right direction, his suggestion was to use jsonb_agg(element ORDER BY expression). I had to use the ORDER BY clause outside of the function and I had to find something to order it with do I just chose the price and the date the product was added. Also this article came in very handy.

EDIT: The first query didn't really solve the problem because it returned duplicates. Here is the one that worked:

SELECT products.id,
    products.activity,
    products.colors,
    products.gender,
    products.images,
    products.name,
    products.sku,
    brands.slug as brand_slug,
    jsonb_agg(to_jsonb(variants) - 'product_id' - 'numerical_value' ORDER BY numerical_value) AS inventory
FROM products
INNER JOIN brands
    ON products.brand = brands.id
LEFT JOIN (
    SELECT products_sizes_suppliers.product_id,
        products_sizes_suppliers.price,
        sizes.display_text as size,
        sizes.numerical_value
    FROM products_sizes_suppliers
    INNER JOIN sizes
        ON products_sizes_suppliers.size_id = sizes.id
) AS variants
    ON products.id = variants.product_id
GROUP BY products.id, brands.slug, variants.product_id
HAVING products.public IS true
ORDER BY variants.product_id, products.added_on DESC NULLS LAST
LIMIT 12

The old query:

SELECT products.id,
    products.sku,
    jsonb_agg(
        to_jsonb(something) - 'product_id'
    ) AS inventory
FROM products
LEFT JOIN (
    SELECT products_sizes_suppliers.product_id,
        products_sizes_suppliers.price,
        sizes.display_text
    FROM products_sizes_suppliers
    INNER JOIN sizes
        ON products_sizes_suppliers.size_id = sizes.id
) AS something ON products.id = something.product_id
GROUP BY products.id, something.price
ORDER BY price, products.added_on DESC NULLS LAST

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.