I have a table and want to transpose its rows to columns, similar to a pivot table but without summarising.
For example I have the following tables:
Question
--QuestionID
--QuestionText
Response
--ResponseID
--ResponseText
--QuestionID
Basically I want to be able to create a dynamic table something like:
Question 1 Text | Question 2 Text | Question 3 Text
---------------------------------------------------
Response 1.1 Text | Response Text 1.2 | Response 1.3
Response 2.1 Text | Response Text 2.2 | Response 2.3
Response 3.1 Text | Response Text 3.2 | Response 3.3
Response 4.1 Text | Response Text 4.2 | Response 4.3
The main requirement would be I don't know at design time what the question text will be.
Please can someone help - I am pulling my hair out :oS
Essentially you can guarantee that there will be a response for each corresponding question in this scenario.
You cannot do it with SQL (except with dynamic queries), unless you know the number of columns (i. e. questions) in design time.
You should pull the data you want in tabular format and then process it on client side:
SELECT *
FROM Question
LEFT OUTER JOIN
Response
ON Response.QuestionId = Question.QuestionID
or, probably, this (in SQL Server 2005+, Oracle 8i+ and PostgreSQL 8.4+):
SELECT *
FROM (
SELECT q.*, ROW_NUMBER() OVER (ORDER BY questionID) AS rn
FROM Question q
) q
LEFT OUTER JOIN
(
SELECT r.*, ROW_NUMBER() OVER (PARTITION BY questionID ORDER BY ResponseID) AS rn
FROM Response r
) r
ON r.QuestionId = q.QuestionID
AND q.rn = r.rn
ORDER BY
q.rn, q.QuestionID
The latter query will give you results in this form (provided you have 4 questions):
rn question response
--- --- ---
1 Question 1 Response 1.1
1 Question 2 Response 2.1
1 Question 3 Response 3.1
1 Question 4 Response 4.1
2 Question 1 Response 1.2
2 Question 2 Response 2.2
2 Question 3 NULL
2 Question 4 Response 4.2
3 Question 1 NULL
3 Question 2 NULL
3 Question 3 Response 3.3
3 Question 4 NULL
, this is it will output the data in tabular form, with rn marking the row number.
Each time you see the rn changing on the client, you just close <tr> and open the new one.
You may safely put your <td>'s one per resultset row, since same number or rows is guaranteed to be returned for each rn
This is quite a frequently asked question.
SQL just not a right tool to return data with dynamic number of columns.
SQL operates on sets, and the column layout is an implicit property of a set.
You should define the layout of the set you want to get in design time, just like you define the datatype of a varible in C.
C works with strictly defined variables, SQL works with strictly defined sets.
Note that I'm not saying it's the best method possible. It's just the way SQL works.
Update:
In SQL Server, you can pull the table in HTML form right out of the database:
WITH a AS
(
SELECT a.*, ROW_NUMBER() OVER (PARTITION BY question_id ORDER BY id) AS rn
FROM answer a
),
rows AS (
SELECT ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM answer a
WHERE question_id =
(
SELECT TOP 1 question_id
FROM answer a
GROUP BY
question_id
ORDER BY
COUNT(*) DESC
)
)
SELECT (
SELECT COALESCE(a.value, '')
FROM question q
LEFT JOIN
a
ON a.rn = rows.rn
AND a.question_id = q.id
FOR XML PATH ('td'), TYPE
) AS tr
FROM rows
FOR XML PATH(''), ROOT('table')
See this entry in my blog for more detail:
Dynamic pivot
Do your grouping and aggregating first, using Quassnoi's answer as an intermediate result.
Crosstabulation should only be done when you are no longer going to be doing set oriented operatons on the results. Some SQL dialects have keywords like PIVOT, TRANSFORM or CROSSTABULATE to accomplish this, but you're probably better off using XSLT.
Related
I have the following table structure and data:
Data is ordered ASC and for each Master Clear there's a subsequent Blend Closed. If there's no subsequent Blend Closed then it should receive NULL.
Now I'd like to get the following result:
Master Clear Blend Closed
2018-09-17 03:12:03 2018-09-17 10:00:03
2018-09-17 10:37:03 2018-09-18 01:05:02
2018-09-18 04:55:02 2018-09-18 21:51:00
2018-09-18 22:55:00 2018-09-19 03:02:01
. .
. .
. .
2018-09-23 20:10:56 NULL
This is getting pairs of rows and moving the Time column value to its appropriate Value column: Master Clear or Blend Closed.
I tried to get this data shape but couldn't by any means. Is there any way to achieve this output using T-SQL?
One option would be to assign a row number to each value group, then do a regular pivot query:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Value ORDER BY Time) rn
FROM [BUR_BLEND].[dbo].[BLEND_START_END_TIMES]
)
SELECT
MAX(CASE WHEN Value = 'Master Clear' THEN Time END) AS [Master Clear],
MAX(CASE WHEN Value = 'Blend Closed' THEN Time END) AS [Blend Closed]
FROM cte
GROUP BY rn
ORDER BY rn;
Demo
Note that this answer assumes that the Master Clear and Blend Closed records always come together in logical pairs, and that there are no gaps. If not, then we would have to do more work to generate your output.
In SQL Server, how can I convert one single row of integer data, like this
ColumnName1 ColumnName2 ColumnName3
1 2 3
into a single row, order by DSEC?
ColumnNameTotalSort
3
2
1
I know the requirement seems simple but I have been struggling for a while.
Thanks for input.
As ZLK mentioned, UNPIVOT is an option
Another option is with a CROSS APPLY and VALUES
Example
Select B.*
From YourTable A
Cross Apply (values (ColumnName1)
,(ColumnName2)
,(ColumnName3)
) B(ColumnNameTotalSort)
-- Where Your Filter Condition Here
Order By ColumnNameTotalSort Desc
Returns
ColumnNameTotalSort
3
2
1
I have a table that has a new column, and updating the values that should go in the new column. For simplicity sake I am reducing my example table structure as well as my query. Below is how i want my output to look.
IDNumber NewColumn
1 1
2 1
3 1
4 2
5 2
WITH CTE_Split
AS
(
select
*,ntile(2) over (order by newid()) as Split
from TableA
)
Update a
set NewColumn = a.Split
from CTE_Split a
Now when I do this I get my table and it looks as such
IDNumber NewColumn
1 1
2 1
3 1
4 1
5 1
However when I do the select only I can see that I get the desire output, now I have done this before to split result sets into multiple groups and everything works within the select but now that I need to update the table I am getting this weird result. Not quiet sure what I'm doing wrong or if anyone can provide any sort of feedback.
So after a whole day of frustration I was able to compare this code and table to another that I had already done this process to. The reason that this table was getting updated to all 1s was because turns out that whoever made the table thought this was supposed to be a bit flag. When it reality it should be an int because in this case its actually only two possible values but in others it could be more than two.
Thank you for all your suggestion and help and it should teach me to scope out data types of tables when using the ntile function.
Try updating your table directly rather than updating your CTE. This makes it clearer what your UPDATE statement does.
Here is an example:
WITH CTE_Split AS
(
SELECT
*,
ntile(2) over (order by newid()) as Split
FROM TableA
)
UPDATE a
SET NewColumn = c.Split
FROM
TableA a
INNER JOIN CTE_Split c ON a.IDNumber = c.IDNumber
I assume that what you want to achieve is to group your records into two randomized partitions. This statement seems to do the job.
I have a database with approximately 10 million rows (and 20 columns - about 4 GB) where about 10% of the rows have a duplicate column. Database is in SQL Server 2014 Express and using SSMS.
I created a new column CNT (int, null) to count the occurrences of each row where I have a duplicate ID. Desired result would look like:
ID CNT
100 1
100 2
101 1
102 1
102 2
103 1
104 1
Not being really familiar with advanced SQL capabilities I did some research and came up with using a CTE to set the CNT column. Worked fine on a small test table - but it was obvious this is not the way to go for a large table (I killed it after 5+ hours on a pretty decent system.)
Here's the code that I attempted to implement:
with CTE as
(select dbo.database.id, dbo.database.cnt,
RN = row_number() over (partition by id order by id)
from dbo.databasee)
update CTE set CNT = RN
Column ID is of type Int. All columns allow nulls - there are no keys or indexed columns.
Edit: Martin is right, I can only offer an alternate solution than the CTE at the moment. Make a new table exactly like your old one, and insert the old table's data into it with this.
INSERT INTO newTable
SELECT ID, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID)
FROM oldTable;
Then you can delete your old table. Definitely not a perfect solution, but it should work.
I have a MSSQL server 2012 express DB that logs user activities. I need some help creating a query to compare timestamps on the user activities based on the text in the notes. I am interested in figuring out how long it takes my users to perform certain activities. The activities they are performing are stored in text in a NOTES column. I want to build a query that will tell me the time difference for each [INVOICEID] from the ‘START NOTE’ to the next note for that invoice by that user. The note that is entered is always the same for the start of the timer (for the purposes of this I used ‘START NOTE’ to indicate the start of the timer, but I have multiple activites I will need to do this for so I plan on simply changing that text in the query), but the end of the timer the text of the note will vary because it will be user entered data. I want to find the time difference between ‘START NOTE’ and the note that immediately follows ‘START NOTE’ entered by the same USERID for the same INVOICEID. Please see the SQLfiddle for an example of my data:
http://sqlfiddle.com/#!3/a00d7/1
With the data in the sql fiddle I would want the results of this query to be:
INVOICE ID USERID TIME_Difference
100 5 1 day
101 5 3 days
102 5 9 days
(time_difference does not need to be formatted like that, standard SQL formatting is fine)
I don’t really know where to start with this. Please let me know if you can help.
Thanks
select a.userid,a.invoiceid,min(a.added),min(b.added),datediff(DAY,min(a.added),min(b.added)) from om_note a
left join om_note b on a.userid=b.userid and a.invoiceid = b.invoiceid and a.added < b.added
where a.notes = 'START NOTE' group by a.userid,a.invoiceid
;with x as (
select
o.*, sum(case when notes='START NOTE' then 1 else 0 end)
over(partition by o.invoiceid, o.userid order by o.added) as grp
from om_note o
),
y as (
select *,
row_number() over(partition by x.invoiceid, x.userid, x.grp order by x.added) as rn
from x
where grp > 0
)
select y1.invoiceid, y1.userid, datediff(hour, y1.added, y2.added)
from y y1
inner join y y2
on y1.invoiceid=y2.invoiceid and y1.userid=y2.userid and y1.grp=y2.grp
where y1.rn=1 and y2.rn=2