SQL Server : display multiple rows in one row and multiple columns - sql-server

I've found similar questions but nothing that has a solution I've been able to make work for me. I've been out of practice and stuck on this.
I have a table that tracks employee positions. Each employee can have any number of active positions. I want to display each employee as a single record with all their positions in that record spanning multiple columns.
A glance at the data shows that there are no employees with more than 6 active positions and since this is a one-time query, I should be able to work with that number.
The positions all have a sequence number assigned but they're not in a nice handy order - inactive positions for an employee retain the sequence number.
So right now I have something that looks like this:
ID | Name | Title | Cat | Seq
------------------------------
10 | John | Asst. | HR | 13
10 | John | Tutor | EDU | 17
11 | Sue | Mgr | PA | 6
11 | Sue | Adj. | EDU | 7
11 | Sue | Tutor | EDU | 13
...
11 | Sue | Asst. | HR | 22
But I want it to look like this:
ID | Name | Title 1 | Cat 1 | Title 2 | Cat 2 | Title 3...| Title 6 | Cat 6
----------------------------------------------------------------------------
10 | John | Asst. | HR | Tutor | EDU | NULL | NULL | NULL
11 | Sue | Mgr. | PA | Adj. | EDU | Tutor | Asst. | HR
Let me know what additional information I need to provide.

You can use a combination of row_number and pivots to do this. Using SQL Server 2012+ the query could look something like below. It's a bit messy, but it should get the job done, although there might be better ways to accomplish the same result.
select
id
, name
, max([Title 1]) as [Title 1]
, max([Cat 1]) as [Cat 1]
, max([Title 2]) as [Title 2]
, max([Cat 2]) as [Cat 2]
, max([Title 3]) as [Title 3]
, max([Cat 3]) as [Cat 3]
-- and so on for the remaining columns...
from (
select
id, name, title, cat
, t = concat('Title ',row_number() over (partition by id order by seq))
, c = concat('Cat ',row_number() over (partition by id order by seq))
from your_table
) src
pivot ( max(title) for t in ([title 1],[title 2],[title 3]) )pt
pivot ( max(cat) for c in ([cat 1],[cat 2],[cat 3]) )pc
group by id, name;
I only wrote out title/cat 1 through 3 to save some space but you should get the idea.
Sample SQL Fiddle
Sample result:
| id | name | Title 1 | Cat 1 | Title 2 | Cat 2 | Title 3 | Cat 3 |
|----|------|---------|-------|---------|-------|---------|--------|
| 10 | John | Asst. | HR | Tutor | EDU | (null) | (null) |
| 11 | Sue | Mgr | PA | Adj. | EDU | Tutor | EDU |

Related

MSSQL recursive query to find furthest parent

