Pair Row data into column data - sql-server

I have a query that returns data in the form
[SetID],[COLID],[DataValue],[Order].
1,1,'Value1',1
1,1,'Value2',2
1,1,'Value3',3
1,2,'value4',1
1,2,'Value5',2
1,2,'Value6',3
How to make it return the data like
[SetID],'Value1','Value4',1
[SetID],'Value2','Value5',2
[SetID],'Value3','Value6',3
So that the data tagged with each columnid is back into a column.
The answer appears to be some sort of pivot, but I'm not looking for a straight conversion, or a one-row answer.

To be able to put each data value into a separate column, you need to know the number of DataValues and the ColId of each. You can't dynamically return variable number of columns (unless you use dynamic SQL to construct the query on the fly), moreover each row needs to have the same number of columns, otherwise the table structure doesn't make sense.
Here's an example that you can quickly test, it uses an in-memory table.
Given the table MyTable:
declare #MyTable table (SetId int, ColId int, DataValue varchar(20), [Order] int);
insert into #MyTable values (1,1,'Value1',1)
insert into #MyTable values (1,1,'Value2',2)
insert into #MyTable values (1,1,'Value3',3)
insert into #MyTable values (1,2,'Value4',1)
insert into #MyTable values (1,2,'Value5',2)
insert into #MyTable values (1,2,'Value6',3)
This query will return the values in separate columns:
select mt.SetId,
( select top 1 m1.DataValue from #MyTable m1
where m1.SetId = mt.SetId and
m1.[Order] = mt.[Order] and
m1.ColId = 1 -- hard-coded ColId, you need to know its value
) 'val1',
( select top 1 m1.DataValue from #MyTable m1
where m1.SetId = mt.SetId and
m1.[Order] = mt.[Order] and
m1.ColId = 2 -- same as above
) 'val2', mt.[Order]
from #MyTable mt
group by mt.SetId, mt.[Order]
Result:
+-----------------------------------+
| SetId | Val1 | Val2 | Order |
|-------+---------+--------+--------|
| 1 | Value1 | Value4 | 1 |
|-------+---------+--------+--------|
| 1 | Value2 | Value5 | 2 |
|-------+---------+--------+--------|
| 1 | Value3 | Value6 | 3 |
+-----------------------------------+
However, I would recommend stuffing all the data values into one column (separated by a delimiter), because this will work with any number of data values. Here's the query:
select mt.SetId,
stuff(( select ', '+ m2.DataValue
from #MyTable m2
where mt.SetId = m2.SetId and
mt.[Order] = m2.[Order]
FOR XML PATH('')) , 1, 2, '') 'DataValues',
mt.[Order]
from #MyTable mt
group by mt.SetId, mt.[Order]
Result
+----------------------------------+
| SetId | DataValues | Order |
|-------+------------------+-------|
| 1 | Value1, Value4 | 1 |
|-------+------------------+-------|
| 1 | Value2, Value5 | 2 |
|-------+------------------+-------|
| 1 | Value3, Value6 | 3 |
+-------+------------------+-------+

Related

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

Get multiple rows from a subquery SQL

