Filling fields with Null values [duplicate] - sql

This question already has answers here:
Group by column and multiple Rows into One Row multiple columns
(2 answers)
Convert Rows to columns using 'Pivot' in SQL Server
(9 answers)
Closed 9 months ago.
I have two tables in SQL Server 2012
Table1
UserID
Name
1
Joe
2
Mary
Table2
UserID
Permission
1
P15
2
P5
2
P330
Each user can have between 1 and 8 Permissions.
I need to create a view that will give me the UserID with 8 permission entries with any unused entries containing null values i.e. The order of the entries does not matter.
UserID
Permit1
Permit2
Permit3
Permit4
Permit5
Permit6
Permit7
Permit8
1
P15
Null
Null
Null
Null
Null
Null
Null
2
P5
P330
Null
Null
Null
Null
Null
Null
This has me stumped, I don't even know where to start?

You can do this easily with conditional aggregation. This has been asked and answered hundreds of times but if it is a new concept it would be hard to know what search terms to use. This is complete with consumable sample data.
declare #Users table
(
UserID int
, Name varchar(10)
)
insert #Users values
(1, 'Joe')
, (2, 'Mary')
declare #Permissions table
(
UserID int
, Permission varchar(10)
)
insert #Permissions values
(1, 'P15')
, (2, 'P5')
, (2, 'P330')
select x.UserID
, Permit1 = max(case when x.RowNum = 1 then x.Permission end)
, Permit2 = max(case when x.RowNum = 2 then x.Permission end)
, Permit3 = max(case when x.RowNum = 3 then x.Permission end)
, Permit4 = max(case when x.RowNum = 4 then x.Permission end)
, Permit5 = max(case when x.RowNum = 5 then x.Permission end)
, Permit6 = max(case when x.RowNum = 6 then x.Permission end)
, Permit7 = max(case when x.RowNum = 7 then x.Permission end)
, Permit8 = max(case when x.RowNum = 8 then x.Permission end)
from
(
select u.UserID
, p.Permission
, RowNum = ROW_NUMBER() over(partition by u.UserId order by p.Permission)
from #Users u
join #Permissions p on p.UserID = u.UserID
) x
group by x.UserID

Related

TSQL Pivot table rows to columns

