Print out same row multiple times based on calculated value - sql-server

I have a query that returns something similar to the following:
Zone | NeededItems
===========================
209 | 5
213 | 1
216 | 1
220 | 2
218 | 1
219 | 4
215 | 1
The query behind it is something like:
SELECT
r.Zone as Zone, r.Required - COUNT(i.Item) as NeededItems
FROM
MyItems i
INNER JOIN
MyRequirements r ON i.Zone = r.Zone
GROUP BY
r.Zone, r.Required
Where MyItems looks like: (Value of Item doesn't matter)
Zone | Item
================
209 | a
209 | b
209 | c
216 | a
220 | a
213 | z
218 | x
219 | q
219 | w
219 | e
219 | r
215 | t
And MyRequirements looks like:
Zone | Required
======================
209 | 8
213 | 2
216 | 2
220 | 3
218 | 2
219 | 5
215 | 2
What I need to be able to do is print out the Zone multiple times based on the value in Needed. The value in Needed is a calculated value which is what is making this difficult (I can't just remove the count!)
So the results I am looking for is simply a list of zones, each appearing the number of times it is needed.
Zone
====
209
209
209
209
209
213
216
220
220
218
219
219
219
219
215
Is there any way in SQL that this can be done? Using SQL Server 2012.

Below is one way to do it - using the e1, e2 and e3 queries are not the cleanest way to do it, but it's the only way that I could manage to get it working.
One bit limitation: it only works for up to 1000 items of each (more than enough for mine.) This could be changed by editing WHERE c<9 but be aware this is recursive so best not to have it more than what is needed.
WITH CTE as
(
SELECT
r.Zone as Zone, r.Required - COUNT(i.Item) as NeededItems
FROM
MyItems i
INNER JOIN
MyRequirements r ON i.Zone = r.Zone
GROUP BY
r.Zone, r.Required
),
e1(n,c ) AS
(
SELECT 1, 0
UNION ALL
SELECT n, c + 1
FROM e1
WHERE c<9
), -- 10
e2(n) AS
(
SELECT 1 FROM e1 CROSS JOIN e1 AS b -- 100
),
e3(n) AS
(
SELECT 1 FROM e1 CROSS JOIN e2 -- 1000
),
Numbers AS
(
SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e3
)
SELECT
Zone
FROM
Numbers
INNER JOIN
CTE on CTE.NeededItems >= n ORDER BY Zone

Related

Ordering SQL query by hierarchy and it's random code

I've tried to search for something related and similar, but, couldn't find it.
This is a table that I need to get as result:
+-----+-----------+------+-------------------+
| ID | PARENT_ID | CODE | NAME |
+-----+-----------+------+-------------------+
| 218 | NULL | 1445 | First One |
| 235 | 218 | 2 | First Child |
| 247 | 235 | 45 | First Grandchild |
| 246 | 235 | 55 | Second Grandchild |
| 230 | 218 | 3 | Second Child |
| 238 | 230 | 12 | Third Grandchild |
| 231 | 230 | 20 | Fourth Grandchild |
+-----+-----------+------+-------------------+
The order must be by it's hierarchy followed by it's code.
I need this to make an assertion. And, if it's possible, I would like to get this only doing a query, without a method to sort this list.
This is a sample of what I'm trying to assert:
Tree Hierarchy
What I've done so far, it's the following recursive query:
WITH CTE (ID, PARENT_ID, CODE, NAME)
AS
-- Anchor:
(SELECT
ID,
PARENT_ID,
CODE,
NAME
FROM WAREHOUSE
WHERE PARENT_ID IS NULL
UNION ALL
-- Level:
SELECT
W.ID,
W.PARENT_ID,
W.CODE,
W.NAME
FROM WAREHOUSE AS W
INNER JOIN CTE
ON R.PARENT_ID = CTE.ID)
SELECT *
FROM CTE
I appreciate any help on this!
Thanks in advance!
Looks like you can use the [CODE] sequence in a hierarchyid path
Example
Declare #YourTable Table ([ID] int,[PARENT_ID] int,[CODE] varchar(50),[NAME] varchar(50))
Insert Into #YourTable Values
(218,NULL,1445,'First One')
,(235,218,2,'First Child')
,(247,235,45,'First Grandchild')
,(246,235,55,'Second Grandchild')
,(230,218,3,'Second Child')
,(238,230,12,'Third Grandchild')
,(231,230,20,'Fourth Grandchild')
;with cteP as (
Select ID
,PARENT_ID
,[Code]
,Name
,HierID = convert(hierarchyid,concat('/',[Code],'/'))
From #YourTable
Where Parent_ID is null
Union All
Select ID = r.ID
,PARENT_ID = r.PARENT_ID
,r.[Code]
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.[Code],'/'))
From #YourTable r
Join cteP p on r.PARENT_ID = p.ID)
Select Lvl = HierID.GetLevel()
,ID
,PARENT_ID
,[Code]
,Name
From cteP A
Order By A.HierID
Returns
Lvl ID PARENT_ID Code Name
1 218 NULL 1445 First One
2 235 218 2 First Child
3 247 235 45 First Grandchild
3 246 235 55 Second Grandchild
2 230 218 3 Second Child
3 238 230 12 Third Grandchild
3 231 230 20 Fourth Grandchild

