SQL Server multiple select on an Offset...fetch...next query - sql-server

I'm trying to get data into datatables (js library for data table) by server-side processing.
The data should be produced as below
+---------+--------+--------+
| Name | TotalA | TotalB |
+---------+--------+--------+
| Person1 | 10 | 40 |
+---------+--------+--------+
The query that I tried
select
a.Name,
(select count(*) from SummaryA where id = a.id) as TotalA,
(select count(*) from SummaryB where id = a.id) as TotalB
from
records a
order by
a.Name
offset 0 rows fetch next 10 rows only
and
select
aa.Name,
(select count(*) from SummaryA where id = aa.id) as TotalA,
(select count(*) from SummaryB where id = aa.id) as TotalB
from
(select
a.Name, a.id
from
records a
order by
a.Name
offset 0 rows fetch next 10 rows only) as aa
However, these queries will result in an error as below
Error in query: Invalid usage of the option NEXT in the FETCH statement.
Running below query is not a problem
select
a.Name
from
records a
offset 0 rows fetch next 10 rows only

Issue- offset_row_count_expression can be a variable, parameter, or constant scalar subquery. When a subquery is used, it cannot reference any columns defined in the outer query scope.
link
Try
;with temp as (select a.name ,
count(b.id) as TotalA ,
count(c.id) as Totalb
FROM records a
left join SummaryA b
b.id=a.id
left join SummaryB c
c.id=a.id
group by a.name)
select * from temp
order by temp.Name
Offset 0 rows
fetch next 10 rows only
This can also be solved
with tmp as (
select a.name ,
a.id
FROM records a
order by temp.Name
Offset 0 rows
fetch next 10 rows only
)
select a.name ,
count(b.id) as TotalA ,
count(c.id) as Totalb
FROM tmp a
left join SummaryA b
b.id=a.id
left join SummaryB c
c.id=a.id
group by a.name order by a.Name

Related

How to split row value according to specific column?

I have Table A and Table B and I'm having trouble getting the fulfilled qty in Table B where sum up of Fulfilled qty is equal to Available field in Table A.
For Item ID 1, the available qty is 99 in Table A and in Table B this Item ID 1 have different rack, with 99 qty, it only able to fulfill 60 qty for Rack A and remaining 39 qty for Rack B.
Table A
ID Available
1 99
2 5
Table B
ID Rack Required
1 A 60
1 B 102
1 C 8
2 A 10
Desired Results
ID Rack Required Fulfilled
1 A 60 60
1 B 102 39
1 C 8 0
2 A 4 4
2 B 2 1
I have tried using query below but seems not able to get the expected results
SELECT ID,
RACK,
REQUIREDQTY,
SUM(FULFILLEDQTY) OVER (ORDER BY ID,
RACK,
rows between unbounded preceding and 1 preceding) AS FULFILLEDQTY
FROM TABLEB
LEFT JOIN TABLEA ON TABLEB.ID = TABLEA.ID;
use sum(Required) over (...) and check against Available
select A.ID, B.Rack, B.Required,
Fulfilled = case when sum(B.Required) over (partition by A.ID order by B.Rack)
<= A.Available
then B.Required
when sum(B.Required) over (partition by A.ID order by B.Rack)
- B.Required <= A.Available
then A.Available
- sum(B.Required) over (partition by A.ID order by B.Rack)
+ B.Required
else 0
end
from A
inner join B on A.ID = B.ID
order by A.ID, B.Rack
db<>fiddle demo
EDIT : due to change of structure (additional column rack)
select A.ID, A.rack, B.bin, B.Required,
Fulfilled = case when sum(B.Required) over (partition by A.ID, A.rack
order by B.bin)
<= A.Available
then B.Required
when sum(B.Required) over (partition by A.ID, A.rack
order by B.bin)
- B.Required
<= A.Available
then A.Available
- sum(B.Required) over (partition by A.ID, A.rack
order by B.bin)
+ B.Required
else 0
end
from A
inner join B on A.ID = B.ID and A.rack = B.rack
order by A.ID, A.rack, B.bin

Need guidance in forming a query in snowflake

