Get running no series on basis of one column value - sql

I have a table like
SELECT str AS company, item#, Qty
FROM temp_on_hand
WHERE qty > 2
ORDER BY Item# ASC
output of that query is -
company item# Qty
1 746 3
5 9526 1
1 14096 1
2 14096 2
3 14095 2
I want to generate new item#( with addition of '-0001' to current item#) on basis of Qty column i.e. if Qty column has value 3 for company 1 than query should return three rows like -
company NewItem# Item# Qty
1 746-00001 746 3
1 746-00002 746 3
1 746-00003 746 3
5 9526-00001 9526 1
1 14096-00001 14096 1
2 14096-00002 14096 2
2 14096-00003 14096 2
3 14095-00001 14095 3
3 14095-00002 14095 3
3 14095-00003 14095 3
. . . . . . .
Table structure like that
CREATE TABLE temp_on_hand(str INT, item# INT,Qty INT)
INSERT INTO temp_on_hand VALUES (1, 746, 3)
INSERT INTO temp_on_hand VALUES (5, 9526, 1)
INSERT INTO temp_on_hand VALUES (1, 14096, 1)
INSERT INTO temp_on_hand VALUES (2, 14096, 2)
INSERT INTO temp_on_hand VALUES (3, 14095, 2)
ALTER TABLE temp_on_hand ADD new_item# VARCHAR)
similarly for upcoming values.
Thanks in advance

You can join to a Numbers table.
You can use a real one, but I will use Itzik Ben-Gan's on-the-fly tally table (it's actually better as an inline Table-valued Function).
EDIT: According to your comments, you don't actually need the numbering from Nums, you want a fresh overall numbering. So you can just select from L1
WITH
L0 AS ( SELECT 1 AS c
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 A, L0 B ), -- add more cross joins for more rows
SELECT
t.str AS company,
t.item# + FORMAT(ROW_NUMBER() OVER (ORDER BY t.item# ASC), '-0000') NewItem#,
t.item#,
t.Qty
FROM temp_on_hand t
CROSS APPLY(
SELECT TOP (t.Qty) c
FROM L1
) n
WHERE t.qty > 2
ORDER BY t.Item#, n.rownum ASC;
db<>fiddle

The key to good performance using a numbers table based approach is to make sure the row expansion is constrained by a row goal, i.e. SELECT TOP(n), without a row goal the full cartesian product is used. Also, the FORMAT function is known to be slow.
You could try something like this
[EDIT]: The sequence assigned to the NewItem# does not reset for each (startdate, enddate) pair.
drop TABLE if exists #temp_on_hand;
go
CREATE TABLE #temp_on_hand(str INT, item# INT,Qty INT)
INSERT INTO #temp_on_hand VALUES
(1, 746, 3),
(5, 9526, 1),
(1, 14096, 1),
(2, 14096, 2),
(3, 14095, 3);
with
l as (select 1 n from (values (1),(1),(1),(1),(1),(1),(1),(1)) as v(n))
select *, concat_ws('-', item#,
right('00000'+cast(row_number() over (order by (select null)) as varchar(5)), 5)) NewItem#
from #temp_on_hand toh
cross apply (select top (toh.Qty) 1 n
from l l1, l l2,l l3, l l4) tally;
str item# Qty n NewItem#
1 746 3 1 746-00001
1 746 3 1 746-00002
1 746 3 1 746-00003
5 9526 1 1 9526-00004
1 14096 1 1 14096-00005
2 14096 2 1 14096-00006
2 14096 2 1 14096-00007
3 14095 3 1 14095-00008
3 14095 3 1 14095-00009
3 14095 3 1 14095-00010