Basically I have 2 Tables, the first with the raw material amount (QT) for each serial number and the second one with how much raw material was spent (Qt_Added) on batch's production. Like this:
Table 1
+----------+------------+-----+
| Code_Raw | Serial_Raw | Qt |
+----------+------------+-----+
| 1 | 1 | 100 |
| 1 | 2 | 150 |
| 2 | 1 | 80 |
| 1 | 3 | 100 |
+----------+------------+-----+
And Table 2
+------------+----------+------------+----------+--+
| Code_Batch | Code_Raw | Serial_Raw | Qt_Added | |
+------------+----------+------------+----------+--+
| 1 | 1 | 1 | 80 | |
| 2 | 1 | 1 | 10 | |
| 3 | 1 | 2 | 150 | |
| 4 | 1 | 3 | 80 | |
+------------+----------+------------+----------+--+
I tried to do a query for a specific Code_Raw, show me how much left for each serial number, But worked only when there's a single serial_raw.
My query:
select *
from
(select
Serial_Raw,
(Select QT From Table_1 where Code_Raw = 1) - Sum(qt_added) as Total_Remaining
from
Table_2
where
Cod_Raw = 1
group by
Serial_Raw) e
where
Total_Remaining > 0
but it throws this error
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression
And I expect :
Serial_Raw Total_Remaining
-------------------------------
1 10
3 20
Is there a struct problem or another way to do this?
I'm using SQL Server 2014
Thanks guys
Try this:
DECLARE #tbl1 TABLE
( CodeRaw INT,
Serial_Raw INT,
Qty INT)
DECLARE #tbl2 TABLE
(
CodeBatch INT,
CodeRaw INT,
Serial_Raw INT,
QtyAdded INT)
INSERT INTO #tbl1 VALUES(1,1,100)
INSERT INTO #tbl1 VALUES(1,2,150)
INSERT INTO #tbl1 VALUES(2,1,80)
INSERT INTO #tbl1 VALUES(1,3,100)
INSERT INTO #tbl2 VALUES(1,1,1,80)
INSERT INTO #tbl2 VALUES(2,1,1,10)
INSERT INTO #tbl2 VALUES(3,1,2,150)
INSERT INTO #tbl2 VALUES(4,1,3,80)
--Inner table has the summary of the Quantity added with columns CodeRaw and SerialRaw. Outer table make join with inner table and just substruct with the Qty and Sum of Qty Added.
SELECT t2.Serial_Raw, t1.Qty - t2.QtyAdded AS Total_Remaining FROM #tbl1 t1
INNER JOIN (SELECT CodeRaw, Serial_Raw , SUM(QtyAdded) QtyAdded FROM #tbl2
GROUP BY CodeRaw, Serial_Raw) AS t2 ON t2.CodeRaw = t1.CodeRaw AND t1.Serial_Raw = t2.Serial_Raw
WHERE t1.Qty - t2.QtyAdded > 0
If I understood you right, this might be what you are after
declare #tbl1 table (CodeRaw INT, Serial_Raw INT, Qty INT)
declare #tbl2 table (CodeBatch INT, CodeRaw INT, Serial_Raw INT, QtyAdded INT)
insert into #tbl1 values (1,1,100), (1,2,150), (2,1,80), (1,3,100)
insert into #tbl2 values (1,1,1,80), (2,1,1,10), (3,1,2,150), (4,1,3,80)
select t2.Serial_Raw,
t3.Qty - sum(t2.QtyAdded) as Total_Remaining
from #tbl2 t2
inner join ( select t1.Serial_Raw,
t1.CodeRaw,
sum(t1.Qty) as Qty
from #tbl1 t1
group by t1.Serial_Raw, t1.CodeRaw
) t3
on t2.Serial_Raw = t3.Serial_Raw
and t2.CodeRaw = t3.CodeRaw
group by t2.Serial_Raw, t3.Qty
So in t2 we get all distinct Serial_Raw values, and sum their QtyAdded from the first table.
In t3 we get all Qty values from the 2nd table.
All we need to do is join them together and subtract
The result of this query is
Serial_Raw Total_Remaining
---------- ---------------
1 10
2 0
3 20

How to generate an External ID based ID's that has negative value on price

