sql compute difference between 2 rows - sql

I'm looking for a methodology to compare the difference between 2 rows in the same table. From what I found here (How to get difference between two rows for a column field?) it's almost what I wanted. I have done the following code:
create table #tmpTest
(
id_fund int null,
id_ShareType int null,
ValueDate datetime null,
VarNAV float null,
FundPerf float null,
)
insert into #tmpTest(id_fund, id_ShareType, ValueDate, VarNAV)
values(1,1,'20140101',100)
insert into #tmpTest(id_fund, id_ShareType, ValueDate, VarNAV)
values(1,1,'20140102',20)
update #tmpTest
set hrc.FundPerf = (isnull(hrn.VarNAV, 0) - hrc.VarNAV)/hrc.VarNAV
from #tmpTest hrc
left join #tmpTest hrn on hrn.ValueDate = (select min(ValueDate) from #tmpTest where ValueDate > hrc.ValueDate)
and hrc.id_fund = hrn.id_fund and hrc.id_ShareType = hrn.id_ShareType
My issue is that the result I'm computing starts on line 1 instead of line 2.
Hereunder the result I'm obtaining:
id_fund id_ShareType ValueDate VarNAV FundPerf
------- ------------ ------------------- ------- -----------------------------
1 1 2014-01-01 00:00:00 100 -0.8
1 1 2014-01-02 00:00:00 20 -1
whereas I'd like it to be that way:
id_fund id_ShareType ValueDate VarNAV FundPerf
------- ------------ ------------------- ------- -----------------------------
1 1 2014-01-01 00:00:00 100 -1
1 1 2014-01-02 00:00:00 20 -0.8
What's wrong with my approach?

You are not restricting the minimum to the same fund and share type.
update #tmpTest
set hrc.FundPerf = (isnull(hrn.VarNAV, 0) - hrc.VarNAV)/hrc.VarNAV
from #tmpTest hrc left join
#tmpTest hrn
on hrn.ValueDate = (select min(ValueDate)
from #tmpTest tt
where tt.ValueDate > hrc.ValueDate and
hrc.id_fund = tt.id_fund and hrc.id_ShareType = tt.id_ShareType
) and
hrc.id_fund = hrn.id_fund and hrc.id_ShareType = hrn.id_ShareType ;

Try this:
update hrn
set FundPerf = (isnull(hrn.VarNAV, 0) - hrc.VarNAV)/hrc.VarNAV
from #tmpTest hrc
left join #tmpTest hrn on hrn.ValueDate = (select min(ValueDate) from #tmpTest where ValueDate > hrc.ValueDate)
and hrc.id_fund = hrn.id_fund and hrc.id_ShareType = hrn.id_ShareType

Hi you can achieve this using by CTE (Common Table Expression)
create table #tmpTest
(
id_fund int null,
id_ShareType int null,
ValueDate datetime null,
VarNAV float null,
FundPerf float null,
)
insert into #tmpTest(id_fund, id_ShareType, ValueDate, VarNAV)
values(1,1,'20140101',100)
insert into #tmpTest(id_fund, id_ShareType, ValueDate, VarNAV)
values(1,1,'20140102',20)
;With tbl as
( Select Row_Number() OVER (Order by T.ValueDate) as RowNumber,*
From #tmpTest T
)SELECT Cur.*,(ISNULL(Cur.VarNAV,0) - ISNULL(Prv.VarNAV,0))/Prv.VarNAV as [Col Name]
FROM tbl Cur
LEFT OUTER JOIN tbl Prv ON Cur.RowNumber = Prv.RowNumber+1
ORDER BY Cur.ValueDate

Related

Get Results based on the latest date in a Joined table

