T SQL Query Format to Build Dynamic Table

大兔子大兔子 提交于 2020-01-15 14:47:13

问题


I need help creating a query to generate the results I am looking for.

I'm creating an application to track employee attendance based off an existing database table. The user selects a date or group of dates from a calendar (say 10/1/18, 10/2/18 and 10/3/18). They click submit and I need to generate a table that displays every employee with a check mark in the date column if they were there that day.

The table is called History and has 2 main columns: EmployeeID; and TransactionDate. Every time an employee walks through a door (including the entrance) a History transaction is created (NFC badges) which adds a new row with the Employee's ID and a date/time stamp. Each employee likely has several swipes per day but all I really need is to know is if there is a single swipe for that day. I'm posting pictures of what the table looks like and what the query results and my table needs to look like...

Table:

Results:

I could do a query like:

select employeeid, TranDate from History 
where (CAST(trandate as DATE) = '2018-10-1' or CAST(trandate as DATE) = '2018-10-2' or CAST(trandate as DATE) = '2018-10-3' ) 
order by employeeid, TranDate

and sort it out programmatically but I feel there is a far more efficient way to query for the results I'm looking for.

Any help is greatly appreciated. Let me know if I need to give a better explanation.


回答1:


What you want to do is called pivot

Try to use this code

SELECT employeeid
      ,case when [2018-10-1] > 0 then '1' else '' end as [2018-10-1]
      ,case when [2018-10-2] > 0 then '1' else '' end as [2018-10-2]
      ,case when [2018-10-3] > 0 then '1' else '' end as [2018-10-3]
FROM(
SELECT employeeid
     , TranDate
     , 1 as num 
FROM History 
WHERE (CAST(trandate as DATE) = '2018-10-1' 
       or CAST(trandate as DATE) = '2018-10-2' 
       or CAST(trandate as DATE) = '2018-10-3' ) 
)
PIVOT(
COUNT(num)
FOR TranDate IN ([2018-10-1],[2018-10-2],[2018-10-3])
) as pvt

You can check sql documentation on PIVOT and UNPIVOT on PIVOT, UNPIVOT SQL, Also if you want something more dynamic for your transdate fields you can check Dynamic Pivot




回答2:


you said: "all I really need is to know is if there is a single swipe for that day."

Answer - aggregate query

select employeeid, TranDate, count(TranDate) as count
from History 
where -- look below
Group by employeeid, TranDate

What to put in WHERE? - depends. If you want month period then do

TranDate between '2018-10-1' and '2018-10-31 23:59:59.999'

If you want particular dates do

CAST(trandate as DATE) in ('2018-10-1', '2018-10-11', '2018-10-11')

In the latter case, you will have to build this dynamically

But in this case you're facing a new issue - what if your person never went though the door? This person will not be on the list. Then you need to letf join this with a table that has all employees

NOW since I am not TSql super-guru but I like coding, I show you working code how it can be done programmatically. Below is a couple of snippets of code that can be combined into one

-- SETUP
create table recs (id int, dt date );
insert into recs values(1, '2018-10-1');
insert into recs values(1, '2018-10-1');
insert into recs values(2, '2018-10-2');
insert into recs values(2, '2018-10-2');
insert into recs values(2, '2018-10-3');
insert into recs values(3, '2018-10-3');
insert into recs values(3, '2018-10-3');
insert into recs values(3, '2018-10-4');
insert into recs values(3, '2018-10-1'); 


-- Prepare Date Grid table
DECLARE crs_dt CURSOR FOR SELECT dt FROM recs group by dt order by dt;
declare @createTbl varchar(1200) = 'create table tbl (id int, ';
declare @fetched varchar(20);

OPEN crs_dt;  
FETCH NEXT FROM crs_dt into @fetched;  

WHILE @@FETCH_STATUS = 0  
BEGIN  
   set @createTbl = @createTbl + '['+@fetched+'] int'
   FETCH NEXT FROM crs_dt into @fetched;
   if @@FETCH_STATUS = 0
   begin set @createTbl = @createTbl + ','; end
END  

set @createTbl = @createTbl + ')';

CLOSE crs_dt;  
DEALLOCATE crs_dt;

select @createTbl; -- just a test
execute (@createTbl)
SELECT * FROM tbl; -- just a test


