Estou com um problema com meu script T-SQL. Estou pensando e trabalhando em como produzir a saída esperada dele.
Aqui está meu script T-SQL:
DECLARE @user_id VARCHAR(50) = 'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL'; -- FOR TESTING PURPOSES ONLY
DECLARE @status_t TABLE (status VARCHAR(50));
INSERT INTO @status_t VALUES ('PENDING'),('APPROVED'),('PROCESSING')
SELECT
orders.batch_code, -- FOR DEMONSTRATION PURPOSES ONLY
oa_prod.nested_data AS nested_prod,
cnb.amount_to_pay
FROM
[testDB].[dbo].[Orders] AS orders
RIGHT OUTER JOIN
[testDB].[dbo].[Payment] AS cnb
ON
orders.order_id = cnb.order_id
LEFT OUTER JOIN
[testDB].[dbo].[StatusRef] AS stat_ref
ON
orders.stat_ref_id = stat_ref.stat_ref_id
CROSS APPLY( -- OR OUTER APPLY
SELECT
orders.batch_code,
orders.order_id,
prod.prod_name,
pv_kn.key_name,
pv_kv.value,
CASE WHEN
orders.prod_id IS NOT NULL
THEN
prod.disc_price
WHEN
orders.prod_var_id IS NOT NULL
THEN
prod_var.disc_price
END AS disc_price,
orders.quantity
FROM
[testDB].[dbo].[Product] AS prod
RIGHT OUTER JOIN
[testDB].[dbo].[SubProduct] AS prod_var
ON
prod.prod_id = prod_var.prod_id
LEFT OUTER JOIN
[testDB].[dbo].[SubProductVarKeyValue] AS pv_kv
ON
prod_var.prod_var_id = pv_kv.prod_var_id
LEFT OUTER JOIN
[testDB].[dbo].[SubProductVarKeyNames] AS pv_kn
ON
pv_kv.pv_key_name_id = pv_kn.pv_key_name_id
WHERE
prod.prod_id = orders.prod_id
OR prod_var.prod_var_id = orders.prod_var_id
FOR JSON PATH,
INCLUDE_NULL_VALUES
) AS oa_prod(nested_data) -- it's a syntax analyzer lint conflict ONLY
WHERE
orders.disable = 0
AND cnb.disable = 0
AND orders.user_id = @user_id
AND stat_ref.com_usd_wrds IN (SELECT status FROM @status_t)
ORDER BY
orders.dt_stamp DESC,
orders.batch_code ASC -- To prevent batch_code from being separated
FOR JSON PATH,
INCLUDE_NULL_VALUES
Aqui está meu exemplo mínimo e reproduzível usando variáveis de tabela:
DECLARE @StatusRef TABLE(
stat_ref_id VARCHAR(50) PRIMARY KEY NOT NULL,
com_usd_wrds NVARCHAR(100) NOT NULL
);
DECLARE @Product TABLE(
prod_id VARCHAR(50) PRIMARY KEY NOT NULL,
prod_name VARCHAR(200) NOT NULL,
stock INT NOT NULL,
disc_price DECIMAL(12, 2) NOT NULL
);
DECLARE @SubProduct TABLE(
prod_var_id VARCHAR(50) PRIMARY KEY NOT NULL,
stock INT NOT NULL,
disc_price DECIMAL(12, 2) NOT NULL,
prod_id VARCHAR(50) NOT NULL
);
DECLARE @Orders TABLE(
order_id VARCHAR(50) PRIMARY KEY NOT NULL,
batch_code VARCHAR(50) NULL,
quantity INT NOT NULL,
stat_ref_id VARCHAR(50) NOT NULL,
disable BIT DEFAULT (0) NOT NULL,
dt_stamp DATETIME NOT NULL,
prod_id VARCHAR(50) NULL,
prod_var_id VARCHAR(50) NULL,
user_id VARCHAR(50) NOT NULL
);
DECLARE @Payment TABLE(
amount_to_pay DECIMAL(14, 2) NOT NULL,
order_id VARCHAR(50) NOT NULL,
disable BIT DEFAULT (0) NOT NULL
);
DECLARE @SubProductVarKeyValue TABLE(
value VARCHAR(100) NOT NULL,
prod_var_id VARCHAR(50) NOT NULL,
pv_key_name_id VARCHAR(50) NOT NULL
);
DECLARE @SubProductVarKeyNames TABLE(
pv_key_name_id VARCHAR(50) PRIMARY KEY NOT NULL,
key_name VARCHAR(100) NOT NULL
);
INSERT INTO @StatusRef
VALUES
(
'STAT-REF-1001', -- stat_ref_id
'PENDING' -- com_usd_wrds
),
(
'STAT-REF-1002', -- stat_ref_id
'APPROVED' -- com_usd_wrds
),
(
'STAT-REF-1003', -- stat_ref_id
'PROCESSING' -- com_usd_wrds
);
INSERT INTO @Product
VALUES
(
'PROD-ID-1001', -- prod_id
'iPhone', -- prod_name
0, -- stock | dependent to @SubProduct
0.00 -- disc_price | dependent to @SubProduct
),
(
'PROD-ID-1002', -- prod_id
'Samsung', -- prod_name
0, -- stock | dependent to @SubProduct
0.00 -- disc_price | dependent to @SubProduct
),
(
'PROD-ID-1003', -- prod_id
'Nokia', -- prod_name
75, -- stock
33150.00 -- disc_price
),
(
'PROD-ID-1004', -- prod_id
'Google', -- prod_name
100, -- stock
53509.00 -- disc_price
),
(
'PROD-ID-1005', -- prod_id
'Sony', -- prod_name
0, -- stock | dependent to @SubProduct
0.00 -- disc_price | dependent to @SubProduct
),
(
'PROD-ID-1006', -- prod_id
'Lenovo', -- prod_name
0, -- stock | dependent to @SubProduct
0.00 -- disc_price | dependent to @SubProduct
);
INSERT INTO @SubProduct
VALUES
(
'PROD-VAR-ID-1', -- prod_var_id
25, -- stock
45809.00, -- disc_price
'PROD-ID-1001' -- prod_id
),
(
'PROD-VAR-ID-2', -- prod_var_id
50, -- stock
40209.00, -- disc_price
'PROD-ID-1002' -- prod_id
),
(
'PROD-VAR-ID-3', -- prod_var_id
0, -- stock | dependent to @Product
0.00, -- disc_price | dependent to @Product
'PROD-ID-1003' -- prod_id
),
(
'PROD-VAR-ID-4', -- prod_var_id
0, -- stock | dependent to @Product
0.00, -- disc_price | dependent to @Product
'PROD-ID-1004' -- prod_id
),
(
'PROD-VAR-ID-5', -- prod_var_id
125, -- stock
25809.00, -- disc_price
'PROD-ID-1005' -- prod_id
),
(
'PROD-VAR-ID-6', -- prod_var_id
150, -- stock
49100.00, -- disc_price
'PROD-ID-1006' -- prod_id
);
INSERT INTO @SubProductVarKeyValue
VALUES
(
'new', -- value
'PROD-VAR-ID-1', -- prod_var_id
'PVKN-ID-1' -- pv_key_name_id
),
(
'new', -- value
'PROD-VAR-ID-2', -- prod_var_id
'PVKN-ID-1' -- pv_key_name_id
),
(
'new', -- value
'PROD-VAR-ID-5', -- prod_var_id
'PVKN-ID-1' -- pv_key_name_id
),
(
'new', -- value
'PROD-VAR-ID-6', -- prod_var_id
'PVKN-ID-1' -- pv_key_name_id
)
INSERT INTO @SubProductVarKeyNames
VALUES
(
'PVKN-ID-1', -- pv_key_name_id
'Condition' -- key_name
)
INSERT INTO @Orders
(
order_id,
batch_code,
quantity,
stat_ref_id,
dt_stamp,
prod_id,
prod_var_id,
user_id
)
VALUES
(
'ORDER-2025-04-11-B71D0E2F5D8C', -- order_id
NULL, -- batch_code
1, -- quantity
'STAT-REF-1003', -- stat_ref_id
'2025-04-14 10:17:20.963', -- dt_stamp
NULL, -- prod_id
'PROD-VAR-ID-1', -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
),
(
'ORDER-2025-04-11-D95EB033CA40', -- order_id
'BGUID-2025-04-11-6D81B58FAE94', -- batch_code
2, -- quantity
'STAT-REF-1001', -- stat_ref_id
'2025-04-13 09:17:20.963', -- dt_stamp
NULL, -- prod_id
'PROD-VAR-ID-2', -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
),
(
'ORDER-2025-04-11-7F04EFA2BB60', -- order_id
'BGUID-2025-04-11-6D81B58FAE94', -- batch_code
2, -- quantity
'STAT-REF-1001', -- stat_ref_id
'2025-04-13 09:17:20.963', -- dt_stamp
'PROD-ID-1003', -- prod_id
NULL, -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
),
(
'ORDER-2025-04-10-3F03EAA47686', -- order_id
'BGUID-2025-04-10-20239FD2059F', -- batch_code
1, -- quantity
'STAT-REF-1002', -- stat_ref_id
'2025-04-12 08:17:20.963', -- dt_stamp
'PROD-ID-1004', -- prod_id
NULL, -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
),
(
'ORDER-2025-04-10-F4A89E2C4A30', -- order_id
'BGUID-2025-04-10-20239FD2059F', -- batch_code
1, -- quantity
'STAT-REF-1002', -- stat_ref_id
'2025-04-12 08:17:20.963', -- dt_stamp
NULL, -- prod_id
'PROD-VAR-ID-5', -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
),
(
'ORDER-2025-04-08-31BD887341FA', -- order_id
NULL, -- batch_code
1, -- quantity
'STAT-REF-1001', -- stat_ref_id
'2025-04-11 07:17:20.963', -- dt_stamp
NULL, -- prod_id
'PROD-VAR-ID-6', -- prod_var_id
'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL' -- user_id
);
INSERT INTO @Payment
(
amount_to_pay,
order_id
)
VALUES
(
45809.00, -- amount_to_pay
'ORDER-2025-04-11-B71D0E2F5D8C' -- order_id
),
(
146718.00, -- amount_to_pay
'ORDER-2025-04-11-D95EB033CA40' -- order_id
),
(
146718.00, -- amount_to_pay
'ORDER-2025-04-11-7F04EFA2BB60' -- order_id
),
(
79318.00, -- amount_to_pay
'ORDER-2025-04-10-3F03EAA47686' -- order_id
),
(
79318.00, -- amount_to_pay
'ORDER-2025-04-10-F4A89E2C4A30' -- order_id
),
(
49100.00, -- amount_to_pay
'ORDER-2025-04-08-31BD887341FA' -- order_id
);
SELECT * FROM @StatusRef
SAÍDA:
stat_ref_id | com_usd_wrds |
---|---|
STAT-REF-1001 | PENDENTE |
STAT-REF-1002 | APROVADO |
STAT-REF-1003 | PROCESSAMENTO |
SELECT * FROM @Product
SAÍDA:
id_do_produto | nome_do_produto | estoque | preço_disc |
---|---|---|---|
PROD-ID-1001 | iPhone | 0 | 0,00 |
PROD-ID-1002 | Samsung | 0 | 0,00 |
PROD-ID-1003 | Nokia | 75 | 33150,00 |
PROD-ID-1004 | 100 | 53509,00 | |
PROD-ID-1005 | Sony | 0 | 0,00 |
PROD-ID-1006 | Lenovo | 0 | 0,00 |
SELECT * FROM @SubProduct
SAÍDA:
id_var_do_produto | estoque | preço_disc | id_do_produto |
---|---|---|---|
PROD-VAR-ID-1 | 25 | 45809,00 | PROD-ID-1001 |
PROD-VAR-ID-2 | 50 | 40209,00 | PROD-ID-1002 |
PROD-VAR-ID-3 | 0 | 0,00 | PROD-ID-1003 |
PROD-VAR-ID-4 | 0 | 0,00 | PROD-ID-1004 |
PROD-VAR-ID-5 | 125 | 25809,00 | PROD-ID-1005 |
PROD-VAR-ID-6 | 150 | 49100,00 | PROD-ID-1006 |
SELECT * FROM @Orders ORDER BY dt_stamp
SAÍDA:
id_do_pedido | código_de_lote | quantidade | stat_ref_id | desabilitar | dt_stamp | id_do_produto | id_var_do_produto | ID do usuário |
---|---|---|---|---|---|---|---|---|
ORDEM-2025-04-08-31BD887341FA | NULO | 1 | STAT-REF-1001 | 0 | 2025-04-11 07:17:20.963 | NULO | PROD-VAR-ID-6 | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
ORDEM-2025-04-10-3F03EAA47686 | BGUID-2025-04-10-20239FD2059F | 1 | STAT-REF-1002 | 0 | 2025-04-12 08:17:20.963 | PROD-ID-1004 | NULO | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
ORDEM-2025-04-10-F4A89E2C4A30 | BGUID-2025-04-10-20239FD2059F | 1 | STAT-REF-1002 | 0 | 2025-04-12 08:17:20.963 | NULO | PROD-VAR-ID-5 | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
PEDIDO-2025-04-11-7F04EFA2BB60 | BGUID-2025-04-11-6D81B58FAE94 | 2 | STAT-REF-1001 | 0 | 2025-04-13 09:17:20.963 | PROD-ID-1003 | NULO | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
ORDEM-2025-04-11-D95EB033CA40 | BGUID-2025-04-11-6D81B58FAE94 | 2 | STAT-REF-1001 | 0 | 2025-04-13 09:17:20.963 | NULO | PROD-VAR-ID-2 | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
ORDEM-2025-04-11-B71D0E2F5D8C | NULO | 1 | STAT-REF-1003 | 0 | 2025-04-14 10:17:20.963 | NULO | PROD-VAR-ID-1 | UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL |
SELECT * FROM @Payment
SAÍDA:
valor_a_pagar | id_do_pedido | desabilitar |
---|---|---|
45809,00 | ORDEM-2025-04-11-B71D0E2F5D8C | 0 |
146718,00 | ORDEM-2025-04-11-D95EB033CA40 | 0 |
146718,00 | PEDIDO-2025-04-11-7F04EFA2BB60 | 0 |
79318,00 | ORDEM-2025-04-10-3F03EAA47686 | 0 |
79318,00 | ORDEM-2025-04-10-F4A89E2C4A30 | 0 |
45809,00 | ORDEM-2025-04-08-31BD887341FA | 0 |
SELECT * FROM @SubProductVarKeyValue
SAÍDA:
valor | id_var_do_produto | pv_key_name_id |
---|---|---|
novo | PROD-VAR-ID-1 | PVKN-ID-1 |
novo | PROD-VAR-ID-2 | PVKN-ID-1 |
novo | PROD-VAR-ID-5 | PVKN-ID-1 |
novo | PROD-VAR-ID-6 | PVKN-ID-1 |
SELECT * FROM @SubProductVarKeyNames
SAÍDA:
pv_key_name_id | nome_da_chave |
---|---|
PVKN-ID-1 | Doença |
Aqui está o exemplo modificado do script que é semelhante ao primeiro fornecido:
DECLARE @user_id VARCHAR(50) = 'UGUID-2025-01-27-14-09-22-1967-ABCDEFGHIJKL'; -- FOR TESTING PURPOSES ONLY
DECLARE @status_t TABLE (status VARCHAR(50));
INSERT INTO @status_t VALUES ('PENDING'),('APPROVED'),('PROCESSING')
SELECT
orders.batch_code, -- FOR DEMONSTRATION PURPOSES ONLY
oa_prod.nested_data AS nested_prod,
cnb.amount_to_pay
FROM
@Orders AS orders
RIGHT OUTER JOIN
@Payment AS cnb
ON
orders.order_id = cnb.order_id
LEFT OUTER JOIN
@StatusRef AS stat_ref
ON
orders.stat_ref_id = stat_ref.stat_ref_id
CROSS APPLY( -- OR OUTER APPLY
SELECT
orders.batch_code,
orders.order_id,
prod.prod_name,
pv_kn.key_name,
pv_kv.value,
CASE WHEN
orders.prod_id IS NOT NULL
THEN
prod.disc_price
WHEN
orders.prod_var_id IS NOT NULL
THEN
prod_var.disc_price
END AS disc_price,
orders.quantity
FROM
@Product AS prod
RIGHT OUTER JOIN
@SubProduct AS prod_var
ON
prod.prod_id = prod_var.prod_id
LEFT OUTER JOIN
@SubProductVarKeyValue AS pv_kv
ON
prod_var.prod_var_id = pv_kv.prod_var_id
LEFT OUTER JOIN
@SubProductVarKeyNames AS pv_kn
ON
pv_kv.pv_key_name_id = pv_kn.pv_key_name_id
WHERE
prod.prod_id = orders.prod_id
OR prod_var.prod_var_id = orders.prod_var_id
FOR JSON PATH,
INCLUDE_NULL_VALUES
) AS oa_prod(nested_data) -- it's a syntax analyzer lint conflict ONLY
WHERE
orders.disable = 0
AND cnb.disable = 0
AND orders.user_id = @user_id
AND stat_ref.com_usd_wrds IN (SELECT status FROM @status_t)
ORDER BY
orders.dt_stamp DESC
--orders.batch_code ASC -- To prevent batch_code from being separated
FOR JSON PATH,
INCLUDE_NULL_VALUES
Infelizmente, quando embelezo a saída JSON, isso é o que ele gera:
[
{
"batch_code": null,
"nested_prod": [
{
"batch_code": null,
"order_id": "ORDER-2025-04-11-B71D0E2F5D8C",
"prod_name": "iPhone",
"key_name": "Condition",
"value": "new",
"disc_price": 45809,
"quantity": 1
}
],
"amount_to_pay": 45809
},
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"order_id": "ORDER-2025-04-11-D95EB033CA40",
"prod_name": "Samsung",
"key_name": "Condition",
"value": "new",
"disc_price": 40209,
"quantity": 2
}
],
"amount_to_pay": 146718
},
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"order_id": "ORDER-2025-04-11-7F04EFA2BB60",
"prod_name": "Nokia",
"key_name": null,
"value": null,
"disc_price": 33150,
"quantity": 2
}
],
"amount_to_pay": 146718
},
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"order_id": "ORDER-2025-04-10-3F03EAA47686",
"prod_name": "Google",
"key_name": null,
"value": null,
"disc_price": 53509,
"quantity": 1
}
],
"amount_to_pay": 79318
},
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"order_id": "ORDER-2025-04-10-F4A89E2C4A30",
"prod_name": "Sony",
"key_name": "Condition",
"value": "new",
"disc_price": 25809,
"quantity": 1
}
],
"amount_to_pay": 79318
},
{
"batch_code": null,
"nested_prod": [
{
"batch_code": null,
"order_id": "ORDER-2025-04-08-31BD887341FA",
"prod_name": "Lenovo",
"key_name": "Condition",
"value": "new",
"disc_price": 49100,
"quantity": 1
}
],
"amount_to_pay": 49100
}
]
No entanto, preciso que a saída seja algo como isto:
[
{
"batch_code": null,
"nested_prod": [
{
"batch_code": null,
"order_id": "ORDER-2025-04-11-B71D0E2F5D8C",
"prod_name": "iPhone",
"key_name": "Condition",
"value": "new",
"disc_price": 45809,
"quantity": 1
}
],
"amount_to_pay": 45809
},
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"order_id": "ORDER-2025-04-11-D95EB033CA40",
"prod_name": "Samsung",
"key_name": "Condition",
"value": "new",
"disc_price": 40209,
"quantity": 2
},
{
"batch_code": "BGUID-2025-04-11-6D81B58FAE94",
"order_id": "ORDER-2025-04-11-7F04EFA2BB60",
"prod_name": "Nokia",
"key_name": null,
"value": null,
"disc_price": 33150,
"quantity": 2
}
],
"amount_to_pay": 146718
},
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"nested_prod": [
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"order_id": "ORDER-2025-04-10-3F03EAA47686",
"prod_name": "Google",
"key_name": null,
"value": null,
"disc_price": 53509,
"quantity": 1
},
{
"batch_code": "BGUID-2025-04-10-20239FD2059F",
"order_id": "ORDER-2025-04-10-F4A89E2C4A30",
"prod_name": "Sony",
"key_name": "Condition",
"value": "new",
"disc_price": 25809,
"quantity": 1
}
],
"amount_to_pay": 79318
},
{
"batch_code": null,
"nested_prod": [
{
"batch_code": null,
"order_id": "ORDER-2025-04-08-31BD887341FA",
"prod_name": "Lenovo",
"key_name": "Condition",
"value": "new",
"disc_price": 49100,
"quantity": 1
}
],
"amount_to_pay": 49100
}
]
Observação: O amount_to_pay
foi pré-calculado no aplicativo cliente. Portanto, ele deve se tornar uma instância única quando batch_code
s duplicados forem agrupados.
Os pedidos do usuário podem causar variações na saída JSON desejada mostrada acima.
Alguém está familiarizado com o problema que estou enfrentando?
Na medida do possível, prefiro não implementar a abordagem Common Table Expression (CTE).
Vou ignorar o dano cerebral de entender o esquema inteiro. Acho que essa abordagem vai agrupar as coisas da maneira que você quiser. Achei que JSON_QUERY() não deveria escapar as aspas nas chaves e valores no JSON aninhado, mas ainda as recebo quando executo isso. Provavelmente deixei passar algo aí.
A maneira como você tenta construir um JSON está correta: aninhar uma subconsulta FOR JSON PATH ou uma tabela unida na instrução SELECT gerará o texto JSON aninhado desejado. No entanto, existem alguns relacionamentos incorretos na sua amostra de dados, o que causa um resultado inesperado.
No meu entendimento:
O nível mais externo da sua saída deve ser o objeto BATCH, que é agregado da tabela @orders que possui o mesmo batch_code. Presumo que pedidos com o mesmo valor de batch_code também tenham os mesmos valores de stat_ref_id, disable, user_id e dt_stamp. Para que eu possa incluir essas colunas em um conjunto de dados, chamo-o de batches.
The BATCH object also has a property amount_to_pay, observe the sample data, it should be the value of sum(disc_price * quantity) of the orders with same value of batch_code. In your query, this value comes from the table @Payment, but in the table, column amount_to_pay is related to the @orders by order_id, it will repeating with @orders, so we need to add a new relationship to the dataset batched.
The most importaint is object BATCH includes an array which contains object ORDER, it look like each ORDER has only one PRODUCT/SUBPRODUCT object, this must be a nested array in JSON. To impliment this, the CROSS APPLY must filter orders by batch_code.
If my understanding is correct, issues in your SQL script are:
Your main query is based on table @orders, it has 6 validated rows, the top level of JSON result has to be a array contain 6 objects.
The result you expect has 4 top level objects, If you aggregate the @orders by batch_code directly, NULL values make you cannot pass it into subquery and filter correct orders/products. We have to find a valid key column during aggregating to represent batch_id.
The column amount_to_pay is come from table @Payment, but the only column can make relationship is order_id, if the main query JOIN with order_id will extend the rows either. We must add a key that can JOIN with the new batch_id in main query.
The subquery of CROSS APPLY should filter the table @orders by batch_code, since the NULL value make this impossible, we need to add a batch_id in this @orders table as filter key too.
Of all the previous points, add a batch_id column in the @orders table is key.
I think no batch number means only has one order, then I can treat the order_id as batch_code when the batch_code is null. So I building a CTE p_orders before the query, using IIF(batch_code IS NULL) to create a new column batch_id as key.
Then I changing the main query to a nested query which SELECT DISTINCT batch_id and some columns as the dataset batches.
I changing the dataset cnb to add a batch_id by JOIN the @Payment and the table p_orders.
At last, I adding a JOIN p_tables in the subquery of CROSS APPLY to get columns value which must show in JSON, then filtering inner rows by the value of batch_id of the main query batches.
Code is here:
Code without CTE: