T-SQL data transformation - sql-server

I have a result set like this:
ID Value
1 100
2 50
3 200
4 30
- -
- -
I want it to transform into following:
Value1 Value2
100 50
200 30
- -
- -
How to do it with T-SQL?

Use this:
select a.Value, b.Value
from
(
select row_number() over(order by ID) [rn], Value
from #t
)a
left join
(
select row_number() over(order by ID) [rn], Value
from #t
)b on b.rn = a.rn + 1
where a.rn % 2 = 1
Sample data:
declare #t table (ID int, Value int)
insert #t values (1,100), (2,50), (3,200), (4,30)
Output:
Value Value
----------- -----------
100 50
200 30

declare #t table (id int, v int)
insert #t values (1, 10)
insert #t values (2, 20)
insert #t values (3, 30)
insert #t values (4, 40)
insert #t values (5, 50)
select t1.v, t2.v
from #t t1
left join #t t2
on t1.id + 1 = t2.id
where t1.id %2 = 1

Related

SQL concat integers and group them with from to

I'm new to stackoverflow, but I'm stuck with my query.
I've got a SQL table whitch looks like this:
+-------+------------+
| col1 | col2 |
+-------+------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 1 | 6 |
+-------+------------+
I don't know how to get the following resultset:
+-------+------------+
| col1 |SerialNumber|
+-------|------------+
| 1 | 1 to 4, 6 |
+--------------------+
With XML Path i can get this:
+-------+------------+
| col1 |SerialNumber|
+-------|------------+
| 1 | 1,2,3,4,6, |
+--------------------+
This is my query for it:
SELECT DISTINCT O.Col1,
(SELECT CAST(P.Col2 As varchar(5)) + ',' AS [text()]
FROM #Test P
WHERE P.Col1 = O.Col1
ORDER BY P.Col1
FOR XML PATH('')) AS 'SerialNumber'
FROM #Test O
I'm sorry if my question was already asked. I'm also lacking Keywords for this topic.
Test data:
CREATE TABLE t(col1 int,col2 int)
INSERT t(col1,col2)VALUES
(1,1),(1,2),(1,3),(1,4),
(1,6),(1,7),(1,8),(1,9),
(1,11),
(1,13),
(2,3),(2,4),(2,5),
(2,7)
A variant with FOR XML PATH:
SELECT col1,col2,outVal
INTO #temp
FROM
(
SELECT
col1,
col2,
outVal,
ISNULL(LEAD(outVal)OVER(PARTITION BY col1 ORDER BY col2),'') nextOutVal
FROM
(
SELECT
col1,
col2,
CASE
WHEN col2-1=LAG(col2)OVER(PARTITION BY col1 ORDER BY col2) AND col2+1=LEAD(col2)OVER(PARTITION BY col1 ORDER BY col2)
THEN 'to'
ELSE CAST(col2 AS varchar(10))
END outVal
FROM t
) q
) q
WHERE outVal<>nextOutVal
ORDER BY col1,col2
SELECT
t1.col1,
REPLACE(STUFF(
(
SELECT ','+t2.outVal
FROM #temp t2
WHERE t2.col1=t1.col1
ORDER BY t2.col2
FOR XML PATH('')
),1,1,''),',to,',' to ') SerialNumber
FROM (SELECT DISTINCT col1 FROM #temp) t1
DROP TABLE #temp
A variant for SQL Server 2017 (with STRING_AGG):
SELECT
col1,
REPLACE(STRING_AGG(outVal,',')WITHIN GROUP(ORDER BY col2),',to,',' to ')
FROM
(
SELECT
col1,
col2,
outVal,
ISNULL(LEAD(outVal)OVER(PARTITION BY col1 ORDER BY col2),'') nextOutVal
FROM
(
SELECT
col1,
col2,
CASE
WHEN col2-1=LAG(col2)OVER(PARTITION BY col1 ORDER BY col2) AND col2+1=LEAD(col2)OVER(PARTITION BY col1 ORDER BY col2)
THEN 'to'
ELSE CAST(col2 AS varchar(10))
END outVal
FROM t
) q
) q
WHERE outVal<>nextOutVal
GROUP BY col1
Result:
col1 SerialNumber
1 1 to 4,6 to 9,11,13
2 3 to 5,7
Solution:
Another possible approach using CTE for start and end values for each sequence and group concatenation:
T-SQL:
-- Table creation
CREATE TABLE #ValuesTable (
Col1 int,
Col2 int
)
INSERT INTO #ValuesTable VALUES (1, 1)
INSERT INTO #ValuesTable VALUES (1, 2)
INSERT INTO #ValuesTable VALUES (1, 3)
INSERT INTO #ValuesTable VALUES (1, 4)
INSERT INTO #ValuesTable VALUES (1, 6)
INSERT INTO #ValuesTable VALUES (2, 1)
INSERT INTO #ValuesTable VALUES (2, 2)
INSERT INTO #ValuesTable VALUES (2, 3)
INSERT INTO #ValuesTable VALUES (2, 4)
INSERT INTO #ValuesTable VALUES (2, 6)
INSERT INTO #ValuesTable VALUES (2, 7);
INSERT INTO #ValuesTable VALUES (2, 10);
-- Find sequences
WITH
TableStart AS (
SELECT t.Col1, t.Col2, ROW_NUMBER() OVER (ORDER BY t.Col1, t.Col2) AS RN
FROM #ValuesTable t
LEFT JOIN #ValuesTable b ON (t.Col1 = b.Col1) AND (t.Col2 = b.Col2 + 1)
WHERE (b.Col2 IS NULL)
),
TableEnd AS (
SELECT t.Col1, t.Col2, ROW_NUMBER() OVER (ORDER BY t.Col1, t.Col2) AS RN
FROM #ValuesTable t
LEFT JOIN #ValuesTable b ON (t.Col1 = b.Col1) AND (t.Col2 = b.Col2 - 1)
WHERE (b.Col2 IS NULL)
),
TableSequences AS (
SELECT
TableStart.Col1 AS Col1,
CASE
WHEN (TableStart.Col2 <> TableEnd.Col2) THEN CONVERT(nvarchar(max), TableStart.Col2) + N' to ' + CONVERT(nvarchar(max), TableEnd.Col2)
ELSE CONVERT(nvarchar(max), TableStart.Col2)
END AS Sequence
FROM TableStart
LEFT JOIN TableEnd ON (TableStart.RN = TableEnd.RN)
)
-- Select with group concatenation
SELECT
t1.Col1,
(
SELECT t2.Sequence + N', '
FROM TableSequences t2
WHERE t2.Col1 = t1.Col1
ORDER BY t2.Col1
FOR XML PATH('')
) SerialNumber
FROM (SELECT DISTINCT Col1 FROM TableSequences) t1
Output:
Col1 SerialNumber
1 1 to 4, 6,
2 1 to 4, 6 to 7, 10,
Notes:
Tested on SQL Server 2005, 2012, 2016.

