SQL Split Single Row into Fixed Number of Columns - sql

We need to split a single row into fixed number of multiple columns. Following is an example for the data set:
1
2
3
4
5
6
7
Desired Output:
Column A Column B Column C Column D
1 2 3 4
5 6 7 NULL
Thanks for your help in advance.

SQL Server Solution:
Create Sample Table:
create table mytable (col1 int)
insert into mytable values
(1),
(2),
(3),
(4),
(5),
(6),
(7);
Using Modulo and Row_Number(), you could easily do this:
Modulo Query:
SELECT
R1.col1 as columnA,
R2.col1 as columnB,
R3.col1 as columnC,
R4.col1 as columnD
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 1
) AS R1
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 2
) AS R2
ON R1.RowNum = R2.RowNum
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 3
) AS R3
ON R2.RowNum = R3.RowNum
FULL OUTER JOIN (
SELECT ROW_NUMBER() OVER (ORDER BY col1 ASC) AS RowNum, col1
FROM mytable
WHERE
col1 % 4 = 0
) AS R4
ON R4.RowNum = R3.RowNum
Result:
+---------+---------+---------+---------+
| columnA | columnB | columnC | columnD |
+---------+---------+---------+---------+
| 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | (null) |
+---------+---------+---------+---------+
SQL Fiddle Demo

Related

T-SQL sequential updating with two columns

I have a table created by:
CREATE TABLE table1
(
id INT,
multiplier INT,
col1 DECIMAL(10,5)
)
INSERT INTO table1
VALUES (1, 2, 1.53), (2, 3, NULL), (3, 2, NULL),
(4, 2, NULL), (5, 3, NULL), (6, 1, NULL)
Which results in:
id multiplier col1
-----------------------
1 2 1.53000
2 3 NULL
3 2 NULL
4 2 NULL
5 3 NULL
6 1 NULL
I want to add a column col2 which is defined as multiplier * col1, however the next value of col1 then updates to take the previous calculated value of col2.
The resulting table should look like:
id multiplier col1 col2
---------------------------------------
1 2 1.53000 3.06000
2 3 3.06000 9.18000
3 2 9.18000 18.36000
4 2 18.36000 36.72000
5 3 36.72000 110.16000
6 1 110.16000 110.16000
Is this possible using T-SQL? I've tried a few different things such as joining id to id - 1 and have played around with a sequential update using UPDATE and setting variables but I can't get it to work.
A recursive CTE might be the best approach. Assuming your ids have no gaps:
with cte as (
select id, multiplier, convert(float, col1) as col1, convert(float, col1 * multiplier) as col2
from table1
where id = 1
union all
select t1.id, t1.multiplier, cte.col2 as col1, cte.col2 * t1.multiplier
from cte join
table1 t1
on t1.id = cte.id + 1
)
select *
from cte;
Here is a db<>fiddle.
Note that I converted the destination type to float, which is convenient for this sort of operation. You can convert back to decimal if you prefer that.
Basically, this would require an aggregate/window function that computes the product of column values. Such set function does not exists in SQL though. We can work around this with arithmetics:
select
id,
multiplier,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2
from table1
Demo on DB Fiddle:
id | multiplier | col1 | col2
-: | ---------: | -----: | -----:
1 | 2 | 1.53 | 3.06
2 | 3 | 3.06 | 9.18
3 | 2 | 9.18 | 18.36
4 | 2 | 18.36 | 36.72
5 | 3 | 36.72 | 110.16
6 | 1 | 110.16 | 110.16
This will fail if there are negative multipliers.
If you wanted an update statement:
with cte as (
select col1, col2,
coalesce(min(col1) over() * exp(sum(log(multiplier)) over(order by id rows between unbounded preceding and 1 preceding)), col1) col1_new,
min(col1) over() * exp(sum(log(multiplier)) over(order by id)) col2_new
from table1
)
update cte set col1 = col1_new, col2 = col2_new

