Troubles with splitting values into multiple rows using T-SQL - tsql

I have a Sql Server 2K8 R2 DB with a table that have a column containings multiples values, separated by (char 13 and char 10).
I'm building a script to import the data in a properly normalized schema.
My source table contains something like this :
ID | Value
________________
1 | line 1
line 2
________________
2 | line 3
________________
3 | line 4
line 5
line 6
________________
and so on.
[edit] FYI, Id is integer and value is nvarchar(3072) [/edit]
What I want is to query the table to ouput somethnig like this :
ID | Value
________________
1 | line 1
________________
1 | line 2
________________
2 | line 3
________________
3 | line 4
________________
3 | line 5
________________
3 | line 6
________________
I've read many answer here on SO, and also around the web, and I find that using master..sptvalues should be the solution. Especially, I tried to reprodude the solution of the question Split one column into multiple rows.
However, without success (suspecting having two chars causing problems).
By now, I wrote this query :
SELECT
T.ID,
T.Value,
RIGHT(LEFT(T.Value,spt.Number-1),
CHARINDEX(char(13)+char(10),REVERSE(LEFT(char(13)+char(10)+T.Value,spt.Number-1)))) as Extracted
FROM
master..spt_values spt,
ContactsNew T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13)+char(10) OR SUBSTRING(T.Value,spt.Number,2) = '')
This query, unfortunately is returning :
ID | Value | Extracted
________________________________
1 | line 1 | <blank>
line 2 |
________________________________
1 | line 1 | line 2
line 2 |
________________________________
2 | line 3 | <blank>
________________________________
3 | line 4 | <blank>
line 5 |
line 6 |
________________________________
3 | line 4 | line 5
line 5 | line 6
line 6 |
________________________________
3 | line 4 | line 6
line 5 |
line 6 |
________________________________
<blank> is an empty string, not null string.
I'd appreciate some help to tune my query.
[Edit2] My source table contains less than 200 records, and performance is not a requirement, so I'm targeting a simple solution rather than an efficient one [Edit2]
[Edit3] The source database is readonly. I can't add stored procedure, function, or clr type. I have to do this in a single query. [Edit3]
[Edit4] Something strange... it seems that whitespaces are also considered as separators.
If I run the following query :
SELECT
T.ID,
replace(T.Value, '#', ' '),
replace(RIGHT(
LEFT(T.Value,spt.Number-1),
CHARINDEX( char(13) + char(10),REVERSE(LEFT(char(10) + char(13)+T.Value,spt.Number-0)))
), '#', ' ')
FROM
master..spt_values spt,
(
select contactID,
replace(Value,' ', '#') Value
from ContactsNew where Value is not null
) T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13) + char(10) OR SUBSTRING(T.Value,spt.Number,1) = '')
I got the correct number of returns (however, still having wrong values), while running this query :
SELECT
T.ID,
T.Value,
RIGHT(
LEFT(T.Value,spt.Number-1),
CHARINDEX( char(13) + char(10),REVERSE(LEFT(char(10) + char(13)+T.Value,spt.Number-0)))
)
FROM
master..spt_values spt,
(
select contactID,
Value
from ContactsNew where Value is not null
) T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
AND
(SUBSTRING(T.Value,spt.Number,2) = char(13) + char(10) OR SUBSTRING(T.Value,spt.Number,1) = '')
splits on spaces also

EDIT #1: I've deleted original answer text. Try following query. I slightly modified your logic. If you should have any questions about it, don't hesitate to ask in comment. If You need another split delimiter just introduce another nested query to replace that delimiter with CHAR(13)+CHAR(10).
SELECT
*
FROM
(
SELECT
T.ID,
T.Value,
CASE
WHEN CHARINDEX(CHAR(13) + CHAR(10), SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)) > 0 THEN
LEFT(
SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1),
CHARINDEX(CHAR(13) + CHAR(10), SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)) - 1)
/* added by Steve B. see comments for the reasons */
when len(T.Value) = spt.Number then right(t.Value, spt.number -1)
/* end of edit */
ELSE
SUBSTRING(T.Value, spt.number, LEN(T.Value) - spt.Number + 1)
END EXTRACTED
FROM
master..spt_values spt,
ContactsNew T
WHERE
Type = 'P' AND
spt.Number BETWEEN 1 AND LEN(T.Value)+1
) X
WHERE
EXTRACTED <> '' AND
(
LEFT(X.VALUE, LEN(EXTRACTED)) = EXTRACTED OR
X.Value LIKE '%' + CHAR(13) + CHAR(10) + EXTRACTED + CHAR(13) + CHAR(10) + '%' OR
X.Value LIKE '%' + CHAR(13) + CHAR(10) + EXTRACTED
)

