1

I have a SQL Server 2008 Express table like this:

rno   gid   uid   dat          origamt   disamt
-----------------------------------------------
1     AA    a     12-05-2016    200       210
2     AA    b     12-05-2016    300       305
3     AA    c     12-05-2016    150       116
4     BB    a     12-05-2016    120       125
5     BB    c     12-05-2016    130       136
6     CC    a     12-05-2016    112       115
7     CC    b     12-05-2016    135       136

and so on for different dates

I want to show it like this:

sno     dat       gid   a_orig   a_dis   b_orig  b_dis   c_orig   c_dis .....
1    12-05-2016   AA    200      210     300     305     150      116
2    12-05-2016   BB    120      125     0       0       130      136
3    12-05-2016   CC    112      115     135     136     0        0

NOTE: the values of uid are not fixed, they may vary dynamically, so, a_orig, a_dis, b_orig, b_dis, etc cannot be hardcoded into SQL.

NOTE: around 300 rows are expected on each date due to the cartesian product of gid and uid. and I will search datewise by implementing the LIKE clause since datatype of dat column is varchar(50).

Note: I would prefer datatype of origamt and disamt to be varchar(50) instead of Decimal(18, 0) but it is not compulsion.

I have tried to use PIVOT by taking reference from several articles posted here on stackoverflow and other website but couldn't get the work done completely.

Here is what I tried and got almost fine results with fixed uid and only fetched origamt:

select *
from 
(
  select gid, uid, dat, origamt
  from vamounts
) as src
pivot
(
  sum(origamt)
  for uid IN ( a, b )
) as piv;

Kindly help me with the least bulky possible solution for this problem. I will prefer least lines of code and least complexity.

Alex
  • 937
  • 3
  • 20
  • 44
Shobhit Gupta
  • 690
  • 4
  • 13
  • Please provide some sql statement and sample of your table structure with some 'dummy' data. This question is not well designed. – Mitrucho May 31 '16 at 07:54
  • 1
    ***WHY*** would you want to store an *obviously* numeric value like `origamt` as a string?? **Don't do this!** Use the **most appropriate** datatype always - if you need to store numbers - use a **numeric** data type - not strings!! – marc_s May 31 '16 at 08:02
  • Hi Mitrucho. Sorry for the issue. Now I have redesigned (May be I have) the post for clarity. – Shobhit Gupta May 31 '16 at 08:05
  • Hi marc_s. Thanks for your reply. as i said that is not the major issue for me. The main issue if the pivot view. – Shobhit Gupta May 31 '16 at 08:07
  • Unpivot then Pivot - there are literally dozens of similar questions on SO. – Liesel May 31 '16 at 08:08
  • Hi Les H. As i have mentioned in the description that I have gone through many posts and tried to implement the code that I have mentioned but, couldnt find exact solution for my question, thats why i have posted another question. If other posts were my answer, i would have spent time on designing this post – Shobhit Gupta May 31 '16 at 08:10

3 Answers3

2

Errr, no. You can't generate your desired table using SQL. This isn't a valid pivot table.

"the values of uid are not fixed, they may vary dynamically, so, a_orig, a_dis, b_orig, b_dis, etc cannot be hardcoded into SQL."

Sorry, this is also not possible. You must specify the exact values to be placed as the column headers. Whenever you write a SELECT statement, you must specify the names of the columns (fields) which you'll be returning. There's no way around this.

However, below are the steps required to create a "valid" SQL Server pivot table from your data:

enter image description here

I've got to admit, when I recently had to write my first PIVOT in SQL Server, I also Googled like mad, but didn't understand how to write it.

However, I eventually worked out what you need to do, so here's the step-by-step guide that you won't find anywhere else..!

(Readers can easily adapt these instructions, to use with your own data !)

1. Create your sample data

If you expect readers to reply to your Question, you should at least give them the SQL to create your sample data, so they have something to work off.

So, here's how I would create the data shown in your question:

CREATE TABLE tblSomething
(
    [gid] nvarchar(100),
    [uid] nvarchar(100),
    [dat] datetime,
    [origamt] int,
    [disamt] int
)
GO

