How do I pivot this dataset into columns for each question - sql

sorry if this has already been posted but I've been through umpteen posts on pivoting the past day and still havn't been able to get the result i want.
Background:
In short, I am developing a set of tables that will store a questionnaire dynamically.
I wont go into detail of it probably isnt relative.
I basically want to query the table that stores the user input for a set question.
These questions branch off each other allowing me to show columns and rows per question etc.
Anyway this query:
SELECT qr.*, Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
Produces this result set :
ID FormRecordId QuestionId ParentQuestionRecordId Value Question
---------------------------------------------------------------------------------------
2 1 31 1 Consultancy Eligible project costs
3 1 32 2 NULL Date
4 1 33 2 25000 Cash Costs £
5 1 34 2 NULL In Kind Costs £
6 1 35 2 25000 Total Costs
7 1 31 1 Orchard day x2 Eligible project costs
8 1 32 7 NULL Date
9 1 33 7 15000 Cash Costs £
10 1 34 7 NULL In Kind Costs £
11 1 35 7 15000 Total Costs
I basically want to Pivot(I think) these rows to look like so:
Eligible project costs Date Cash Costs £ In Kind Costs Total Costs
--------------------------------------------------------------------------------
Consultancy NULL 25000 NULL 25000
Orchard day x2 NULL 15000 NULL 15000
I have tried:
SELECT [Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs]
FROM
(
SELECT qr.*, Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
)pvt
PIVOT
(
MIN(Value)
FOR Question IN
([Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs])
)pivotTable
but this returns each column on a seperate row:
Eligible project costs Date Cash Costs £ In Kind Costs Total Costs
--------------------------------------------------------------------------------
Consultancy NULL NULL NULL NULL
NULL NULL NULL NULL NULL
NULL NULL 25000 NULL NULL
NULL NULL NULL NULL NULL
NULL NULL NULL NULL 25000
So that's as close as i have managed to get with it, i was wondering if you guys/girls could help me out :)
Thanks!

Try the following changes to your script (strikethrough = deleted, bold = added):
SELECT [Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs]
FROM
(
SELECT qr.*,
grp = ROW_NUMBER() OVER (PARTITION BY qr.QuestionId ORDER BY qr.ID),
Value,
Question
FROM QuestionRecord qr
INNER JOIN
QuestionRecord P
ON P.ID = qr.ParentQuestionRecordId
JOIN Questions q ON q.ID = qr.QuestionID
)pvt
PIVOT
(
MIN(Value)
FOR Question IN
([Eligible project costs],[Date],[Cash Costs £],[In Kind Costs £],[Total Costs])
)pivotTable
I think it must give your the result you are after.

Change SELECT qr.*, Question to SELECT Value, Question. PIVOT groups by the remaining columns.

what you need, like andriy kinda pointed out, is something to make each record unique depending on how you want them grouped. now, if this is a survey system i'm going to guess that you've got some sort of id to identify who the record belongs to. the reason why it's returning on seperate rows is that you have unique records for each row based on those ids, what you need is to add the respondent id to your derived table and get rid of your other id's.
see my example:
declare #table table (ID int identity(1,1), QuestionID int, value varchar(50), Respondent int)
declare #questions table (QID int, name varchar(50))
insert into #questions values (31,'Eligible project costs')
insert into #questions values (32,'Date')
insert into #questions values (33,'Cash Costs')
insert into #questions values (34,'In Kind Costs')
insert into #questions values (35,'Total Costs')
insert into #table values (31,'Consultancy',1)
insert into #table values (32,null,1)
insert into #table values (33,25000,1)
insert into #table values (34,null,1)
insert into #table values (35,25000,1)
insert into #table values (31,'Orchard day x2',2)
insert into #table values (32,null,2)
insert into #table values (33,15000,2)
insert into #table values (34,null,2)
insert into #table values (35,15000,2)
select
[Eligible project costs],[Date],[Cash Costs],[In Kind Costs],[Total Costs]
from
(
select
Respondent,
q.name,
t.Value
from #table t
inner join #questions q
on t.QuestionID=QID
) a
pivot
(
min(Value)
for name in ([Eligible project costs],[Date],[Cash Costs],[In Kind Costs],[Total Costs])
) p

Related

SQL Query combine 2 rows into 1 adding values