We have a db structure where each company has a parent company defined. What we want to do is walk up the structure from a given start point until the next 'most-parent' company is found and pull what that users assignment is to that 'most-parent' company. Below is a mock example.
+===================+ +=======+ +=============+
| Company | | User | | UserAccess |
+===================+ +=======+ +=============+
| id | | id | | id |
| Name | | Name | | fkUserId |
| fkParentCompanyId | +=======+ | fkCompanyId |
+===================+ | AccessLevel |
+=============+
+=======+
|Company|
+=============================================+
| id | Name | fkParentCompanyId |
+=============================================+
| 1 | ABC Corp | 1 |
| 2 | Outside Company | 1 |
| 3 | Inside Company | 1 |
| 4 | My Company | 3 |
| 5 | Other LLC | 4 |
| 6 | Yet Another Comp | 5 |
+=============================================+
+====+
|User|
+======================+
| id | Name |
+======================+
| 1 | Mike |
| 2 | Jackie |
| 3 | Sam |
+======================+
+==========+
|UserAccess|
+=================================================+
| id | fkUserId | fkCompanyId | AccessLevel |
+=================================================+
| 1 | 1 | 1 | Administrator |
| 2 | 2 | 1 | User |
| 3 | 3 | 1 | Administrator |
| 4 | 3 | 3 | Parent |
| 5 | 3 | 4 | Parent |
| 6 | 3 | 5 | Parent |
| 7 | 3 | 6 | Parent |
+=================================================+
So take 'Same', user.id == 3. I want to do a query that finds the nearest "not-Parent" relationship when given a starting Company.id along with Sam's User.id. Next is what I'm looking to get from the given inputs.
Inputs: User.id = 3, Company.id = 6
Output: Administrator
Inputs: User.id = 3, Company.id = 5
Output: Administrator
Inputs: User.id = 3, Company.id = 4
Output: Administrator
Inputs: User.id = 2, Company.id = 1
Output: User
I've been looking at recursive queries using the CTE model, having some difficulty understanding what's it's really doing and thus not yet able to translate that model into something that would work for the above example.
Any guidance would be greatly appreciated.
Update
Been working on CTE in SSMS and feel like I'm getting close... Here is an example query that I'm trying, but not getting the result I expect...
Here I'm trying to do a recursive CTE on Sam # "Yet Another Comp". What I want is a list of Sams access up the chain.
with cte(UserId, CompanyId, UserAccess, ParentCompanyId)
as
(
select
[UserId],
[CompanyId],
[UserAccess],
[ParentCompanyId]
from [UserAccess]
where [UserId] = 3 and [CompanyId] = 6
union all
select
[UserAccess].[UserId],
[UserAccess].[CompanyId],
[UserAccess].[AccessLevel],
[cte].[ParentCompanyId]
from [UserAccess]
join [cte] on [UserAccess].[ParentCompanyId] = [cte].[CompanyId]
where [UserAccess].[UserId] = 3 [UserAccess].[CompanyId] != 6
)
select * from cte
I'm expecting this:
+===+
|cte|
+==========================================================+
| UserId | CompanyId | UserAccess | ParentCompanyId |
+==========================================================+
| 3 | 6 | Parent | 5 |
| 3 | 5 | Parent | 4 |
| 3 | 4 | Parent | 3 |
| 3 | 3 | Parent | 1 |
| 3 | 1 | Administrator | 1 |
+==========================================================+
But what I'm actually getting is this:
+===+
|cte|
+==========================================================+
| UserId | CompanyId | UserAccess | ParentCompanyId |
+==========================================================+
| 3 | 6 | Parent | 5 |
+==========================================================+
The recursive query isn't pulling additional rows into the table. So I commented out the where statement on the recursive query, expecting to get a max recursion error and see a blip of all of the results. But no, still just get the single row back.

Select results based on nearest date window