INSERT INTO tblSomething VALUES ('AA', 'a', '2016-05-12', 200, 210)
INSERT INTO tblSomething VALUES ('AA', 'b', '2016-05-12', 300, 305)
INSERT INTO tblSomething VALUES ('AA', 'c', '2016-05-12', 150, 116)
INSERT INTO tblSomething VALUES ('BB', 'a', '2016-05-12', 120, 125)
INSERT INTO tblSomething VALUES ('BB', 'c', '2016-05-12', 130, 136)
INSERT INTO tblSomething VALUES ('CC', 'a', '2016-05-12', 112, 115)
INSERT INTO tblSomething VALUES ('CC', 'b', '2016-05-12', 135, 136)
GO

2. Write a SQL Query which returns exactly three columns

The first column will contain the values which will appear in your PIVOT table's left-hand column.

The second column will contain the list of values which will appear on the top row.

The values in the third column will be positioned within your PIVOT table, based on the row/column headers.

Okay, here's the SQL to do this:

SELECT [gid], [uid], [origamt]
FROM tblSomething

This is the key to using a PIVOT. Your database structure can be as horribly complicated as you like, but when using a PIVOT, you can only work with exactly three values. No more, no less.

So, here's what that SQL will return. Our aim is to create a PIVOT table containing (just) these values:

enter image description here

3. Find a list of distinct values for the header row

Notice how, in the pivot table I'm aiming to create, I have three columns (fields) called a, b and c. These are the three unique values in your [uid] column.

So, to get a comma-concatenated list of these unique values, I can use this SQL:

DECLARE @LongString nvarchar(4000)

SELECT @LongString = COALESCE(@LongString + ', ', '') + '[' + [uid] + ']' 
FROM [tblSomething]
GROUP BY [uid]

SELECT @LongString AS 'Subquery'

When I run this against your data, here's what I get:

enter image description here

Now, cut'n'paste this value: we'll need to place it twice in our overall SQL SELECT command to create the pivot table.

4. Put it all together

This is the tricky bit.

You need to combine your SQL command from Step 2 and the result from Step 3, into a single SELECT command.

Here's what your SQL would look like:

SELECT [gid],
        --  Here's the "Subquery" from part 3
        [a], [b], [c]
FROM (
    --  Here's the original SQL "SELECT" statement from part 2
    SELECT [gid], [uid], [origamt]
    FROM tblSomething

) tmp ([gid], [uid], [origamt])
pivot (
    MAX([origamt]) for [uid] in (
        --  Here's the "Subquery" from part 3 again
        [a], [b], [c]
    )
) p

... and here's a confusing image, which shows where the components come from, and the results of running this command.

enter image description here

As you can see, the key to this is that SELECT statement in Step 2, and putting your three chosen fields in the correct place in this command.

And, as I said earlier, the columns (fields) in your pivot table come from the values obtained in step 3:

[a], [b], [c]

You could, of course, use a subset of these values. Perhaps you just want to see the PIVOT values for [a], [b] and ignore [c].

Phew !

So, that's how to create a pivot table out of your data.

I will prefer least lines of code and least complexity.

Yeah, good luck on that one..!!!

5. Merging two pivot tables

If you really wanted to, you could merge the contents of two such PIVOT tables to get the exact results you're looking for.

This is easy enough SQL for Shobhit to write himself.

enter image description here

Mike Gledhill
  • 27,846
  • 7
  • 149
  • 159
0

A join might help

declare @t table (rno int,  gid varchar(2),  uid varchar(1),  dat varchar(10), origamt int,  disamt int)
insert into @t
values 
(1,     'AA',    'a',     '12-05-2016',    200,       210),
(2 ,    'AA',    'b',     '12-05-2016',    300,       305),
(3  ,   'AA',    'c',     '12-05-2016',    150,       116),
(4 ,    'BB',    'a',     '12-05-2016',    120,       125),
(5 ,    'BB',    'c',     '12-05-2016',    130,       136),
(6 ,    'CC',    'a',     '12-05-2016',    112,       115),
(7 ,    'CC',    'b',     '12-05-2016',    135,       136)

    select   -- piv.*,piv2.*
             piv.gid,piv.dat
            ,piv.a as a_org
            ,piv2.a as a_dis
            ,piv.b as b_org
            ,piv2.b as b_dis
            ,piv.c as c_org
            ,piv2.c as c_dis
    from 
    (
      select gid, uid, dat, origamt
      from  @t
    ) as src
    pivot
    (
      sum(origamt)
      for uid IN ([a],[b],[c] )
    ) as piv
    join 
    (select piv2.*
    from 
    (
      select gid, uid, dat, disamt
      from  @t
    ) as src
    pivot
    (
      sum(disamt)
      for uid IN ([a],[b],[c] )
    ) as piv2
    ) piv2
    on piv2.gid = piv.gid and piv2.dat = piv.dat 

