What is the best way to duplicate rows based on multiple columns? - sql

I have the following table below:
ID Start_Repeat_1 End_Repeat_1 Start_Repeat_2 End_Repeat_2
A 3 7 2 5
B 1 4 2 5
My goal is to duplicate "A" 5 times and "B" 4 times with the output below
ID Repeat_1 Repeat_2
A 3 2
A 4 3
A 5 4
A 6 5
A 7 NULL
B 1 2
B 2 3
B 3 4
B 4 5
The logic is that "A" needs to be duplicated with numbers between 3 and 7 in one column and numbers between 2 and 5 in another column so it needs to be duplicated at least 5 times.
The version below would also do. The order between the two columns does not matter.
ID Repeat_1 Repeat_2
A 3 2
A 7 5
A 6 3
A 5 NULL
A 4 4
Can someone help me with this using SQL Server 2018?
The dataset size is about 10,000 rows and each row is duplicated at most 10 times with a total of 10 columns like this
ID Repeat_1 Repeat_2 Repeat_3 Repeat_4 Repeat_10
A 3 2 1 1 1
B 7 5 1 1 1

You need to make use of a tally table. In the following solution, I have use a recursive cte to generate one.
Alternatively you can use a recursive cte to generate one on the fly
-- Tally Table
create table tally
(
n int
)
-- generate 1000 numbers for tally table
with cte as
(
select n = 0
union all
select n = n + 1
from cte
where n < 1000
)
insert into tally (n)
select n
from cte
The cross apply is to find the maximum difference between the repeat_1 or 2 etc.
-- The query
select t.ID,
Repeat_1 = case when n.n <= (End_Repeat_1 - Start_Repeat_1)
then t.Start_Repeat_1 + n.n
end,
Repeat_2 = case when n.n <= (End_Repeat_2 - Start_Repeat_2)
then t.Start_Repeat_2 + n.n
end,
Repeat_3 = case when n.n <= (End_Repeat_3 - Start_Repeat_3)
then t.Start_Repeat_3 + n.n
end
from tbl t
cross apply
(
select m = max(d)
from (
values
(End_Repeat_1 - Start_Repeat_1 + 1),
(End_Repeat_2 - Start_Repeat_2 + 1),
(End_Repeat_3 - Start_Repeat_3 + 1)
) n (d)
) m
inner join tally n on n.n >= 0
and n.n < m.m
dbfiddel demo

you can use recursive cte :
with cte as (
select * from test
union all
select Id
, case when Start_Repeat_1 + 1 > End_Repeat_1 then null else Start_Repeat_1 + 1 end
,End_Repeat_1
,case when Start_Repeat_2+ 1 > End_Repeat_2 then null else Start_Repeat_2+ 1 end
,End_Repeat_2
from cte
where Start_Repeat_1 <= End_Repeat_1 and Start_Repeat_2 <= End_Repeat_2
)
select ID,Start_Repeat_1,Start_Repeat_2
from cte
where coalesce(Start_Repeat_1,Start_Repeat_2) is not null
order by ID
ID | Start_Repeat_1 | Start_Repeat_2
:- | -------------: | -------------:
A | 3 | 2
A | 4 | 3
A | 5 | 4
A | 6 | 5
A | 7 | null
B | 1 | 2
B | 2 | 3
B | 3 | 4
B | 4 | 5
db<>fiddle here

Related

Get max record for each group of records, link multiple tables

I seek to find the maximum timestamp (ob.create_ts) for each group of marketid's (ob.marketid), joining tables obe (ob.orderbookid = obe.orderbookid) and market (ob.marketid = m.marketid). Although there are a number of solutions posted like this for a single table, when I join multiple tables, I get redundant results. Sample table and desired results below:
table: ob
orderbookid
marketid
create_ts
1
1
1664635255298
2
1
1664635255299
3
1
1664635255300
4
2
1664635255301
5
2
1664635255302
6
2
1664635255303
table: obe
orderbookentryid
orderbookid
entryname
1
1
'entry-1'
2
1
'entry-2'
3
1
'entry-3'
4
2
'entry-4'
5
2
'entry-5'
6
3
'entry-6'
7
3
'entry-7'
8
4
'entry-8'
9
5
'entry-9'
10
6
'entry-10'
table: m
marketid
marketname
1
'market-1'
2
'market-2'
desired results
ob.orderbookid
ob.marketid
obe.orderbookentryid
obe.entryname
m.marketname
3
1
6
'entry-6'
'market-1'
3
1
7
'entry-7'
'market-1'
6
2
10
'entry-10'
'market-2'
Use ROW_NUMBER() to get a properly filtered ob table. Then JOIN the other tables onto that!
WITH
ob_filtered AS (
SELECT
orderbookid,
marketid
FROM
(
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY
marketid
ORDER BY
create_ts DESC
) AS create_ts_rownumber
FROM
ob
) ob_with_rownumber
WHERE
create_ts_rownumber = 1
)
SELECT
ob_filtered.orderbookid,
ob_filtered.marketid,
obe.orderbookentryid,
obe.entryname,
m.marketname
FROM
ob_filtered
JOIN m
ON m.marketid = ob_filtered.marketid
JOIN obe
ON ob_filtered.orderbookid = obe.orderbookid
;

