Multiple insert statements - increment variable - sql

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

Related

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 Generate 2 Levels Loop

If I have 2 variables
A = 2
B = 3
I want to generate result like this
A | B | Text
1 | 1 | Text1
1 | 2 | Text2
1 | 3 | Text3
2 | 1 | Text4
2 | 2 | Text5
2 | 3 | Text6
I try to Google and can achieved 1 level with this query
declare #start int = 1
declare #end int = 3
;with numcte
AS
(
SELECT #start as [SEQUENCE]
UNION all
SELECT [SEQUENCE] + 1
FROM numcte WHERE [SEQUENCE] < #end
)
SELECT [SEQUENCE], 'text' + CAST([SEQUENCE] as varchar) as [text] FROM numcte
How can I achieve 2 levels loop?
One rather simple method is:
select a.a, b.b, concat('text', row_number() over (order by a, b))
from (values (1), (2)) a(a) cross join
(values (1), (2), (3)) b(b);
Or if you really want to declare variables:
declare #a int = 2;
declare #b int = 3;
with n as (
select 1 as n union all
select n + 1
from n
where n < #a or n < #b
)
select na.n as a, nb.n as b, concat('text', row_number() over (order by na.n, nb.n))
from n na join
n nb
on na.n <= #a and nb.n <= #b;
Here is a db<>fiddle.
Use a numbers table (many examples you can use by searching). One way to produce what is likely a vastly simplified example is:
with cte as (
select 1 as num
union all select num + 1 from cte where num < 3 )
select cte.num, cte2.num from cte
cross join cte as cte2
where cte.num in (1, 2)
order by cte.num, cte2.num
;
Work through that - it may look daunting. Start thinking in terms of sets! Fiddle

select one column, split to multiple columns

I have a table in Oracle Database like below:
res_code column1 ...
-------------------------------
001 A
002 B
003 C
004 D
005 E
I want to select from this table to get the result like:
res_code1 res_code2
-------------------------------
001 002
003 004
005 ...
tip: res_code1 and res_code2 both belongs to res_code, can't be duplicated.
Is is possible using sql?
You can do this simply with an SQL like:
with odds as
(
select res_code, cast(res_code as int) as rn
from myTable
where mod(cast(res_code as int) ,2) =1
),
evens as
(
select res_code, cast(res_code as int) as rn
from myTable
where mod(cast(res_code as int) ,2) =0
)
select odds.res_code as res_code1, evens.res_code as res_code2
from odds
left join evens on evens.rn = odds.rn+1;
you can try this, use row_number to make rn then do MOD to split the group.
Doing some arithmetic in main query.
CREATE TABLE T(
res_code VARCHAR(50),
column1 VARCHAR(50)
);
INSERT INTO T VALUES ('001' ,'A');
INSERT INTO T VALUES ('002' ,'B');
INSERT INTO T VALUES ('003' ,'C');
INSERT INTO T VALUES ('004' ,'D');
INSERT INTO T VALUES ('005' ,'E');
Query 1:
with cte as (
select t1.*,
MOD(row_number() over (order by column1) ,2) rn,
ceil(row_number() over (order by column1) / 2) grp1
from T t1
)
select MAX(CASE WHEN rn = 1 THEN t1.RES_CODE END) res_code1,
MAX(CASE WHEN rn = 0 THEN t1.RES_CODE END) res_code2
from cte t1
group by grp1
Results:
| RES_CODE1 | RES_CODE2 |
|-----------|-----------|
| 001 | 002 |
| 003 | 004 |
| 005 | (null) |
Use cursor to do double fetch and single insert:
declare #res_code1 nvarchar(100), #res_code2 nvarchar(100), #even int
declare #newtable table(res_code1 nvarchar(100), res_code2 nvarchar(100))
declare cur cursor for (select res_code from #mytable)
open cur
-- assume first 2 rows are available
fetch next from cur into #res_code1
fetch next from cur into #res_code2
-- set #even to 1 meaning even value has been fetched
set #even = 1
while ##FETCH_STATUS = 0
begin
if #even = 0
begin
-- fetch #res_code2
fetch next from cur into #res_code2
set #even = 1
end
else
begin
-- insert into table since #even = 1 (has even number)
insert into #newtable values(#res_code1,#res_code2)
-- fetch #res_code1
fetch next from cur into #res_code1
set #even = 0
end
end
-- insert the last odd
if #even = 1
insert into #newtable values(#res_code1,'')

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

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

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