select one column, split to multiple columns - sql

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,'')

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

Split unlimited length SQL String into two columns

I have seen multiple answers, but none that worked for me.
I send in a string like this desc1$100$desc2$200 to a stored procedure.
Then I want to to insert it into a temp table like so:
|descr|meter|
|desc1|100 |
|desc2|200 |
Wanted output ^
declare #string varchar(max)
set #string = 'desc1$100$desc2$200'
declare #table table
(descr varchar(max),
meter int
)
-- Insert statement
-- INSERT NEEDED HERE
-- Test Select
SELECT * FROM #table
How should I split it?
Here's an example using JSON.
Declare #S varchar(max) = 'desc1$100$desc2$200'
Select Descr = max(case when ColNr=0 then Value end )
,Meter = max(case when ColNr=1 then Value end )
From (
Select RowNr = [Key] / 2
,ColNr = [Key] % 2
,Value
From OpenJSON( '["'+replace(string_escape(#S,'json'),'$','","')+'"]' )
) A
Group By RowNr
Results
Descr Meter
desc1 100
desc2 200
If it helps with the visualization, the subquery generates the following:
RowNr ColNr Value
0 0 desc1
0 1 100
1 0 desc2
1 1 200
Please try the following solution based on XML and XQuery.
It allows to get odd vs. even tokens in the input string.
SQL
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, descr varchar(max), meter int);
DECLARE #string varchar(max) = 'desc1$100$desc2$200';
DECLARE #separator CHAR(1) = '$';
DECLARE #xmldata XML = TRY_CAST('<root><r><![CDATA[' +
REPLACE(#string, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
INSERT INTO #tbl (descr, meter)
SELECT c.value('(./text())[1]', 'VARCHAR(30)') AS descr
, c.value('(/root/*[sql:column("seq.pos")]/text())[1]', 'INT') AS meter
FROM #xmldata.nodes('/root/*[position() mod 2 = 1]') AS t(c)
CROSS APPLY (SELECT t.c.value('let $n := . return count(/root/*[. << $n[1]]) + 2','INT') AS pos
) AS seq;
-- Test
SELECT * FROM #tbl;
Output
+----+-------+-------+
| ID | descr | meter |
+----+-------+-------+
| 1 | desc1 | 100 |
| 2 | desc2 | 200 |
+----+-------+-------+

Get the immediate parent child relation in a table

I have a table with following value
userid roleid ranklvl
123 a 1
456 b 2
789 c 3
I need the output data from the above in the following format :
userid roleid ranklvl parentroleid
123 a 1 null
456 b 2 a
789 c 3 b
Thanks in advance for any guidance on this.
EDIT :
I used the while loop approach to achieve this but trying to avoid the while loop.
declare #a table
(
userid int,roleid char(1),ranklvl int,parentroleid char(1) )
insert into #a(userid,roleid,ranklvl) select userid,roleid,ranklvl from Table [where some condition]
declare #maxcount smallint = (select max(ranklvl) from #a)
declare #counter smallint = 1
declare #parentroleid char(1)
while( #maxcount > #counter)
BEGIN
Select #parentroleid = roleid from #a where ranklvl = #maxcount - 1
UPDATE #a SET parentrole = #parentroleid where ranklvl = #maxcount
SET #maxcount = #maxcount -1
END
select * from #a
The while loop logic worked if proper sequence of ranklvl is there for a given record set like 1->2->3. But it did not work if data is in following way :
userid roleid ranklvl
123 a 1
789 c 3
The following expected result is not coming by the while loop logic.
userid roleid ranklvl parentroleid
123 a 1 null
789 c 3 a
I think you want a self-join:
select t.*, p.roleid parentroleid
from mytable t
left join mytable p on p.ranklvl = t.ranklvl - 1
If there are gaps in the ordering column, you can enumerate first:
with cte as (
select t.*, row_number() over(order by ranklvl) rn
from mytable t
)
select c.*, p.roleid parentroleid
from cte c
left join cte p on p.rn = c.rn - 1

SQL COUNT the number of records on a certain date

I trying to create a SQL query/stored procedure to count the number of applications in-progress on a certain day for a certain team.
Where I am having trouble is the following scenario: when the application is transferred to another user, the count for the day should not double count (a count for each team on the day of transfer) and should go to the transferred user.
My Tables
**Users**
Id || Name || TeamId
---------------------------------
1 User 1 1
2 User 2 2
**Application**
Id || Name
-------------
1 Application1
**ApplicationUser**
Id || ApplicationId || UserId || AssignedDate || UnassignedDate
----------------------------------------------------------
1 1 1 2018-03-01 2018-03-02
2 1 2 2018-03-02 2018-03-03
so in the Stored Procedure I am sending a date in as a parameter and the result i want to return is the following.
Date || Team 1 || Team 2 || Total
-------------------------------------------
2018-03-02 0 1 1
so if I put all results together they would look like this.
Date || Team 1 || Team 2 || Total
-------------------------------------------
2018-02-28 0 0 0
2018-03-01 1 0 1
2018-03-02 0 1 1
2018-03-03 0 1 1
Thank you very much in advance :)
Try this:
DDL:
Here I define table variables and insert data you provided:
declare #users table (id int, name varchar(10), teamid int)
insert into #users values (1, 'user1', 1),(2, 'user2',2)
declare #application table (id int, name varchar(15))
insert into #application values (1, 'application1')
declare #applicationuser table (id int, applicationid int, userid int, assigneddate date, unassigneddate date)
insert into #applicationuser values (1,1,1,'2018-03-01','2018-03-02'),(2,1,2,'2018-03-02','2018-03-03')
Query:
--here I sum values for each team using cumulative sum
select [date],
sum(team1) over (order by [date] rows between unbounded preceding and current row) [team1],
sum(team2) over (order by [date] rows between unbounded preceding and current row) [team2],
sum(total) over (order by [date] rows between unbounded preceding and current row) [total]
from (
--here I am pivoting inner query, replacing NULL values using COALESCE
select [date],
coalesce(max(case when teamid = 1 then value end), 0) [team1],
coalesce(max(case when teamid = 2 then value end), 0) [team2],
coalesce(max(case when teamid = 1 then value end), 0) + coalesce(max(case when teamid = 2 then value end), 0) [total]
from (
--here I join assigned and unassigned dates with team ids
select [AU].assigneddate [date], [U].teamid, 1 [value] from #applicationuser [AU]
join #users [U] on [AU].userid = [U].id
union all
select [AU].unassigneddate, [U].teamid, -1 from #applicationuser [AU]
join #users [U] on [AU].userid = [U].id
) a
group by [date]
) a
In order to understand this better is to try every query separatly, i.e. execute most inner query first, then wrap it with outer query and see the results. This way you'll know what's happening at each step.
These kind of queries are best dealt with using a dates table. If you don't have one in your database already I strongly suggest you get one.
In the meanwhile, you can create a dates table on the fly using either an inline table valued function or a derived table within your query (Note I have added an additional open application):
-- Declare test data
declare #users table (id int, name varchar(10), teamid int);
declare #application table (id int, name varchar(15));
declare #applicationuser table (id int, applicationid int, userid int, assigneddate date, unassigneddate date);
insert into #users values (1, 'user1', 1),(2, 'user2',2);
insert into #application values (1, 'application1'),(2, 'application1');
insert into #applicationuser values (1,1,1,'2018-03-01','2018-03-02'),(2,1,2,'2018-03-02','2018-03-03'),(2,2,2,'2018-03-02','2018-03-05');
-- Find the maximum date range possible to create a dates table that covers all possible application periods
declare #MinDate date = (select min(AssignedDate) from #applicationuser);
declare #MaxDate date = (select max(UnassignedDate) from #applicationuser);
-- This is a derived table that simply returns 10 rows
with t(t) as(select * from(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(t))
-- This then CROSS JOINs the 10 rows to create 10*10*10*10*10 = 100,000 rows
-- Then uses the ROW_NUMBER function to add a number of days to the #MinDate value, to get a table of incrementing dates
,d(d) as(select top(datediff(day,#MinDate,#MaxDate)+1) dateadd(day,row_number() over (order by (select null))-1,#MinDate) from t,t t2,t t3,t t4,t t5)
select d.d -- Output is a row per date and teamid where there was an open application.
,u.teamid -- When grouped together this gives you a COUNT of open applications by teamid
,count(1) as OpenApplications
from d
join #ApplicationUser as au
on d.d between au.assigneddate and au.unassigneddate
join #users as u
on au.userid = u.id
group by d.d
,u.teamid
order by d.d
,u.teamid
Output:
+------------+--------+------------------+
| d | teamid | OpenApplications |
+------------+--------+------------------+
| 2018-03-01 | 1 | 1 |
| 2018-03-02 | 1 | 1 |
| 2018-03-02 | 2 | 2 |
| 2018-03-03 | 2 | 2 |
| 2018-03-04 | 2 | 1 |
| 2018-03-05 | 2 | 1 |
+------------+--------+------------------+
I have purposefully not pivoted your data to match your desired output as this is almost always a bad idea. As your number of teams change, your number and names of columns outputted will change, which will require constant maintenance. Instead, you should just output a normal set of data and leave the pivoting to the presentation layer once it eventually gets there.
is this use full:
DECLARE #users TABLE (id INT, name VARCHAR(10), teamid INT)
INSERT INTO #users VALUES (1, 'user1', 1),(2, 'user2',2)
DECLARE #application TABLE (id INT, name VARCHAR(15))
INSERT INTO #application VALUES (1, 'application1')
DECLARE #applicationuser TABLE (id INT, applicationid INT, userid INT, assigneddate DATE, unassigneddate DATE)
INSERT INTO #applicationuser VALUES (1,1,1,'2018-03-01','2018-03-02'),(2,1,2,'2018-03-02','2018-03-03')
DECLARE #assignment TABLE([date] DATE,teamid INT,[value] INT)
INSERT INTO #assignment
SELECT [AU].assigneddate [date], [U].teamid, 1 [value]
FROM #applicationuser [AU]
JOIN #users [U] ON [AU].userid = [U].id
INSERT INTO #assignment
SELECT [AU].unassigneddate, [U].teamid,
CASE WHEN LEAD(AssignedDate) OVER(ORDER BY [AU].id)=unassigneddate
THEN -1
ELSE
1
END
FROM #applicationuser [AU]
JOIN #users [U] ON [AU].userid = [U].id
SELECT b.[date],
CASE WHEN t1.teamid=1
THEN 1
ELSE
0
END AS Team1,
CASE WHEN t1.teamid=2
THEN 1
ELSE
0
END AS Team2,
total
FROM
(
SELECT [date], MAX([value]) AS Total
FROM #assignment
group by [date]
)b
INNER JOIN #assignment t1 ON b.Total=t1.Value AND b.[date]=t1.[date]
ORDER BY b.[date]
Thank you very much for all your answers.
I have been trying this myself as well and I seem to have found the answer to my question.
DECLARE #DATE DATETIME
SELECT #DATE = '20180301'
SELECT ApplicationId, Max(UnassignedDate) as UnassignedDate
INTO #TempApplicationUsers
FROM ApplicationUsers
WHERE #DATE > DAteAdd(Day, -1, AssignedDate) and (#DATE < UnassignedDate OR
UnassignedDate is NULL)
Group BY ApplicationId
SELECT * From #TempApplicationUsers
SELECT UserId, COUNT(*) FROM ApplicationUsers, #TempApplicationUsers
WHERE ApplicationUsers.ApplicationId = #TempApplicationUsers.ApplicationId
and ISNULL(ApplicationUsers.UnassignedDate, 0) =
ISNULL(#TempApplicationUsers.UnassignedDate, 0)
GROUP BY UserId
DROP TABLE #TempApplicationUsers
This returns the count for each user on that day and from this I can get the count for each team by the TeamId in the users table.
Thank you again

Update each row of one table with multiple row data from other table using join condition in sql server 2012

DECLARE #outerCounter INT = 1, #rowCount INT,
#query NVARCHAR(MAX), #load numeric(18,3), #batcolno NVARCHAR(MAX), #inCounter INT = 1,#innerrowsCount INT
SELECT #rowCount = COUNT(*) FROM tempA
WHILE(#outercounter <= #rowCount)
BEGIN
SELECT #innerrowsCount = COUNT(*)
FROM
tempB INNER JOIN tempA
ON tempB.batteryid = tempA.batteryid
AND
tempB.uniquerowid = #outercounter
WHILE(#inCounter <= #innnerrowCounter)
BEGIN
SELECT #laod = laod FROM tempB INNER JOIN tempA
ON tempB.batteryid = tempA.batteryid
AND
tempB.uniquerowid = #inCounter
SET #testcolno = 'batt' + REPLACE(STR(#inCounter,3),' ','0')
SET #query = ' UPDATE tempA
SET '+ #testcolno + ' = '+ #load +'
WHERE tempA.rowid = '+ #outercounter + ' '
EXEC(#query)
SET #inCounter = #inCounter + 1
END
SET #outercounter = #outercounter + 1
END
I have 2 tables tempA and tempB.
tempA:
batteryid | batname | batt01 | batt02 | batt03 | batt04
----------+---------+---------+--------+---------+--------
01 | trixon | null | null | null | null
03 | jaguarv | null | null | null | null
tempB:
batteryid | load
-----------+---------
01 | 14.58
01 | 58.12
01 | 16.89
03 | 25.47
03 | 87.65
Final output in tempA should be like this:
batteryid | batname | batt01 | batt02 | batt03 | batt04
----------|----------|----------|--------|---------|--------------
01 | trixon | 14.58 | 58.12 | 16.89 | null
03 | jaguarv | 25.47 | 87.65 | null | null
Above code uses while loop to update the tempA table by joining batteryid with that of tempB table.
Thanks
You can get away with using a PIVOT in SQL Server, and you'll be able to do away with the loop and update statements.
SELECT SourceA.[batteryid],
SourceA.[batname],
SourcePivot.[1] AS 'Battery1',
SourcePivot.[2] AS 'Battery2',
SourcePivot.[3] AS 'Battery3',
SourcePivot.[4] AS 'Battery4'
FROM #tempA AS SourceA
INNER JOIN (
SELECT *
FROM
(
SELECT batteryId,
loadval,
row_number() OVER (PARTITION BY batteryid ORDER BY loadval) rn
FROM #tempB
) s
PIVOT (MIN(loadval) FOR rn IN ([1], [2], [3], [4])) pvt
) AS SourcePivot ON SourcePivot.batteryid = SourceA.batteryid
Here is the code that I used to test the query
DECLARE #tempA TABLE (
batteryid VARCHAR(2),
batname VARCHAR(50),
batt01 DECIMAL(18, 2),
batt02 DECIMAL(18, 2),
batt03 DECIMAL(18, 2),
batt04 DECIMAL(18, 2)
)
DECLARE #tempB TABLE (
batteryid VARCHAR(2),
loadval DECIMAL(18, 2)
)
INSERT INTO #tempA (batteryid, batname) VALUES ('01', 'trixon')
INSERT INTO #tempA (batteryid, batname) VALUES ('03', 'jaguarv')
INSERT INTO #tempB VALUES ('01', '14.58')
INSERT INTO #tempB VALUES ('01', '58.12')
INSERT INTO #tempB VALUES ('01', '16.89')
INSERT INTO #tempB VALUES ('03', '25.47')
INSERT INTO #tempB VALUES ('03', '87.65')