Calculate row difference within groups

I'm looking for help with calculating the difference between consecutive ordered rows within groups in SQL (Microsoft SQL server).
I have a table like this:
ID School_ID Enrollment_Start_Date Order
1 56 1/1/2018 10
1 56 5/5/2018 24
1 56 7/7/2018 35
1 103 4/4/2019 26
1 103 3/3/2019 19
I want to calculate the difference between Order, group by ID, School_ID, and order by Enrollment_Start_Date.
so I want something like this:
ID School_ID Enrollment_Start_Date Order Diff
1 56 1/1/2018 10 10 # nothing to be subtracted from 10
1 56 5/5/2018 24 14 # 24-10
1 56 7/7/2018 35 11 # 35-24
1 103 3/3/2019 19 19 # nothing to be subtracted from 19
1 103 4/4/2019 26 7 # 26-19
I have hundreds of IDs, and each ID can have at most 6 Enrollment_Start_Date, so I'm looking for some generalizable implementations.
Use LAG(<column>) analytic function to obtain a "previous" column value specified within the OVER part, then substract current value from it and make it a positive number multiplying it by -1. If previous value isn't present (is null) then take the current value.
Pseudo code would be:
If previous_order_value exists:
-1 * (previous_order_value - current_order_value)
Else
current_order_value
where previous_order_value is based on the same id & school_id and is sorted by enrollment_start_date in ascending order
SQL Code:
select
id,
school_id,
enrollment_start_date,
[order],
coalesce(-1 * (lag([order]) over (partition by id, school_id order by enrollment_start_date ) - [order]), [order]) as diff
from yourtable
Also note, that order keyword is reserved in SQL Server, which is why your column was created with name wrapped within [ ]. I suggest using some other word for this column, if possible.
use lag() analytic function for getting difference of two row and case when for getting orginal value of order column where no difference exist
with cte as
(
select 1 as id, 56 as sclid, '2018-01-01' as s_date, 10 as orders
union all
select 1,56,'2018-05-05',24 union all
select 1,56,'2018-07-07',35 union all
select 1,103,'2019-04-04',26 union all
select 1,103,'2019-03-03',19
) select t.*,
case when ( lag([orders])over(partition by id,sclid order by s_date ) -[orders] )
is null then [orders] else
( lag([orders])over(partition by id,sclid order by s_date ) -[orders] )*(-1) end
as diff
from cte t
output
id sclid s_date orders diff
1 56 2018-01-01 10 10
1 56 2018-05-05 24 14
1 56 2018-07-07 35 11
1 103 2019-03-03 19 19
1 103 2019-04-04 26 7
demo link
Use LAG(COLUMN_NAME)
Query
SELECT id, School_ID, Enrollment_Start_Date, cOrder,
ISNULL((cOrder - (LAG(cOrder) OVER(PARTITION BY id, School_ID ORDER BY Enrollment_Start_Date))),cOrder)Diff
FROM Table1
Samle Output
| id | School_ID | Enrollment_Start_Date | cOrder | Diff |
|----|-----------|-----------------------|--------|------|
| 1 | 56 | 2018-01-01 | 10 | 10 |
| 1 | 56 | 2018-05-05 | 24 | 14 |
| 1 | 56 | 2018-07-07 | 35 | 11 |
| 1 | 103 | 2019-03-03 | 19 | 19 |
| 1 | 103 | 2019-04-04 | 26 | 7 |
SQL Fiddle Demo