I have a query that will potentially return multiple rows for the same ID from my database. This is because it is a payment table and an invoice can be paid on multiple times.
So my results can look like this.
ID Company BillAmount AmountPaid
----- --------- ------------ ------------
123 ABC 1000.00 450.00
123 ABC 1000.00 250.00
456 DEF 1200.00 1200.00
I am building this query to put into Crystal Reports. If I just pull the raw data, I won't be able to do any sub totaling in CR as Bill amount on this will show $3200 when it is really $2200. I'll need to show balance and I can do that in CR but if I am pulling balance on each line returned, the total balance due for all records shown will be wrong as the "duplicate" rows will be counted wrong.
I am not sure what kind of report you need but maybe a query like this might be useful:
select ID, Company, max(BillAmount), sum(AmountPaid)
from Payment
group by ID
-improved after Juan Carlos' suggestion
For this, there are 2 option available.
at Crystal report side
In crystal report, there is facility to group, as suggested in this link, follow steps
for group summary, after add group, put all fields in group footer, check this link
at Sql side the below suggestion (you are not define which sql or db use, I assume Sqlserver 2012 and above)
Get the records with extra 2 column ( TotalBill ,TotalPaid)
declare #Invoice table(id int , Company varchar(25), BillAmount int )
declare #payment table(id int , InvoiceId int, AmountPaid int )
insert into #Invoice values (1, 'ABC', 1000), (2, 'DFE', 1200)
insert into #payment values (1, 1, 450), (2, 1, 250), (3, 2, 1200)
;with cte as
( select sum(BillAmount) TotalBill from #Invoice i )
Select
i.*, p.AmountPaid ,
Sum(AmountPaid) over ( partition by i.id ) InvoiceWiseTotalPaid,
cte.TotalBill,
Sum(AmountPaid) over ( order by i.id ) TotalPaid
from
#Invoice i
Join #payment p on i.id= p.InvoiceId
, cte
Output will be

Sum records and add note what was summed up in sql

I have a simple table looks like this one:
company_Id user_Id price sub_price
123456 11111 200 NULL
123456 11111 500 NULL
456789 22222 300 NULL
And I want to consolidate records which has count(*) >= 2 into one row by summing up the price but with note what was summed up in column sub_price. Desired output should look like this one:
company_Id user_Id price sub_price
123456 11111 700 200,500
456789 22222 300 300
Is there any simple approach how to achieve desired output? Many thanks for your help in advance.
You can use listagg to turn the elements of a group into a string:
SELECT ...
, LISTAGG(price, ',') WITHIN GROUP (ORDER BY price) sub_price
FROM ...
Although listagg is SQL standard, it is not yet supported by all databases. However, most database offer similar functionality by a different name—e.g. string_agg in PostgreSQL and SQL Sever (since 2017) or group_concat in MySQL.
More info: http://modern-sql.com/feature/listagg (also showing alternatives if listagg is not supported)
This is one possible solution;
More info about concatenating multiple rows into single row you can find here
DECALRE #tbl AS table (
company_Id int
,user_Id int
,price int
,sub_price varchar(25)
)
INSERT INTO #tbl values (123456, 11111, 200, NULL)
INSERT INTO #tbl values (123456, 11111, 500, NULL)
INSERT INTO #tbl values (456789, 22222, 300, NULL)
SELECT
company_Id
,user_Id
,SUM(price) AS price
,STUFF(
(SELECT ',' + cast(price as varchar)
FROM #tbl
WHERE company_Id = a.company_id
AND user_Id = a.user_Id
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'') AS sub_price
FROM #tbl a
GROUP BY company_Id, user_Id

T-SQL Combine Ranges Based On Value

I am using SQL Server 2012 and have been struggling with this query for hours. I am trying to aggregate mile post ranges based off the value in the Value column. The results should have unique segments with the highest value from the Value field for each segment. Here's an example:
Mile_Marker_Start | Mile_Marker_End | Value
0 100 5
50 150 6
100 200 10
75 300 9
150 200 7
And here's the result I'm looking for:
Mile_Marker_Start | Mile_Marker_End | Value
0 50 5
50 75 6
75 100 9
100 200 10
200 300 9
As you can see, the row with a value of 9 got split into 2 rows because Value 10 was bigger. Also, the row with Value 7 does not display because Value 10 was bigger. Can this be done without using a cursor? Any help would be much appreciated.
Thanks
I believe the following now does what you need. I'd recommend running all the parts separately so you can see what they do and how they work.
DECLARE #input AS TABLE
(Mile_Marker_Start int, Mile_Marker_End int, Value int)
INSERT INTO #input VALUES
(0,100,5), (50,150,6), (100,200,10), (75,300,9), (150,200,7)
DECLARE #staging as table
(Mile_Marker int)
INSERT INTO #staging
SELECT Mile_Marker_Start from #input
UNION -- this will remove duplicates
SELECT Mile_Marker_End from #input
; -- we need semi-colon for the following CTE
-- this CTE gets the right values, but the rows aren't "collapsed"
WITH all_markers AS
(
SELECT
groups.Mile_Marker_Start,
groups.Mile_Marker_End,
max(i3.Value) Value
FROM
(
SELECT
s1.Mile_Marker Mile_Marker_Start,
min(s2.Mile_Marker) Mile_Marker_End
FROM
#staging s1
JOIN #staging s2 ON
s1.Mile_Marker < s2.Mile_Marker
GROUP BY
s1.Mile_Marker
) as groups
JOIN #input i3 ON
i3.Mile_Marker_Start < groups.Mile_Marker_End AND
i3.Mile_Marker_End > groups.Mile_Marker_Start
GROUP BY
groups.Mile_Marker_Start,
groups.Mile_Marker_End
)
SELECT
MIN(collapse.Mile_Marker_Start) as Mile_Marker_Start,
MAX(collapse.Mile_Marker_End) as Mile_Marker_End,
collapse.Value
FROM
(-- Subquery get's IDs for the groups we're collapsing together
SELECT
am.*,
ROW_NUMBER() OVER (ORDER BY am.Mile_Marker_Start) - ROW_NUMBER() OVER (PARTITION BY am.Value ORDER BY am.Mile_Marker_Start) GroupID
FROM
all_markers am
) AS COLLAPSE
GROUP BY
collapse.GroupID,
collapse.Value
ORDER BY
MIN(collapse.Mile_Marker_Start)
Since you are on 2012 you could maybe use LEAD. Here is my code but as noted on your question by #stevelovell , we need clarification on how you are getting your result table.
--test date
declare #tablename TABLE
(
Mile_Marker_Start int,
Mile_Marker_End int,
Value int
);
insert into #tablename
values(0,100, 5),
(50,150, 6),
(100,200,10),
(75,300, 9),
(150,200, 7);
--query
select *
from #tablename
order by Mile_Marker_Start
select Mile_Marker_Start,
case when lead(mile_marker_start) over(order by mile_marker_start) < Mile_Marker_End THEN
lead(mile_marker_start) over(order by mile_marker_start)
ELSE
Mile_marker_end
END
AS MILE_MARKER_END,
Value
from #tablename
order by Mile_Marker_Start
Once you update your notes I will come back and update my answer.
Update: wasn't able to get LEAD and the other windowing functions to work with your requirements. With the way you need to move up and down the table current, and calculated values...

Merge 2 tables into 1 SQL Server

This is my current table:
Sales Table
OrderID Customer_ID Customer_Name
1 12 Bob
2 18 Ben
3 11 Harry
OrderID is the primary key
I have a temporary table Temp1:
Order_CreateDate Order_ReturnDate
20051102 20051104
20051103 20051108
20051104 20051105
I want to change the dates YYYYMMDD in Temp1 table, to YYYY-MM-DD, and move it to table, this code below does not work if I insert straight into Sales Table as it displays error saying:
Cannot insert the value NULL into column 'Order_ID', table 'car_rental.dbo.DataInCentralDatabase2'; column does not allow nulls. INSERT fails
However if I test it out by outputting to another temporary table, temp2, it works.
INSERT INTO [dbo].[sales]([Order_CreateDate])
SELECT
CONVERT(date,Order_CreateDate,111) AS Order_CreateDate
FROM dbo.temp1
But running this code two times for temp2 table (for the two converted columns) has the following result:
Order_CreateDate Order_ReturnDate
2005-11-02
2005-11-03
2005-11-04
NULL 2005-11-04
NULL 2005-11-08
NULL 2005-11-05
I know this question is extremely confusing, but as the end result I want it to become like this:
OrderID Customer_ID Customer_Name Order_CreateDate Order_ReturnDate
1 12 Bob 2005-11-02 2005-11-04
2 18 Ben 2005-11-03 2005-11-08
3 11 Harry 2005-11-04 2005-11-05
Any ideas on how to tackle this?
You need another column in Temp1 table as
OrderID Order_CreateDate Order_ReturnDate
1 20051102 20051104
2 20051103 20051108
3 20051104 20051105
Use Update query and not Insert query
UPDATE a set
Order_CreateDate=CONVERT(datetime,b.Order_CreateDate,111),
Order_ReturnDate=CONVERT(datetime,b.Order_ReturnDate,111)
from [dbo].[sales] a join [dbo].[Temp1] b on a.OrderID = b.OrderID
At the first, you should get an updated version of temp1 like this:
select
CONVERT(date,Order_CreateDate,111) as Order_CreateDate,
CONVERT(date,Order_ReturnDate,111) as Order_ReturnDate,
OrderID
into #temp2
from temp1
then, update your main table with join on temp1, like this:
update s
s.Order_CreateDate = t.Order_CreateDate,
s.Order_ReturnDate = t.Order_ReturnDate
from sales s
inner join #temp2 t
on s.OrderID = t.OrderID

How to select info from row above?

I want to add a column to my table that is like the following:
This is just an example of how the table is structured, the real table is more than 10.000 rows.
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
200 Underwear 0 250 *100
300 Bikes 0 250 *100
400 Profit 3
500 Cash 0 450 *400
So for every time there is a value in 'Subgroup' I want the (New_Column) to get the value [No_] from the row above
No_ Name Account_Type Subgroup (New_Column)
100 Sales 3
150 TotalSales 3
200 Underwear 0 250 *150
300 Bikes 0 250 *150
400 Profit 3
500 Cash 0 450 *400
There are cases where the table is like the above, where two "Headers" are above. And in that case I also want the first above row (150) in this case.
Is this a case for a cursor or what do you recommend?
The data is ordered by No_
--EDIT--
Starting from the first line and then running through the whole table:
Is there a way I can store the value for [No_] where [Subgroup] is ''?
And following that insert this [No_] value in the (New_Column) in each row below having value in the [Subgroup] row.
And when the [Subgroup] row is empty the process will keep going, inserting the next [No_] value in (New_Column), that is if the next line has a value in [Subgroup]
Here is a better image for what I´m trying to do:
SQL Server 2012 suggests using Window Offset Functions.
In this case : LAG
Something like this:
SELECT [No_]
,[Name]
,[Account_Type]
,[Subgroup]
,LAG([No_]) OVER(PARTITION BY [Subgroup]
ORDER BY [No_]) as [PrevValue]
FROM table
Here is an example from MS:
http://technet.microsoft.com/en-us/library/hh231256.aspx
The ROW_NUMBER function will allow you to find out what number the row is, but because it is a windowed function, you will have to use a common table expression (CTE) to join the table with itself.
WITH cte AS
(
SELECT [No_], Name, Account_Type, Subgroup, [Row] = ROW_NUMBER() OVER (ORDER BY [No_])
FROM table
)
SELECT t1.*, t2.[No_]
FROM cte t1
LEFT JOIN cte t2 ON t1.Row = t2.Row - 1
Hope this helps.
Next query will return Name of the parent row instead of the row itself, i.e. Sales for both Sales, Underwear, Bikes; and Profit for Profit, Cash:
select ISNULL(t2.Name, t1.Name)
from table t1
left join table t2 on t1.NewColumn = t2.No
So in SQL Server 2008 i created test table with 3 values in it:
create table #ttable
(
id int primary key identity,
number int,
number_prev int
)
Go
Insert Into #ttable (number)
Output inserted.id
Values (10), (20), (30);
Insert in table, that does what you need (at least if understood correctly) looks like this:
declare #new_value int;
set #new_value = 13; -- NEW value
Insert Into #ttable (number, number_prev)
Values (#new_value,
(Select Max(number) From #ttable t Where t.number < #new_value))
[This part added] And to work with subgroup- just modify the inner select to filter out it:
Select Max(number) From #ttable t
Where t.number < #new_value And Subgroup != #Subgroup
SELECT
No_
, Name
, Account_Type
, Subgroup
, ( SELECT MAX(above.No_)
FROM TableX AS above
WHERE above.No_ < a.No_
AND above.Account_Type = 3
AND a.Account_Type <> 3
) AS NewColumn
FROM
TableX AS a