A sample query showing how to perform this kind of operation against some test data similar to described.
If you aren't able to declare variables in your final statement you can find/replace them for their values, but it makes things a bit simpler.
This works by replacing CR+LF with a single character before doing the split.
If '|' is in use in your data, select another single character which isn't to use as the temporary delimiter.
declare #crlf nvarchar(2) = char(10) + char(13)
declare #cDelim nvarchar(1) = N'|'
-- test data
declare #t table
(id int
,value nvarchar(3072))
insert #t
select 1, 'line1' + #crlf + 'line2'
union all select 2, 'line3'
union all select 3, 'line4' + #crlf + 'line5' + #crlf + 'line6'
-- /test data
;WITH charCTE
AS
(
--split the string into a dataset
SELECT D.id, D.value, SUBSTRING(D.s,n,CHARINDEX(#cDelim, D.s + #cDelim,n) -n) AS ELEMENT
FROM (SELECT id, value, REPLACE(value,#crlf,#cDelim) as s from #t) AS D
JOIN (SELECT TOP 3072 ROW_NUMBER() OVER (ORDER BY a.type, a.number, a.name) AS n
FROM master.dbo.spt_values a
CROSS
JOIN master.dbo.spt_values b
) AS numsCte
ON n <= LEN(s)
AND SUBSTRING(#cDelim + s,n,1) = #cDelim
)
SELECT id, ELEMENT
FROM charCTE
order by id, element

Related

Extract words before and after a specific word

I need to extract words before and after a word like '%don%' in a ntext column.
table A, column name: Text
Example:
TEXT
where it was done it will retrieve the...
at the end of the trip clare done everything to improve
it is the only one done in these times
I would like the following results:
was done it
clare done everything
one done in
I am using T-SQL, Left and right functions did not work with ntext data type of the column containing text.
As others have said, you can use a string splitting function to split out each word and then return those you require. Using the previously linked DelimitedSplit8K:
CREATE FUNCTION dbo.DelimitedSplit8K
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
go
declare #t table (t ntext);
insert into #t values('where it was done it will retrieve the...'),('at the end of the trip clare done everything to improve'),('we don''t take donut donations here'),('ending in don');
with t as (select cast(t as nvarchar(max)) as t from #t)
,d as (select t.t
,case when patindex('%don%',s.Item) > 0 then 1 else 0 end as d
,s.ItemNumber as i
,lag(s.Item,1,'') over (partition by t.t order by s.ItemNumber) + ' '
+ s.Item + ' '
+ lead(s.Item,1,'') over (partition by t.t order by s.ItemNumber) as r
from t
cross apply dbo.DelimitedSplit8K(t.t, ' ') as s
)
select t
,r
from d
where d = 1
order by t
,i;
Output:
+---------------------------------------------------------+-----------------------+
| t | r |
+---------------------------------------------------------+-----------------------+
| at the end of the trip clare done everything to improve | clare done everything |
| ending in don | in don |
| we don't take donut donations here | we don't take |
| we don't take donut donations here | take donut donations |
| we don't take donut donations here | donut donations here |
| where it was done it will retrieve the... | was done it |
+---------------------------------------------------------+-----------------------+
And a working example:
http://rextester.com/RND43071

convert array of aclitem into multiple rows redshift

I have one array with column values as
{james=UC/james,adam=C/james,chris=UC/james,john=U/james}
The above column values are not json. They are in string in the following form:
{ username=privilegestring/grantor }
How to convert above column into multiple rows
Edit #3:
Updated the query to specifically target pg_catalog.pg_namespace for acl permissions grants, via CTE pg_catalog. Currently this CTE is filtered in the where clause to select a single namespace name ('avengers'); if you want to select from multiple namespace names, you should be able to add them into the WHERE clause of this CTE directly, or in the case of wanting all namespace names, remove the clause altogether.
It's worth noting as well, that you will need to expand the case statements in access_privilege_types to handle all permissions cases: 'r', 'w', 'a', 'd', and 'x', for the operations: SELECT, UPDATE, INSERT, DELETE, REFERENCE, respectively.
Edit #2:
The final posted version of the query below should get you the data you want in the format that you want it in. I don't know how many possible values there are for the permissions types; if you have more than the two specified currently, you will need to expand the case statements in the CTE* access_privilege_types*. Obviously you'll also need to replace your table name within the query, etc.. Let me know if you run into any trouble and I'll help as necessary.
Edit #1:
Was able to validate that this query works in Redshift. Updated the query to break out separate rows by grantee and owner. The current version doesn't break out individual permissions by row yet -- Will take a look later tonight to see if I can get that working as well.
Original:
I don't have access to my Redshift cluster to test this at the moment, but I will when I get home. The general idea behind the following method, is to create a numbered index table to cross join against that will expand the data in the permissions field into a row-based representation.
I had inquired about the size limit, because this will currently only handle 10,000 possible delimited values, however you can adjust the CTEs to scale up to larger amounts if needed for your specific application:
Revision 3 Query:
WITH
pg_namespace AS (
SELECT
nspname
, nspowner
, rtrim(ltrim(array_to_string(nspacl, ','), '{'), '}') as nspacl
FROM pg_catalog.pg_namespace
WHERE nspname = 'public'
),
-- Generating a table with the numbers 1 - 10 in a single column.
ten_numbers AS (
SELECT
1 AS num
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 0
),
-- Expands the values in ten_numbers to create a single column with the values 1 - 10,000.
depivot_index AS (
SELECT
(1000 * t1.num) + (100 * t2.num) + (10 * t3.num) + t4.num AS gen_num
FROM ten_numbers AS t1
JOIN ten_numbers AS t2 ON 1 = 1
JOIN ten_numbers AS t3 ON 1 = 1
JOIN ten_numbers AS t4 ON 1 = 1
),
-- Filters down generated_numbers to house only the numbers up to the maximum times that the delimiter appears.
splitter AS (
SELECT
*
FROM depivot_index
WHERE gen_num BETWEEN 1 AND (
SELECT max(REGEXP_COUNT(nspacl, '\\,') + 1)
FROM pg_namespace
)
),
-- Cross joins permissions_groups and splitter to populate all requests, delimited on ','.
expanded_input AS (
SELECT
pg.nspname
, pg.nspacl
, trim(split_part(pg.nspacl, ',', s.gen_num)) AS raw_permissions_string
FROM pg_namespace AS pg
JOIN splitter AS s ON 1 = 1
WHERE split_part(nspacl, ',', s.gen_num) <> ''
),
-- Breaks out the owner and grantee fields into their own columns respectively.
users_with_raw_permissions_data AS (
SELECT
e.raw_permissions_string
, e.nspname
, trim(split_part(e.raw_permissions_string, '=', 1)) AS grantee
, trim(split_part(trim(split_part(e.raw_permissions_string, '=', 2)), '/', 2)) AS owner
, trim(split_part(trim(split_part(e.raw_permissions_string, '=', 2)), '/', 1)) AS raw_permissions_data
FROM
expanded_input e
),
-- Mines privilege types from raw string data.
access_privilege_types AS (
SELECT
u.nspname
, u.owner
, u.grantee
,CASE
WHEN position('C*' IN u.raw_permissions_data) > 0 THEN 'C*'
WHEN position('U*' IN u.raw_permissions_data) > 0 THEN 'U*'
WHEN position('C' IN u.raw_permissions_data) > 0 THEN 'C'
WHEN position('U' IN u.raw_permissions_data) > 0 THEN 'U'
ELSE u.raw_permissions_data
END AS first_access_privilege
, CASE
WHEN position('U*' IN u.raw_permissions_data) > 0 THEN 'U*'
WHEN position('C*' IN u.raw_permissions_data) > 0 THEN 'C*'
WHEN position('U' IN u.raw_permissions_data) > 0 THEN 'U'
WHEN position('C' IN u.raw_permissions_data) > 0 THEN 'C'
ELSE u.raw_permissions_data
END AS second_access_privilege
, first_access_privilege || ',' || second_access_privilege AS merged_access_privileges
FROM users_with_raw_permissions_data u
),
-- Cross joins access_privilge_types and splitter to populate all privilege_types, delimited on ','.
expanded_access_privilege_types AS (
SELECT
a.nspname
, a.owner
, a.grantee
, trim(split_part(a.merged_access_privileges, ',', s.gen_num)) AS access_privileges
FROM access_privilege_types AS a
JOIN splitter AS s ON 1 = 1
WHERE split_part(a.merged_access_privileges, ',', s.gen_num) <> ''
GROUP BY 1, 2, 3, 4
)
SELECT
ea.nspname
, ea.owner
, ea.grantee
, LEFT(ea.access_privileges, 1) AS access_privilege
, CASE
WHEN POSITION('*' IN ea.access_privileges) > 0 THEN 'YES'
ELSE 'NO'
END AS is_grantable
FROM expanded_access_privilege_types ea
ORDER BY 1, 2, 3, 4, 5
Edit #4:
Adding some clarification on how the ten_numbers, depivot_index, and splitter tables work to break apart the pg_catalog.pg_namespace.nspacl field. The general overview, is that ten_numbers and depivot_index are created purely to return tables with numbered rows to use as an index when joining in thesplit_partvalues ofnspacl`.
ten_numbers generates a table with a single column, containing the numbers 0-9:
-------
| num |
-------
| 0 |
-------
| 1 |
-------
| etc |
-------
| 9 |
-------
This table is then expanded to house the range 0-9999 during the CTE depivot_index:
-----------
| gen_num |
-----------
| 0 |
-----------
| 1 |
-----------
| 2 |
-----------
| etc |
-----------
| 9998 |
-----------
| 9999 |
-----------
splitter then narrows down the table to house only the numbers up to the maximum count of the specified delimiter within the nspacl field:
-------
| num |
-------
| 0 |
-------
| 1 |
-------
| etc |
-------
| 6 |
-------
The table returned by splitter is then used as the target of a CROSS JOIN via the join on 1 = 1 in CTE expanded_input. This ensures that each member returned by split_part will have its own row:
---------------------------------------------------------------------------
| nspname | nspacl | raw_permissions_string |
---------------------------------------------------------------------------
| avengers | "{james=UC/james,adam=C/james}" | "james=UC/james" |
---------------------------------------------------------------------------
| avengers | "{james=UC/james,adam=C/james}" | "adam=C/james" |
---------------------------------------------------------------------------
| avengers | etc. | etc. |
---------------------------------------------------------------------------

Parsing a comma delimited column and transposing into rows [duplicate]

This question already has answers here:
Turning a Comma Separated string into individual rows
(16 answers)
Closed 5 years ago.
Suppose I have a table like this with an undetermined number of comma-delimited values in one column:
thingID personID
1 123,234,345
2 456,567
and I want to get it into a form like this:
thingID personID
1 123
1 234
1 345
2 456
2 567
What is my best option for doing this?
Oh I should mention the data is in a SQL 2008 R2 database so I may not be able to use the very latest functionality.
Use CROSS APPLY with a string splitting function.
To find the string splitting function that works best for you, read Aaron Bertrand's Split strings the right way – or the next best way.
For this demonstration I've chosen to use the SplitStrings_XML function, simply because it's the first pure t-sql function in the article:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
Now that we have a string splitting function, create and populate the sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
thingID int,
personID varchar(max)
)
INSERT INTO #T VALUES
(1, '123,234,345'),
(2, '456,567')
The query:
SELECT thingId, Item
FROM #T
CROSS APPLY dbo.SplitStrings_XML(personID, ',')
Results:
thingId Item
1 123
1 234
1 345
2 456
2 567
You can see a live demo on rextester.
There are several ways to do that. Here are two methods for SQL Server 2008:
XML-Method: requires the string to allow for the xml-trick (no invalid XML chars)
SELECT a.thingID, Split.a.value('.', 'VARCHAR(100)') AS Data
FROM (SELECT OtherID,
CAST('<M>' + REPLACE(personID, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM table1) AS A CROSS APPLY Data.nodes ('/M') AS Split(a);
Recursive method:
;WITH tmp(thingID, DataItem, Data) AS (
SELECT thingID, LEFT(personID, CHARINDEX(',', personID + ',') - 1),
STUFF(personID, 1, CHARINDEX(',', personID + ','), '')
FROM table1
UNION ALL
SELECT thingID, LEFT(personID, CHARINDEX(',', personID + ',') - 1),
STUFF(personID, 1, CHARINDEX(',', personID + ','), '')
FROM tmp
WHERE Data > ''
)
SELECT thingID, DataItem AS personID
FROM tmp

Pivot table with dynamic columns in date order

I have a pivot table creating 2 columns for each line that I would like to go in date order.
Below is the data in the raw format
-------------------------------------------------------------
partnum | period | TotalQty | ToldSold
005483-6 | 2015-08 | 100.00000000 | 389.379000
0551105 | 2015-08 | 10.00000000 | 4560.773000
0CT202305 | 2015-09 | 4.00000000 | 2285.430800
0CTR00905 | 2015-10 | 2.00000000 | 654.305400
183386-32 | 2016-01 | 20.00000000 | 75.060400
24-175UV50| 2016-03 | 450.00000000 | 42.723000
I have the following code to generate the dynamic pivot table
DECLARE #cols AS NVARCHAR(MAX),
#colsName AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(period +'_'+c.col)
from #orderhistory
cross apply
(
select 'TotalQty' col
union all
select 'ToldSold'
) c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #cols
select #colsName
= STUFF((SELECT distinct ', ' + QUOTENAME(period +'_'+c.col)
+' as ['
+ period + case when c.col = 'TotalQty' then ' QtySold]' else 'Total $ Sold]' end
from #orderhistory
cross apply
(
select 'TotalQty' col
union all
select 'ToldSold'
) c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsName
set #query
= 'SELECT partnum, ' + #colsName + '
from
(
select
partnum,
period +''_''+col col,
value
from
(
select partnum,
period,
cast(TotalQty as numeric(10, 2)) TotalQty,
cast(ToldSold as numeric(10, 2)) ToldSold
from #orderhistory
) src
unpivot
(
value
for col in (TotalQty, ToldSold)
) unpiv
) s
pivot
(
sum(value)
for col in (' + #cols + ')
) p
order by partnum'
execute(#query)
It creates data like the following
partnum 2016-02 QtySold 2015-08Total $ Sold 2015-11Total $ Sold 2015-12 QtySold
005483-10 NULL NULL NULL 100.00
005483-12 NULL NULL 1249.68 450.00
005483-14 NULL NULL NULL 70.00
005483-2 NULL NULL 1234.19 350.00
005483-3 10.00 NULL NULL NULL
What I would like to see is the headers go across in date order
partnum | 2015-08 QtySold | 2015-08 Total $ Sold | 2015-09 QtySold | 2015-09 Total $ Sold........
All the way across until I get to my current month.
I think I need to add an order by somewhere, I just don't know where. This is my first multiple column dynamic pivot table so I'm a little lost. Everywhere i have tried has given me some sort of error.
Any help is greatly appreciated!!
you would order your #colNames data..
SELECT #colsName = STUFF((
SELECT DISTINCT
', '
+ QUOTENAME(period + '_' + c.col)
+ ' as ['
+ period
+ CASE WHEN c.col = 'TotalQty' THEN ' QtySold]' ELSE 'Total $ Sold]' END
FROM #orderhistory
CROSS APPLY (SELECT 'TotalQty' col UNION ALL SELECT 'ToldSold') c
--ORDER BY HERE
ORDER BY period, c.col
FOR XML PATH(''), TYPE
).value('.','NVARCHAR(MAX)'),1,1,'')

TSQL pivot a single column table

Sorry for the late response I was off for few days, and for not specifying the exact table structure. Please ignore the previous description above. I have more information so my original question is not any longer valid as I obtain more information regarding what I really need as described below:
I have the following table (simplified version for the sake of discussion). First line is the headers:
VariableID DocumentID Revision Value
44 12 2 Val1
45 12 2 Val2
45 12 3 Val3
44 13 1 Val4
46 13 2 Val5
47 14 1 Val6
I’d like to convert it (assuming n number of rows) to the following grouped by (DocumentId, revision) table:
Documentid revision variable1 (44) variable2 (45) variable3(46) variable(47) variable (n)
12 2 Val1 Val2 null null
12 3 null Val3 null null
13 1 Val4 null null null
13 2 null null Val5 null
14 1 null null null Val6
Number of variable will be retrieved dynamically. I do not know how many variable will be in source table as input.
Please advise.
You did not provide a lot of details about your current table structure or sample data. So I will provide you with some samples of the PIVOT function that will perform this.
There are two options for PIVOT, a Static if you know the number of columns to transform or a Dynamic which will get the list of columns at run-time to transform.
EDIT:
Based on the change to your question, you can still perform a PIVOT
A Static Pivot would look like this (Sql Fiddle sample):
select *
from
(
select *
from t
) x
pivot
(
max(value)
for variableid in([44], [45], [46], [47])
) p
As Dynamic Pivot looks like this (Sql Fiddle Sample):
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsAlias AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(variableid)
from t
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #colsAlias = STUFF((SELECT DISTINCT ',' + QUOTENAME(m.variableid) + ' AS ' + QUOTENAME('variable' + cast(n.variableid as varchar(10)))
FROM t m INNER JOIN t n ON m.variableid = n.variableid
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #query = 'SELECT documentid, revision, ' + #colsAlias + ' from
(
select *
from t
) x
pivot
(
max(value)
for variableid in (' + #cols + ')
) p '
execute(#query)
The PIVOT operator sounds like it is what you are looking for, it is available in both Microsoft SQL Server and Microsoft Access, I'm not sure if it is available in Sybase though. Here is the MSDN article on it: T-SQL Pivot Operator