T-SQL Query to get ending position in a result set - sql

I have a result set to which I will provide an input position and a number for iteration. I expect a result of the end position.
-------------
ID
-------------
1
2
3
4
5
6
7
--------------------------------
InputPosition Iteration
--------------------------------
4 6
----------------
ID Iteration
----------------
1 5
2 6 (EndPosition = 2)
3
4 1
5 2
6 3
7 4
Hence I need to get the 'EndPosition' for this scenario.

I'm not sure how important the tables are to the task at hand. If the answer is not very, the following may work for you:
declare #input as int = 4
declare #itemCount as int = 7
declare #iterations as int = 6
select
case when (#input + #iterations - 1) % #itemCount = 0 then 7
else (#input + #iterations - 1) % #itemCount
end as EndPosition
If the tables are important, then you may be able to use this logic in combination with the row_number() function.

This will work only for your sequential number set.
declare #table table (id int)
insert into #table (id)
values
(1),
(2),
(3),
(4),
(5),
(6),
(7)
declare #inputPosition int = 4
declare #iteration int = 6
;with cte as(
select
id,
row_number() over (order by case when id = #inputPosition then 1 else #inputPosition +1 end) as rn
from #table)
select
*
from
cte
where rn = #iteration

Related

Multiple insert statements - increment variable

I have a table
number | letter
-------+---------
1 | A
2 | B
And I have this code:
declare #counter as int = 0
while (#counter < 16)
begin
set #counter = #counter + 1
insert into table (number, letter)
values (#counter, 'A')
insert into table (number, letter)
values (#counter, 'B')
end
The problem with I have with this statement is that it is producing something like this:
number | letter
-------+----------
1 | A
1 | B
2 | A
2 | B
What I wanted is there are 8 rows since the counter stops after 15 and #counter started from 0
number | letter
-------+---------
1 | A
2 | B
3 | A
4 | B
5 | A
6 | B
7 | A
8 | B
I have tried putting BEGIN and END per statement but I still can't achieve my goal:
declare #counter as int = 0
while (#counter < 16)
begin
insert into table (number, letter)
values (#counter, 'A')
end
begin
insert into table (number, letter)
values (#counter, 'B')
set #counter = #counter + 1
end
I'm with #Larnu, why not simply using IDENTITY on number?
But if you insist doing it on your own, one method would be to increment the counter by 2 instead of 1 and insert the current value along with 'A' and the current value + 1 along with 'B'.
DECLARE #counter AS integer = 1;
WHILE #counter <= 8
BEGIN
INSERT INTO elbat
(number,
letter)
VALUES (#counter,
'A');
INSERT INTO elbat
(number,
letter)
VALUES (#counter + 1,
'B');
SET #counter = #counter + 2;
END;
db<>fiddle
You can just use two cross-joined virtual VALUES clauses for this. No WHILE loops needed.
INSERT INTO [table] (number, letter)
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
v2.letter
FROM (VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
) v1(counter)
CROSS JOIN (VALUES ('A'), ('B') ) v2(letter);
dbfiddle.uk
For a variable amount of rows, you can still use this, by cross-joining multiple times and using TOP.
This is similar to Itzik Ben-Gan's numbers function
DECLARE #I int = 16;
WITH L0 AS (
SELECT n = 1
FROM (VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
) v(n)
),
L1 AS (
SELECT TOP (#i)
n = 1
FROM L0 a, L0 b, L0 c, L0 d -- add more cross-joins for more than 65536
)
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
v2.letter
FROM L1
CROSS JOIN (VALUES ('A'), ('B') ) v2(letter);
db<>fiddle

How to use #tmp Table in Loop in Recursive Function

I am trying to create a loop that when given a part id, it will search a table of assembly parts and explode all the parts into a large list.
It needs to be recursive because Part 123 may have parts 1, 2, 3, 4, 5 and parts 4 and 5 are also assembly items.
I thought I had come up with something pretty good and easily returns the part id and the part level for each item. Then I find out that I can't use temp tables, so it shoots my loop down.
What can I use in place of the temp table to give me the same function here?
CREATE FUNCTION [dbo].[fn_getParts] (
#source_part_id int
, #level int
)
RETURNS #parts_list TABLE (
[part] int NOT NULL,
[level] int NOT NULL
)
AS
BEGIN
DECLARE
#max int = 0,
#cnt int = 0,
#PID int = 0,
#Plvl int = 0,
#id int = 0
INSERT INTO #parts_list VALUES (#source_part_id, #level)
SET #level += 1
SELECT [Comp_Part_ID] AS [PID], #level AS [level]
INTO #chkParts
FROM [assemblies]
WHERE [Assy_PID] = #source_part_id
SELECT #max = COUNT(*) FROM #chkParts
WHILE #cnt <= #max
BEGIN
SELECT #PID = [PID], #Plvl = [level] FROM #chkParts
INSERT INTO #parts_list
SELECT * FROM [fn_getParts](#PID, #Plvl)
SET #cnt += 1
END
RETURN
END
Here's some sample data:
CREATE TABLE [Assemblies] (
[PartID] int,
[Comp_PartID] int
)
INSERT INTO [Assemblies] VALUES
(1,2),
(1,3),
(1,4),
(1,5),
(1,6),
(3,9),
(3,10),
(10,11),
(10,23),
(10,24),
(10,31),
(11,24),
(11,23)
If I enter SELECT * FROM [fn_getParts](1,0) I would expect the following:
part,level
1,0
2,1
3,1
4,1
9,2
10,2
11,3
23,3
24,3
The code can be simplified somewhat by wrapping an Inline Table-Valued Function around a Recursive CTE, e.g.:
create function dbo.fn_getParts (
#source_part_id int
)
returns table as return (
with PartsHierarchy as (
select #source_part_id as part, 0 as level
union all
select Comp_PartID, 1 + level
from Assemblies
join PartsHierarchy on part = PartID
)
select part, level
from PartsHierarchy
);
And then, invoking it for different part numbers...
select * from dbo.fn_getParts(1);
part level
---- ----
1 0
2 1
3 1
4 1
5 1
6 1
9 2
10 2
11 3
23 3
24 3
31 3
24 4
23 4
select * from dbo.fn_getParts(10);
part level
---- -----
10 0
11 1
23 1
24 1
31 1
24 2
23 2
select * from dbo.fn_getParts(11);
part level
---- -----
11 0
24 1
23 1

How to reduce the value of a column in a row with?

I have a table with two columns:
No Value
1 20
2 10
3 50
4 35
5 17
I also have a variable or parameter where the variables will reduce the value of a column in a row.
So, if my variable V = 5 then my column will update:
No Value
1 15
2 10
3 50
4 35
5 17
Or if V = 50 then:
No Value
1 0
2 0
3 30
4 35
5 17
How can I do that?
First prepare the structure and the data:
CREATE TABLE TAB
(
[No] int identity(1,1) primary key,
[Value] int
);
INSERT INTO TAB VALUES (20);
INSERT INTO TAB VALUES (10);
INSERT INTO TAB VALUES (50);
INSERT INTO TAB VALUES (35);
INSERT INTO TAB VALUES (17);
So now we define your variable to reduce the [Value]:
DECLARE #var int
SET #var = 5
And now you can query your table:
SELECT [No], CASE WHEN [Value] - #var < 0 THEN 0 ELSE [Value] - #var END AS [Value]
FROM TAB
Very easy. You can set your variable to 50 an try again.
Here is a fiddle for this example.
Use CTE can be one of the options
declare #Values table
(
[No] int identity(1,1) not null,
Value int not null
)
declare #DeductAmount int = 50
INSERT #Values VALUES (20), (10), (50), (35), (17)
;WITH cte AS
(
SELECT *,
#DeductAmount - CASE WHEN Value >= #DeductAmount THEN #DeductAmount ELSE Value END AS RemainDeductAmount,
Value - CASE WHEN Value >= #DeductAmount THEN #DeductAmount ELSE Value END DeductedValue
FROM #Values WHERE [No] = 1
UNION ALL
SELECT
v.*,
cte.RemainDeductAmount - CASE WHEN v.Value >= cte.RemainDeductAmount THEN cte.RemainDeductAmount ELSE v.Value END,
v.Value - CASE WHEN v.Value >= cte.RemainDeductAmount THEN cte.RemainDeductAmount ELSE v.Value END DeductedValue
FROM #Values v INNER JOIN cte ON v.[No] = cte.[No] + 1
)
UPDATE target SET Value = DeductedValue
FROM #Values target INNER JOIN cte ON target.[No] = cte.[No]
SELECT * FROM #Values
The secret inside the cte
No Value RemainDeductAmount DeductedValue
----------- ----------- ------------------ -------------
1 20 30 0
2 10 20 0
3 50 0 30
4 35 0 35
5 17 0 17
Change 50 with your variable
Query:
SELECT t.No,
CASE WHEN SUM(t.Value)over(ORDER BY t.No) - 50 <0 THEN 0
WHEN SUM(t2.Value)over(ORDER BY t2.No) > 50
AND SUM(t.Value)over(ORDER BY t.No) >50 THEN t.Value
ELSE SUM(t.Value)over(ORDER BY t.No) - 50
END AS Value
FROM Table1 t
LEFT JOIN Table1 t2 ON t.No - 1 = t2.No
Here is another version:
DECLARE #t TABLE(ID INT IDENTITY(1, 1) , v INT )
DECLARE #x INT = 5
INSERT INTO #t VALUES ( 20 ), ( 10 ), ( 50 ), ( 35 ), ( 17 );
WITH cte
AS ( SELECT * ,
#x - ( SELECT ISNULL(SUM(v), 0) AS v
FROM #t t2
WHERE t2.ID <= t1.ID) s
FROM #t t1
)
SELECT ID ,
CASE WHEN s >= 0 THEN 0
WHEN s < 0 AND v + s > 0 THEN -s
ELSE v END
FROM cte
DECLARE #qty int
SET #qty= 50
WHILE #qty> 0
BEGIN
SELECT #qty= #qty- value
FROM table
WHERE no = (SELECT MIN(no) FROM table WHERE value > 0)
IF #qty< 0
BEGIN
UPDATE table
SET value = ABS(#qty)
WHERE (SELECT MIN(no) FROM table WHERE value > 0)
END
ELSE
BEGIN
UPDATE table
SET value = 0
WHERE (SELECT MIN(no) FROM table WHERE value > 0)
END
END

How can I avoid cursor in this situation?

I have a table with following columns
Period, WIP_Close, WIP_Open, WIP_Add, WIP_Minus.
I need to update
WIP_Open = WIP_Close for previous period
and then
update WIP_Close = WIP_Open + WIP_Add - WIP_Minus.
Currently I am using cursor as follows:
declare y_curs cursor for select distinct period
from abc
order by period
declare #period as int
declare #old_period as int
set #old_period = 0
open y_curs
fetch y_curs into #period
while ##fetch_status = 0
begin
update f set f.wip_open = isnull(f1.wip_close,0)
from abc f join abc f1 on 1=1
where f.period = #period and f1.period=#old_period
update abc set wip_close = (isnull(wip_open,0) + wip_add - wip_minus) where period = #period
set #old_period = #period
fetch y_curs into #period
end
close y_curs
deallocate y_curs
This is working fine and giving correct result, however due to having more than 5 million records, it takes almost an hour to process.
Is there a better way where I can avoid cursor for better performance?
Thanks for any suggestions.
Regards
Sorry i don't have data to test this out. Setup a sample table and give it a try.
But maybe something like this:
WITH abc_Order (n,period,WIP_Close,WIP_Open,WIP_Add,WIP_Minus) AS (
select
ROW_NUMBER() OVER(
ORDER BY
period
) n,
n,period,WIP_Close,WIP_Open,WIP_Add,WIP_Minus
from abc
)
update curr
set
WIP_Open = prev.WIP_Close,
WIP_Close = prev.WIP_Close + curr.WIP_Add - curr.WIP_Minus
from abc_Order curr
inner join abc_Order prev on
curr.n = prev.n+1
Lets make some test data
DECLARE #MyTable TABLE
(
Period int IDENTITY,
WIP_Open int,
WIP_Close int,
WIP_Add int,
WIP_Minus int
)
the first record has the default opening data set.
INSERT INTO #MyTable
( WIP_Open, WIP_ADD, WIP_Minus )
VALUES
( 10, 1, 2 )
Now we have some plus / minus from inventory being added / sold
INSERT INTO #MyTable
( WIP_Add, WIP_Minus )
VALUES
( 2, 1 ),
( 5, 3 ),
( 6, 1 ),
( 1, 7 );
Now lets sum up all of the changes along with the original record to see the running total and get the closing for each record.
WITH T AS
(
SELECT
Period,
WIP_Open,
WIP_Add,
WIP_Minus,
SUM(ISNULL(WIP_Open, 0) + WIP_Add - WIP_Minus) OVER (ORDER BY Period) RunningWipClose
FROM #MyTable
)
SELECT * FROM T
here is the output:
Period WIP_Open WIP_Add WIP_Minus RunningWipClose
1 10 1 2 9
2 NULL 2 1 10
3 NULL 5 3 12
4 NULL 6 1 17
5 NULL 1 7 11
After this, you use lag function to set the null opens with the prev close.
Input -
period WIP_Close WIP_Open WIP_Add WIP_Minus
1 1 1 1 10
2 8 3 7 5
3 6 4 9 15
Output-
period WIP_Close WIP_Open WIP_Add WIP_Minus
1 1 1 1 10
2 3 1 7 5
3 -3 3 9 15
I guess you want to solve above problem.
Here is the solution.Let me know if you are not able to understand this.
SELECT * INTO #T
FROM
(
SELECT 1 period, 1 WIP_Close, 1 WIP_Open, 1 WIP_Add, 10 WIP_Minus UNION ALL
SELECT 2 period, 8 WIP_Close, 3 WIP_Open, 7 WIP_Add, 5 WIP_Minus UNION ALL
SELECT 3 period, 6 WIP_Close, 4 WIP_Open, 9 WIP_Add, 15 WIP_Minus ) TAB
SELECT * FROM #T
DECLARE #TopC INT = (SELECT TOP 1 WIP_Close FROM #T)
DECLARE #TopO INT = (SELECT TOP 1 WIP_Open FROM #T)
DECLARE #TopDIff INT = (SELECT TOP 1 WIP_Add - WIP_Minus FROM #T)
SELECT period,
#TopC + SUM(WIP_Add - WIP_Minus) OVER (ORDER BY period ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) - #TopDIff AS WIP_Close,
COALESCE(#TopC + SUM(WIP_Add - WIP_Minus) OVER (ORDER BY period ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING ) - #TopDIff,#TopO) AS WIP_Open,
WIP_Add,
WIP_Minus
FROM #T

Selecting a Range of Numbers while joining

I'm selecting a range of numbers 1-6 with a CTE. I have another table that has two columns. One with any number between 1-6 and a column called code.
I want to always return 1-6 but join on my other table that has the code.
An Example would be:
Table 1
Number
------
1
2
3
4
5
6
Table 2
Number | Code
------ -----
1 B
3 A
5 C
I want the Select to return:
CodeNumber | Code
------ -----
1 B
2 NULL
3 A
4 NULL
5 C
6 NULL
If I join on the number it doesn't return all the values.
DECLARE #Start INT
DECLARE #End INT
DECLARE #Priority CHAR(2)
SELECT #Start = -3
, #End = 3
, #Code = ''
WITH Numbers (Number, Code)
AS ( SELECT #Start AS Number, #Code
UNION ALL
SELECT Number + 1, #Code
FROM Numbers
WHERE Number < #End
)
Use a left join:
WITH Numbers(Number) as (
SELECT #Start AS Number
UNION ALL
SELECT Number + 1
FROM Numbers
WHERE Number < #End
)
select n.number, t.code
from numbers n left join
table2 t
on t.number = n.number;