Splitting values in one column to 2 columns - sql

I need to do the following in sql.
table 1:
Year Client Investment
1999 X 100
1999 Y 200
2000 X 1000
2000 Y 2000
I want to display it in below format for my report:
Client 1999Year 2000Year
X 100 1000
Any idea how to do the above?
I am using sql server 2008
Please help.

This type of data transformation is known an a PIVOT. Some database products have a function that will turn the data from rows to columns.
You can use an aggregate function with a CASE expression in any database:
select client,
sum(case when year = 1999 then investment end) Year_1999,
sum(case when year = 2000 then investment end) Year_2000
from yourtable
group by client
See SQL Fiddle with Demo
Since you are using SQL Server 2008, you can use the PIVOT function to transform the data into columns:
select *
from
(
select client,
'Year_'+cast(year as varchar(4)) year,
investment
from yourtable
) src
pivot
(
sum(investment)
for year in (Year_1999, Year_2000)
) piv
See SQL Fiddle with Demo.
The other queries will work great if you have a known number of year values, but if you have an unknown number, then you will want to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Year_'+cast(year as varchar(4)))
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT client,' + #cols + ' from
(
select client,
''Year_''+cast(year as varchar(4)) year,
investment
from yourtable
) x
pivot
(
sum(investment)
for year in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
This can also be done by joining on the table multiple times:
select t1.client,
t1.investment Year_1999,
t2.investment Year_2000
from yourtable t1
left join yourtable t2
on t1.client = t2.client
and t2.year = 2000
where t1.year = 1999
See SQL Fiddle with Demo
All queries give the result:
| CLIENT | YEAR_1999 | YEAR_2000 |
----------------------------------
| X | 100 | 1000 |
| Y | 200 | 2000 |

There are many possible solutions for this. One is by using MAX() and CASE,
SELECT Client,
MAX(CASE WHEN YEAR = 1999 THEN Investment END) [1999Year],
MAX(CASE WHEN YEAR = 2000 THEN Investment END) [2000Year]
FROM TableName
WHERE Client = 'X'
GROUP BY Client
SQLFiddle Demo
or using PIVOT function
SELECT Client,
[1999] AS [1999YEAR],
[2000] AS [2000YEAR]
FROM
(
SELECT YEAR, CLient, Investment
FROM TableName
WHERE Client = 'X'
) pvt
PIVOT
(
MAX(InvestMent)
FOR YEAR IN ([1999],[2000])
) s
SQLFiddle Demo

Related

SQL query to duplicate each row 12 times

I have a table which has columns site,year and sales . this table is unique on site+year eg
site year sales
-------------------
a 2012 50
b 2013 100
a 2006 35
Now what I want to do is make this table unique on site+year+month. Thus each row gets duplicated 12 times, a month column is added which is labelled from 1-12 and the sales values get divided by 12 thus
site year month sales
-------------------------
a 2012 1 50/12
a 2012 2 50/12
...
a 2012 12 50/12
...
b 2013 1 100/12
...
a 2006 12 35/12
I am doing this on python currently and it works like a charm, but I need to do this in SQL (ideally PostgreSQL since I will be using this as a datasource for tableau)
It would be very helpful if someone can provide the explanations with the solution as well, since I am a novice at this
You can use generate_series() for that
select t.site, t.year, g.month, t.sales / 12
from the_table t
cross join generate_series(1,12) as g (month)
order by t.site, t.year, g.month;
If the column sales is an integer, you should cast that to a numeric to avoid the integer division: t.sales::numeric / 12
Online example: http://rextester.com/GUWPI39685
Try this approach (For T-SQL - MS SQL) :
DECLARE #T TABLE
(
[site] VARCHAR(5),
[year] INT,
sales INT
)
INSERT INTO #T
VALUES('A',2012,50),('B',2013,100),('C',2006,35)
;WITH CTE
AS
(
SELECT
MonthSeq = 1
UNION ALL
SELECT
MonthSeq = MonthSeq+1
FROM CTE
WHERE MonthSeq <12
)
SELECT
T.[site],
T.[year],
[Month] = CTE.MonthSeq,
sales = T.[sales]/12
FROM CTE
CROSS JOIN #T T
ORDER BY T.[site],CTe.MonthSeq

SQL Server: Select multiple records in one select statement

In a query like this one:
SELECT *
FROM `Order`
WHERE `CustID` = '1'
My results are displayed like so:
| CustID| Order |
-----------------
| 1 | Order1|
| 1 | Order2|
| 1 | Order3|
-----------------
How do I write SQL statement, to get a result like this one?:
| CustID| Order |
---------------------------------
| 1 | Order1, Order2, Order3|
---------------------------------
In mySQL it's possible with Group_Concat, but in SQL Server it gives error like syntax error or some.
Use xml path (see fiddle)
SELECT distinct custid, STUFF((SELECT ',' +[order]
FROM table1 where custid = t.custid
FOR XML PATH('')), 1, 1, '')
FROM table1 t
where t.custid = 1
STUFF replaces the first , with an empty string, i.e. removes it. You need a distinct otherwise it'll have a match for all orders since the where is on custid.
FOR XML
PATH Mode
STUFF
You can use Stuff function and For xml clause like this:
SELECT DISTINCT CustId, STUFF((
SELECT ','+ [Order]
FROM [Order] T2
WHERE T2.CustId = T1.CustId
FOR XML PATH('')
), 1, 1, '')
FROM [Order] T1
fiddle here
Note: Using order as a table name or a column name is a very, very bad idea. There is a reason why they called reserved words reserved.
See this link for my favorite way to avoid such things.
try this.
Change table name and column names for what you need;
SELECT custID,
LISTAGG(Order, ', ') WITHIN GROUP (ORDER BY Order) text
FROM table_name
GROUP BY custID
edit for MSSQL . You should use group_concat function.
SELECT custID, GROUP_CONCAT(Order)
FROM table_name
WHERE CustID = 1
GROUP BY custID;

Single Column as N Rows [duplicate]

This question already has answers here:
Efficiently convert rows to columns in sql server
(5 answers)
Closed 7 years ago.
I have a large table with 3 columns as follows:
Invoice Product Color
1 Pant Red
1 Pant Black
1 Shirt Green
2 Pant White
2 Pant Black
2 Pant Blue
I'd like to group on Invoice & Product and then have all unique Color values appear on the related grouped record as follows:
Invoice Product Colour1 Colour2 Colour3
1 Pant Red Black
1 Shirt Green
2 Pant White Black Blue
Is this possible in SQL Server?
It is possible in SQL Server -- if you know that there are three color columns. If there are a variable number, then it is still possible, but it requires dynamic SQL.
I would approach this using conditional aggregation:
select invoice, product,
max(case when seqnum = 1 then colour end) as colour1,
max(case when seqnum = 2 then colour end) as colour2,
max(case when seqnum = 3 then colour end) as colour3
from (select t.*,
row_number() over (partition by invoice, product order by (select nULL)) as seqnum
from table t
) t
group by invoice, product;
To convert rows into columns, you need to use Pivot in Sql Server. If you know the number of columns in advance, you can use pivoting statically as the answer suggested by Gordin Linoff.
Sometimes, the number of colors may vary(in your example there are only 3 colors). In such case, you cannot hardcode the column names. For that first of all you need to get columns names dynamically into a variable.
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + COLUMNNAME + ']', '[' + COLUMNNAME + ']')
FROM
(
SELECT DISTINCT
'COLOR'+CAST(ROW_NUMBER() OVER(PARTITION BY INVOICE,PRODUCT ORDER BY (SELECT 0)) AS VARCHAR(10)) COLUMNNAME
FROM #TEMP
) PV
ORDER BY COLUMNNAME
Now the above variable have values of columns as Comma Separated Values which can be used with IN operator dynamically for the below query. Since your table doesn't have values like COLOR1, COLOR2 etc, I have provided logic to get column names for each INVOICE and its PRODUCT using PARTITION BY clause.
DECLARE #query NVARCHAR(MAX)
SET #query = '-- This outer query forms your pivoted result
SELECT * FROM
(
-- Source data for pivoting
SELECT DISTINCT INVOICE,PRODUCT,COLOR,
''COLOR''+CAST(ROW_NUMBER() OVER(PARTITION BY INVOICE,PRODUCT ORDER BY (SELECT 0)) AS VARCHAR(10)) COLUMNNAME
FROM #TEMP
) x
PIVOT
(
--Defines the values in each dynamic columns
MIN(COLOR)
-- Get the names from the #cols variable to show as column
FOR COLUMNNAME IN (' + #cols + ')
) p
ORDER BY INVOICE;'
EXEC SP_EXECUTESQL #query
Click here to view result

