How To Separate Data Into Two Columns [duplicate]

2019-04-13 07:26发布

问题:

This question already has an answer here:

  • Attendance IN and OUT 3 answers

Consider the following table structure and sample data -

EmpID InputDateTime      StatusINOUT
-------------------------------------
1     2018-05-26 08:44     1
1     2018-05-26 08:44     2
2     2018-05-28 08:44     1
2     2018-05-28 12:44     2                   
1     2018-05-21 08:44     1
1     2018-05-21 10:44     2
2     2018-05-23 08:44     1
2     2018-05-23 08:44     2   

Now I want to separate column InputDateTime into two columns i.e., INTIME(1) and OUTTIME(2). The logic behind this is the date for which StatusInOut is 1 will be InTime and for StatusInOut is 2 that date value will be OUTTIME(2).

The expected output format is as shown below:

Empid   INTIME(1)          OUTIME(2)
--------------------------------------------
1      2018-05-26 08:44    2018-05-26 08:44
2      2018-05-28 08:44    2018-05-28 12:44
1      2018-05-21 08:44    2018-05-21 10:44
2      2018-05-23 08:44    2018-05-23 08:44

This is what I have tried so far

create table #tempStatus (EmpId int, intTime datetime, sStatus int)    
insert into #tempStatus    
values(1, '2018-05-26 08:44', 1),    
    (1, '2018-05-26 08:44', 2),    
    (2, '2018-05-28 08:44', 1),    
    (2, '2018-05-28 12:44', 2),        
    (1, '2018-05-21 08:44', 1),    
    (1, '2018-05-21 10:44', 2),        
    (2, '2018-05-23 08:44', 1),    
    (2, '2018-05-23 08:44', 2)    

select EmpId, MIN(intTime) as intTime, MIN(intTime) as OutTime into #tempA from (  
select EmpId, intTime, intTime as OutTime  
from #tempStatus where sStatus = 1  
)a   
group by EmpId, intTime  

select EmpId, MAX(outTime) as outTime into #tempB from(   
select EmpId, intTime as outTime  
from #tempStatus where sStatus = 2  
)b   
group by empId,outTime     

select * from #tempA order by EmpId  

drop table #tempA  
drop table #tempB  
DROP TABLE #tempStatus

回答1:

You need row_number()s & use differences of them to do conditional aggregation, This also called as Gaps and Islands problem :

select empid, 
       max(case when sStatus = 1 then intTime end) as INTIME,
       max(case when sStatus = 2 then intTime end) as OUTIME
from (select t.*, 
             row_number () over ( order by inttime) as seq1,
             row_number () over (partition by empid order by inttime) as seq2
      from #tempStatus t
     ) t
group by empid, (seq1-seq2);

EDIT : If you want to display OutTime whenever InTime it not there, then you can use subquery :

select t.empid, 
       coalesce(INTIME, OUTIME) as INTIME,
       coalesce(OUTIME, INTIME) as OUTIME
from ( <query here> 
     ) t;


回答2:

Try this PIVOT Sample data

IF OBJECT_ID('Tempdb..#tempStatus')IS NOT NULL
DROP TABLE #tempStatus

CREATE TABLE #TEMPSTATUS (EMPID INT, INTTIME DATETIME, SSTATUS INT)    
INSERT INTO #TEMPSTATUS    
VALUES(1, '2018-05-26 08:44', 1),    
    (1, '2018-05-26 08:44', 2),    
    (2, '2018-05-28 08:44', 1),    
    (2, '2018-05-28 12:44', 2),        
    (1, '2018-05-21 08:44', 1),    
    (1, '2018-05-21 10:44', 2),        
    (2, '2018-05-23 08:44', 1),    
    (2, '2018-05-23 08:44', 2)    

Sql script

SELECT Empid,[INTIME(1)],[OUTIME(2)]
FROM
(
    SELECT EmpId,intTime, CASE WHEN sStatus=1 THEN 'INTIME(1)' 
                       WHEN sStatus=2 THEN 'OUTIME(2)'  END INOutTimes
    FROM #tempStatus
    ) AS SRC
    PIVOT 
    (MAX(intTime) FOR INOutTimes IN ([INTIME(1)],[OUTIME(2)])
    ) AS PVT
UNION ALL
SELECT Empid,[INTIME(1)],[OUTIME(2)]
FROM
(
    SELECT EmpId,intTime, CASE WHEN sStatus=1 THEN 'INTIME(1)' 
                       WHEN sStatus=2 THEN 'OUTIME(2)'  END INOutTimes
    FROM #tempStatus
    ) AS SRC
    PIVOT 
    (MIN(intTime) FOR INOutTimes IN ([INTIME(1)],[OUTIME(2)])
    ) AS PVT

Result

Empid    INTIME(1)                   OUTIME(2)
---------------------------------------------------------------
1       2018-05-26 08:44:00.000     2018-05-26 08:44:00.000
2       2018-05-28 08:44:00.000     2018-05-28 12:44:00.000
1       2018-05-21 08:44:00.000     2018-05-21 10:44:00.000
2       2018-05-23 08:44:00.000     2018-05-23 08:44:00.000


回答3:

This is a problem of matching end time with a start time:

WITH cte AS (
    SELECT EmpId, intTime, sStatus
         , ROW_NUMBER() OVER (PARTITION BY EmpId ORDER BY intTime) AS rn
    FROM #tempStatus
)
SELECT o.EmpId, i.intTime AS INTIME, o.intTime AS OUTIME
FROM cte o
LEFT JOIN cte AS i ON i.EmpId = o.EmpId AND i.rn = o.rn - 1 AND i.sStatus = 1
WHERE o.sStatus = 2

Demo on DB<>Fiddle and some tests



回答4:

I have tried and find out the solution after updating with inner join as shown below. Here I have skipped the date and row where InTime is not there.

create table #tempStatus (EmpId int, intTime datetime, sStatus int)        
insert into #tempStatus        
values(1, '2018-05-26 08:44', 1),        
    (1, '2018-05-26 08:44', 2),        
    (2, '2018-05-28 08:44', 1),        
    (2, '2018-05-28 12:44', 2),            
    (1, '2018-05-21 08:44', 1),        
    (1, '2018-05-21 10:44', 2),            
    (2, '2018-05-23 08:44', 1),        
    (2, '2018-05-23 08:44', 2)  
    ,(3, '2018-05-23 08:44', 1)  

select EmpId, MIN(intTime) as intTime, MAX(intTime) as OutTime into #tempA from (      
select EmpId, intTime, intTime as OutTime      
from #tempStatus where sStatus = 1      
)a       
group by EmpId, intTime      

update s      
set s.OutTime = t.outTime      
from #tempA s      
left join     
(    
select EmpId, MAX(outTime) as outTime from(       
select EmpId, intTime as outTime      
from #tempStatus where sStatus = 2      
)b       
group by empId,outTime) t     
on s.EmpId = t.EmpId and Convert(Varchar,s.OutTime,112) =  Convert(Varchar,t.outTime,112)      

select * from #tempA order by EmpId      

drop table #tempA      
DROP TABLE #tempStatus

Here is the demo- Spltting Column