Generate a serial number based on quantity column in sql

Hi Experts I have a table like this
T1
Order_no
Qty
1
3
2
5
3
1
4
3
I need to generate a column 'serial no' having values based on 'qty'
Output needed
OrderNo
Qty
SerailNo
1
3
1
1
3
2
1
3
3
2
5
1
2
5
2
2
5
3
2
5
4
2
5
5
3
1
1
4
3
1
4
3
2
4
3
3
Any suggestions?
Thanks in advance!!
You don't mention the specific database so I'll assume you are using PostgreSQL, aren't you?
You can use a Recursive CTE to expand the rows. For example:
with recursive
n as (
select order_no, qty, 1 as serial_no from t1
union all
select order_no, qty, serial_no + 1
from n
where serial_no < qty
)
select * from n order by order_no, serial_no
Result:
order_no qty serial_no
--------- ---- ---------
1 3 1
1 3 2
1 3 3
2 5 1
2 5 2
2 5 3
2 5 4
2 5 5
3 1 1
4 3 1
4 3 2
4 3 3
See running example at DB Fiddle.
EDIT FOR ORACLE
If you are using Oracle the query changes a bit to:
with
n (order_no, qty, serial_no) as (
select order_no, qty, 1 from t1
union all
select order_no, qty, serial_no + 1
from n
where serial_no < qty
)
select * from n order by order_no, serial_no
Result:
ORDER_NO QTY SERIAL_NO
--------- ---- ---------
1 3 1
1 3 2
1 3 3
2 5 1
2 5 2
2 5 3
2 5 4
2 5 5
3 1 1
4 3 1
4 3 2
4 3 3
See running example at db<>fiddle.
You should first provide the database you're using. Whether it's oracle, Sql Server, PostGreSQL will determine which procedural language to use. It's very likely that you'll need to do this in two steps:
1st: Duplicate the number of rows based on the column Qty using a decreasing loop
2nd: You'll need to create a sequential partionned column based on the Qty column

SQL Server column Sorting with 2 or more columns being sorted