Sum values from 2 tables and subtract it to each other

I have 2 tables, ord_tbl and pay_tbl. o_tbl with these data.
ord_tbl
invoice | emp_id | prod_id | amount
123 | 101 | 1 | 1000
123 | 101 | 2 | 500
123 | 101 | 3 | 500
124 | 101 | 2 | 300
125 | 102 | 3 | 200
pay_tbl
invoice | new_invoice | amount
123 | 321 | 300
123 | 322 | 200
124 | 323 | 300
125 | 324 | 100
I would like the selection statement to give me this result
invoice | emp_id | orig_amt | balance | status
123 | 101 | 2000 | 1500 | unsettled
The invoice that has 0 balance will not be included anymore. I know that I have to use the join and sub queries here but I don't even know how to start it! For me, as a beginner, this is very complex already. This is what I tried so far...
SELECT
ord_tbl.invoice,
SUM(ord_tbl.amount) As 'origAmt',
SUM(pay_tbl.amount) As 'payAmt',
origAmt - payAmt As 'bal'
FROM
ord_tbl
INNER JOIN pay_tbl
ON ord_tbl.invoice = pay_tbl.invoice
WHERE
ord_tbl.emp_id = #emp_id AND
bal != 0
GROUP BY
ord_tbl.invoice
You need to prepare your data joining on a non unique field will lead to a cross JOIN,that`s why you get 4000
;WITH CTE as
(SELECT ot.invoice,MAX(ot.emp_id) as emp_id,SUM(ot.amount) as origAmt FROM ord_tbl ot GROUP BY ot.invoice),
CTE2 as
( SELECT pt.invoice,SUM(pt.ammount) as payAmt FROM pay_tbl pt GROUP BY pt.invoice)
SELECT CTE.invoice,CTE.emp_id,CTE.origAmt,CTE.origAmt-NULLIF(CTE2.payAmt,0) as bal,'unsettled' as status
FROM
CTE LEFT JOIN CTE2 ON CTE.invoice=CTE2.invoice
AND CTE.emp_id=101 AND CTE.origAmt-NULLIF(CTE2.payAmt,0)>0

SQL Rank does not work as expected

Im trying to use SQL function Rank() to get a list the top records of several groups. Here is what im tring that does not work :
select hc.hId, hc.DpId, hc.Rank
from (
select d.hId, DpId, Rank()
OVER (Partition by DpId ORDER BY d.hId) AS Rank
FROM CurDp d
INNER JOIN HostList h on d.DpId = h.hId
INNER JOIN Coll_hList pch on d.hId = pch.hId
where h.Model = 'PRIMARY'
) hc where hc.Rank <= 10
I get the top 10 records as follows :
HId | DpId | Rank
-------x------x------
7 | 590 | 1
18 | 590 | 2
23 | 590 | 3
24 | 590 | 4
26 | 590 | 5
36 | 590 | 6
63 | 590 | 7
80 | 590 | 8
84 | 590 | 9
88 | 590 | 10
But when I use CROSS APPLY, which the function I need because i have to get that kind of records on different models, I use this code :
select pch.hId, cc.DpId, cc.Rank from from Coll_hList pch
cross apply
(
select hc.hId, hc.DpId, hc.Rank
from (
select d.hId, DpId, Rank()
OVER (Partition by DpId ORDER BY d.hId) AS Rank
FROM CurrDp d
INNER JOIN HostList h on d.DpId = h.hId
where h.Model = 'PRIMARY' and d.hId = pch.hId
) hc where hc.Rank <= 10
) cc
Here, I get always rank 1, and it doesn't filter anything (not showing the whole result) :
HId | DpId | Rank
-------x------x------
7 590 1
18 590 1
23 590 1
24 590 1
26 590 1
36 590 1
63 590 1
80 590 1
84 590 1
88 590 1
124 590 1
125 590 1
133 590 1
Am I doing it wrong ? Is it because of CROSS APPLY ?
I also used dense_rank() instead of rank(), but it shows the same result.
Any help to achieve this request with CROSS APPLY would be greatly appreciated.
Thanks
In the first case, you join on Coll_hList and get a result set of more than 10 entries which then are ranked.
In the second case, in your apply-sub-select, you only create a one-entry result set. Ranking of that results in rank one.
Your ranking has to be done in the outer statement:
select pch.hId, cc.DpId, Rank()
OVER (Partition by cc.DpId ORDER BY cc.hId) AS Rank
from Coll_hList pch
cross apply
(
select d.hId, DpId
FROM CurrDp d
INNER JOIN HostList h on d.DpId = h.hId
where h.Model = 'PRIMARY' and d.hId = pch.hId
) cc

Recursive function with sum of count of each child SQL Server 2005

I'm new here, so please accept my apologize if there is a rule that I ignored it.
I use SQL Server 2005.
I have a table which contains a parent-child structured tree.
There is a query that scans above mentioned tree. There is also another tree including some of children of the 1st table. Each child of 1st table could be repeated for more than 1 time in the 2nd table.
1st table (called TBL1):
CID Parent_ID Seq_NO CName
650 NULL 1 A
135 650 1 B
950 135 1 C
124 135 2 D
725 135 3 E
421 135 4 F
632 421 1 G
906 421 2 H
119 421 3 I
215 650 2 J
436 215 1 K
150 215 2 L
260 150 1 M
501 260 1 N
154 260 2 O
132 260 3 P
721 150 2 Q
960 215 3 R
Query for scannning the tree of TBL1
WITH SCAN_TREE(IID, QName, Parent_ID, levleTREE, HRName, SO) AS
(
SELECT
CID, CName, Parent_ID,
0 AS initlvl,
CAST(INIT_POINT.CName AS VARCHAR(MAX)) AS initName,
CAST(INIT_POINT.Seq_NO AS VARBINARY(MAX)) AS initSO
FROM TBL1 AS INIT_POINT
WHERE (INIT_POINT.CID=650)
UNION ALL
SELECT
LOOP_Q.CID, LOOP_Q.CName, LOOP_Q.Parent_ID,
FINAL_Q.levleTREE + 1 AS looplvl,
CAST(FINAL_Q.HRName + '-' + LOOP_Q.CName AS VARCHAR(MAX)) AS loopName,
CAST(FINAL_Q.SO + CAST(LOOP_Q.Seq_NO AS BINARY(4)) AS VARBINARY(MAX)) AS loopSO
FROM TBL1 AS LOOP_Q
INNER JOIN SCAN_TREE AS FINAL_Q ON LOOP_Q.Parent_ID = FINAL_Q.IID
)
SELECT
LAST_Q.IID, LAST_Q.QName, LAST_Q.Parent_ID, LAST_Q.levleTREE, LAST_Q.HRName
FROM SCAN_TREE AS LAST_Q
ORDER BY SO
The output of above query:*
IID QName Parent_ID levleTREE HRName
650 A NULL 0 A
135 B 650 1 A-B
950 C 135 2 A-B-C
124 D 135 2 A-B-D
725 E 135 2 A-B-E
421 F 135 2 A-B-F
632 G 421 3 A-B-F-G
906 H 421 3 A-B-F-H
119 I 421 3 A-B-F-I
215 J 650 1 A-J
436 K 215 2 A-J-K
150 L 215 2 A-J-L
260 M 150 3 A-J-L-M
501 N 260 4 A-J-L-M-N
154 O 260 4 A-J-L-M-O
132 P 260 4 A-J-L-M-P
721 Q 150 3 A-J-L-Q
960 R 215 2 A-J-R
2nd table (called TBL2):
MID
----
950
124
124
632v
632
632
421
What I want is the sum of all occurance of each child of 1nd table in 2nd table for each parent of 1st table.
I need a query to retrieve below result, actually I need first column (MID):
MID IID QName Parent_ID levleTREE HRName
7 650 A NULL 0 A
7 135 B 650 1 A-B
1 950 C 135 2 A-B-C
2 124 D 135 2 A-B-D
0 725 E 135 2 A-B-E
4 421 F 135 2 A-B-F
3 632 G 421 3 A-B-F-G
0 906 H 421 3 A-B-F-H
0 119 I 421 3 A-B-F-I
0 215 J 650 1 A-J
0 436 K 215 2 A-J-K
0 150 L 215 2 A-J-L
0 260 M 150 3 A-J-L-M
0 501 N 260 4 A-J-L-M-N
0 154 O 260 4 A-J-L-M-O
0 132 P 260 4 A-J-L-M-P
0 721 Q 150 3 A-J-L-Q
0 960 R 215 2 A-J-R
Thank you in advance.
You can do a few standard left joins here:
WITH SCAN_TREE(IID, QName, Parent_ID, levleTREE, HRName, SO, MID) AS
(
SELECT
CID,
CName,
Parent_ID,
0 AS initlvl,
CAST(INIT_POINT.CName AS VARCHAR(MAX)) AS initName,
CAST(INIT_POINT.Seq_NO AS VARBINARY(MAX)) AS initSO,
COUNT(T2.MID) as MID
FROM
TBL1 AS INIT_POINT
LEFT JOIN TBL2 AS T2 ON
INIT_POINT.CID = T2.MID
WHERE
INIT_POINT.CID=650
GROUP BY
CID,
CName,
ParentId,
Seq_NO
UNION ALL
SELECT
LOOP_Q.CID,
LOOP_Q.CName,
LOOP_Q.Parent_ID,
FINAL_Q.levleTREE + 1 AS looplvl,
CAST(FINAL_Q.HRName + '-' + LOOP_Q.CName AS VARCHAR(MAX)) AS loopName,
CAST(FINAL_Q.SO + CAST(LOOP_Q.Seq_NO AS BINARY(4)) AS VARBINARY(MAX)) AS loopSO,
COUNT(T2.MID) as MID
FROM
TBL1 AS LOOP_Q
INNER JOIN SCAN_TREE AS FINAL_Q ON
LOOP_Q.Parent_ID = FINAL_Q.IID
LEFT JOIN TBL2 AS T2 ON
LOOP_Q.CID = T2.MID
GROUP BY
LOOP_Q.CID,
LOOP_Q.CName,
LOOP_Q.Parent_ID,
FINAL_Q.levleTREE,
FINAL_Q.HRName,
FINAL_Q.SO,
FINAL_Q.Seq_NO
)
SELECT
LAST_Q.IID,
LAST_Q.QName,
LAST_Q.Parent_ID,
LAST_Q.levleTREE,
LAST_Q.HRName,
SUM(T2.MID) as MID
FROM
SCAN_TREE AS LAST_Q
LEFT JOIN SCAN_TREE as CHILDREN ON
CHILDREN.HRName like LAST_Q.HRName + '%'
GROUP BY
LAST_Q.IID,
LAST_Q.QName,
LAST_Q.Parent_ID,
LAST_Q.levleTREE,
LAST_Q.HRName
order by SO
All we're doing is joining TBL2 to TBL1 based on when the CID column equals the MID column. Then, we're counting the number of results we get. Since a left join will give us a null value for the MID column if one doesn't exist, we can rely on this for counting--null equals 0 to count. Next, outside of the CTE, we just join the CTE to itself and sum up those counts where the HRName (a nice hierarchy) begins with the given HRName, so A-B-C will grab the MID of A-B-C, A-B-C-D, A-B-C-D-E, and A-B-C-J, for example.

Resources