A recursive CTE is a simple method:
WITH cte as (
SELECT str AS company, item#, Qty, 1 as n
FROM temp_on_hand
WHERE qty > 2
UNION ALL
SELECT company, item#, Qty, n + 1
FROM cte
WHERE n < Qty
)
SELECT str, item# + format(n, '0000') as newitem#, item#, qty
FROM cte;
Note that if qty exceeds 100, you will also need option (maxrecursion 0).
EDIT:
If you want the numbering within a company, you can use window functions and a cumulative sum:
WITH cte as (
SELECT str AS company, item#, Qty, 1 as n,
SUM(qty) OVER (PARTITION BY str ORDER BY item#) - qty as start_qty
FROM temp_on_hand
WHERE qty > 2
UNION ALL
SELECT company, item#, Qty, n + 1, start_qty
FROM cte
WHERE n < Qty
)
SELECT str,
item# + format(n + start_qty, '0000') as newitem#, item#, qty
FROM cte;

Related

The way for insert and fill table base on column in another table in sql with Several million rows

I have a table similar A With 2 million recordes
ROW ID ITEM NoOfUnit
1 1 A 2
2 2 B 1
3 3 C 3
.
.
.
I want fill table B base on NoOfUnit from A Similar to the below
ROW ID ITEM QTY
1 1 A 1
2 1 A 1
3 2 B 1
4 3 C 1
5 3 C 1
6 3 C 1
.
.
.
Number of rows in table B very large and cursor very slow...
I would just use a recursive CTE:
with cte as (
select id, item, NoOfUnit, 1 as n
from a
union all
select id, item, NoOfUnit, n + 1
from a
where n < NoOfUnit
)
insert into b (id, item, qty)
select id, item, 1
from cte;
If qty is ever greater than 100, then you need option (maxrecursion 0).
All you need to do here is duplicate your rows based on the number held in NoOfUnit, which you could do with a numbers table. You then insert the result of this into your destination table.
An example of how to do this is as follows:
Query
declare #d table(ID int, ITEM char(1),NoOfUnit int);
insert into #d values
(1,'A',2)
,(2,'B',1)
,(3,'C',3)
;
with t as(select t from(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as t(t)) -- table with 10 rows
,n as(select row_number() over (order by (select null)) as n from t,t t2,t t3,t t4,t t5) -- cross join 10 rows 5 times for 10 * 10 * 10 * 10 * 10 = 100,000 rows with incrementing value
select d.ID
,d.ITEM
,1 as QTY
from #d as d
join n
on d.NoOfUnit >= n.n
order by d.ID
,d.ITEM;
Output
ID
ITEM
QTY
1
A
1
1
A
1
2
B
1
3
C
1
3
C
1
3
C
1

how to divide the obtained sum of data

I need to get the sum of the records matching the key and the result of it is my TOTALQUANTITY
then i need to split it for packages, max package size is 5.
so when i get a result(TOTALQUANTITY) equals to 13 i should get something like this:
package |ofpackages |totalquantity |quantityofpackage
1 |3 |13 |5
2 |3 |13 |5
3 |3 |13 |3
my attempt:
SELECT
count(*) as TOTALQUANTITY,
get_token(data1) data1,
get_token(data2) data2,
get_token(data3) data3,
floor((row_number() over (partition BY get_token(data2) order by get_token(data2) ) - 1 ) / 5) package
FROM
--my working code
WHERE
--my working conditions
GROUP BY
get_token(data1),
get_token(data2),
get_token(data3)
ORDER BY
get_token(data2)
Totalquantity gives the correct value, but the package unfortunately doesn't :(
How it should be? how can i get the remaining values?
Add the use MOD() to determine the remainder, if zero output the division, otherwise add the package amount to the totalquantity then divide and round.
Using a derived table makes this a little easier I believe:
SELECT
TOTALQUANTITY,
get_token(data1) data1,
get_token(data2) data2,
get_token(data3) data3,
package,
case when MOD(TOTALQUANTITY,package) = 0
then TOTALQUANTITY + package
else round((TOTALQUANTITY + package)/package,0)
end as quantityofpackage
FROM (
SELECT
count(*) as TOTALQUANTITY,
get_token(data1) data1,
get_token(data2) data2,
get_token(data3) data3,
floor((row_number() over (partition BY get_token(data2) order by get_token(data2) ) - 1 ) / 5) package
FROM
--my working code
WHERE
--my working conditions
GROUP BY
get_token(data1),
get_token(data2),
get_token(data3)
) derived
ORDER BY
data2
Note you don't specify which dbms you are using so I have assumed it supports MOD() if not it will have an equivalent e.g. % in MS SQL Server
I'm not sure from the example code how that's supposed to work. Suppose there's a table which contains packages and quantities.
drop table if exists #package_quantities;
go
create table #package_quantities(
package int not null,
quantity int not null);
go
insert #package_quantities(package, quantity) values
(10, 10),
(10, 1),
(10, 2),
(20, 5),
(20, 9),
(30, 12),
(40, 20);
To generate new rows the query uses a tally tvf called fnNumbers. The CTE pack_cte sums quantitites for each package. The query then splits the packages into bundles with a maximum quantity of 5. Each subpackage has a unique sequence number 1, 2, 3...
fnNumbers tvf
create function [dbo].[fnNumbers](
#zero_or_one bit,
#n bigint)
returns table with schemabinding as return
with n(n) as (select null from (values (1),(2),(3),(4)) n(n))
select 0 n where #zero_or_one = 0
union all
select top(#n) row_number() over(order by (select null)) n
from n na, n nb, n nc, n nd, n ne, n nf, n ng, n nh,
n ni, n nj, n nk, n nl, n nm, n np, n nq, n nr;
Query
with pack_cte(package, sum_quantity) as (
select package, sum(quantity)
from #package_quantities
group by package)
select p.package, p.sum_quantity, p_calc.num_packages, fn.n subpackage,
case when p_calc.num_packages<>fn.n or (p_calc.num_packages=fn.n and p.sum_quantity%5=0)
then 5 else p.sum_quantity%5 end quanityofpackage
from pack_cte p
cross apply (select ceiling(p.sum_quantity/5.0) num_packages) p_calc
cross apply fnNumbers(1, p_calc.num_packages) fn;
Output
package sum_quantity num_packages subpackage quanityofpackage
10 13 3 1 5
10 13 3 2 5
10 13 3 3 3
20 14 3 1 5
20 14 3 2 5
20 14 3 3 4
30 12 3 1 5
30 12 3 2 5
30 12 3 3 2
40 20 4 1 5
40 20 4 2 5
40 20 4 3 5
40 20 4 4 5

SQL Join table to itself

I have a table where there are two columns like below.
value1 DerivedFrom
1 0
2 1
3 2
4 3
5 4
Basically, what it is saying is 1 was new, 2 was derived from 1, 3 was derived from 2 and so on.
I want the out put with 1 as the master key and 2,3,4 and 5 as children.
value1 DerivedFrom
1 0
1 1
1 2
1 3
1 4
Is it achiveble in SQL ? Thanks in advance
As mentioned in the comment, the simplest way is with an rCTE (recursive Common Table Expression):
--Sample Data
WITH YourTable AS(
SELECT *
FROM (VALUES(1,0),
(2,1),
(3,2),
(4,3),
(5,4))V(value1,DerivedFrom)),
--Solution
rCTE AS(
SELECT YT.value1 as rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
WHERE YT.DerivedFrom = 0
UNION ALL
SELECT r.rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
JOIN rCTE r ON YT.DerivedFrom = r.value1)
SELECT r.rootValue AS value1,
r.DerivedFrom
FROM rCTE r;

Repeat Rows N Times According to Column Value

I have following table.
Table A:
ID ProductFK Quantity Price
------------------------------------------------
10 1 2 100
11 2 3 150
12 1 1 120
----------------------------------------------
I need select that repeat Rows N Time According to Quantity Column Value.
So I need following select result:
ID ProductFK Quantity Price
------------------------------------------------
10 1 1 100
10 1 1 100
11 2 1 150
11 2 1 150
11 2 1 150
12 1 1 120
You can use a simple JOIN to get the desired result as below:
SELECT t1.*, t2.number + 1 RepeatNumber
FROM TableA t1
JOIN master.dbo.spt_values t2 ON t2.type = 'P' AND t2.number < t1.Quantity
The above query repeats each record by the specified number in Quantity column.
Note for master.dbo.spt_values on type = 'P':
This table is used for getting a series of number which is hard-coded in it by condition of type = 'P'.
You could do that with a recursive CTE using UNION ALL:
;WITH cte AS
(
SELECT * FROM Table1
UNION ALL
SELECT cte.[ID], cte.ProductFK, (cte.[Order] - 1) [Order], cte.Price
FROM cte INNER JOIN Table1 t
ON cte.[ID] = t.[ID]
WHERE cte.[Order] > 1
)
SELECT [ID], ProductFK, 1 [Order], Price
FROM cte
ORDER BY 1
Here's a working SQLFiddle.
Here's a longer explanation of this technique.
Since your input is too large for this recursion, you could use an auxillary table to have "many" dummy rows and then use SELECT TOP([Order]) for each input row (CROSS APPLY):
;WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b)
SELECT t.[ID], t.ProductFK, 1 [Order], t.Price
FROM Table1 t CROSS APPLY (
SELECT TOP(t.[Order]) N
FROM E16) ca
ORDER BY 1
(The auxillary table is borrowed from here, it allows up to 65536 rows per input row and can be extended if required)
Here's a working SQLFiddle.
CREATE TAblE #temp
(
T_Name VARCHAR(50),
T_Times BIGINT
)
INSERT INTO #temp(T_Name,T_Times) VALUES ('ASHISH',4)
INSERT INTO #temp(T_Name,T_Times) VALUES ('PANKAJ',3)
INSERT INTO #temp(T_Name,T_Times) VALUES ('RUPESH',2)
INSERT INTO #temp(T_Name,T_Times) VALUES ('MANISH',5)
SELECT t.T_Name ,t.T_Times FROM
(SELECT T_Name,T_Times,CAST(('<val>'+REPLICATE(T_Name+'</val><val>',T_Times-1)
+'</val>') AS XML )AS X FROM #temp)t CROSS APPLY t.X.nodes('/val')y(z)
drop table #temp

ROW_NUMBER query

I have a table:
Trip Stop Time
-----------------
1 A 1:10
1 B 1:16
1 B 1:20
1 B 1:25
1 C 1:31
1 B 1:40
2 A 2:10
2 B 2:17
2 C 2:20
2 B 2:25
I want to add one more column to my query output:
Trip Stop Time Sequence
-------------------------
1 A 1:10 1
1 B 1:16 2
1 B 1:20 2
1 B 1:25 2
1 C 1:31 3
1 B 1:40 4
2 A 2:10 1
2 B 2:17 2
2 C 2:20 3
2 B 2:25 4
The hard part is B, if B is next to each other I want it to be the same sequence, if not then count as a new row.
I know
row_number over (partition by trip order by time)
row_number over (partition by trip, stop order by time)
None of them will meet the condition I want. Is there a way to query this?
create table test
(trip number
,stp varchar2(1)
,tm varchar2(10)
,seq number);
insert into test values (1, 'A', '1:10', 1);
insert into test values (1, 'B', '1:16', 2);
insert into test values (1, 'B', '1:20', 2);
insert into test values (1 , 'B', '1:25', 2);
insert into test values (1 , 'C', '1:31', 3);
insert into test values (1, 'B', '1:40', 4);
insert into test values (2, 'A', '2:10', 1);
insert into test values (2, 'B', '2:17', 2);
insert into test values (2, 'C', '2:20', 3);
insert into test values (2, 'B', '2:25', 4);
select t1.*
,sum(decode(t1.stp,t1.prev_stp,0,1)) over (partition by trip order by tm) new_seq
from
(select t.*
,lag(stp) over (order by t.tm) prev_stp
from test t
order by tm) t1
;
TRIP S TM SEQ P NEW_SEQ
------ - ---------- ---------- - ----------
1 A 1:10 1 1
1 B 1:16 2 A 2
1 B 1:20 2 B 2
1 B 1:25 2 B 2
1 C 1:31 3 B 3
1 B 1:40 4 C 4
2 A 2:10 1 B 1
2 B 2:17 2 A 2
2 C 2:20 3 B 3
2 B 2:25 4 C 4
10 rows selected
You want to see if the stop changes between one row and the next. If it does, you want to increment the sequence. So use lag to get the previous stop into the current row.
I used DECODE because of the way it handles NULLs and it is more concise than CASE, but if you are following the text book, you should probably use CASE.
Using SUM as an analytic function with an ORDER BY clause will give the answer you are looking for.
select *, dense_rank() over(partition by trip, stop order by time) as sqnc
from yourtable;
Use dense_rank so you get all the numbers consecutively, with no skipped numbers in between.
I think this is more complicated than a simple row_number(). You need to identify groups of adjacent stops and then enumerate them.
You can identify the groups using a difference of row numbers. Then, a dense_rank() on the difference does what you want if there are no repeated stops on a trip:
select t.*,
dense_rank() over (partition by trip order by grp, stop)
from (select t.*,
(row_number() over (partition by trip order by time) -
row_number() over (partition by trip, stop order by time)
) as grp
from table t
) t;
If there are:
select t.*, dense_rank() over (partition by trip order by mintime)
from (select t.*,
min(time) over (partition by trip, grp, stop) as mintime
from (select t.*,
(row_number() over (partition by trip order by time) -
row_number() over (partition by trip, stop order by time)
) as grp
from table t
) t
) t;