I have these records in my table:
id sequence question answer letter
1 1 1 + 1 2 B
2 2 2 + 2 4 E
3 3 4 + 4 8 A
4 4 8 + 8 16 D
5 5 16 + 16 32 C
Is there any possibilites to make my table look like this? :
id sequence question answer letter
1 1 1 + 1 8 A
2 2 2 + 2 2 B
3 3 4 + 4 32 C
4 4 8 + 8 16 D
5 5 16 + 16 4 E
I mean the first one is i order by sequence, but i also wanted to get the order of it in letters because if not they will be in same position as therefore the question can be answered easily, I wanted the answer to come with the letter and order by it alphabetically. Please badly need your help :(
--DDL
CREATE TABLE test(id int,sequence int,question varchar(20),answer int, letter varchar(5) );
INSERT INTO test VALUES(1,1,'1+1',2,'B');
INSERT INTO test VALUES(2,2,'2+2',4,'E');
INSERT INTO test VALUES(3,3,'4+4',8,'A');
INSERT INTO test VALUES(4,4,'8+8',16,'D');
INSERT INTO test VALUES(5,5,'16+16',32,'C');
--SQL
SELECT A.id,
A.sequence,
A.question,
B.answer,
B.letter
FROM
(
SELECT id,
sequence,
question,
ROW_NUMBER() OVER ( ORDER BY id ) AS rn
FROM test
) A
INNER JOIN
(
SELECT answer,
letter,
ROW_NUMBER() OVER ( ORDER BY letter ) AS rn
FROM test
) B
ON A.rn = B.rn;
SQL Fiddle :-
http://sqlfiddle.com/#!6/96e09/3

SQL Inner Join and Partitioning To obtain RowNumbers when matching

I have 2 tables. The first table 'a' the second 'b'.
I am writing a query that grabs every row in table a (there is 33 rows defined) and inner joins table b where the EnclLocation or the BackPanLoc match the Workcell in table A.
I only want a row from table B where they match based off BackPan and EnclLocation but they are not the same records. table b has a few rows of data that is assigned to the same workcell as table a. I am just trying to retrieve those additional rows and partition it.
I attached table a and table b. I also attached the desired results for this query with respect to Workcell 10 only as an example... As you can see, table B has 4 records that has either the EnclLocation or the BackPanLoc = 10. But my results only show the same DelvNumber 4 times. any help is most appreicated.
Table a
Table b
Incorrect Results
Desired Results (showing only Workcell 10 as an example)
workcell DelvNumber RowNum
1 447910-02 1
2 445710-01 1
2 445710-01 2
3 444291-01 1
3 444291-01 2
4 447910-03 1
4 447910-03 2
5 648020-01 1
6 647800-02 1
7 646920-01 1
7 646920-01 2
8 644830-4-8 1
8 644830-4-8 2
9 443990-01 1
10 645960-01-03 1
10 445710-11 2
10 445710-02 3
10 445710-09 4
Code Used
WITH ss
AS (SELECT a.*,
Row_number()
OVER(
partition BY a.workcell
ORDER BY a.workcell) AS rownum
FROM nwcurrent a
INNER JOIN nwdeliverables b
ON b.encllocation = a.workcell
OR b.backpanloc = a.workcell
WHERE ( b.status < 9
AND ( b.encllocation <> 0
OR b.backpanloc <> 0 )
OR a.delvnumber = '123' ))
SELECT *
FROM ss
copy and paste format
1 447910-02 1
2 445710-01 1
2 445710-01 2
3 444291-01 1
3 444291-01 2
4 447910-03 1
4 447910-03 2
5 648020-01 1
6 647800-02 1
7 646920-01 1
7 646920-01 2
8 644830-4-8 1
8 644830-4-8 2
9 443990-01 1
10 645960-01-03 1
10 445710-11 2
10 445710-02 3
10 445710-09 4
SQLFiddle
http://sqlfiddle.com/#!3/a8682/4
A new try...
SELECT a.workcell
,a.DelvNumber AS A_DelvNumber
,b.DelvNumber AS B_DelvNumber
,CASE WHEN a.DelvNumber<>b.DelvNumber THEN b.DelvNumber ELSE a.DelvNumber END AS DelvNumber_Resolved
,Row_number() OVER(partition BY a.workcell ORDER BY a.workcell) AS rownum
FROM NWCurrent a
INNER JOIN NWDeliverables AS b ON b.EnclLocation=a.WorkCell OR b.BackPanLoc=a.WorkCell
WHERE (b.status <9 AND (b.EnclLocation<>0 OR b.BackPanLoc<>0)OR a.DelvNumber='123')

Adding non existing data to SQL query

My SQL query returns the following result (screenshot):
x y count
----------- ----------- -----------
1 1 10
1 2 2
2 4 3
2 5 5
4 1 5
5 1 8
what i want is x, y should always contain 1 to 5 values, even if the query doesn't return them, in the above scenario x is missing 3. How to add the missing values here that are between 1 & 5.
Thanks in Advance
First you need to generate the desired data. You can use a table of numbers for this. Use CROSS JOIN to generate all possible combinations of two tables. Finally, OUTER JOIN the generated data with your table.
In the following query I have used union to build a list of numbers instead of fetching them from a table. But the idea remains same:
SELECT XList.x, YList.y, #temp.count
FROM (
SELECT 1 AS x UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5
) AS XList
CROSS JOIN (
SELECT 1 AS y UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5
) AS YList
LEFT JOIN #temp ON XList.x = #temp.x AND YList.y = #temp.y
Result:
x y count
----------- ----------- -----------
1 1 10
2 1 NULL
3 1 NULL
4 1 5
5 1 8
1 2 2
2 2 NULL
3 2 NULL
4 2 NULL
5 2 NULL
1 3 NULL
2 3 NULL
3 3 NULL
4 3 NULL
5 3 NULL
1 4 NULL
2 4 3
3 4 NULL
4 4 NULL
5 4 NULL
1 5 NULL
2 5 5
3 5 NULL
4 5 NULL
5 5 NULL
You can do it this way:
select t1.x, t2.y, s.count from
(values(1),(2),(3),(4),(5)) t1(x) cross join
(values(1),(2),(3),(4),(5)) t2(y)
left join #temp s on t1.x = s.x and t2.y = s.y