Stored procedure that returns a table with a matrix

I've got the following 3 tables.
Staff:
Name, LastName, SSN
Course Instance:
Course, Year, Period
Attended By:
Course, SSN, Year, Period, Hours
I'm trying to make a stored procedure that returns a table with a matrix over the first 4 course instances in a given year (taken as parameter to the procedure).
The returned matrix needs to look something like this:
Name | LastName | Course | Course | Course | Course
The four different Course are the first four courses from a given year, so what I've got now is this code to find those courses:
DECLARE myCursor CURSOR FOR
SELECT top 4 Course
FROM Course Instance
WHERE Year = #year
ORDER by Period
The rest of the matrix should just get the values from the tables above somehow, basically every Staff member that has Attended any of the Courses should be in the matrix with correct information. And under each Course column I want the number of hours from the attending staff.
Sample tables
Staff:
Name | LastName | SSN
Steve Lastname 234
Pete Steven 132
Course Instance:
Course | Year | Period
DVA123 2013 1
DVA222 2014 2
Attended by:
Course | SSN | Year | Period | Hours
DVA123 234 2013 1 200
DVA222 132 2014 2 50
Expected output from this:
Name | LastName | DVA123 | DVA222 | nothing | nothing
Pete Steven 200
Steve Lastname 50
I am unsure of exactly how CourseInstance links to attended by, but you can achive this using PIVOT and ROW_NUMBER:
WITH Data AS
( SELECT s.Name,
s.LastName,
s.SSN,
ci.Course,
CourseNum = ROW_NUMBER() OVER(PARTITION BY s.SSN ORDER BY ci.Period)
FROM CourseInstance ci
INNER JOIN AttendedBy a
ON a.Course = ci.Course
AND a.Year = ci.Year
AND a.Period = ci.Period
INNER JOIN Staff s
ON s.SSN = a.SSN
WHERE ci.Year = #year
)
SELECT pvt.Name,
pvt.LastName,
Course1 = pvt.[1],
Course2 = pvt.[2],
Course3 = pvt.[3],
Course4 = pvt.[4]
FROM DATA
PIVOT
( MAX(Course)
FOR CourseNum IN ([1], [2], [3], [4])
) pvt;
It uses MAX(Course), but this is not really relevant since CourseNum is unique to each SSN you are only ever selecting the max of one row anyway. You could just as easily use MIN.
I do not advocate this approach one bit, but nevertheless, it will get you started should you chose the dynamic SQL approach instead of doing this in the presentation layer:
DECLARE #Courses NVARCHAR(MAX) = STUFF((SELECT TOP 4 ',' + QUOTENAME(course)
FROM ( SELECT course, Period, SortOrder = 0
FROM CourseInstance
WHERE Year = #Year
UNION ALL
-- THIS IS REQUIRED TO FILL GAPS WHERE THERE AREN'T ENOUGH COURSES
SELECT TOP 4
'Nothing' + CAST(ROW_NUMBER() OVER(ORDER BY object_id) AS CHAR(1)),
0,
1
FROM sys.all_objects
) t
ORDER BY SortOrder, Period, course
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
DECLARE #SQL NVARCHAR(MAX) =
' SELECT Name, LastName, ' + #Courses + '
FROM ( SELECT s.Name, s.LastName, s.SSN, ci.Course, a.Hours
FROM AttendedBy a
INNER JOIN Staff s
ON s.SSN = a.SSN
INNER JOIN CourseInstance ci
ON a.Course = ci.Course
AND a.Year = ci.Year
AND a.Period = ci.Period
) d
PIVOT
( SUM(Hours)
FOR Course IN (' + #Courses + ')
) pvt;';
EXECUTE SP_EXECUTESQL #SQL;