This is a POC you would have to use dynamic sql to deal with the variable number of uids. Let me know if you don't know how to use dynamic SQL and I'll work up an example for you.

P.Salmon
  • 17,104
  • 2
  • 12
  • 19
  • Hi. The solution seems much closer but with a major issue. Every gid is shown in resultset but every alternate record is repeated with null values. Eg. a comes once, b comes twice (once with value, 2nd with NULLs), c comes once, d comes twice (once with value, 2nd with NULLs) ..... an so on although there is no repetition in the source table. Yes, i need the columns to be added dynamically without which I cannot use this solution however, I will fire this query from frontend in asp.net and show it in a gridview so, i can pass a string variable as a parameter. Kindly reply. – Shobhit Gupta May 31 '16 at 09:59
  • Every select creates a temporary table- ergo nulls are generated if there is no result (normal for SQL). If you want to substitute 0 for nulls then use ISNULL. – P.Salmon May 31 '16 at 11:07
  • Hey Mike, I tried the solution but it didn't work for me. Here, it returned multiple rows for each gid. my gid has 5 instances in table with 5 different uids and in final result, it gave me 5 rows for gid. Please tell me how can i post a snapshot. I want to share the outcomes. – Shobhit Gupta May 31 '16 at 11:07
  • Ok Mike, no problems, you solution didn't work either. – Shobhit Gupta May 31 '16 at 11:08
0

You need dynamic SQL for this stuff.

At first create table with your data:

CREATE TABLE #temp (
    rno int, 
    gid nvarchar(10), 
    [uid] nvarchar(10), 
    dat date, 
    origamt int, 
    disamt int 
) 

INSERT INTO #temp VALUES 
(1,     'AA',    'a',     '12-05-2016',    200,       210),
(2,     'AA',    'b',     '12-05-2016',    300,       305),
(3,     'AA',    'c',     '12-05-2016',    150,       116),
(4,     'BB',    'a',     '12-05-2016',    120,       125),
(5,     'BB',    'c',     '12-05-2016',    130,       136),
(6,     'CC',    'a',     '12-05-2016',    112,       115),
(7,     'CC',    'b',     '12-05-2016',    135,       136)

And then declare variables with columns:

DECLARE @columns nvarchar(max), @sql nvarchar(max), @columns1 nvarchar(max), @columnsN nvarchar(max)
--Here simple columns like [a],[b],[c] etc
SELECT @columns =STUFF((SELECT DISTINCT ','+QUOTENAME([uid]) FROM #temp FOR XML PATH('')),1,1,'')
--Here with ISNULL operation ISNULL([a],0) as [a],ISNULL([b],0) as [b],ISNULL([c],0) as [c]
SELECT @columnsN = STUFF((SELECT DISTINCT ',ISNULL('+QUOTENAME([uid])+',0) as '+QUOTENAME([uid]) FROM #temp FOR XML PATH('')),1,1,'')
--Here columns for final table orig.a as a_orig, dis.a as a_dis,orig.b as b_orig, dis.b as b_dis,orig.c as c_orig, dis.c as c_dis
SELECT @columns1 = STUFF((SELECT DISTINCT ',orig.'+[uid] + ' as ' +[uid]+ '_orig, dis.'+[uid] + ' as ' +[uid]+ '_dis' FROM #temp FOR XML PATH('')),1,1,'')

And main query:

SELECT @sql = '
SELECT  orig.gid,
        orig.dat,
        '+@columns1+'
FROM (
        SELECT gid, dat, '+@columnsN+'
        FROM (
            SELECT gid, [uid], LEFT(dat,10) as dat, origamt
            FROM #temp
            ) as p
        PIVOT (
        SUM(origamt) FOR [uid] in ('+@columns+')
        ) as pvt
    ) as orig
LEFT JOIN (
        SELECT gid, dat, '+@columnsN+'
        FROM (
            SELECT gid, [uid], LEFT(dat,10) as dat, disamt
            FROM #temp
            ) as p
        PIVOT (
        SUM(disamt) FOR [uid] in ('+@columns+')
        ) as pvt
    ) as dis
    ON dis.gid = orig.gid and dis.dat = orig.dat'

EXEC(@sql)

Output:

gid dat         a_orig  a_dis   b_orig  b_dis   c_orig  c_dis
AA  2016-12-05  200     210     300     305     150     116
BB  2016-12-05  120     125     0       0       130     136
CC  2016-12-05  112     115     135     136     0       0
gofr1
  • 15,741
  • 11
  • 42
  • 52
  • Hi. I will be formatting the string with the required fields before executing it through asp.net with C#. So, kindly tell me what is the expected value set of columns1, columnsN and columns so that I can stuff them through string.Format() in the frontend. – Shobhit Gupta May 31 '16 at 10:52
  • Expected values are demonstrated in comments for each variable. – gofr1 May 31 '16 at 11:03
  • What same issue? Why you are replacing variables? You can put all this query in stored procedure - and then get table from it. The answer on you main question - how to get this data with dynamic (no-need-to-count-columns) with pivot, and `0` instead of `NULL`s is given. If you have another questions - please ask a new question. – gofr1 May 31 '16 at 11:18
  • Hi 'gofr1'. I have create the stored procedure and executed it. results are wonderful but there is one issue. please help out. I have 5 instances of gid with different uid. What happens is when I execute the stored procedure, i get a set of 5 rows wherein each row corresponds to the origamt and dis for each uid. Please tell me how i can upload a snapshot here to be more clear. – Shobhit Gupta May 31 '16 at 11:28
  • You may have as much `gid` as you need, in final query it will show distinct `gid` and `dat` with pivoting data. If you have `gid`s with same `[uid]`s and `dat` you should use `SUM` instead of `MAX`. – gofr1 May 31 '16 at 11:36
  • Hi 'gofr1'. Here is th elink to the image in which i have shown original table as well as the result i have achieved.: http://imageshack.com/a/img923/5899/TreWLV.png ... I have also tried SUM instead of MAX, but the results are same – Shobhit Gupta May 31 '16 at 11:44
  • `CAST([dat] as date)` in both `select` statement which are joining. I add this to the final query. – gofr1 May 31 '16 at 11:48
  • Hey 'gofr1'. please go through the snapshot and please why there are multiple rows for each vid (the gid) while in your resultset, there is only 1 row for each gid. Where I am doing a mistake? snapshot is at: http://imageshack.com/a/img923/5899/TreWLV.png – Shobhit Gupta May 31 '16 at 11:56
  • Due `datetime` value of `dat` - cast it to `date` as I wrote in my last comment. – gofr1 May 31 '16 at 11:58
  • Oh !!! I am so sorry. My dat column is in varchar(50) while yours is in datetime. OK. But, i tried the following updation and whereever I tried casting, I got this error: Conversion failed when converting date and/or time from character string. I tried this in the last line of stored procedure: ON dis.vid = orig.vid and CAST(dis.dat as date) = CAST(orig.dat as date)' – Shobhit Gupta May 31 '16 at 12:07
  • If it is `varchar` - just take `LEFT(dat,10)` – gofr1 May 31 '16 at 12:09
  • 'gofr1', i know now it is getting frustrating for you too, but, I am too new to this pivoting concept with limited hands-on with stored procedures and datetime handlings. I have tried CAST() and LEFT() on every existence of 'dat' and kept on getting errors only. I cannot change the datatype of column to datetime. Humble request is to please tell me where should I at which exact line of code I should replace the LEFT() to close this work. Sorry for this much overhead, please help. – Shobhit Gupta May 31 '16 at 12:19
  • I put it to the answer, please check! – gofr1 May 31 '16 at 12:21
  • Yeah !!! BINGO ... This worked perfectly ... Thanks a lot gofr1 for your quality time. 10+ stars for the help... You saved my life ... i have upvoted although, my reputation points arent 15 yet, so it may not be visible. but a very big Thumbs Up :) Thank a lot again – Shobhit Gupta May 31 '16 at 12:29
  • I just was downvoted... strange. Mark answer as accepted, if it helps you. Thanks! Nice coding! – gofr1 May 31 '16 at 12:31
  • Ok, I have marked the answer as accepted too ... That solved all my issues, Thanks. – Shobhit Gupta May 31 '16 at 12:35