I have 2 tables tbl_job & tbl_appointment
I want to get all active jobs starting with job_refrence = '1%' also the latest active appointment.created_when and appointment_type associated with it ordered by the appointment.created_when
A job can have many appointments
If all the appointments associated with the job has deleted_flag = 1
then the resultset should return tbl_job.created-when as the
latest_appointment_date
CREATE TABLE [dbo].[tbl_job]
(
[job_id] UNIQUEIDENTIFIER NOT NULL,
[account_id] INT NOT NULL,
[description] NVARCHAR(1024) NULL,
[deleted_flag] BIT NOT NULL,
[created_when] DATETIME2 (6) NOT NULL,
[job_reference] NVARCHAR(15) NOT NULL
)
CREATE TABLE [dbo].[tbl_appointment]
(
[appointment_id] UNIQUEIDENTIFIER NOT NULL,
[job_id] UNIQUEIDENTIFIER NOT NULL,
[deleted_flag] BIT NOT NULL,
[appointment_type] NVARCHAR(35) NOT NULL,
[created_when] DATETIME2 (6) NOT NULL,
[account_id] INT NULL,
)
insert into dbo.tbl_job (job_id, account_id, [description], deleted_flag, created_when, job_reference) values
('A29A6381-EF0D-47F6-BFC3-051679E343D0', 1, 'descr1', 0, '2020-06-12 00:58:17.7221410', 1 )
,('4D8C1B04-9E00-41FA-BDB8-653C26712144', 1, 'descr2', 0, '2020-06-10 00:58:17.7221410', 12 )
,('F8DC690E-74AB-46F4-90D3-55E032F21C99', 1, 'descr3', 0, '2020-06-26 00:58:17.7221410', 123 )
,('C5D8AA45-FA45-41A4-877D-5B803C1BE61B', 1, 'descr4', 0, '2020-06-27 00:58:17.7221410', 76 )
insert into dbo.tbl_appointment(appointment_id, job_id, deleted_flag, appointment_type, created_when, account_id) Values
('9E24451F-5703-414F-ACF1-9304AFBEA8F1', 'A29A6381-EF0D-47F6-BFC3-051679E343D0', 0, 'job1_cat1', '2020-06-12 00:58:17.7221410', 1)
,('A8121DC1-271E-4BD0-A6AA-D753CF4D310E', 'A29A6381-EF0D-47F6-BFC3-051679E343D0', 0, 'job1_cat2', '2020-06-14 00:58:17.7221410', 1)
,('61ED5B48-DF95-4FC8-AF1D-1418C6DD9088', '4D8C1B04-9E00-41FA-BDB8-653C26712144', 0, 'job2_cat1', '2020-06-15 00:58:17.7221410', 1)
,('0e4fc735-96c3-4cab-8ade-796bae4639d1', 'F8DC690E-74AB-46F4-90D3-55E032F21C99', 1, 'job3_cat1', '2020-06-28 00:58:17.7221410', 1)
Expected ResultSet
job_id job_reference latest_appointment_date appointment_type total_rows
F8DC690E-74AB-46F4-90D3-55E032F21C99 123 2020-06-26 00:58:17.722141 NULL 3
4D8C1B04-9E00-41FA-BDB8-653C26712144 12 2020-06-15 00:58:17.722141 job2_cat1 3
A29A6381-EF0D-47F6-BFC3-051679E343D0 1 2020-06-14 00:58:17.722141 job1_cat2 3
Below query works, but its not an efficient as we have millions of rows in the tables. I would like to replace the OUTER apply with a Left join or some other way to make it more efficient
DECLARE #filtered_jobs TABLE
(
job_domain_id UNIQUEIDENTIFIER
,job_reference NVARCHAR(15)
,job_created_when DATETIME2(6)
,latest_appointment_date DATETIME2(6)
,appointment_type NVARCHAR(35)
);
declare #account_id int = 1
declare #job_reference nvarchar(35) = '1'
declare #offset int = 0
declare #limit int = 10
declare #is_sort_ascending int = 0
INSERT INTO #filtered_jobs (job_domain_id, job_reference, job_created_when, latest_appointment_date,appointment_type)
SELECT
j.job_id
,j.job_reference
,j.created_when
,ap.created_when AS latest_appointment_date
,ap.appointment_type
FROM dbo.tbl_job j
OUTER APPLY (
SELECT TOP (1) ap.appointment_type,ap.created_when,ap.deleted_flag
FROM dbo.tbl_appointment ap
WHERE ap.job_id = j.job_id AND ap.deleted_flag = 0
ORDER BY ap.created_when desc
) ap
WHERE j.account_id = #account_id
AND j.job_reference LIKE (#job_reference + '%')
AND j.deleted_flag = 0
SELECT
fj.job_domain_id
,fj.job_reference
,ISNULL(fj.latest_appointment_date,fj.job_created_when) AS latest_appointment_date
,fj.appointment_type
FROM #filtered_jobs fj
ORDER BY
CASE WHEN #is_sort_ascending = 0 THEN ISNULL(fj.latest_appointment_date,fj.job_created_when) END DESC,
CASE WHEN #is_sort_ascending = 1 THEN ISNULL(fj.latest_appointment_date,fj.job_created_when) END ASC
OFFSET #offset ROWS FETCH NEXT #limit ROWS ONLY;
SELECT COUNT(1) AS total_records
FROM #filtered_jobs;
You could use a ranking function but you need to test whether it is faster:
From the query you posted, replace the insert into #filtered_jobs with these 2:
INSERT INTO #filtered_jobs (job_domain_id, job_reference, job_created_when)
select j.job_id
,j.job_reference
,j.created_when
FROM dbo.tbl_job j
WHERE j.account_id = #account_id
AND j.job_reference LIKE (#job_reference + '%')
AND j.deleted_flag = 0
update f
set latest_appointment_date=x.latest_appointment_date,
appointment_type=x.appointment_type
from #filtered_jobs f
inner join (
select f.job_domain_id
,ap.created_when AS latest_appointment_date
,ap.appointment_type as appointment_type
, rank() over (partition by ap.job_id order by ap.created_when desc) rnk
from #filtered_jobs f
inner join dbo.tbl_appointment ap on ap.job_id = f.job_domain_id
where ap.deleted_flag = 0) x on f.job_domain_id=x.job_domain_id
where x.rnk=1
Make sure you have a index on dbo.tbl_appointment(column job_id, ap.deleted_flag), preferably including (created_when, appointment_type) if this query is ran alot.

Nested while loop in SQL Server is not showing the expected result

I am trying to connect records from two different tables so I can display the data in a tabular format in an SSRS tablix.
The code below does not return the expected results.
As is, for each item in Temp_A the loop updates everything with the last item in Temp_C. Here is the code:
CREATE TABLE #Temp_A
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_A ([ID], [Name])
VALUES (1, 'A'), (2, 'B')
CREATE TABLE #Temp_C
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_C ([ID], [Name])
VALUES (1, 'C'), (2, 'D')
CREATE TABLE #Temp_Main
(
[Temp_A_ID] INT,
[Temp_A_Name] VARCHAR(255),
[Temp_C_ID] INT,
[Temp_C_Name] VARCHAR(255),
)
DECLARE #MIN_AID int = (SELECT MIN(ID) FROM #Temp_A)
DECLARE #MAX_AID int = (SELECT MAX(ID) FROM #Temp_A)
DECLARE #MIN_DID int = (SELECT MIN(ID) FROM #Temp_C)
DECLARE #MAX_DID int = (SELECT MAX(ID) FROM #Temp_C)
WHILE #MIN_AID <= #MAX_AID
BEGIN
WHILE #MIN_DID <= #MAX_DID
BEGIN
INSERT INTO #Temp_Main([Temp_A_ID], [Temp_A_Name])
SELECT ID, [Name]
FROM #Temp_A
WHERE ID = #MIN_AID
UPDATE #Temp_Main
SET [Temp_C_ID] = ID, [Temp_C_Name] = [Name]
FROM #Temp_C
WHERE ID = #MIN_DID
SET #MIN_DID = #MIN_DID + 1
END
SET #MIN_AID = #MIN_AID + 1
SET #MIN_DID = 1
END
SELECT * FROM #Temp_Main
DROP TABLE #Temp_A
DROP TABLE #Temp_C
DROP TABLE #Temp_Main
Incorrect result:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 2 D
1 A 2 D
2 B 2 D
2 B 2 D
Expected results:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 1 C
1 A 2 D
2 B 1 C
2 B 2 D
What am I missing?
You seem to want a cross join:
select a.*, c.*
from #Temp_A a cross join
#Temp_C c
order by a.id, c.id;
Here is a db<>fiddle.
There is no need to write a WHILE loop to do this.
You can use insert to insert this into #TempMain, but I don't se a need to have a temporary table for storing the results of this query.

Select nearest date on the basis of ID in SQL

I have two table Dispense & Pro Table, I want to select all rows from Dispense table and nearest Shipment date from Pro table.
Dispense Table
ID Dispense date Row ID
604743 10/18/2016 1
604743 11/4/2016 2
604743 11/28/2016 3
604743 12/16/2016 4
Pro table
ID Shipment Date Row ID
604743 11/1/2016 1
604743 11/19/2016 2
604743 11/21/2016 3
604743 11/28/2016 4
604743 12/13/2016 5
Output Needed
ID Dispense date Pre Ship date
604743 10/18/2016 NULL
604743 11/4/2016 11/1/2016
604743 11/28/2016 11/19/2016
604743 12/16/2016 12/13/2016
Reason for NULL: Because less than 10/18 dispense date no nearest date in Shipping date in Pro Table
I tried with recursive CTE, but they don't all to use the anchor in a subquery.
So a while-loop is the best I could do.
The script allows for several Dispense or Pro at same date, see the comments on which part you can throw out if that is not allowed by constraints.
--- Dispense_Date must not be unique
declare #Dispense table(
ID int not null,
Dispense_Date date not null,
Row_ID int not null identity primary key
)
-- Shipment_Date must not be unique
declare #Pro table(
ID int not null,
Shipment_Date date not null,
Row_ID int not null identity primary key
)
declare #Result table(
ID int not null,
Dispense_Date date not null,
Dispense_Row_ID int not null unique,
Shipment_Date date not null,
Pro_Row_ID int not null unique,
dayDiff int not null,
Row_ID int not null identity primary key,
iter int not null
)
insert into #Dispense(ID, Dispense_Date)
values (604743, '10/18/2016'),
(604743, '11/4/2016'),
/* (604743, '11/26/2016'),
(604743, '11/27/2016'),
(604743, '11/27/2016'),
(604743, '11/28/2016'), */
(604743, '11/28/2016'),
(604743, '12/16/2016')
insert into #Pro(ID, Shipment_Date)
values (604743, '11/1/2016'),
/* (604743, '11/16/2016'),
(604743, '11/19/2016'), */
(604743, '11/19/2016'),
(604743, '11/21/2016'),
(604743, '11/28/2016'),
(604743, '12/13/2016')
declare #iter int = 0
while exists(
select 1
from #Dispense Dispense
inner join
#Pro Pro
on Pro.ID = Dispense.ID
and
Pro.Shipment_Date < Dispense.Dispense_Date
where not exists(
select 1
from #Result Result
where Result.Dispense_Row_ID = Dispense.Row_ID
or
Result.Pro_Row_ID = Pro.Row_ID
)
)
begin
set #iter = #iter + 1
;
with distance(
ID, Dispense_Row_ID, Dispense_Date, Pro_Row_ID, Shipment_Date, dayDiff
) as(
select Dispense.ID,
Dispense.Row_ID Dispense_Row_ID,
Dispense.Dispense_Date,
Pro.Row_ID Pro_Row_ID,
Pro.Shipment_Date,
DATEDIFF(DAY, Pro.Shipment_Date, Dispense.Dispense_Date) dayDiff
from #Dispense Dispense
inner join
#Pro Pro
on Pro.ID = Dispense.ID
and
Pro.Shipment_Date < Dispense.Dispense_Date
where not exists(
select 1
from #Result Result
where Result.Dispense_Row_ID = Dispense.Row_ID
or
Result.Pro_Row_ID = Pro.Row_ID
)
)
insert into #Result(ID, Dispense_Row_ID, Dispense_Date, Pro_Row_ID, Shipment_Date, daydiff, iter)
select Dispense.ID,
Dispense.Row_ID Dispense_Row_ID,
Dispense.Dispense_Date,
distance.Pro_Row_ID,
distance.Shipment_Date,
distance.dayDiff,
#iter
from #Dispense Dispense
inner join
distance
on distance.Dispense_Row_ID = Dispense.Row_ID
and
not exists(
select 1
from distance dtExists
where dtExists.ID = distance.ID
and
dtExists.Shipment_Date = distance.Shipment_Date
and
(
dtExists.dayDiff < distance.dayDiff
-- below OR not needed if Dispense_Date and Shipment_Date are unique
or
(
dtExists.dayDiff = distance.dayDiff
and
(
dtExists.Pro_Row_ID < distance.Pro_Row_ID
or
(
dtExists.Pro_Row_ID = distance.Pro_Row_ID
and
dtExists.Dispense_Row_ID < distance.Dispense_Row_ID
)
)
)
)
)
and
not exists(
select 1
from distance dtExists
where dtExists.ID = distance.ID
and
dtExists.Dispense_Date = distance.Dispense_Date
and
(
dtExists.dayDiff < distance.dayDiff
-- below OR not needed if Dispense_Date and Shipment_Date are unique
or
(
dtExists.dayDiff = distance.dayDiff
and
(
dtExists.Pro_Row_ID < distance.Pro_Row_ID
or
(
dtExists.Pro_Row_ID = distance.Pro_Row_ID
and
dtExists.Dispense_Row_ID < distance.Dispense_Row_ID
)
)
)
)
)
end
select Dispense.ID,
Dispense.Row_ID Dispense_Row_ID,
Dispense.Dispense_Date,
Result.Pro_Row_ID,
Result.Shipment_Date,
Result.dayDiff,
Result.iter
from #Dispense Dispense
left join
#Result Result
on Result.Dispense_Row_ID = Dispense.Row_ID
order by Dispense.ID,
Dispense.Dispense_Date,
Result.Shipment_Date,
Result.Dispense_Row_ID,
Result.Pro_Row_ID