I've got some data in a table that looks like the following. I'm trying to run a query that will get my data on a single row per requestId. I don't need the dates or the denial reason just the eprojman and apvStatus for each groupId
requestId - projMan1 - apvStatus1 - projMan2 - apvStatus2 - projMan3 - apvStatus3 etc.. for all 5 groupIds
requestId
groupId
entryDate
approvalDate
apvStatus
projMan
denialReason
1
1
2020-11-02
2019-07-25
APPROVED
rx1942
NULL
1
2
2020-11-02
2019-07-25
APPROVED
ma2674
NULL
1
3
2020-11-02
2019-07-25
APPROVED
cb9097
NULL
1
4
2020-11-02
2019-07-25
APPROVED
bj1763
NULL
1
5
2020-11-02
2019-07-25
APPROVED
tr5972
NULL
2
1
2020-11-02
NULL
NOT APPROVED
NULL
6
2
2
2020-11-02
NULL
PENDING
ma2674
NULL
2
3
2020-11-02
NULL
PENDING
cb9097
NULL
2
4
2020-11-02
NULL
PENDING
bj1763
NULL
2
5
2020-11-02
NULL
PENDING
tr5972
NULL
I've been trying to use a PIVOT table but all the examples I find involves summing data or something. I just pretty much want to take the 5 rows and turn it into 1 for each requestID
The only thing I've been able to come up with is to select from the same table 5 times for each groupID and union it but that's slower than heck. Got to be a better way
Thanks.
Current query:
select group1.requestId
, group1.apvStatus as apvStatus1
, group1.projMan as projMan1
, group2.apvStatus as apvStatus2
, group2.projMan as projMan2
, group3.apvStatus as apvStatus3
, group3.projMan as projMan3
,group4.apvStatus as apvStatus4
, group4.projMan as projMan4
,group5.apvStatus as apvStatus5
, group5.projMan as projMan5
,group1.denialReason
INTO #TEMPBAOrganized
from (
select requestId, apvStatus, projMan, denialReason from #TEMPBULKAPPROVAL where groupId = 1) group1
INNER JOIN
(select requestId, apvStatus, projMan, denialReason from #TEMPBULKAPPROVAL where groupId = 2) group2
on group1.requestId = group2.requestId
INNER JOIN
(select requestId, apvStatus, projMan from #TEMPBULKAPPROVAL where groupId = 3) group3
on group1.requestId = group3.requestId
INNER JOIN
(select requestId, apvStatus, projMan from #TEMPBULKAPPROVAL where groupId = 4) group4
on group1.requestId = group4.requestId
INNER JOIN
(select requestId, apvStatus, projMan from #TEMPBULKAPPROVAL where groupId = 5) group5
on group1.requestId = group5.requestId
For pivoting of multiple column, it is easier to use CASE expression with aggregate.
select t.requestId,
projMan1 = max(case when t.groupId = 1 then t.projMan end),
apvStatus1 = max(case when t.groupId = 1 then t.apvStatus end),
projMan2 = max(case when t.groupId = 2 then t.projMan end),
apvStatus2 = max(case when t.groupId = 2 then t.apvStatus end),
projMan3 = max(case when t.groupId = 3 then t.projMan end),
apvStatus3 = max(case when t.groupId = 3 then t.apvStatus end),
projMan4 = max(case when t.groupId = 4 then t.projMan end),
apvStatus4 = max(case when t.groupId = 4 then t.apvStatus end),
projMan5 = max(case when t.groupId = 5 then t.projMan end),
apvStatus5 = max(case when t.groupId = 5 then t.apvStatus end)
from #TEMPBULKAPPROVAL t
group by t.requestId
Note : max is also an aggregate function
You can do it with something like this:
SELECT
requestId,
approvalDate_1 = MAX(approvalDate_1),
approvalDate_2 = MAX(approvalDate_2),
approvalDate_3 = MAX(approvalDate_3),
approvalDate_4 = MAX(approvalDate_4),
approvalDate_5 = MAX(approvalDate_5),
projMan_1 = MAX(projMan_1),
projMan_2 = MAX(projMan_2),
projMan_3 = MAX(projMan_3),
projMan_4 = MAX(projMan_4),
projMan_5 = MAX(projMan_5)
FROM
(
SELECT
requestId,
groupId,
approvalDate,
projMan,
'approvalDate_' + CAST( groupId AS VARCHAR(2)) AS approvalDatePivot,
'projMan_' + CAST( groupId AS VARCHAR(2)) AS projManPivot
FROM
#tbl
) T
PIVOT (
MAX(approvalDate) FOR approvalDatePivot IN ([approvalDate_1],[approvalDate_2],[approvalDate_3],[approvalDate_4],[approvalDate_5])
) pvt_1
PIVOT (
MAX(projMan) FOR projManPivot IN ([projMan_1],[projMan_2],[projMan_3],[projMan_4],[projMan_5])
) pvt_2
GROUP BY requestId

Oracle SQL: How to select only ID‘s which are member in specific groups?

I want to select only those ID‘s which are in specific groups.
For example:
ID GroupID
1 11
1 12
2 11
2 12
2 13
Here I want to select the ID's which are in the groups 11 and 12 but in no other groups.
So the result should show just the ID 1 and not 2.
Can someone provide a SQL for that?
I tried it with
SELECT ID FROM table
WHERE GroupID = 11 AND GroupID = 12 AND GroupID != 13;
But that didn't work.
You can use aggregation:
select id
from mytable
group by id
having min(groupID) = 11 and max(groupID) = 12
This having condition ensures that the given id belongs to groupIDs 11 and 12, and to no other group. This works because 11 and 12 are sequential numbers.
Other options: if you want ids that belong to group 11 or 12 (not necessarily both), and to no other group, then:
having sum(case when groupId in (11, 12) then 1 end) = count(*)
If numbers are not sequential, and you want ids in both groups (necessarily) and in no other group:
having
max(case when groupID = 11 then 1 end) = 1
and max(case when groupID = 12 then 1 end) = 1
and max(case when groupID in (11, 12) then 0 else 1 end) = 0
SELECT t.id FROM table t
where exists(
SELECT * FROM table
where group = 11
and t.id = id
)
and exists(
SELECT * FROM table
where group = 12
and t.id = id
)
and not exists(
SELECT * FROM table
where group = 13
and t.id = id
)
group by t.id
One method is conditional aggregation:
select id
from t
group by id
having sum(case when groupid = 1 then 1 else 0 end) > 0 and
sum(case when groupid = 2 then 1 else 0 end) > 0 and
sum(case when groupid in (1, 2) then 1 else 0 end) = 0 ;
You can use GROUP BY with HAVING and a conditional COUNT:
SELECT id
FROM table_name
GROUP BY ID
HAVING COUNT( CASE Group_ID WHEN 11 THEN 1 END ) > 0
AND COUNT( CASE Group_ID WHEN 12 THEN 1 END ) > 0
AND COUNT( CASE WHEN Group_ID NOT IN ( 11, 12 ) THEN 1 END ) = 0
Or you can use collections:
CREATE TYPE int_list IS TABLE OF NUMBER(8,0);
and:
SELECT id
FROM table_name
GROUP BY id
HAVING int_list( 11, 12 ) SUBMULTISET OF CAST( COLLECT( group_id ) AS int_list )
AND CARDINALITY( CAST( COLLECT( group_id ) AS int_list )
MULTISET EXCEPT int_list( 11, 12 ) ) = 0
(Using collections has the advantage that you can pass the collection of required values as a single bind parameter whereas using conditional aggregation is probably going to require dynamic SQL if you want to pass a variable number of items to the query.)
Both output:
| ID |
| -: |
| 1 |
db<>fiddle here
Use joins:
SELECT DISTINCT c11.ID
FROM (SELECT ID FROM WORK_TABLE WHERE GROUPID = 11) c11
INNER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID = 12) c12
ON c12.ID = c11.ID
LEFT OUTER JOIN (SELECT ID FROM WORK_TABLE WHERE GROUPID NOT IN (11, 12)) co
ON co.ID = c11.ID
WHERE co.ID IS NULL;
The INNER JOIN between the first two subqueries ensures that rows exist for both GROUPID 11 and 12, and the LEFT OUTER JOIN and WHERE verify that there are no rows for any other GROUPIDs.
dbfiddle here

SQL How do I transpose and group data into static columns? [duplicate]

This question already has answers here:
TSQL Pivot without aggregate function
(9 answers)
Closed 4 years ago.
I have a table with the following data:
UID LAST FIRST FUND AMOUNT STATUS
1 Smith John C 100 1
1 Smith John B 250 1
1 Smith John E 150 1
2 Jones Meg B 275 1
2 Jones Meg F 150 1
3 Carter Bill A 100 1
I would like to transpose the FUND, AMOUNT and STATUS values for each UID into a single row for each UID. The resulting table would have columns added for FUND_1, AMT_1, STATUS_1, FUND_2, AMT_2, STATUS_2, FUND_3, AMT_3, STATUS_3. Each UID may or may not have a total of 3 funds. If they do not, the remaining fund, amt, and status columns are left blank. The resulting table would appear as:
UID LAST FIRST FUND_1 AMT_1 STATUS_1 FUND_2 AMT_2 STATUS_2 FUND_3 AMT_3 STATUS_3
1 Smith John C 100 1 B 250 1 E 150 1
2 Jones Meg B 275 1 F 150 1
3 Carter Bill A 100 1
For clarification, this is how the data would move from the existing table to the resulting table for UID 1:
It seems I am unable to use PIVOT because FUND_1, FUND_2, FUND_3 will be different fund categories for each person. The question, TSQL Pivot without aggregate function helps but doesn't answer my question since I have multiple rows in what would be the the DBColumnName in that question.
This is a pretty common conditional aggregation. Notice how I posted consumable data as a table and insert statements. To be honest it took longer to do that portion than the actual code to select the data. You should do this in the future. Also you should avoid using keywords as column names.
declare #Something table
(
UID int
, LAST varchar(10)
, FIRST varchar(10)
, FUND char(1)
, AMOUNT int
, STATUS int
)
insert #Something values
(1, 'Smith', 'John', 'C', 100, 1)
, (1, 'Smith', 'John', 'B', 250, 1)
, (1, 'Smith', 'John', 'E', 150, 1)
, (2, 'Jones', 'Meg', 'B', 275, 1)
, (2, 'Jones', 'Meg', 'F', 150, 1)
, (3, 'Carter', 'Bill', 'A', 100, 1)
;
with SortedValues as
(
select *
, RowNum = ROW_NUMBER() over(partition by UID order by (select null))
from #Something
)
select UID
, Last
, First
, Fund_1 = max(case when RowNum = 1 then Fund end)
, Amt_1 = max(case when RowNum = 1 then Amount end)
, Status_1 = max(case when RowNum = 1 then Status end)
, Fund_2 = max(case when RowNum = 2 then Fund end)
, Amt_2 = max(case when RowNum = 2 then Amount end)
, Status_2 = max(case when RowNum = 2 then Status end)
, Fund_3 = max(case when RowNum = 3 then Fund end)
, Amt_3 = max(case when RowNum = 3 then Amount end)
, Status_3 = max(case when RowNum = 3 then Status end)
from SortedValues
group by UID
, Last
, First
order by UID
, Last
, First

Pivot Multiple Fields With Dynamic Descriptions

I'm trying to pivot the following and I don't know why I'm having such a hard time figuring it out.
Data Script
create table #data (ID varchar(50)
, nm varchar(50)
, val decimal(18,2)
)
insert into #data values (1,'Name1', 100.00),
(1,'Name2', 200.00),
(2,'Name3', 300.00),
(2,'Name4', 400.00),
(2,'Name5', 500.00),
(3,'Name6', 600.00),
(4,'Name7', 700.00),
(4,'Name8', 800.00),
(5,'Name9', 900.00)
Wanted Results As A Table in SQL Server
1 Name1 100 Name2 200
2 Name3 300 Name4 400 Name5 500
3 Name6 600
4 Name7 700 Name8 800
5 Name9 900
Update:
The following provides results in two fields, but what I really want is for the Name and Values to all exist in separate columns, not in one,
SELECT id,
(
SELECT nm,val
FROM #data
WHERE id = d.id
ORDER BY id FOR XML PATH('')
)
FROM #data d
WHERE
id IS NOT NULL
GROUP BY id;
This is an example of "pivot" not aggregate string concatenation. One issue with SQL queries is that you need to specify the exact columns being returned. So, this cannot be dynamic with respect to the returning columns.
The following returns up to three values per nm:
select id,
max(case when seqnum = 1 then nm end) as nm_1,
max(case when seqnum = 1 then val end) as val_1,
max(case when seqnum = 2 then nm end) as nm_2,
max(case when seqnum = 2 then val end) as val_2,
max(case when seqnum = 3 then nm end) as nm_3,
max(case when seqnum = 3 then val end) as val_3
from (select d.*,
row_number() over (partition by id order by (select null)) as seqnum
from #data d
) d
group by id;
Note that you probably want the columns in insertion order. If so, you need to specify a column with the ordering. I would recommend defining the table as:
create table #data (
dataId int identity(1, 1,) primary key,
ID varchar(50),
nm varchar(50),
val decimal(18,2)
);

T-SQL multiple datetime diff on one line

Using SQL Server (T-SQL), and I have two tables:
tblTrial:
TrialID (PK) int
TrialDate
...
tblLaps:
LapID (PK) int
TrialID (FK) int
LapNumber int
LapStart smalldatetime
...
For TrialID = 1, there are four Lap rows:
LapID TrialID LapNumber LapStart
1 1 1 t1 (some smalldatetime value)
2 1 2 t2
3 1 3 t3
4 1 4 t4
I want to display the SQL so that for each Trial, only one row is displayed, and it has the time differences.
For example, a row for TrialID = 1 might look like:
Trial# 1stLap 2ndLap 3rdLap
---------------------------------
1 3min 4min 5min
where 1stLap is time difference t2-t1, 2ndLap is t3-t2, 3rdLap is t4-t3.
How do I make everything go on one line in a SQL statement?
Thanks
select
t.TrialID
, datediff(minute, max(case when LapNumber = 1 then LapStart end) , max(case when LapNumber = 2 then LapStart end) ) lap1_2
, datediff(minute, max(case when LapNumber = 2 then LapStart end) , max(case when LapNumber = 3 then LapStart end) ) lap2_3
, datediff(minute, max(case when LapNumber = 3 then LapStart end) , max(case when LapNumber = 4 then LapStart end) ) lap3_4
from tblTrial as t
inner join tblLaps as l on t.TrialID = l.TrialID
group by
t.TrialID
see this sqlfiddle demo
For time difference in minutes, you could do like this:
with laps as -- First CTE table to join every lap with the next lap to get end time
(
select TrialID,t1.LapID, datediff(mi,t1.LapStart, t2.LapStart ) as Lap
from tblLaps t1
join tblLaps t2
on t1.LapID = t2.LapID - 1 and t1.TrialID = t2.TrialID
)
select TrialID,
max(case t1.LapID when 1 then Lap else null end) as [1stLap],
max(case t1.LapID when 2 then Lap else null end) as [2ndLap],
max(case t1.LapID when 3 then Lap else null end) as [3rdLap],
from laps
group by TrialID
If you want differ by seconds, use datediff(ss,startdate , enddate ), here's the datediff document.