SELECT id,
login_id,
count,
case when count = 0 then 'Cat_A'
WHEN count between 1 and 10 then 'Cat_B'
WHEN count > 10 then 'Cat_C'
WHEN count IS NULL THEN 'Cat D'
END as Category
FROM
(
select id,login_id,min(ord_count) AS count
FROM table_1 X
JOIN table_2 Y
ON X.id_col = Y.id_col
WHERE date = '2022-02-02'
AND login_id = 'True'
group by id,login_id
)A
LEFT JOIN
(
SELECT id,COUNT(X.ord_no) AS count_of_orders
FROM table_1 X
WHERE X.date = '2022-02-02'
group by id
)B
ON A.id=B.id
When I join these two tables, I'm getting NULL values for the unmatched records.
I need to replace those NULL records to some hardcoded value say 'XYZ'.
Any guidance on how to achieve this please?
So the top level select needs to name which ID it is using (other DB's don't require this snowflake does), given you are selecting from A and b.id might be missing, it should be a.id
count_of_orders is not used, so currently the LEFT JOIN to B is pointless, given your question is about LEFT JOIN this must be the column you a referring to??
The replace NULL values can be done via COALESCE or NVL or ZEROIFNULL, given the only null thing is a count, zeroifnull seems to make sense here.
which all make me think your SQL needs to look like:
SELECT
a.id,
a.login_id,
a.count,
case
WHEN a.count = 0 then 'Cat_A'
WHEN a.count between 1 and 10 then 'Cat_B'
WHEN a.count > 10 then 'Cat_C'
WHEN a.count IS NULL THEN 'Cat D'
END as Category,
ZEROIFNULL(b.count_of_orders) as count_of_orders
FROM (
SELECT
id,
login_id,
min(ord_count) AS count
FROM table_1 AS X
JOIN table_2 AS Y
ON X.id_col = Y.id_col
WHERE date = '2022-02-02'
AND login_id = 'True'
group by id,login_id
) as A
LEFT JOIN (
SELECT
x.id,
COUNT(X.ord_no) AS count_of_orders
FROM table_1 as X
WHERE X.date = '2022-02-02'
group by x.id
)as B
ON A.id=B.id
The A sub-select really should use the aliases you named X, Y so we know which tables id, login_id, ord_count, & date all come from.

Recurcive cte to identify circular reference in data

I am trying to identify recursive/circular reference in my data for which I need recursive cte.
For example I have table that contains Product_ID and Inner_Product_ID. I want results when Product_ID A is inner to Product_ID B, which is inner to Product_ID C, which is inner to Product_ID A.
Sample data
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
1 2
3 4
Expected output
PRODUCT_ID INNER_PRODUCT_ID
12 36
24 12
36 24
I have tried basic query with cte but not sure how to implement recursive cte for this problem:
;WITH RNCTE
AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY pr1.PRODUCT_ID
ORDER BY pr1.PRODUCT_ID
) rn
FROM
TableName pr1),
cte
AS ( SELECT *
FROM RNCTE
WHERE RNCTE.rn = 1
UNION ALL
SELECT *
FROM cte c
JOIN RNCTE r
ON r.PRODUCT_ID = c.PRODUCT_ID
AND r.rn = c.rn + 1)
SELECT *
FROM cte;
try this - it walks through the linked records, and finds if the 'walk' eventually terminates, or not. If it lasts for more than the number of records in the table, then it must be a loop. 'Efficient' I am not sure of that!
;WITH UCNT AS (SELECT count(0) c from products),
RNCTE
AS (SELECT 1 as Levle, Product_ID, INNER_PRODUCT_ID FROM Products
UNION ALL
SELECT levle + 1, P.Product_ID, P.INNER_PRODUCT_ID
FROM RNCTE R
JOIN Products P
ON P.PRODUCT_ID = R.INNER_PRODUCT_ID
WHERE levle <= (SELECT c + 2 FROM UCNT))
--when the recursion count levle exceeds the count of records in the table,
--we must have recursion, because
--termination has to otherwise occur. The most extreme case is
--that all records are linked, with termination
--after this, we have to be in a 'loop'
SELECT TOP 1 with ties * FROM RNCTE order by levle desc
option (maxrecursion 0)
I think you don't need to use CTE or RECUSRIVE CTE :
SELECT pr1.*
FROM TableName pr1
WHERE EXISTS (SELECT 1 FROM TableName pr2 WHERE pr2.INNER_PRODUCT_ID = pr1.PRODUCT_ID);

Create a stored procedure to aggregate rows