I have a SQL Server table as follows. I would like to group by name and place of test taken, order by date ascending as partition based on above mentioned grouping.
now a configurable window of eg:4 days is provided. In below table if first test taken date is
02/01/2019 (1st Feb) - its score is taken, and any other test score which has been retaken within the next 4 day window shall not be considered. If record also falls within 4 day window of already excluded item example row id - 4 , that also shall be excluded.
Any SQL statements for this logic is much appreciated.
CREATE TABLE test(
[recordid] int IDENTITY(1,1) PRIMARY KEY,
[name] [nvarchar](25) NULL,
[testcentre] [nvarchar](25) NULL,
[testdate] [smalldatetime] NOT NULL,
[testscore] [int],
[Preferred_Output] [int],
[Result] [nvarchar](75) NULL
)
GO
INSERT INTO test
(
[name],
[testcentre],
[testdate],
[testscore],
[Preferred_Output],
[Result] )
VALUES
('George','bangalore',' 02/01/2019',1,1,'Selected as first item -grouped by name and location'),
('George','bangalore',' 02/02/2019',0,0,'ignore as within 4 days'),
('George','bangalore',' 02/04/2019',1,0,'ignore as within 4 days'),
('George','bangalore',' 02/06/2019',3,0,'ignore as within 4 days from already ignored item -04-02-2019'),
('George','bangalore',' 02/15/2019',2,2,'Selected as second item -grouped by name and location'),
('George','bangalore',' 02/18/2019',5,0,'ignore as within 4 days of previous'),
('George','Pune',' 02/15/2019',4,3,'Selected as third item'),
('George','Pune',' 02/18/2019',6,0,'ignore as within 4 days of previous'),
('George','Pune',' 02/19/2019',7,0,'ignore as within 4 days of previous'),
('George','Pune',' 02/20/2019',8,0,'ignore as within 4 days of previous')
GO
select * from test
GO
+----------+--------+------------+------------+-----------+------------------+
| recordid | name | testcentre | testdate | testscore | Preferred_Output |
+----------+--------+------------+------------+-----------+------------------+
| 1 | George | bangalore | 02/01/2019 | 1 | 1 |
| 2 | George | bangalore | 02/02/2019 | 0 | 0 |
| 3 | George | bangalore | 02/04/2019 | 1 | 0 |
| 4 | George | bangalore | 02/06/2019 | 3 | 0 |
| 5 | George | bangalore | 02/15/2019 | 2 | 2 |
| 6 | George | bangalore | 02/18/2019 | 5 | 0 |
| 7 | George | Pune | 02/15/2019 | 4 | 3 |
| 8 | George | Pune | 02/18/2019 | 6 | 0 |
| 9 | George | Pune | 02/19/2019 | 7 | 0 |
| 10 | George | Pune | 02/20/2019 | 8 | 0 |
+----------+--------+------------+------------+-----------+------------------+
I don't think that a recursive query is required for this. You want to compare the dates across consecutive records, so this is a kind of gaps-and-island problem, where want to identify the start of each island.
Window functions can do that:
select t.*,
case when lag_testdate is null or testdate > dateadd(day, 4, lag_testdate)
then testscore
else 0
end new_core
from (
select t.*, lag(testdate) over(partition by name, testcentre order by testdate) lag_testdate
from test t
) t
Demo on DB Fiddle

SQL Server - get values from X months ago according to columndata

Let's say I have the following table (data is completely fiction):
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck
1 | 2017-12 | 900 | Jack | Ill | -
2 | 2018-01 | 900 | Jack | Ill | 1
3 | 2018-02 | 900 | Jack | Ill | 2
4 | 2018-03 | 900 | Jack | Healthy | 1
5 | 2017-02 | 901 | Bill | Ill | -
6 | 2017-03 | 901 | Bill | Ill | 1
7 | 2017-05 | 901 | Bill | Healthy | 1
For each record, I would like to see the previous status that person had X months ago since last check (column MonthsAgoSinceLastCheck). Notice that MonthDate can skip months.
So in this case, the result would be
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck | PreviousSatus
1 | 2017-12 | 900 | Jack | Ill | - | -
2 | 2018-01 | 900 | Jack | Ill | 1 | Ill
3 | 2018-02 | 900 | Jack | Ill | 2 | Ill
4 | 2018-03 | 900 | Jack | Healthy | 1 | Ill
5 | 2017-02 | 901 | Bill | Healthy | - | -
6 | 2017-03 | 901 | Bill | Healthy | 1 | Healthy
7 | 2017-05 | 901 | Bill | Ill | 2 | Healthy
Any sugestions/tips? I tried to do this with CTE's and self-joins but failed on both.
It's way easier to use full dates than year and months separately. The first thing you should do is generate a full date from your year + month. Then just self join with previous month, depending on the last check.
;WITH DataWithDates AS
(
SELECT
T.ID,
MonthDate = CONVERT(DATE, T.MonthDate + '-01'),
T.PersonID,
T.Name,
T.Status,
T.MonthsAgoSinceLastCheck
FROM
YourTable AS T
)
SELECT
D.ID,
D.MonthDate,
D.PersonID,
D.Name,
D.Status,
D.MonthsAgoSinceLastCheck,
PreviousStatus = N.Status
FROM
DataWithDates AS D
LEFT JOIN DataWithDates AS N ON
D.PersonID = N.PersonID AND
N.MonthDate = DATEADD(MONTH, -1 * D.MonthsAgoSinceLastCheck, D.MonthDate)
I'm assuming your MonthDate has values for all rows, otherwise the conversion will fail. I'm also assuming that your - values for MonthsAgoSinceLastCheck are actually NULL.
try this:
select *,LAG(Status) OVER(Partition by Name Order by MonthDate,Id) AS PreviousSatus
from tab1
order by id
SQl Fiddle:http://sqlfiddle.com/#!18/04407/4