SQL: Upsert and get the old and the new values

I have the following table Items:
Id MemberId MemberGuid ExpiryYear Hash
---------------------------------------------------------------------------
1 1 Guid1 2017 Hash1
2 1 Guid2 2018 Hash2
3 2 Guid3 2020 Hash3
4 2 Guid4 2017 Hash1
I need to copy the items from a member to another (not just to update MemberId, to insert a new record). The rule is: if I want to migrate all the items from a member to another, I will have to check that that item does not exists in the new member.
For example, if I want to move the items from member 1 to member 2, I will move only item with id 2, because I already have an item at member 2 with the same hash and with the same expiry year (this are the columns that I need to check before inserting the new items).
How to write a query that migrates only the non-existing items from a member to another and get the old id and the new id of the records? Somehow with an upsert?
You can as the below:
-- MOCK DATA
DECLARE #Tbl TABLE
(
Id INT IDENTITY NOT NULL PRIMARY KEY,
MemberId INT,
MemberGuid CHAR(5),
ExpiryYear CHAR(4),
Hash CHAR(5)
)
INSERT INTO #Tbl
VALUES
(1, 'Guid1', '2017', 'Hash1'),
(1, 'Guid2', '2018', 'Hash1'),
(2, 'Guid3', '2020', 'Hash3'),
(2, 'Guid4', '2017', 'Hash1')
-- MOCK DATA
-- Parameters
DECLARE #FromParam INT = 1
DECLARE #ToParam INT = 2
DECLARE #TmpTable TABLE (NewDataId INT, OldDataId INT)
MERGE #Tbl AS T
USING
(
SELECT * FROM #Tbl
WHERE MemberId = #FromParam
) AS F
ON T.Hash = F.Hash AND
T.ExpiryYear = F.ExpiryYear AND
T.MemberId = #ToParam
WHEN NOT MATCHED THEN
INSERT ( MemberId, MemberGuid, ExpiryYear, Hash)
VALUES ( #ToParam, F.MemberGuid, F.ExpiryYear, F.Hash)
OUTPUT inserted.Id, F.Id INTO #TmpTable;
SELECT * FROM #TmpTable
Step 1:
Get in cursor all the data of member 1
Step 2:
While moving through cursor.
Begin
select hash, expirydate from items where memberid=2 and hash=member1.hash and expirydate=member1.expirydate
Step 3
If above brings any result, do not insert.
else insert.
Hope this helps
Note: this is not actual code. I am providing you just steps based on which you can write sql.
Actually you just need an insert. When ExpiryYear and Hash matched you don't wanna do anything. You just wanna insert from source to target where those columns doesn't match. You can do that with Merge or Insert.
CREATE TABLE YourTable
(
Oldid INT,
OldMemberId INT,
Id INT,
MemberId INT,
MemberGuid CHAR(5),
ExpiryYear CHAR(4),
Hash CHAR(5)
)
INSERT INTO YourTable VALUES
(null, null, 1, 1, 'Guid1', '2017', 'Hash1'),
(null, null, 2, 1, 'Guid2', '2018', 'Hash2'),
(null, null, 3, 2, 'Guid3', '2020', 'Hash3'),
(null, null, 4, 2, 'Guid4', '2017', 'Hash1')
DECLARE #SourceMemberID AS INT = 1
DECLARE #TargetMemberID AS INT = 2
MERGE [YourTable] AS t
USING
(
SELECT * FROM [YourTable]
WHERE MemberId = #SourceMemberID
) AS s
ON t.Hash = s.Hash AND t.ExpiryYear = s.ExpiryYear AND t.MemberId = #TargetMemberID
WHEN NOT MATCHED THEN
INSERT(Oldid, OldMemberId, Id, MemberId, MemberGuid, ExpiryYear, Hash) VALUES (s.Id, s.MemberId, (SELECT MAX(Id) + 1 FROM [YourTable]), #TargetMemberID, s.MemberGuid, s.ExpiryYear, s.Hash);
SELECT * FROM YourTable
DROP TABLE YourTable
/* Output:
Oldid OldMemberId Id MemberId MemberGuid ExpiryYear Hash
-----------------------------------------------------------------
NULL NULL 1 1 Guid1 2017 Hash1
NULL NULL 2 1 Guid2 2018 Hash2
NULL NULL 3 2 Guid3 2020 Hash3
NULL NULL 4 2 Guid4 2017 Hash1
2 1 5 2 Guid2 2018 Hash2
If you just want to select then do as following
SELECT null AS OldID, null AS OldMemberID, Id, MemberId, MemberGuid, ExpiryYear, Hash FROM YourTable
UNION ALL
SELECT A.Id AS OldID, A.MemberId AS OldMemberID, (SELECT MAX(Id) + 1 FROM YourTable) AS Id, #TargetMemberID AS MemberId, A.MemberGuid, A.ExpiryYear, A.Hash
FROM YourTable A
LEFT JOIN
(
SELECT * FROM YourTable WHERE MemberId = #TargetMemberID
) B ON A.ExpiryYear = B.ExpiryYear AND A.Hash = B.Hash
WHERE A.MemberId = #SourceMemberID AND B.Id IS NULL

SQL Server, Merge two records in one record

We have these tables
CREATE TABLE tbl01
(
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
CREATE TABLE tbl02
(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL REFERENCES tbl01(id),
[val] nvarchar(50) NULL,
[code] int NULL
)
If we run this query:
SELECT
tbl01.id, tbl01.name, tbl02.val, tbl02.code
FROM
tbl01
INNER JOIN
tbl02 ON tbl01.id = tbl02.id
we get these results:
-------------------------------
id | name | val | code
-------------------------------
1 | one | FirstVal | 1
1 | one | SecondVal | 2
2 | two | YourVal | 1
2 | two | OurVal | 2
3 | three | NotVal | 1
3 | three | ThisVal | 2
-------------------------------
You can see that each two rows are related to same "id"
The question is: we need for each id to retrieve one record with all val, each val will return in column according to the value of column code
if(code = 1) then val as val-1
else if (code = 2) then val as val-2
Like this:
-------------------------------
id | name | val-1 | val-2
-------------------------------
1 | one | FirstVal | SecondVal
2 | two | YourVal | OurVal
3 | three | NotVal | ThisVal
-------------------------------
Any advice?
Use can use MAX and Group By to achieve this
SELECT id,
name,
MAX([val1]) [val-1],
MAX([val2]) [val-2]
FROM ( SELECT tbl01.id, tbl01.name,
CASE code
WHEN 1 THEN tbl02.val
ELSE ''
END [val1],
CASE code
WHEN 2 THEN tbl02.val
ELSE ''
END [val2]
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
) Tbl
GROUP BY id, name
Is it the PIVOT operator (http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx) that you are looking for?
You've already got a few answers, but heres one using PIVOT as an alternative. The good thing is this approach is easy to scale if there are additional columns required later
-- SETUP TABLES
DECLARE #t1 TABLE (
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
DECLARE #t2 TABLE(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL,
[val] nvarchar(50) NULL,
[code] int NULL
)
-- SAMPLE DATA
INSERT #t1 ( id, name )
VALUES ( 1, 'one'), (2, 'two'), (3, 'three')
INSERT #t2
( subId, id, val, code )
VALUES ( 1,1,'FirstVal', 1), ( 2,1,'SecondVal', 2)
,( 3,2,'YourVal', 1), ( 4,2,'OurVal', 2)
,( 5,3,'NotVal', 1), ( 6,3,'ThisVal', 2)
-- SELECT (using PIVOT)
SELECT id, name, [1] AS 'val-1', [2] AS 'val-2'
FROM
(
SELECT t2.id, t1.name, t2.val, t2.code
FROM #t1 AS t1 JOIN #t2 AS t2 ON t2.id = t1.id
) AS src
PIVOT
(
MIN(val)
FOR code IN ([1], [2])
) AS pvt
results:
id name val-1 val-2
---------------------------------
1 one FirstVal SecondVal
2 two YourVal OurVal
3 three NotVal ThisVal
If there are always only two values, you could join them or even easier, group them:
SELECT tbl01.id as id, Min(tbl01.name) as name, MIN(tbl02.val) as val-1, MAX(tbl02.val) as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
GROUP BY tbl02.id
note: this query will always put the lowest value in the first column and highest in the second, if this is not wanted: use the join query:
Join query
If you always want code 1 in the first column and code 2 in the second:
SELECT tbl01.id as id, tbl01.name as name, tbl02.val as val-1, tbl03.val as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
ON tbl02.code = 1
INNER JOIN tbl03 ON tbl01.id = tbl03.id
ON tbl03.code = 2
Variable amount of columns
You cannot get an variable amount of columns, only when you do this by building your query in code or t-sql stored procedures.
My advice:
If its always to values: join them in query, if not, let your server-side code transform the data. (or even better, find a way which makes it not nessecery to transform data)
Try this - it uses a pivot function but it also creates creates the dynamic columns dependent on code
DECLARE #ColumnString varchar(200)
DECLARE #sql varchar(1000)
CREATE TABLE #ColumnValue
(
Value varchar(500)
)
INSERT INTO #ColumnValue (Value)
SELECT DISTINCT '[' + 'value' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id )) + ']'
FROM Test
SELECT #ColumnString = COALESCE(#ColumnString + ',', '') + Value
FROM #ColumnValue
Drop table #ColumnValue
SET #sql =
'
SELECT *
FROM
(
SELECT
id,name,val,''value'' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id ))as [values]
FROM Test
) AS P
PIVOT
(
MAX(val) FOR [values] IN ('+#ColumnString+')
) AS pv
'
--print #sql
EXEC (#sql)