如何在循环中运行我的自定义函数和查询不同的时间范围?

时间:2016-04-01 19:01:16

标签: sql sql-server

我正在编写一个函数来计算用户在我网站上的总秒数。之后,我将秒数转换为hh:mm:ss

select * into #temp from [MyFunction](timestamp1, timestamp2);

select u.Name, 
       convert(varchar(8), t.Seconds / 3600) + ':'
             + right('0', convert(varchar(2) t.Seconds % 3600/60), 2) + ':'
             + right('0', convert(varchar(2) t.Seconds % 60), 2)
    as [Total Time]
from #temp t left join Users u
    on t.UserID = u.UserID;

示例时间戳为2016-04-01 00:00:00.000的位置。我现在想要的是看到我在网站上花费的总时间,而不是1个范围,而是一系列范围,例如:

2016-01-01 to 2016-01-15
2016-01-16 to 2016-01-31
2016-02-01 to 2016-02-15

是否可以将我的代码置于动态查询中,以便每次都运行相同的代码来计算所有这些范围?

上面代码的输出是:

Name    [Total Time]
--------------------
Anton   6:34:55
Bert    5:22:14

我想要的是输出,例如

Name    [Period_1] [Period_2] [Period_3] [Period_4]
---------------------------------------------------
Anton   6:34:55    5:00:22    null       10:44:32
Bert    5:22:14    null       null        9:22:53

因此,每个范围或代码循环都应该是一列。

我相信pivot()会在这里帮助我,但是我会非常感谢任何用动态SQL(或任何更好的解决方案)启动我的帮助。

3 个答案:

答案 0 :(得分:0)

将当前代码包装到带参数的过程中,例如:

CREATE PROCEUDRE dbo.CalcTime
  @Period       varchar(100)  --  Name of the period
 ,@PeriodStart  datetime      --  Period starts
 ,@PeriodEnd    datetime      --  Period ends

并使用适当的数据类型。

接下来,创建第二个过程。在这一个中,定义另一个临时表,如

CREATE TABLE #Results
 (
   Name       varchar(100)  not null  --  Or however long it might get
  ,Period     varchar(100)  not null  --  Ditto
  ,TotalTime  int           null      --  *
 )

遍历您希望为其定义数据的每个时段。对于每个时期,请调用" CalcTime"存储过程,并将结果转储到临时表中。有两种方法,使用

INSERT #Results
 execute dbo.CalcTime  'Period', 'Jan 1, 2016', 'Jan 15, 2016'

或者,在调用过程中定义了临时表后,您可以在标准INSERT... SELECT...语句中的被调用过程中引用它。

同样在循环内,构建一个逗号分隔的字符串,列出所有句点标签,例如

SET @AllPeriodLabels = isnull(@AllPeriodLabels + ',', '') + @ThisPeriodLabel

,或者

SET @AllPeriodLabels = isnull(@AllPeriodLabels + ',', '') + '[' + @ThisPeriodLabel + ']'  --  **

使用它来针对临时表构建动态SQL pivot语句,您就完成了。正如评论中所提到的,有关于如何做到这一点的SO帖子,这里有两个链接:first讨论了构建动态数据透视表语句,second使用了类似的策略一个不透明的声明。

*避免在对象名称中嵌入空格,它们只会给你带来痛苦。

**好的,有时你必须这样做。

答案 1 :(得分:0)

两个伪表:

persons:
  personId int
  lastname nvarchar(50)

visits:
  personid int
  created datetime
  duration int -- (store things like duration in seconds)

首先列出列,这里我使用了创建的列并将其转换为月份。结果如下:[201501],[201502],[201503],....

