Transpose a word written vertically to horizontally in SQL Server









up vote
3
down vote

favorite












I want to use T-SQL to transpose a column with a word, such as



t
r
a
i
n


into



train


I tried using pivot, but instead of getting only one row back with the transposed column, I got a 5x5 table showing 'train' along the diagonal, and NULL everywhere else. This result makes sense to me, but it's not what I want. I just want to transpose a word written vertically into the same word but written horizontally. How should I approach this with pivot? Or is there an easier way to do it otherwise?



Conversely, if I had instead started out with a word



train


how would I transpose this word to make it print vertically?



t
r
a
i
n


Thank you!










share|improve this question



















  • 2




    What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
    – Ross Bush
    Nov 10 at 4:09











  • I'm using Microsoft SQL Server Management Studio.
    – kkun
    Nov 10 at 4:14






  • 1




    Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
    – Richardissimo
    Nov 10 at 7:39










  • SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
    – e_i_pi
    Nov 10 at 7:55










  • Is this a homework question? There would never be a valid real life scenario for this...
    – theMayer
    Nov 10 at 13:45














up vote
3
down vote

favorite












I want to use T-SQL to transpose a column with a word, such as



t
r
a
i
n


into



train


I tried using pivot, but instead of getting only one row back with the transposed column, I got a 5x5 table showing 'train' along the diagonal, and NULL everywhere else. This result makes sense to me, but it's not what I want. I just want to transpose a word written vertically into the same word but written horizontally. How should I approach this with pivot? Or is there an easier way to do it otherwise?



Conversely, if I had instead started out with a word



train


how would I transpose this word to make it print vertically?



t
r
a
i
n


Thank you!










share|improve this question



















  • 2




    What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
    – Ross Bush
    Nov 10 at 4:09











  • I'm using Microsoft SQL Server Management Studio.
    – kkun
    Nov 10 at 4:14






  • 1




    Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
    – Richardissimo
    Nov 10 at 7:39










  • SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
    – e_i_pi
    Nov 10 at 7:55










  • Is this a homework question? There would never be a valid real life scenario for this...
    – theMayer
    Nov 10 at 13:45












up vote
3
down vote

favorite









up vote
3
down vote

favorite











I want to use T-SQL to transpose a column with a word, such as



t
r
a
i
n


into



train


I tried using pivot, but instead of getting only one row back with the transposed column, I got a 5x5 table showing 'train' along the diagonal, and NULL everywhere else. This result makes sense to me, but it's not what I want. I just want to transpose a word written vertically into the same word but written horizontally. How should I approach this with pivot? Or is there an easier way to do it otherwise?



Conversely, if I had instead started out with a word



train


how would I transpose this word to make it print vertically?



t
r
a
i
n


Thank you!










share|improve this question















I want to use T-SQL to transpose a column with a word, such as



t
r
a
i
n


into



train


I tried using pivot, but instead of getting only one row back with the transposed column, I got a 5x5 table showing 'train' along the diagonal, and NULL everywhere else. This result makes sense to me, but it's not what I want. I just want to transpose a word written vertically into the same word but written horizontally. How should I approach this with pivot? Or is there an easier way to do it otherwise?



Conversely, if I had instead started out with a word



train


how would I transpose this word to make it print vertically?



t
r
a
i
n


Thank you!







sql sql-server tsql transpose






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 10 at 9:48









marc_s

567k12710961246




567k12710961246










asked Nov 10 at 3:50









kkun

253




253







  • 2




    What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
    – Ross Bush
    Nov 10 at 4:09











  • I'm using Microsoft SQL Server Management Studio.
    – kkun
    Nov 10 at 4:14






  • 1




    Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
    – Richardissimo
    Nov 10 at 7:39










  • SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
    – e_i_pi
    Nov 10 at 7:55










  • Is this a homework question? There would never be a valid real life scenario for this...
    – theMayer
    Nov 10 at 13:45












  • 2




    What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
    – Ross Bush
    Nov 10 at 4:09











  • I'm using Microsoft SQL Server Management Studio.
    – kkun
    Nov 10 at 4:14






  • 1




    Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
    – Richardissimo
    Nov 10 at 7:39










  • SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
    – e_i_pi
    Nov 10 at 7:55










  • Is this a homework question? There would never be a valid real life scenario for this...
    – theMayer
    Nov 10 at 13:45