Spliting a single column into two different columns SQL Server [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I have something like this:
Column
1
2
3
4
-1
-2
-3
-4
And I wanted an output something like this:
C1 C2
1 -1
2 -2
3 -3
4 -4
Can anyone help me on writing a query to get this output in T-SQL??
and what if something is like this:
values
1
-5
10
-9
15
-3
8
-11
12
-17
and I have to make two columns that separates the positive values in one column and negative values in another.
You can use aggregation:
select max(col1), min(col1)
from t
group by abs(col1);
This will do what you want.. a simple self-join
create table test1
(
col1 int not null
)
insert into test1 (col1) values (1)
insert into test1 (col1) values (2)
insert into test1 (col1) values (3)
insert into test1 (col1) values (4)
insert into test1 (col1) values (-1)
insert into test1 (col1) values (-2)
insert into test1 (col1) values (-3)
insert into test1 (col1) values (-4)
select a.col1 C1, b.col1 C2 from test1 a
join test1 b on a.col1 = -b.col1
where a.col1 > 0
SELECT t1.col1 as C1,
t2.col1 AS C2
FROM tbl t1
JOIN tbl t2
ON t1.col1 = (t2.col1 * -1)
WHERE t1.col1 > 0
GO
C1 | C2
-: | -:
1 | -1
2 | -2
3 | -3
4 | -4
dbfiddle here
You can join the values of the two queries by using the analytic function ROW_NUMBER():
WITH A AS (
SELECT col1, ROW_NUMBER() OVER (ORDER BY col1 desc) AS row_num
FROM TABLE_1
WHERE col1 < 0),
B AS (
SELECT col1, ROW_NUMBER() OVER (ORDER BY col1) AS row_num
FROM TABLE_1
WHERE col1 >= 0)
SELECT A.col1 AS C1, B.col1 AS C2
FROM A
INNER JOIN B
ON A.row_num = B.row_num;
You have to JOIN the table with itself and then limit the result so the negative ones are excluded from the first column
SELECT t1.Col1 c1, t2.Col1 c2 FROM tbl t1
JOIN tbl t2 ON t1.Col1 = t2.Col1*-1 AND t1.Col1 > 0
Answer to second question:
The idea is to SELECT positive and negative numbers separately and then do a FULL OUTER JOIN on their row numbers.
SELECT t1.Col1 c1, t2.Col1 c2 FROM (
SELECT t11.Col1,
ROW_NUMBER() OVER(ORDER BY t11.Col1 ASC) rn
FROM tbl t11
WHERE t11.Col1 > 0
) t1 FULL JOIN (
SELECT t22.Col1,
ROW_NUMBER() OVER(ORDER BY t22.Col1 DESC) rn
FROM tbl t22
WHERE t22.Col1 < 0
) t2 ON t2.rn = t1.rn;
Example:
CREATE TABLE tbl ( Col1 int );
INSERT INTO tbl VALUES (6), (7), (-4), (1), (-2), (3), (5);
-- RESULT
| c1 | c2 |
|----|--------|
| 1 | -2 |
| 3 | -4 |
| 5 | (null) |
| 6 | (null) |
| 7 | (null) |

DENSE_RANK() without duplication

Here's what my data looks like:
| col1 | col2 | denserank | whatiwant |
|------|------|-----------|-----------|
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 1 |
| 3 | 2 | 2 | 2 |
| 4 | 2 | 2 | 2 |
| 5 | 1 | 1 | 3 |
| 6 | 2 | 2 | 4 |
| 7 | 2 | 2 | 4 |
| 8 | 3 | 3 | 5 |
Here's the query I have so far:
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
ORDER BY [col1] asc
What I'd like to achieve is for my denserank column to increment every time there is a change in the value of col2 (even if the value itself is reused). I can't actually order by the column I have denserank on, so that won't work). See the whatiwant column for an example.
Is there any way to achieve this with DENSE_RANK()? Or is there an alternative?
I would do it with a recursive cte like this:
declare #Dept table (col1 integer, col2 integer)
insert into #Dept values(1, 1),(2, 1),(3, 2),(4, 2),(5, 1),(6, 2),(7, 2),(8, 3)
;with a as (
select col1, col2,
ROW_NUMBER() over (order by col1) as rn
from #Dept),
s as
(select col1, col2, rn, 1 as dr from a where rn=1
union all
select a.col1, a.col2, a.rn, case when a.col2=s.col2 then s.dr else s.dr+1 end as dr
from a inner join s on a.rn=s.rn+1)
col1, col2, dr from s
result:
col1 col2 dr
----------- ----------- -----------
1 1 1
2 1 1
3 2 2
4 2 2
5 1 3
6 2 4
7 2 4
8 3 5
The ROW_NUMBER is only required in case your col1 values are not sequential. If they are you can use the recursive cte straight away
Try this using window functions:
with t(col1 ,col2) as (
select 1 , 1 union all
select 2 , 1 union all
select 3 , 2 union all
select 4 , 2 union all
select 5 , 1 union all
select 6 , 2 union all
select 7 , 2 union all
select 8 , 3
)
select t.col1,
t.col2,
sum(x) over (
order by col1
) whatyouwant
from (
select t.*,
case
when col2 = lag(col2) over (
order by col1
)
then 0
else 1
end x
from t
) t
order by col1;
Produces:
It does a single table read and forms group of consecutive equal col2 values in increasing order of col1 and then finds dense rank on that.
x: Assign value 0 if previous row's col2 is same as this row's col2 (in order of increasing col1) otherwise 1
whatyouwant: create groups of equal values of col2 in order of increasing col1 by doing an incremental sum of the value x generated in the last step and that's your output.
Here is one way using SUM OVER(Order by) window aggregate function
SELECT col1,Col2,
Sum(CASE WHEN a.prev_val = a.col2 THEN 0 ELSE 1 END) OVER(ORDER BY col1) AS whatiwant
FROM (SELECT col1,
col2,
Lag(col2, 1)OVER(ORDER BY col1) AS prev_val
FROM Yourtable) a
ORDER BY col1;
How it works:
LAG window function is used to find the previous col2 for each row ordered by col1
SUM OVER(Order by) will increment the number only when previous col2 is not equal to current col2
I think this is possible in pure SQL using some gaps and islands tricks, but the path of least resistance might be to use a session variable combined with LAG() to keep track of when your computed dense rank changes value. In the query below, I use #a to keep track of the change in the dense rank, and when it changes this variable is incremented by 1.
DECLARE #a int
SET #a = 1
SELECT t.col1,
t.col2,
t.denserank,
#a = CASE WHEN LAG(t.denserank, 1, 1) OVER (ORDER BY t.col1) = t.denserank
THEN #a
ELSE #a+1 END AS [whatiwant]
FROM
(
SELECT col1, col2, DENSE_RANK() OVER (ORDER BY COL2) AS [denserank]
FROM [table1]
) t
ORDER BY t.col1

MS SQL Set Group ID Without Looping

I would like create a query in MS-SQL to make a column containing an incrementing group number.
This is how I want my data to return:
Column 1 | Column 2 | Column 3
------------------------------
I | 1 | 1
O | 2 | 2
O | 2 | 3
I | 3 | 4
O | 4 | 5
O | 4 | 6
O | 4 | 7
O | 4 | 8
I | 5 | 9
O | 6 | 10
Column 1 is the I and O meaning In and Out.
Column 2 is the row Group (this should increment when Column 1 changes).
Column 3 is the Row-number.
So how can I write my query so that Column 2 increments every time Column 1 changes?
Firstly, to perform this kind of operation you need some column that can identify the order of the rows. If you have a column that determines this order, an identity column for example, it can be used to do something like this:
Runnable sample:
CREATE TABLE #Groups
(
id INT IDENTITY(1, 1) , -- added identity to provide order
Column1 VARCHAR(1)
)
INSERT INTO #Groups
( Column1 )
VALUES ( 'I' ),
( 'O' ),
( 'O' ),
( 'I' ),
( 'O' ),
( 'O' ),
( 'O' ),
( 'O' ),
( 'I' ),
( 'O' );
;
WITH cte
AS ( SELECT id ,
Column1 ,
1 AS Column2
FROM #Groups
WHERE id = 1
UNION ALL
SELECT g.id ,
g.Column1 ,
CASE WHEN g.Column1 = cte.Column1 THEN cte.Column2
ELSE cte.Column2 + 1
END AS Column2
FROM #Groups g
INNER JOIN cte ON cte.id + 1 = g.id
)
SELECT *
FROM cte
OPTION (MAXRECURSION 0) -- required to allow for more than 100 recursions
DROP TABLE #Groups
This code effectively loops through the records, comparing each row to the next and incrementing the value of Column2 if the value in Column1 changes.
If you don't have an identity column, then you might consider adding one.
Credit #AeroX:
With 30K records, the last line: OPTION (MAXRECURSION 0) is required to override the default of 100 recursions when using a Common Table Expression (CTE). Setting it to 0, means that it isn't limited.
This will work if you have sqlserver 2012+
DECLARE #t table(col1 char(1), col3 int identity(1,1))
INSERT #t values
('I'), ('O'), ('O'), ('I'), ('O'), ('O'), ('O'), ('O'), ('I'), ('O')
;WITH CTE AS
(
SELECT
case when lag(col1) over (order by col3) = col1
then 0 else 1 end increase,
col1,
col3
FROM #t
)
SELECT
col1,
sum(increase) over (order by col3) col2,
col3
FROM CTE
Result:
col1 col2 col3
I 1 1
O 2 2
O 2 3
I 3 4
O 4 5
O 4 6
O 4 7
O 4 8
I 5 9
O 6 10

Insert data into temp table from 2 source tables

I have 2 SELECT statements that both return 13 rows from dirrefernt tables
I would like to create 1 temporary table with 2 columns and insert the 2 result rows into the 2 columns. Is there a way to do this?
So
1 - SELECT INPOS FROM TABLE1 returns
1,2,3,4,5,6,7,18,9,10,11,12,13
2 - SELECT CODE FROM TABLE2 returns
CODEA,CODEB,CODEC,CODED,CODEE,CODEF,CODEG,CODEH,CODEI,CODEJ,CODEK,CODEL,CODEM
I would like my temporary table to be
1 | CODEA
2 | CODEB
3 | CODEC
4 | CODED
5 | CODEE
6 | CODEF
7 | CODEG
8 | CODEH
9 | CODEI
10 | CODEJ
11 | CODEK
12 | CODEL
13 | CODEM
Try this:
WITH T1 AS (
SELECT ROW_NUMBER() OVER(ORDER BY INPOS) ID, INPOS FROM TABLE1
),
WITH T2 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY CODE) ID, CODE FROM TABLE2
),
SELECT T1.INPOS, T2.CODE
FROM T1 INNER JOIN T2 ON T1.ID = T2.ID
Try something like this:
SELECT a.impos, b.code
FROM (
(
SELECT impos, RANK() OVER (ORDER BY impos ASC) AS link
FROM table1
) AS a INNER JOIN (
SELECT code, RANK() OVER (ORDER BY code ASC) AS link
FROM table2
) AS b ON a.link = b.link
)
sqlfiddle demo