SQL: Lookup table rows into columns for reporting purposes

I have the following two table data structure for dealing with custom user fields:
[UserFieldID] [UserFieldName]
-------------------------------
1 Location
2 Color
[UserID] [UserFieldID] [UserFieldValue]
----------------------------------------
1 1 Home
1 2 Orange
2 1 Office
2 2 Red
This allows any number of fields to be defined (globally) and users to have values for those custom fields. I need to figure out how to display this information for reporting purposes as part of a pre-existing report, in the following format:
UserID ... Location Color
----------------------------------------------------
1 Home Orange
2 Office Red
I know this probably involves using either PIVOT or UNPIVOT, but try as I might, they just confuse me.
Thanks in advance
There are several different ways that you can get the result, you can use an aggregate function with a CASE expression or you can use the PIVOT function to get this. Based on your comment that any number of fields can be defined, it sounds like you will need to use dynamic SQL to get the final result. Before writing a dynamic SQL version, I would always start with a static or hard-coded version of the query, then convert it to dynamic SQL.
Besides using these methods, I would also recommend using the windowing function row_number() to generate a unique value for each combination of userid and fieldname. Since you are pivoting string values, then you have to use either the max/min aggregate function which will return only one value for each fieldname, by adding the row_number you will be able to return multiple combinations of Location, etc for each user.
If you were using an aggregate function with a CASE expression the query would be:
select
userid,
max(case when userfieldname = 'Location' then userfieldvalue end) location,
max(case when userfieldname = 'Color' then userfieldvalue end) Color
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) d
group by userid, seq
order by userid;
See SQL Fiddle with Demo
If you were using PIVOT, the hard-coded version of the query would be:
select userid, Location, Color
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) d
pivot
(
max(userfieldvalue)
for userfieldname in (Location, Color)
) p
order by userid;
See SQL Fiddle with Demo.
Once you have the correct logic you can convert the PIVOT to dynamic SQL to be executed:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(UserFieldName)
from UserFields
group by UserFieldName, userfieldId
order by userfieldid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid, ' + #cols + '
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) x
pivot
(
max(userfieldvalue)
for userfieldname in (' + #cols + ')
) p
order by userid'
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| USERID | LOCATION | COLOR |
|--------|----------|--------|
| 1 | Home | Orange |
| 1 | Office | (null) |
| 2 | Office | Red |