I have this Data set
InvoiceID CDamount companyname
1 2500 NASA
1 -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5 -8000 SpaceX
I want to be able to get that as shown below:
External ID CDamount companyname
1 2500 NASA
1-C -2500 NASA
2 1600 Airjet
3 5000 Boeing
4 -600 EXEarth
5 8000 SpaceX
5-C -8000 SpaceX
I cannot use CASE WHEN CDamount < 0 THEN InvoiceID + '-' + 'C' ELSE InvoiceID END AS "External ID" because some of other companies have negative amount as well that do not fall under this category.
I was wondering how can I say IF InvoiceID is Duplicated AND CDAmount is Negative then Create a new External ID?
Is this something possible?
Below you can create the sample data
Create Table #Incident (
InvoiceID int,
CDamount int,
Companyname Nvarchar(255))
insert into #Incident Values (1,2500,'NASA')
insert into #Incident Values (1,-2500,'NASA')
insert into #Incident Values (2,1600,'Airjet')
insert into #Incident Values (3, 5000, 'Boeing')
insert into #Incident Values (4, -600, 'ExEarth')
insert into #Incident Values (5,8000,'SpaceX')
insert into #Incident Values (5, -8000, 'SpaceX')
Here is What I used but as I mentioned since ID number 4 has negative value as well I get "-C" for it which I do not want to.
Select CASE WHEN T1.CDamount < 0
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.Companyname
from #Incident AS T1
So I got this based on my knowledge of SQL and that works for my case.
Not sure if it is an smart way to go with but can be a good start for someone who is struggling with a Scenario like this:
;With CTE1 AS (
SELECT Count(*) AS Duplicate, T1.InvoiceID
From #Incident AS T1
Group by T1.InvoiceID
),
Main AS (
Select CASE WHEN T1.CDamount < 0 AND T2.Duplicate > 1
THEN CAST(T1.InvoiceID AS nvarchar (255)) + '-' + 'C'
ELSE CAST(T1.InvoiceID AS nvarchar (255))
END AS ExternalID,
T1.InvoiceID AS count,
T1.CDamount,
T1.Companyname
from #Incident AS T1
Join CTE1 AS T2 ON T1.InvoiceID = T2.InvoiceID
)
SELECT * FROM Main
Alternative solution without CTE, using ROW_NUMBER() function.
SELECT
CASE WHEN CDAmount < 0 AND RowID > 1
THEN InvoiceID + '-C'
ELSE InvoiceID
END AS ExternalID
, CDAmount
, CompanyName
FROM
(
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
) AS SourceTable
The trick is using ROW_NUMBER() function to generate a sequence which resets when InvoiceID changes. Here's the subquery and its result. Use CASE statement when CDAmount is negative and RowID greater than 1.
SELECT
CAST(InvoiceID AS NVARCHAR(255)) AS InvoiceID
, CDAmount
, CompanyName
, ROW_NUMBER() OVER (PARTITION BY InvoiceID ORDER BY CompanyName) AS RowID
FROM
#Incident
Subquery result:
+-----------+----------+-------------+-------+
| InvoiceID | CDAmount | CompanyName | RowID |
+-----------+----------+-------------+-------+
| 1 | 2500 | NASA | 1 |
| 1 | -2500 | NASA | 2 |
| 2 | 1600 | Airjet | 1 |
| 3 | 5000 | Boeing | 1 |
| 4 | -600 | ExEarth | 1 |
| 5 | 8000 | SpaceX | 1 |
| 5 | -8000 | SpaceX | 2 |
+-----------+----------+-------------+-------+

SQL Server - transpose table and return temporary table with correct format

I have a non-modifiable SQL transaction that returns the following data:
----------------------
| NAME | VALUE |
----------------------
| Amount | ... |
----------------------
| Target | ... |
----------------------
| Date | ... |
----------------------
| Amount | ... |
----------------------
| Target | ... |
----------------------
| Date | ... |
----------------------
e.g:
I want to format it so it looks like:
-----------------------------------------
| Amount | Target | Date |
-----------------------------------------
| ... | ... | ... |
-----------------------------------------
How would I go on accomplishing this?
The code I have is:
SELECT
NAME, VALUE
FROM
function(#data)
/* AS PIVOT TABLE(...)*/
And I want to return a temporary table with the correct format.
Try this just use the select and replace #foo with your source of records, and ignore all of the setup unless you just want to run this specific example.
The grouping is based on position, and you can use floor((row_number() over (order by (select 1)) - 1) / 3) to get a group for every three rows.
create table #foo (
Name varchar(50),
StringValue varchar(50))
insert into #foo values ('Amount', '200')
insert into #foo values ('Target', '66')
insert into #foo values ('Date', '2017-1-1')
insert into #foo values ('Amount', '205')
insert into #foo values ('Target', '67')
insert into #foo values ('Date', '2017-3-1')
select
max(case when Name = 'Amount' then StringValue else null end) as Amount,
max(case when Name = 'Target' then StringValue else null end) as Target,
max(case when Name = 'Date' then StringValue else null end) as Date
from (
select floor((row_number() over (order by (select 1)) - 1) / 3) as group_on, * from #foo
) temp
group by group_on
drop table #foo

SQL Server to combine multi rows into single row where col0=col1