declare     @cols nvarchar(max)
set         @cols = STUFF((select ',' + quotename(convert(VARCHAR(6), created, 112))
                        from        visits
                        group by    convert(VARCHAR(6), created, 112)
                        order by    convert(VARCHAR(6), created, 112)
                    for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '')

我需要动态SQL来填充可变数量的COL,我建议你从NON动态SQL开始,让它动态应该是最后一步。

declare     @sql nvarchar(max)
set         @sql = N'
                    select      *
                    -- lazy create a temp so you don't have to bother about the column definitions
                    -- into #temp 
                    from (
                        select      p.lastname, convert(VARCHAR(6), created, 112) as period
                        -- this is optional to get a Grand Row total
                        -- ,(select sum(duration) from visits v where v.personId = p.personId) as total
                        from        visits v
                                    inner join persons p on v.personId = p.personId
                    ) src
                    pivot (
                        sum(duration) for period in (' + @cols + ')
                    ) pvt;
            '

您可以将其打印出来进行验证或运行...

exec sp_executesql @sql

您可以通过将结果转储到临时表(即时创建)来进行扭曲。这创造了为输出添加额外列的机会,例如组织等等。

alter table #temp add organization nvarchar(100)
祝你好运!

答案 2 :(得分:0)

这是一个有效的测试代码。根据需要调整它。

设定:

-- create test tables
CREATE TABLE Users 
  ( 
     UserId   INT, 
     UserName NVARCHAR(max) 
  ) 

CREATE TABLE Access 
  ( 
     UserId    INT, 
     StartTime DATETIME2, 
     EndTime   DATETIME2 
  ) 

CREATE TABLE Periods 
  ( 
     NAME      NVARCHAR(max), 
     StartTime DATETIME2, 
     EndTime   DATETIME2 
  ) 

go 

-- function to format the time
CREATE FUNCTION ToTime(@SECONDS BIGINT) 
returns NVARCHAR(max) 
AS 
  BEGIN 
      RETURN CONVERT(VARCHAR(8), @SECONDS / 3600) + ':' 
             + RIGHT('00'+CONVERT(VARCHAR(2), @SECONDS % 3600/60), 2) 
             + ':' 
             + RIGHT('00'+CONVERT(VARCHAR(2), @SECONDS % 60), 2) 
  END 

go 

-- populate values
INSERT INTO Users 
VALUES     (1, 'Anton'), 
           (2,'Bert') 

DECLARE @I INT=100 
DECLARE @D1 DATETIME2 
DECLARE @D2 DATETIME2 

WHILE ( @I > 0 ) 
  BEGIN 
      SET @D1=Dateadd(second, Rand() * 8640000, Getdate()) 
      SET @D2=Dateadd(second, Rand() * 1000, @D1) 

      INSERT INTO Access 
      VALUES     (Floor(Rand() * 2 + 1), @D1, @D2); 
      SET @I=@I - 1 
  END 

SET @I=1 
SET @D1=Getdate() 

WHILE ( @I < 6 ) 
  BEGIN 
      SET @D2=Dateadd(day, 15, @D1) 

      INSERT INTO Periods 
      VALUES     (Concat('Period_', @I), 
                  @D1, 
                  @D2); 

      SET @D1=@D2 
      SET @I=@I + 1 
  END 

go 

工作代码:

-- Getting the values
DECLARE @COLS NVARCHAR(max) 

SET @COLS = Stuff((SELECT ',' + Quotename(NAME) 
                   FROM   Periods 
                   GROUP  BY NAME 
                   ORDER  BY NAME 
                   FOR xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '' 
            ) 

DECLARE @SQL NVARCHAR(max) 

SET @SQL = N'SELECT * FROM
( 
           SELECT     u.UserName, 
                      p.Name                                                  AS Period,
                      dbo.Totime(Sum(Datediff(SECOND,a.StartTime,a.EndTime))) AS [Time] 
           FROM       Access a 
           INNER JOIN Users u 
           ON         a.UserId=u.UserId 
           INNER JOIN Periods p 
           ON         p.StartTime<=a.StartTime 
           AND        a.StartTime<p.EndTime 
           GROUP BY   u.UserName, 
                      p.Name ) x PIVOT ( Max([Time]) FOR Period IN (' + @COLS +') 
) p;' 

--PRINT @SQL 

EXECUTE(@SQL)