Having a transaction table with the following rows:
Id UserId PlatformId TransactionTypeId
-------------------------------------------------
0 1 3 1
1 1 1 2
2 2 3 2
3 3 2 1
4 2 3 1
How do I write a stored procedure that can aggregate the rows into a new table with the following format?
Id UserId Platforms TransactionTypeId
-------------------------------------------------
0 1 {"p3":1,"p1":1} {"t1":1,"t2":1}
1 2 {"p3":2} {"t2":1,"t1":1}
3 3 {"p2":1} {"t1":1}
So the rows are gouped by User, count each platform/transactionType and store as key/value json string.
Ref: My previous related question
You could use GROUP BY and FOR JSON:
SELECT MIN(ID) AS ID, UserId, MIN(sub.x) AS Platforms, MIN(sub2.x) AS Transactions
FROM tab t
OUTER APPLY (SELECT CONCAT('p', platformId) AS platform, cnt
FROM (SELECT PlatformId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY PlatformId) s
FOR JSON AUTO) sub(x)
OUTER APPLY (SELECT CONCAT('t', TransactiontypeId) AS Transactions, cnt
FROM (SELECT TransactiontypeId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY TransactiontypeId) s
FOR JSON AUTO) sub2(x)
GROUP BY UserId;
DBFiddle Demo
Result is a bit different(array of key-value) but please treat it as starting point.
Your sample JSON is not really a json, but since you want it that way:
SELECT u.UserId, plt.pValue, ttyp.ttValue
FROM Users AS [u]
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+pn.pName+'":'+LTRIM(STR(pn.pCount))
FROM (SELECT p.Name AS pName, COUNT(*) AS pCount
FROM transactions t
left JOIN Platforms p ON p.PlatformID = t.PlatformId
WHERE t.UserId = u.UserId
GROUP BY p.PlatformId, p.Name
) pn
FOR XML PATH('')),1,1,'')+'}'
) plt(pValue)
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+tty.ttName+'":'+LTRIM(STR(tty.ttCount))
FROM (SELECT tt.Name AS ttName, COUNT(*) AS ttCount
FROM transactions t
left JOIN dbo.TransactionType tt ON tt.TransactionTypeId = t.TransactionTypeID
WHERE t.UserId = u.UserId
GROUP BY tt.TransactionTypeId, tt.Name
) tty
FOR XML PATH('')),1,1,'')+'}'
) ttyp(ttValue)
WHERE EXISTS (SELECT * FROM transactions t WHERE u.UserId = t.UserId)
ORDER BY UserId;
DBFiddle Sample

Left join with Sum Clause with more than 1 table gives incorrect Sum

I am trying to get the Sum of rows while applying a left join with more than 1 table. It seems it is creating a matrix of result which results in wrong sum function.
Example:
First Table: Customer
Second Table: TotalAssets
Third Table: TotalLiability
Table Structure:
Customer
CustID(int) CustomerName(varchar)
1 Abc
2 Def
3 Ghi
TotalAssets
CustID Amount
1 2000
1 1000
2 600
TotalLiability
CustID Amount
1 1000
1 1000
2 800
Output Expected
CustID TotalAssets TotalLiability
1 3000 2000
2 600 800
Current Query
Select c.CustID , Sum(a.Amount) , Sum(l.Amount) From Customer c
left join TotalAssests a on a.CustID = c.CustID
left join TotalLiability l on l.CustID = c.CustID
Group by c.CustID
The problem with this current query is the sum is not correct as i think the first left join create a first set with multiple records and then second one is applied.
Any help is appreciated
UPDATE:
I find some luck by following method but it seems a bad/hacky option as in my case i have over 7-8 elements in group by and adding more left clauses results in query difficult to manage.
New Query which is resulting correct result but looks very bad to maintains
Select Set1.CustID , Set1.TotalAssets, Sum(l.Amount) from (Select c.CustID , Sum(a.Amount) as TotalAssets From Customer c
left join TotalAssests a on a.CustID = c.CustID
Group by c.CustID)Set1
left join TotalLiability l on l.CustID = Set1.CustID.
Group by Set1.CustID , Set1.TotalAssets
I think this gets you what you want with minimum complexity:
select c.CustId, isnull(a.Amount, 0) as TotalAssets, isnull(l.Amount, 0) as TotalLiability
from Customers c
left join (
select CustId, sum(Amount) as Amount from TotalAssets group by CustId
) a on a.CustId = c.CustId
left join (
select CustId, sum(Amount) as Amount from TotalLiability group by CustId
) l on l.CustId = c.CustId
You need to group/sum the two tables separately, since the data in them is independent. Left-joining both to the customers table ensures that customers with no entries in either/both tables are still reported.
This should work:
Select c.CustID
, (select sum(a.amount) from TotalAssests a where a.CustId = c.CustID) as SumAsset
, (select Sum(l.Amount) TotalLiability l where l.CustID = c.CustID) as SumLiability
From Customer c
Hope the below works with less maintenance,
DECLARE #Customer TABLE (CustID int, CustomerName varchar(50)) DECLARE #TotalAssets TABLE (CustID int, Amount INT) DECLARE #TotalLiability TABLE (CustID int, Amount INT)
INSERT INTO #Customer
SELECT 1,
'ABC'
UNION
SELECT 2,
'DEF'
UNION
SELECT 3,
'GHI'
--Select * From #Customer
INSERT INTO #TotalAssets
SELECT 1,
2000
UNION
SELECT 1,
1000
UNION
SELECT 2,
600
--Select * From #TotalAssets
INSERT INTO #TotalLiability
SELECT 1,
1000
UNION
SELECT 1,
1000
UNION
SELECT 2,
800
--Select * From #TotalLiability
SELECT *
FROM #Customer
SELECT C.CustID,
C.CustomerName,
Sum(A.Amount) TotalAssets,
Sum(L.Amount) TotalLiability
FROM #Customer C
JOIN #TotalAssets A ON C.CustID = A.CustID
JOIN #TotalLiability L ON C.CustId = L.CustID
GROUP BY C.CustID,
C.CustomerName

Resources