-- LOAD date grid table with data
DECLARE crs_rec CURSOR FOR 
SELECT id, dt, FORMAT ( dt, 'yyyy-MM-dd' ) colName, 
(case count(dt)  when 0 then 0 else 1 end) cnt
FROM recs group by id, dt order by dt;

declare @createInsert varchar(1200);
declare @id int, @dt date, @colName varchar(20),@yesNo int;

OPEN crs_rec;  
FETCH NEXT FROM crs_rec into @id, @dt, @colName, @yesNo;  

WHILE @@FETCH_STATUS = 0  
BEGIN
   if exists(select 1 from tbl where id = @id)
       set @createInsert = 'update tbl set ['+@colName+']='+cast(@yesNo as varchar(1))+ ' where id='+ cast(@id as varchar(1000));
   else  
       set @createInsert = 
           'insert into tbl (id,['+@colName+']) values ('+cast(@id as varchar(1000))+','+cast(@yesNo as varchar(1))+')';
   select @createInsert; -- just a test
   execute (@createInsert);

   FETCH NEXT FROM crs_rec into  @id, @dt, @colName, @yesNo;
END  
CLOSE crs_rec;  
DEALLOCATE crs_rec;

commit;

-- Lets Load data 
SELECT * FROM tbl

And the result is....

Not the fastest or most code-efficient, but certainly fun. And, 1-this is totally dynamic. 2-if you look at the result, now going back to the problem I mentioned earlier, you can join this table to full list of employees and get entire set of data, including employees who has not swipe during the selected period.




回答3:


So I've figured one way to get the results I'm looking for.

First I need to get the distinct employeeID's and store them into a temp table:

create table #TempTable
(
    EmployeeID varchar(25)
)

insert into #TempTable (EmployeeID)

select distinct employeeID from History order by employeeID;

From here, I am able to join my temp table onto my History table and select exactly what I was looking for:

SELECT distinct M.EmployeeID AS Employee, 
(SELECT CASE WHEN EXISTS
(SELECT 1 FROM HISTORY WHERE TAGID = M.EmployeeID AND (CAST(TRANDATE AS DATE) = '10/12/2018')) 
THEN CAST (1 AS BIT) ELSE CAST (0 AS BIT) END) AS '10/12/2018'           
FROM #TempTable AS M left JOIN HISTORY AS H ON M.EmployeeID = H.TAGID where m.EmployeeID = '000000000000000000000001' order BY Employee

Finally drop the temp table:

drop table #TempTable

For more dates, I just add a new column giving it the date as the header. Thanks for everyone's help on this issue.




回答4:


with using @T.S. created table :

create table recs (id int, dt date );
insert into recs values(1, '2018-10-1');
insert into recs values(1, '2018-10-1');
insert into recs values(2, '2018-10-2');
insert into recs values(2, '2018-10-2');
insert into recs values(2, '2018-10-3');
insert into recs values(3, '2018-10-3');
insert into recs values(3, '2018-10-3');
insert into recs values(3, '2018-10-4');
insert into recs values(3, '2018-10-1'); 

declare @dts as varchar(max) =''
declare @dtsSelect as varchar(max) =''
select  @dts =  @dts + ',[' +dt +']',
             @dtsSelect =  @dtsSelect + ',Case WHEN [' +dt+']>0 then 1 else 0 end ['+dt+']'
From (select distinct cast(dt as varchar(100)) dt from recs) recs
//we use some variables to create columns should apear in pivot list and 
// also to check if we have a value for column then put 1 instead
set @dts = Stuff(@dts,1,1,'')
set @dtsSelect = Stuff(@dtsSelect,1,1,'')
//delete the first comma

declare @sql nvarchar(max) = '
select id,'+@dtsSelect+' from recs 
pivot(Count(dt) For dt in('+@dts+'))PV

'
exec sp_executesql @stme = @sql

assume that dates are not always same




回答5:


If the pivot columns are fixed you can use this

Edit - I guess it can be dynamic as well because you can dynamically create the SQL statement.

Microsoft Docs : FROM - Using PIVOT and UNPIVOT

Example

select * from History
PIVOT  
(
    COUNT(TranDate)
    FOR TranDate IN ([2018-10-01], [2018-10-02], [2018-10-03])  
) AS PivotTable;


来源:https://stackoverflow.com/questions/52672409/t-sql-query-format-to-build-dynamic-table

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!