SQL Server group and inverse

I'm stuck with an easy SQL Server thing for my own personal project.
I have a table X:
| Name | Lecture | Points |
|:-----------|------------:|:------------:|
| John | Math | 2
| John | Bio | 5
| Tom | Physics | 8
| Tom | Math | 2
| Bob | Physics | 1
| Bob | Bio | 6
And I want to group by Name and to put all points I one row for each person:
| Name | Math | Bio | Physics |
|:-----------|------:|:----:|:-------:|
| John | 2 | 5 | NULL
| Tom | 2 | NULL | 8
| Bob | NULL | 6 | 1
I tried doing this:
SELECT Name, ? AS Math, ? AS Bio, ? AS Physics
FROM X
GROUP BY Name
but I don't know what to put instead of "?". How can I do that ?
You need a pivot table and you need to know the values, unless you want to use dynamic sql (not recommended unless absolutely necessary):
SELECT Name,
ISNULL([Math], 0) as [Math],
ISNULL([Bio], 0) as [Bio],
ISNULL([Physics], 0) as [Physics]
FROM
(
SELECT Name, Lecture, SUM(Points)
FROM Table X
GROUP BY Name, Lecture
) as t1
PIVOT (SUM([Points]) for [Lecture] in ([Math], [Bio], [Physics])) as t2

SQL Server. Join two tables n a view, take rows from one and turn into columns [duplicate]

This question already has answers here:
Efficiently convert rows to columns in sql server
(5 answers)
Closed 8 years ago.
I'm pretty new to SQL Server so don't really know what I'm doing with this. I have two tables, which might look like this:
table 1
| ID | customer | Date |
| 1 | company1 | 01/08/2014 |
| 2 | company2 | 10/08/2014 |
| 3 | company3 | 25/08/2014 |
table 2
| ID | Status | Days |
| 1 | New | 6 |
| 1 | In Work | 25 |
| 2 | New | 17 |
| 3 | New | 14 |
| 3 | In Work | 72 |
| 3 | Complete | 25 |
What I need to do is join based on the ID, and create new columns to show how long each ID has been in each status. Every time an order goes to a new status, a new line is added and the number of days is counted as in the 2nd table above. What I need to create from this, should look like this:
| ID | customer | Date | New | In Work | Complete |
| 1 | company1 | 01/08/2014 | 6 | 25 | |
| 2 | company2 | 10/08/2014 | 17 | | |
| 3 | company3 | 25/08/2014 | 14 | 72 | 25 |
So what do I need to to to create this?
Thanks for any help, as I say I'm pretty new to this.
I would suggest that AHiggins' link is a better candidate to mark this as a dupe rather than the one that's actually been selected because his link involves a join.
WITH [TimeTable] AS (
SELECT
T1.ID,
T1.[Date],
T2.[Status] AS [Status],
T2.[Days]
FROM
dbo.Table1 T1
INNER JOIN dbo.Table2 T2
ON T2.ID = T1.ID
)
SELECT *
FROM
[TimeTable]
PIVOT (MAX([Days]) FOR [Status] IN ([New], [Complete], [In Work])) TT
;

Resources