Recursive CTE trouble

I'm having some trouble coming up with the CTE way of doing a task. I already have a loop method and it is fast enough, but I wanted to do it in a proper way to better learn and understand CTE usage.
SQL:
DECLARE #Income MONEY=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor DECIMAL(6,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
WITH GradientCTE ([Year], Active, UpperLimit, Factor,[Income],WeightedValue,[Row])
AS
(
SELECT [Year], Active, UpperLimit, Factor
,#Income AS [Income]
,CAST(0.0 AS DECIMAL(16,3))AS WeightedValue
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
SELECT *
FROM GradientCTE
ORDER BY UpperLimit
TLDR version; current output:
Year Active UpperLimit Factor Income WeightedValue Row
2018 0 5000.000 1.000 125000.000 0.000 1
2018 0 100000.000 0.850 125000.000 0.000 2
2018 0 500000.000 0.800 125000.000 0.000 3
2018 0 999999999.000 0.750 125000.000 0.000 4
What I would like:
Year Active UpperLimit Factor Income WeightedValue Row
2018 0 5000.000 1.000 125000.000 5000.000 1
2018 0 100000.000 0.850 120000.000 85000.000 2
2018 0 500000.000 0.800 20000.000 16000.000 3
2018 0 999999999.000 0.750 0.000 0.000 4
Explained:
Currently, the looping logic goes over the set row by row and reduces #Income by the UpperLimit for each row until no money is left. It uses that to multiply that amount by the Factor to get a weighted amount. So, in the example provided, the starting income is 125,000.00. The first 5000 are at full weight (1.00), so we reduce the income by 5000 and move the the next row saving the summed weighted value. This is done until income is 0. So, 125,000 should come out to (5000 * 1.0) + (100000 * 0.85) + (20000 * 0.80) + (0.00 * 0.75) or 106,000 total if summed.
Thanks to Ross Bush's answer, it led me down the right track to solve the problem. From a maintenance standpoint, I think a looping pattern is easier to understand, so I likely won't implement the CTE version and performance isn't an issue as the data set is tiny.
DECLARE #Income DECIMAL(18,3)=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE
AS
(
SELECT DISTINCT
[YEAR],Active,UpperLimit=0.00, Factor = 0.00, [Row] = 0
FROM #T
UNION ALL
SELECT [Year],Active,UpperLimit, Factor
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
,Reduce AS (
SELECT
[YEAR],Active,CAST(#Income AS DECIMAL(18,3)) AS [RemainingIncome],
Row,
Factor
,UpperLimit
,CAST(0.00 AS DECIMAL(18,3)) AS WeightedValue
FROM GradientCTE
WHERE UpperLimit=0
UNION ALL
SELECT
g.[YEAR],g.Active,CASE WHEN CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST([RemainingIncome] - G.UpperLimit AS DECIMAL(18,3)) END AS [RemainingIncome],
G.Row,
g.Factor
,g.UpperLimit
,CAST(CASE WHEN [RemainingIncome]>G.UpperLimit THEN G.UpperLimit * G.Factor ELSE R.[RemainingIncome] * G.Factor END AS DECIMAL(18,3)) AS WeightedValue
FROM GradientCTE G
INNER JOIN Reduce R ON R.Row = G.Row -1
AND g.Year=r.Year
AND g.Active=r.Active
)
SELECT
*
-- [Year],Active,SUM(WeightedValue)
FROM Reduce
WHERE [RemainingIncome] >= 0
--GROUP BY [Year],Active
You can reduce the results within another CTE, recursively. I added a UNION with 0 to in the first set to produce the first line showing the starting income.
DECLARE #Income DECIMAL(18,3)=125000.00,
#Active INT=0,
#Year CHAR(4)='2018'
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit DECIMAL(18,3),Factor DECIMAL(18,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT '2018',0,999999999.0,0.75;
;WITH GradientCTE
AS
(
SELECT ReduceAmount = 0, UpperLimit=0.00, Factor = 0.00, Row = 0
UNION ALL
SELECT ReduceAmount = UpperLimit * Factor, UpperLimit, Factor
,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY UpperLimit ASC) AS [Row]
From #T
)
,Reduce AS (
SELECT
Income = CAST(#Income AS DECIMAL(18,3)),
Row,
ReduceAmount
FROM GradientCTE
WHERE ReduceAmount=0
UNION ALL
SELECT
Income = CASE WHEN CAST(Income - G.ReduceAmount AS DECIMAL(18,3)) < 0 THEN 0 ELSE CAST(Income - G.ReduceAmount AS DECIMAL(18,3)) END,
G.Row,
G.ReduceAmount
FROM GradientCTE G
INNER JOIN Reduce R ON R.Row = G.Row -1
)
SELECT * FROM Reduce
WHERE
Income >= 0
DECLARE #Income MONEY=125000.00,
#Active INT=0,
#Year CHAR(4)='2018',
#vIncome Money = 0
DECLARE #T TABLE ([Year] CHAR(4),Active INT,UpperLimit MONEY,Factor
DECIMAL(6,3))
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,5000.0,1.00;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,100000.0,0.85;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,500000.0,0.80;
INSERT INTO #T ([Year],Active,UpperLimit,Factor) SELECT
'2018',0,999999999.0,0.75;
Select * ,ROW_NUMBER() OVER(PARTITION BY [Year],Active ORDER BY
UpperLimit ASC) AS [Row],CAST(0.0 AS DECIMAL(16,3))AS Income,CAST(0.0 AS
DECIMAL(16,3))AS WeightedValue
into #tmp from #T t1
--Select (t2.UpperLimit * t2.Factor),t2.*,t1.Row as prev,t1.UpperLimit
--from #tmp t1
--inner join #tmp t2 on (t1.Row = t2.Row +1)
update t2
set
#vIncome = #Income,
#Income = case when (#Income > t2.UpperLimit) then
#Income - t2.UpperLimit
else
0
end,
t2.Income = #Income,
t2.WeightedValue = case when (#vIncome > t2.UpperLimit) then
(t2.UpperLimit * t2.Factor)
else
#vIncome *t2.Factor
end
from #tmp t1
inner join #tmp t2 on (t1.Row = t2.Row +1)
Select * from #tmp
drop table #tmp

SQL group by xml path columns

Tables:
Table 1(unique values):
id | NameDep
15 X
16 Y
Table 2:
id id_department id_resource
1 15 45
2 15 47
3 16 99
....
Table 3(about 400records):
id_resource resourceName
45 name1
47 name2
99 name3
Table 4(contains multiple columns, but we only need for joining this one):
id_shift
185281
185282
185283
185284
Table 5(shiftName = table3.resourceName):
id_shift shiftName id_department
185281 name1 15
185282 name1 15
185283 name2 15
185284 name2 15
My current output is:
NameDep id resourceName shifts
X 15 name1 185281,185282,185283,185284
X 15 name2 185281,185282,185283,185284
X 15 name3 185281,185282,185283,185284
X 15 name4 185281,185282,185283,185284
X 15 name5 185281,185282,185283,185284
...etc
So basically, in resourceName i have all the data from table3
My goal is to group the shifts with the resourceName.
In my exemple 182581 and 182582 belong to resourceName = name1.
182583 and 182584 to resourceName = name2
So, I would like my output to be like this.
Desired output:
NameDep id resourceName shifts
X 15 name1 185281,185282
X 15 name2 185283,185284
...so on
I've used a temporary table, because I've read it's the only way to have a join between tables and results in xml path as I need. I can't figure out how to change the join / or use the XML PATH in order to have my desired output.
My current SQL IS:
select NameDep as region,d.id,r.resourceName
into #deps from table1 d
join table2 rd on rd.id_department=d.id
join table3 r on r.id_resource=rd.id_resource
where NameDep = 'x';
select SUBSTRING( ( select (',' + CONVERT(VARCHAR(MAX),table5.id_shift))
from table4
join table5 on table5.id_shift =table4 .id_shift
join #deps on #deps.id_department=table5.id_department
and #deps.resourceName=table5.shiftName
FOR XML PATH( '' ) ) , 2, 100000) as trip3 into #Planned
select * from (
select *
from
#deps,#Planned
) t
drop table #Planned,#deps
Thank you
You can use STUFF without using a subquery. STUFF is usually a good place to start when you're trying to compile a delimited list. Also, you don't need table 4.
In this case, you can use ',' + shift FOR XML PATH('') to create a list of comma-delimited shifts grouped by department and resource:
,185281,185282
And then you can use STUFF to chop off the first character and replace it with an empty string, leaving you with:
185281,185282
declare #t1 table (id int, namedep varchar(1))
insert into #t1 values (15, 'X')
insert into #t1 values (16, 'Y')
declare #t2 table (id int, id_department int, id_resource int)
insert into #t2 values (1, 15, 45)
insert into #t2 values (1, 15, 47)
insert into #t2 values (1, 16, 99)
declare #t3 table (id_resource int, resourceName varchar(10))
insert into #t3 values (45, 'name1')
insert into #t3 values (47, 'name2')
insert into #t3 values (99, 'name3')
declare #t5 table (id_shift int, shiftName varchar(10), id_department int)
insert into #t5 values (185281, 'name1', 15)
insert into #t5 values (185282, 'name1', 15)
insert into #t5 values (185283, 'name2', 15)
insert into #t5 values (185284, 'name2', 15)
select t1.namedep, t1.id, t3.resourceName,
stuff(
(select ',' + cast(id_shift as varchar(10))
from #t5
where shiftName = t3.resourceName and id_department = t1.id
for xml path('')), 1, 1, '') as shifts
from #t1 t1
inner join #t2 t2 on t1.id = t2.id_department
inner join #t3 t3 on t2.id_resource = t3.id_resource
Output:
namedep id resourceName shifts
X 15 name1 185281,185282
X 15 name2 185283,185284
...etc

Retrieve rows when values of one column changes at least once for another column value

I have a table like below in SQL Server:
DECLARE #tbl TABLE (val1 VARCHAR(10), val2 VARCHAR(10) )
INSERT INTO #tbl
VALUES ('x', 'a'), ('x', 'a'), ('p', 'b'), ('y', 'a'), ('p', 'b');
val1 | val2
------+-------
x | a
x | a
p | b
y | a
p | b
The result should be rows of a (of val2) only as value of a in val1 changes to y at least once. But the rows of val2.b should be ignored in the result as its value in val1 does not change.
val1 | val2
------+------
x | a
x | a
y | a
Try this,
select t.*
from #tbl t
INNER JOIN ( select t2.val2
from #tbl t2
group by t2.val2
having min(t2.val1) <> max(t2.val1)
) AS tt ON tt.val2 = t.val2
I have finally end up using like below.
SELECT t.*
FROM #tbl t
WHERE EXISTS (
SELECT t2.val2
FROM #tbl t2
WHERE t.val2 = t2.val2
GROUP BY t2.val2
HAVING min(t2.val1) <> max(t2.val1)
)
SELECT
*
FROM #tbl
WHERE val2 IN
(
SELECT
Z.val2
FROM
(
SELECT
*,ROW_NUMBER() OVER(Partition BY val2 Order BY Val2) AS PartNo
FROM
(
SELECT
*
FROM #tbl
GROUP BY val1,val2
)X
)Z WHERE Z.PartNo>1
)
Does this do what you want?
select t.*
from #tbl t
where t.val2 = (select t2.val2
from #tbl t2
group by t2.val2
having min(t2.val1) <> max(t2.val1)
);

Select Minimum difference of summary of two columns in T-SQL

I need help building a query, which returns the Minimum difference between Value + another Value from same table and the other ID that gave the result (plus the sum can't be sum of the value itself)
Table:
ID Value
1 1
2 2
3 5
4 -10
5 -5
6 3
7 -15
Expected result:
ID Value MinDif IDofTheOtherValue
1 1 3 2 <-- MinDif = 1 + 2 (ID 1 + ID 2)
2 2 3 1 <-- MinDif = 2 + 1 (ID 2 + ID 1)
3 5 0 5 <-- MinDif = 5 + -5 (ID 3 + ID 5)
4 -10 -5 3 <-- MinDif = -10 + 5 (ID 4 + ID 3)
5 -5 0 3 <-- MinDif = -5 + 5 (ID 5 + ID 3)
6 3 -2 5 <-- MinDif = 3 + -5 (ID 6 + ID 5)
7 -15 -10 3 <-- MinDif = -15 + 5 (ID 7 + ID 3)
Here's a query to create the table:
DECLARE #myTable TABLE(ID int, Value int)
INSERT INTO #myTable VALUES (1, 1), (2,2), (3, 5), (4, -10), (5, -5), (6, 3), (7, -15)
And here's what I have tried, but this gives an SQL error (Cannot perform an aggregate function on an expression containing an aggregate or a subquery.)
SELECT m.ID, MIN(ABS(m.Value + (SELECT m2.Value FROM #myTable m2)))
FROM #myTable m
This is giving your required results:
with diffRank as
(
select ID = t1.ID
, minDif = t1.value + t2.value
, IDofTheOtherValue = t2.ID
, diffRank = row_number() over (partition by t1.ID order by abs(t1.value + t2.value), t2.ID)
from #myTable t1
inner join #myTable t2 on t1.ID <> t2.ID
)
select ID
, minDif
, IDofTheOtherValue
from diffRank
where diffRank = 1
order by ID;
SQL Fiddle with demo.
I resolved this by myself. Here's the Select clause:
SELECT tab.ID, tab.Value, test.*
FROM #myTable tab
OUTER APPLY
(SELECT TOP 1 ID AS [AnotherID], [SUM]
FROM
(
SELECT m.ID, m2.ID AS [ID2], m.Value + m2.Value AS [SUM]
FROM #myTable m
JOIN #myTable m2 ON m2.ID <> m.ID
) apu WHERE ID2 = tab.ID ORDER BY ABS([SUM])) test
In Oracle I would do:
select x.id, (select min(abs(x.value + y.value)) from my_table y),
(select first value (y.id) over (order by abs(x.value + y.value))
from my_table y)
from my_table x
think of something similar in TSQL
Try this.. it should work.
DECLARE #myTable TABLE(ID int, Value int)
INSERT INTO #myTable VALUES (1, 1), (2,2), (3, 5), (4, -10), (5, -5), (6, 3), (7, -15)
SELECT C.ID, C.Value
, C.Value + (SELECT TOP 1 E.Value FROM #myTable E WHERE C.AbsMinDif = ABS(C.Value + E.Value) ORDER BY E.ID) MinDif
, (SELECT TOP 1 F.ID FROM #myTable F WHERE C.AbsMinDif = ABS(C.Value + F.Value) ORDER BY F.ID) IDofTheOtherValue
FROM (
SELECT A.ID, MIN(A.Value) Value, MIN(ABS(A.Value + B.Value)) AbsMinDif
FROM #myTable A
CROSS JOIN #myTable B
WHERE A.ID <> B.ID
GROUP BY A.ID
) C

Resources