my table :
previousid|CurrentID|Data
| 1 | 2 | Data 1
| 2 | 3 | Data 2
| 3 | 4 | Data 3
| 4 | 5 | Data 4
Result i look for :
Select .... where PreviousID=1 :
|Col0|Col1|Col2 |Col3|Col 4|Col5| Col6 | Col7 | Col8
|1 |2 |Data 1|3 |Data 2| 4 | data 3| 5 | data 4
Select .....where PreviousID=2
|Col0|Col1|Col2 |Col3|Col 4|Col5| Col6 |
|2 |3 |Data 2|4 |Data 3| 5 | data 4|
i tried to create some SQL server query to get result with no luck, please help me guys
We can do this in a few steps:
Declare and set a variable to use for our root node, and create a temporary table to store the results from our recusive query:
Insert the results from the recusive query into the temporary table
Generate and execute dynamic sql to pivot() the temporary table.
(alternate) Generate and execute dynamic sql to use conditional aggregation instead of pivot():
rextester demo: http://rextester.com/MRFZC75180
test setup:
create table t (PreviousID int, CurrentID int, Data varchar(32));
insert into t values
(1,2,'Data 1'),(2,3,'Data 2'),(3,4,'Data 3'),(4,5,'Data 4');
Declare and set a variable to use for our root node, and create a temporary table to store the results from our recusive query:
declare #PreviousId int = 2;
create table #temp (PreviousID int
, Level int
, Col varchar(32)
, Value varchar(32)
, rn int
);
Insert the results from the recusive query into the temporary table
;with cte as (
select PreviousID, CurrentID, Data, level = 0
from t
where previousId = #PreviousId
union all
select c.PreviousID, c.CurrentID, c.Data, level = p.level +1
from t c
inner join cte as p
on c.PreviousID = p.CurrentID
)
insert into #temp
select p.PreviousId, t.level, x.col, x.value
, rn = row_number() over (order by t.level, x.col)
from cte t
cross apply (
select top 1
PreviousId
from cte i
order by level
) as p (PreviousId)
cross apply (
values ('CurrentId',convert(varchar(32),CurrentId)),('Data',Data)
) as x (col,value);
results so far:
+------------+-------+-----------+--------+----+
| PreviousID | Level | Col | Value | rn |
+------------+-------+-----------+--------+----+
| 2 | 0 | CurrentId | 3 | 1 |
| 2 | 0 | Data | Data 2 | 2 |
| 2 | 1 | CurrentId | 4 | 3 |
| 2 | 1 | Data | Data 3 | 4 |
| 2 | 2 | CurrentId | 5 | 5 |
| 2 | 2 | Data | Data 4 | 6 |
+------------+-------+-----------+--------+----+
Generate and execute dynamic sql to pivot() the temporary table.
/* pivot */
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select
', Col'+convert(nvarchar(10),rn)
from #temp
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql ='
select Col0=PreviousID, ' + #cols +'
from (
select PreviousID, Value, rn= ''Col''+convert(nvarchar(10),rn)
from #temp
) as t
pivot (max([Value]) for [rn] in (' + #cols +')) p'
select #sql as CodeGenerated;
exec sp_executesql #sql;
code generated:
select Col0=PreviousID, Col1, Col2, Col3, Col4, Col5, Col6
from (
select PreviousID, Value, rn= 'Col'+convert(nvarchar(10),rn)
from #temp
) as t
pivot (max([Value]) for [rn] in ( Col1, Col2, Col3, Col4, Col5, Col6)) p
returns:
+------+------+--------+------+--------+------+--------+
| Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+------+------+--------+------+--------+------+--------+
| 2 | 3 | Data 2 | 4 | Data 3 | 5 | Data 4 |
+------+------+--------+------+--------+------+--------+
(alternate) Generate and execute dynamic sql to use conditional aggregation instead of pivot():
/* conditional aggregation */
--declare #cols nvarchar(max);
--declare #sql nvarchar(max);
select #cols = stuff((
select
char(10)+' , '
+ 'Col'+convert(nvarchar(10),rn)
+' = max(case when rn = '+convert(nvarchar(10),rn)+' then Value end)'
from #temp
order by 1
for xml path (''), type).value('.','nvarchar(max)')
,1,0,'')
select #sql ='
select Col0 = PreviousID'+#cols+'
from #temp
group by PreviousID'
select #sql as CodeGenerated;
exec sp_executesql #sql;
code generated:
select Col0 = PreviousID
, Col1 = max(case when rn = 1 then Value end)
, Col2 = max(case when rn = 2 then Value end)
, Col3 = max(case when rn = 3 then Value end)
, Col4 = max(case when rn = 4 then Value end)
, Col5 = max(case when rn = 5 then Value end)
, Col6 = max(case when rn = 6 then Value end)
from #temp
group by PreviousID
returns:
+------+------+--------+------+--------+------+--------+
| Col0 | Col1 | Col2 | Col3 | Col4 | Col5 | Col6 |
+------+------+--------+------+--------+------+--------+
| 2 | 3 | Data 2 | 4 | Data 3 | 5 | Data 4 |
+------+------+--------+------+--------+------+--------+
I think try with concatenating column and give them alias.
For better understanding go through this link https://www.mssqltips.com/sqlservertip/2985/concatenate-sql-server-columns-into-a-string-with-concat/

Resources