2




2




What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
– Ross Bush
Nov 10 at 4:09





What RDMS are you using? I deleted my answer because it was SQL Server specific and you only tagged [sql].
– Ross Bush
Nov 10 at 4:09













I'm using Microsoft SQL Server Management Studio.
– kkun
Nov 10 at 4:14




I'm using Microsoft SQL Server Management Studio.
– kkun
Nov 10 at 4:14




1




1




Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
– Richardissimo
Nov 10 at 7:39




Whichever answer you go with for the first part of this question, please think about the query which returns one letter on each row...The solution is dependent upon the order. So if you use it in a real scenario, make sure you have an order by clause in the query which returns the letters; otherwise at some point in future, SQL might decide a different access path, returning your letters in a different order.
– Richardissimo
Nov 10 at 7:39












SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
– e_i_pi
Nov 10 at 7:55




SQL Server 2017 has a function STRING_AGG() which is worth looking into if you're up to date with versions.
– e_i_pi
Nov 10 at 7:55












Is this a homework question? There would never be a valid real life scenario for this...
– theMayer
Nov 10 at 13:45




Is this a homework question? There would never be a valid real life scenario for this...
– theMayer
Nov 10 at 13:45












5 Answers
5






active

oldest

votes

















up vote
2
down vote













create table #a (word varchar(1)

insert into #a values('T'),('R'),('A'),('I'),('N')

declare @a varchar (10)=''
select @a=@a+ word from #a
select @a





share|improve this answer
















  • 1




    The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
    – Shnugo
    Nov 11 at 10:05

















up vote
2
down vote













A simpler approach :



DECLARE 
@t TABLE(word VARCHAR(50) )
INSERT INTO @t VALUES
('t'),
('r'),
('a'),
('i'),
('n')


SELECT (SELECT word FROM @t FOR XML PATH(''), TYPE).value('.', 'VARCHAR(50)')





share|improve this answer






















  • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:10










  • @Shnugo I've fixed it the special character case, thanks for noting that.
    – iSR5
    Nov 11 at 15:18










  • oh no... Did you try this? What about < and many more? See my answer for a general approach
    – Shnugo
    Nov 11 at 15:21










  • @Shnugo didn't know that trick. thanks
    – iSR5
    Nov 11 at 19:09

















up vote
1
down vote













One way you can try to use CTE recursive



CREATE TABLE T(
col varchar(50)
);

insert into t values ('train');

;with cte as (
select
1 startIdx,
len(col) maxlenght,
col
from t
UNION ALL
select
startIdx+1,
maxlenght,
col
from cte
where startIdx + 1 <= maxlenght
)

select substring(col,startIdx,1),col
from cte


sqlfiddle



Result



t
r
a
i
n



If you want to let char to tranpose a column with a word, you can try to use FOR XML



create TABLE T2 (c NVARCHAR(1))
INSERT T2 VALUES('t'),('r'),('a'),('i'),('n');

SELECT STUFF((SELECT c FROM T2 FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,0,'')


sqfiddle



RESULT



train





share|improve this answer






















  • @kkun Is that word for you?
    – D-Shih
    Nov 10 at 4:56










  • No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
    – Shnugo
    Nov 11 at 10:07

















up vote
1
down vote













Here's a way to do the first one, especially if you need to be able to group by an account number or other columns. I also included a commented out way to do the letters as comma separated in case that's a potential need. I've used that to display all service codes on an account.



DECLARE @t TABLE (
Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
Account BIGINT NOT NULL,
Value NVARCHAR(1) NOT NULL
);

INSERT INTO @t
(Account,Value)
VALUES
(1,'t'),(1,'r'),(1,'a'),(1,'i'),(1,'n'),
(2,'c'),(2,'a'),(2,'t');

SET NOCOUNT ON;

SELECT
a.Account,
REPLACE(REPLACE(v.Value,'<Value>',''),'</Value>','') AS Value
FROM (
SELECT DISTINCT t.Account
FROM @t AS t
) AS a
CROSS APPLY (
SELECT v.Value
FROM @t AS v
WHERE v.Account = a.Account
ORDER BY v.Id
FOR XML PATH ('')
) AS v (Value);

/*
SELECT *
FROM (
SELECT DISTINCT t.Account
FROM @t AS t
) AS a
CROSS APPLY (
SELECT
CASE ROW_NUMBER() OVER (ORDER BY v.Id)
WHEN 1 THEN ''
ELSE ', '
END + (v.Value)
FROM @t AS v
WHERE v.Account = a.Account
ORDER BY v.Id
FOR XML PATH ('')
) AS v (Value);
*/

SET NOCOUNT OFF;





share|improve this answer




















  • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:12










  • You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
    – Utrolig
    Nov 11 at 20:44

















up vote
0
down vote













There are many answers already, most of them working for this tiny case, but all of them not unproblematic:



  • The quirky update has side-effects one should really deeply know about

  • All XML-approaches get in troubles with escaped characters like & -> &amp;

  • All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode

Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:



DECLARE @Word1 NVARCHAR(100)='train';
DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML

DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));


