Reputation: 463
I have a strange requirement where I need to convert a variable amount of rows into columns. I need help figuring out if this could be done in SQL, or if I should just write a program in a different language.
Let's assume I have a table Clients, which only holds minimal client data. Then, a table called Attributes, which names the different possible attributes (say, Phone number, address, etc). Finally, I have the third table, ClientAttributes which holds the two FKs and the value.
So for each client I have any number of attributes. A client can have zero, 1 or infinite phone numbers, zero, 1, or infinite addresses, and so on.
What I need is a table view of all that data. Client Name, Phone, Phone 2, Phone 3, ..., Address, Address 2, Address 3.... and so on. If a client has no such value, the value will be null. Obviously this means that the number of columns may be different every time the query is executed.
This needs to be compatible as far back as SQL Server 2008.
Could this be done purely in T-SQL, or should do this client-side by just dumping the data and let C# handle it? I could easily do it in C# but I'm not sure this could be done in SQL. SQL would be preferred because the dataset may be too large to fit in RAM.
Upvotes: 0
Views: 241
Reputation: 1267
This can be done in SQL through dynamic sql, if you need to. The basic theory for doing it for one item (I'll use phone) is as follows, you'd repeat this for each other grouping of columns you want. Note that no one will ever say that it is pretty.
Make parts of a dynamic SQL query string by looping from i = 1 to MaxRowNo. In each loop, you build up a selection string, and a join string. The selection string should add something like the following in each loop
Set @SelectStr = @SelectStr + 'P' + cast(i as varchar(10)) + '.Phone,';
The join string should add something like this in each loop
Set @JoinStr = @JoinStr + ' left outer join baseData P' + cast(i as varchar(10)) + ' on P' + cast(i as varchar(10)) + '.ClientID = C.ClientID and P' + cast(i as varchar(10)) + '.RowNo = ' + cast(i as varchar(10));
You would repeat the whole above process for addresses, and any other repeating groups of columns - make sure you don't double up on alias names. Then you would make up your final dynamic sql query by adding any fixed, unchanging parts of the query (client data), something like this
Set @FinalQuery = 'SELECT C.ClientID, C.ClientName, ' + @SelectStr + ' From Client C ' + @JoinStr
Your final built up query (assuming there is max three phones and two addresses as an example) would look something like this - then EXEC this string
SELECT C.ClientID, C.ClientName, --any other client stuff you need here
P1.Phone, P2.Phone, P3.Phone, A1.Address, A2.Address
From Client C
left outer join baseData P1 on P1.ClientID = C.ClientID and P1.RowNo = 1
left outer join baseData P2 on P2.ClientID = C.ClientID and P2.RowNo = 2
left outer join baseData P3 on P3.ClientID = C.ClientID and P3.RowNo = 3
left outer join baseAddr A1 on A1.ClientID = C.ClientID and A1.RowNo = 1
left outer join baseAddr A2 on A2.ClientID = C.ClientID and A2.RowNo = 2
Upvotes: 1