--Tally (a set of running numbers)

--Best was an existing (physical) numbers table, but this shows the principles



WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
INSERT INTO @singleChars(WordId,Chr)
SELECT 1,SUBSTRING(@Word1,nmbr,1)
FROM tally;


--recursive

--This is a hidden RBAR approach (rather iterativ than recursive)



WITH cte AS
(
SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
UNION ALL
SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
FROM cte
WHERE cte.ChrPos+1<=LEN(@Word2)
)
INSERT INTO @singleChars(WordId,Chr)
SELECT 2,Chr
FROM cte
ORDER BY ChrPos;


--check the result



SELECT * FROM @singleChars;


--Now we re-concatenate the single characters.

--XML approach



SELECT (SELECT sc1.Chr 
FROM @singleChars sc1
WHERE sc.WordId=sc1.WordId
ORDER BY ID --never forget the "ORDER BY"!
FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
FROM @singleChars sc
GROUP BY WordId;


--STRING_AGG(needs v2017+)



SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
FROM @singleChars sc
GROUP BY WordId





share|improve this answer






















    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53235852%2ftranspose-a-word-written-vertically-to-horizontally-in-sql-server%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    5 Answers
    5






    active

    oldest

    votes








    5 Answers
    5






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    2
    down vote













    create table #a (word varchar(1)

    insert into #a values('T'),('R'),('A'),('I'),('N')

    declare @a varchar (10)=''
    select @a=@a+ word from #a
    select @a





    share|improve this answer
















    • 1




      The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
      – Shnugo
      Nov 11 at 10:05














    up vote
    2
    down vote













    create table #a (word varchar(1)

    insert into #a values('T'),('R'),('A'),('I'),('N')

    declare @a varchar (10)=''
    select @a=@a+ word from #a
    select @a





    share|improve this answer
















    • 1




      The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
      – Shnugo
      Nov 11 at 10:05












    up vote
    2
    down vote










    up vote
    2
    down vote









    create table #a (word varchar(1)

    insert into #a values('T'),('R'),('A'),('I'),('N')

    declare @a varchar (10)=''
    select @a=@a+ word from #a
    select @a





    share|improve this answer












    create table #a (word varchar(1)

    insert into #a values('T'),('R'),('A'),('I'),('N')

    declare @a varchar (10)=''
    select @a=@a+ word from #a
    select @a






    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 10 at 5:14









    Sarvesh Bandekar

    768




    768







    • 1




      The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
      – Shnugo
      Nov 11 at 10:05












    • 1




      The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
      – Shnugo
      Nov 11 at 10:05







    1




    1




    The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
    – Shnugo
    Nov 11 at 10:05




    The quirky update (SELECT @v=@v + ...) is nothing to recommend to new-comers... This article explains the backgrounds... Might be the fastest, but has some dangerous traps...
    – Shnugo
    Nov 11 at 10:05












    up vote
    2
    down vote













    A simpler approach :



    DECLARE 
    @t TABLE(word VARCHAR(50) )
    INSERT INTO @t VALUES
    ('t'),
    ('r'),
    ('a'),
    ('i'),
    ('n')


    SELECT (SELECT word FROM @t FOR XML PATH(''), TYPE).value('.', 'VARCHAR(50)')





    share|improve this answer






















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:10










    • @Shnugo I've fixed it the special character case, thanks for noting that.
      – iSR5
      Nov 11 at 15:18










    • oh no... Did you try this? What about < and many more? See my answer for a general approach
      – Shnugo
      Nov 11 at 15:21










    • @Shnugo didn't know that trick. thanks
      – iSR5
      Nov 11 at 19:09














    up vote
    2
    down vote













    A simpler approach :



    DECLARE 
    @t TABLE(word VARCHAR(50) )
    INSERT INTO @t VALUES
    ('t'),
    ('r'),
    ('a'),
    ('i'),
    ('n')


    SELECT (SELECT word FROM @t FOR XML PATH(''), TYPE).value('.', 'VARCHAR(50)')





    share|improve this answer






















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:10










    • @Shnugo I've fixed it the special character case, thanks for noting that.
      – iSR5
      Nov 11 at 15:18










    • oh no... Did you try this? What about < and many more? See my answer for a general approach
      – Shnugo
      Nov 11 at 15:21










    • @Shnugo didn't know that trick. thanks
      – iSR5
      Nov 11 at 19:09












    up vote
    2
    down vote










    up vote
    2
    down vote









    A simpler approach :



    DECLARE 
    @t TABLE(word VARCHAR(50) )
    INSERT INTO @t VALUES
    ('t'),
    ('r'),
    ('a'),
    ('i'),
    ('n')


    SELECT (SELECT word FROM @t FOR XML PATH(''), TYPE).value('.', 'VARCHAR(50)')





    share|improve this answer














    A simpler approach :



    DECLARE 
    @t TABLE(word VARCHAR(50) )
    INSERT INTO @t VALUES
    ('t'),
    ('r'),
    ('a'),
    ('i'),
    ('n')


    SELECT (SELECT word FROM @t FOR XML PATH(''), TYPE).value('.', 'VARCHAR(50)')






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 11 at 19:08

























    answered Nov 10 at 4:50









    iSR5

    1,338278




    1,338278











    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:10










    • @Shnugo I've fixed it the special character case, thanks for noting that.
      – iSR5
      Nov 11 at 15:18










    • oh no... Did you try this? What about < and many more? See my answer for a general approach
      – Shnugo
      Nov 11 at 15:21










    • @Shnugo didn't know that trick. thanks
      – iSR5
      Nov 11 at 19:09
















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:10










    • @Shnugo I've fixed it the special character case, thanks for noting that.
      – iSR5
      Nov 11 at 15:18










    • oh no... Did you try this? What about < and many more? See my answer for a general approach
      – Shnugo
      Nov 11 at 15:21










    • @Shnugo didn't know that trick. thanks
      – iSR5
      Nov 11 at 19:09















    Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:10




    Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:10












    @Shnugo I've fixed it the special character case, thanks for noting that.
    – iSR5
    Nov 11 at 15:18




    @Shnugo I've fixed it the special character case, thanks for noting that.
    – iSR5
    Nov 11 at 15:18












    oh no... Did you try this? What about < and many more? See my answer for a general approach
    – Shnugo
    Nov 11 at 15:21




    oh no... Did you try this? What about < and many more? See my answer for a general approach
    – Shnugo
    Nov 11 at 15:21












    @Shnugo didn't know that trick. thanks
    – iSR5
    Nov 11 at 19:09




    @Shnugo didn't know that trick. thanks
    – iSR5
    Nov 11 at 19:09










    up vote
    1
    down vote













    One way you can try to use CTE recursive



    CREATE TABLE T(
    col varchar(50)
    );

    insert into t values ('train');

    ;with cte as (
    select
    1 startIdx,
    len(col) maxlenght,
    col
    from t
    UNION ALL
    select
    startIdx+1,
    maxlenght,
    col
    from cte
    where startIdx + 1 <= maxlenght
    )

    select substring(col,startIdx,1),col
    from cte


    sqlfiddle



    Result



    t
    r
    a
    i
    n



    If you want to let char to tranpose a column with a word, you can try to use FOR XML



    create TABLE T2 (c NVARCHAR(1))
    INSERT T2 VALUES('t'),('r'),('a'),('i'),('n');

    SELECT STUFF((SELECT c FROM T2 FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)')
    ,1,0,'')


    sqfiddle



    RESULT



    train





    share|improve this answer






















    • @kkun Is that word for you?
      – D-Shih
      Nov 10 at 4:56










    • No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
      – Shnugo
      Nov 11 at 10:07














    up vote
    1
    down vote













    One way you can try to use CTE recursive



    CREATE TABLE T(
    col varchar(50)
    );

    insert into t values ('train');

    ;with cte as (
    select
    1 startIdx,
    len(col) maxlenght,
    col
    from t
    UNION ALL
    select
    startIdx+1,
    maxlenght,
    col
    from cte
    where startIdx + 1 <= maxlenght
    )

    select substring(col,startIdx,1),col
    from cte


    sqlfiddle



    Result



    t
    r
    a
    i
    n



    If you want to let char to tranpose a column with a word, you can try to use FOR XML



    create TABLE T2 (c NVARCHAR(1))
    INSERT T2 VALUES('t'),('r'),('a'),('i'),('n');

    SELECT STUFF((SELECT c FROM T2 FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)')
    ,1,0,'')


    sqfiddle



    RESULT



    train





    share|improve this answer






















    • @kkun Is that word for you?
      – D-Shih
      Nov 10 at 4:56










    • No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
      – Shnugo
      Nov 11 at 10:07












    up vote
    1
    down vote










    up vote
    1
    down vote









    One way you can try to use CTE recursive



    CREATE TABLE T(
    col varchar(50)
    );

    insert into t values ('train');

    ;with cte as (
    select
    1 startIdx,
    len(col) maxlenght,
    col
    from t
    UNION ALL
    select
    startIdx+1,
    maxlenght,
    col
    from cte
    where startIdx + 1 <= maxlenght
    )

    select substring(col,startIdx,1),col
    from cte


    sqlfiddle



    Result



    t
    r
    a
    i
    n



    If you want to let char to tranpose a column with a word, you can try to use FOR XML



    create TABLE T2 (c NVARCHAR(1))
    INSERT T2 VALUES('t'),('r'),('a'),('i'),('n');

    SELECT STUFF((SELECT c FROM T2 FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)')
    ,1,0,'')


    sqfiddle



    RESULT



    train





    share|improve this answer














    One way you can try to use CTE recursive



    CREATE TABLE T(
    col varchar(50)
    );

    insert into t values ('train');

    ;with cte as (
    select
    1 startIdx,
    len(col) maxlenght,
    col
    from t
    UNION ALL
    select
    startIdx+1,
    maxlenght,
    col
    from cte
    where startIdx + 1 <= maxlenght
    )

    select substring(col,startIdx,1),col
    from cte


    sqlfiddle



    Result



    t
    r
    a
    i
    n



    If you want to let char to tranpose a column with a word, you can try to use FOR XML



    create TABLE T2 (c NVARCHAR(1))
    INSERT T2 VALUES('t'),('r'),('a'),('i'),('n');

    SELECT STUFF((SELECT c FROM T2 FOR XML PATH(''), TYPE
    ).value('.', 'NVARCHAR(MAX)')
    ,1,0,'')


    sqfiddle



    RESULT



    train






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 10 at 4:35

























    answered Nov 10 at 4:30









    D-Shih

    24.4k61431




    24.4k61431











    • @kkun Is that word for you?
      – D-Shih
      Nov 10 at 4:56










    • No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
      – Shnugo
      Nov 11 at 10:07
















    • @kkun Is that word for you?
      – D-Shih
      Nov 10 at 4:56










    • No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
      – Shnugo
      Nov 11 at 10:07















    @kkun Is that word for you?
    – D-Shih
    Nov 10 at 4:56




    @kkun Is that word for you?
    – D-Shih
    Nov 10 at 4:56












    No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
    – Shnugo
    Nov 11 at 10:07




    No need for STUFF() here... This is only needed in cases, where you add a delimiter, which must be removed to avoid it at the string's beginning.
    – Shnugo
    Nov 11 at 10:07










    up vote
    1
    down vote













    Here's a way to do the first one, especially if you need to be able to group by an account number or other columns. I also included a commented out way to do the letters as comma separated in case that's a potential need. I've used that to display all service codes on an account.



    DECLARE @t TABLE (
    Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Account BIGINT NOT NULL,
    Value NVARCHAR(1) NOT NULL
    );

    INSERT INTO @t
    (Account,Value)
    VALUES
    (1,'t'),(1,'r'),(1,'a'),(1,'i'),(1,'n'),
    (2,'c'),(2,'a'),(2,'t');

    SET NOCOUNT ON;

    SELECT
    a.Account,
    REPLACE(REPLACE(v.Value,'<Value>',''),'</Value>','') AS Value
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT v.Value
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);

    /*
    SELECT *
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT
    CASE ROW_NUMBER() OVER (ORDER BY v.Id)
    WHEN 1 THEN ''
    ELSE ', '
    END + (v.Value)
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);
    */

    SET NOCOUNT OFF;





    share|improve this answer




















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:12










    • You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
      – Utrolig
      Nov 11 at 20:44














    up vote
    1
    down vote













    Here's a way to do the first one, especially if you need to be able to group by an account number or other columns. I also included a commented out way to do the letters as comma separated in case that's a potential need. I've used that to display all service codes on an account.



    DECLARE @t TABLE (
    Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Account BIGINT NOT NULL,
    Value NVARCHAR(1) NOT NULL
    );

    INSERT INTO @t
    (Account,Value)
    VALUES
    (1,'t'),(1,'r'),(1,'a'),(1,'i'),(1,'n'),
    (2,'c'),(2,'a'),(2,'t');

    SET NOCOUNT ON;

    SELECT
    a.Account,
    REPLACE(REPLACE(v.Value,'<Value>',''),'</Value>','') AS Value
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT v.Value
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);

    /*
    SELECT *
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT
    CASE ROW_NUMBER() OVER (ORDER BY v.Id)
    WHEN 1 THEN ''
    ELSE ', '
    END + (v.Value)
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);
    */

    SET NOCOUNT OFF;





    share|improve this answer




















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:12










    • You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
      – Utrolig
      Nov 11 at 20:44












    up vote
    1
    down vote










    up vote
    1
    down vote









    Here's a way to do the first one, especially if you need to be able to group by an account number or other columns. I also included a commented out way to do the letters as comma separated in case that's a potential need. I've used that to display all service codes on an account.



    DECLARE @t TABLE (
    Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Account BIGINT NOT NULL,
    Value NVARCHAR(1) NOT NULL
    );

    INSERT INTO @t
    (Account,Value)
    VALUES
    (1,'t'),(1,'r'),(1,'a'),(1,'i'),(1,'n'),
    (2,'c'),(2,'a'),(2,'t');

    SET NOCOUNT ON;

    SELECT
    a.Account,
    REPLACE(REPLACE(v.Value,'<Value>',''),'</Value>','') AS Value
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT v.Value
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);

    /*
    SELECT *
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT
    CASE ROW_NUMBER() OVER (ORDER BY v.Id)
    WHEN 1 THEN ''
    ELSE ', '
    END + (v.Value)
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);
    */

    SET NOCOUNT OFF;





    share|improve this answer












    Here's a way to do the first one, especially if you need to be able to group by an account number or other columns. I also included a commented out way to do the letters as comma separated in case that's a potential need. I've used that to display all service codes on an account.



    DECLARE @t TABLE (
    Id BIGINT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Account BIGINT NOT NULL,
    Value NVARCHAR(1) NOT NULL
    );

    INSERT INTO @t
    (Account,Value)
    VALUES
    (1,'t'),(1,'r'),(1,'a'),(1,'i'),(1,'n'),
    (2,'c'),(2,'a'),(2,'t');

    SET NOCOUNT ON;

    SELECT
    a.Account,
    REPLACE(REPLACE(v.Value,'<Value>',''),'</Value>','') AS Value
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT v.Value
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);

    /*
    SELECT *
    FROM (
    SELECT DISTINCT t.Account
    FROM @t AS t
    ) AS a
    CROSS APPLY (
    SELECT
    CASE ROW_NUMBER() OVER (ORDER BY v.Id)
    WHEN 1 THEN ''
    ELSE ', '
    END + (v.Value)
    FROM @t AS v
    WHERE v.Account = a.Account
    ORDER BY v.Id
    FOR XML PATH ('')
    ) AS v (Value);
    */

    SET NOCOUNT OFF;






    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 10 at 6:30









    Utrolig

    242211




    242211











    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:12










    • You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
      – Utrolig
      Nov 11 at 20:44
















    • Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
      – Shnugo
      Nov 11 at 10:12










    • You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
      – Utrolig
      Nov 11 at 20:44















    Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:12




    Not the best advise... At least not as a general how to approach... If there was a character in the input, which needs escaping, you'll get it back unescaped. Try this with an & in the input.
    – Shnugo
    Nov 11 at 10:12












    You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
    – Utrolig
    Nov 11 at 20:44




    You're correct on those characters. I was going based on the requirement of "transpose a column with a word". Not as dynamic as a column with any character, but there ya go. That STRING_AGG function is pretty cool though. Didn't know about that. Much cleaner.
    – Utrolig
    Nov 11 at 20:44










    up vote
    0
    down vote













    There are many answers already, most of them working for this tiny case, but all of them not unproblematic:



    • The quirky update has side-effects one should really deeply know about

    • All XML-approaches get in troubles with escaped characters like & -> &amp;

    • All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode

    Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:



    DECLARE @Word1 NVARCHAR(100)='train';
    DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML

    DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));


    --Tally (a set of running numbers)

    --Best was an existing (physical) numbers table, but this shows the principles



    WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
    INSERT INTO @singleChars(WordId,Chr)
    SELECT 1,SUBSTRING(@Word1,nmbr,1)
    FROM tally;


    --recursive

    --This is a hidden RBAR approach (rather iterativ than recursive)



    WITH cte AS
    (
    SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
    UNION ALL
    SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
    FROM cte
    WHERE cte.ChrPos+1<=LEN(@Word2)
    )
    INSERT INTO @singleChars(WordId,Chr)
    SELECT 2,Chr
    FROM cte
    ORDER BY ChrPos;


    --check the result



    SELECT * FROM @singleChars;


    --Now we re-concatenate the single characters.

    --XML approach



    SELECT (SELECT sc1.Chr 
    FROM @singleChars sc1
    WHERE sc.WordId=sc1.WordId
    ORDER BY ID --never forget the "ORDER BY"!
    FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
    FROM @singleChars sc
    GROUP BY WordId;


    --STRING_AGG(needs v2017+)



    SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
    FROM @singleChars sc
    GROUP BY WordId





    share|improve this answer


























      up vote
      0
      down vote













      There are many answers already, most of them working for this tiny case, but all of them not unproblematic:



      • The quirky update has side-effects one should really deeply know about

      • All XML-approaches get in troubles with escaped characters like & -> &amp;

      • All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode

      Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:



      DECLARE @Word1 NVARCHAR(100)='train';
      DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML

      DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));


      --Tally (a set of running numbers)

      --Best was an existing (physical) numbers table, but this shows the principles



      WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
      INSERT INTO @singleChars(WordId,Chr)
      SELECT 1,SUBSTRING(@Word1,nmbr,1)
      FROM tally;


      --recursive

      --This is a hidden RBAR approach (rather iterativ than recursive)



      WITH cte AS
      (
      SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
      UNION ALL
      SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
      FROM cte
      WHERE cte.ChrPos+1<=LEN(@Word2)
      )
      INSERT INTO @singleChars(WordId,Chr)
      SELECT 2,Chr
      FROM cte
      ORDER BY ChrPos;


      --check the result



      SELECT * FROM @singleChars;


      --Now we re-concatenate the single characters.

      --XML approach



      SELECT (SELECT sc1.Chr 
      FROM @singleChars sc1
      WHERE sc.WordId=sc1.WordId
      ORDER BY ID --never forget the "ORDER BY"!
      FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
      FROM @singleChars sc
      GROUP BY WordId;


      --STRING_AGG(needs v2017+)



      SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
      FROM @singleChars sc
      GROUP BY WordId





      share|improve this answer
























        up vote
        0
        down vote










        up vote
        0
        down vote









        There are many answers already, most of them working for this tiny case, but all of them not unproblematic:



        • The quirky update has side-effects one should really deeply know about

        • All XML-approaches get in troubles with escaped characters like & -> &amp;

        • All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode

        Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:



        DECLARE @Word1 NVARCHAR(100)='train';
        DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML

        DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));


        --Tally (a set of running numbers)

        --Best was an existing (physical) numbers table, but this shows the principles



        WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
        INSERT INTO @singleChars(WordId,Chr)
        SELECT 1,SUBSTRING(@Word1,nmbr,1)
        FROM tally;


        --recursive

        --This is a hidden RBAR approach (rather iterativ than recursive)



        WITH cte AS
        (
        SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
        UNION ALL
        SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
        FROM cte
        WHERE cte.ChrPos+1<=LEN(@Word2)
        )
        INSERT INTO @singleChars(WordId,Chr)
        SELECT 2,Chr
        FROM cte
        ORDER BY ChrPos;


        --check the result



        SELECT * FROM @singleChars;


        --Now we re-concatenate the single characters.

        --XML approach



        SELECT (SELECT sc1.Chr 
        FROM @singleChars sc1
        WHERE sc.WordId=sc1.WordId
        ORDER BY ID --never forget the "ORDER BY"!
        FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
        FROM @singleChars sc
        GROUP BY WordId;


        --STRING_AGG(needs v2017+)



        SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
        FROM @singleChars sc
        GROUP BY WordId





        share|improve this answer














        There are many answers already, most of them working for this tiny case, but all of them not unproblematic:



        • The quirky update has side-effects one should really deeply know about

        • All XML-approaches get in troubles with escaped characters like & -> &amp;

        • All approaches might get in troubles with larger strings, when there is no explicit sort order and the engine decides to go in parallel mode

        Here are two secure approaches to get single characters and two secure approaches to get the strings back. Try it out:



        DECLARE @Word1 NVARCHAR(100)='train';
        DECLARE @Word2 NVARCHAR(100)='this & that'; --the "&" can lead to troubles in XML

        DECLARE @singleChars TABLE(ID INT IDENTITY,WordId INT,Chr CHAR(1));


        --Tally (a set of running numbers)

        --Best was an existing (physical) numbers table, but this shows the principles



        WITH tally(nmbr) AS (SELECT TOP(LEN(@Word1)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) --the "FROM master..spt_values" is just to pick a table with a high number of rows... We do not need the values, just the set...
        INSERT INTO @singleChars(WordId,Chr)
        SELECT 1,SUBSTRING(@Word1,nmbr,1)
        FROM tally;


        --recursive

        --This is a hidden RBAR approach (rather iterativ than recursive)



        WITH cte AS
        (
        SELECT 1 AS ChrPos,SUBSTRING(@Word2,1,1) AS Chr
        UNION ALL
        SELECT cte.ChrPos +1, SUBSTRING(@Word2,cte.ChrPos+1,1)
        FROM cte
        WHERE cte.ChrPos+1<=LEN(@Word2)
        )
        INSERT INTO @singleChars(WordId,Chr)
        SELECT 2,Chr
        FROM cte
        ORDER BY ChrPos;


        --check the result



        SELECT * FROM @singleChars;


        --Now we re-concatenate the single characters.

        --XML approach



        SELECT (SELECT sc1.Chr 
        FROM @singleChars sc1
        WHERE sc.WordId=sc1.WordId
        ORDER BY ID --never forget the "ORDER BY"!
        FOR XML PATH(''),TYPE).value('.','nvarchar(100)') --read with `.value()` to ensure re-escaping
        FROM @singleChars sc
        GROUP BY WordId;


        --STRING_AGG(needs v2017+)



        SELECT STRING_AGG(sc.Chr,NULL) WITHIN GROUP(ORDER BY ID) --again the "ORDER BY"!
        FROM @singleChars sc
        GROUP BY WordId






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 11 at 10:50

























        answered Nov 11 at 10:44









        Shnugo

        47.7k72566




        47.7k72566



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53235852%2ftranspose-a-word-written-vertically-to-horizontally-in-sql-server%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Use pre created SQLite database for Android project in kotlin

            Darth Vader #20

            Ondo