【问题标题】:How do I fill a temp table iteratively and stuff() the result into columns?如何迭代地填充临时表并将结果填充到列中?
【发布时间】:2016-04-15 10:38:05
【问题描述】:

如果我想知道每个用户在某一天在 Intranet 上花费了多少时间,我可以使用自定义函数 - 2 个示例:

select * from [dbo].[usertime]('2016-04-08')

userid  totaltime
-----------------
1       4430
2       11043
5       13045


select * from [dbo].[usertime]('2016-04-09')

userid  totaltime
-----------------
1       345
3       12066
9       15344

我无法控制函数,只能使用它的输出。 totaltime 以秒为单位。

从另一个表中,我可以选择一年中的日期:

select * from dates;

date
----------
2016-01-01
...
2016-04-08
2016-04-09

我想为dates表中的每个date运行自定义函数usertime并将结果存储在临时表中,如下所示:

userid  2016-01-01  ..  2016-04-08  2016-04-09
----------------------------------------------
1       ..              4430        345
2       ..              11043       0
3       ..              0           12066
5       ..              13045       0
9       ..              0           15344

这需要我在一个循环中调用usertime,伪:

create table #usertime
(
    userid  int
    date    date
    seconds int
)

select * into #dates from dates;

foreach (#dates as _date)
    update #usertime with [dbo].[usertime](_date)

select * from #usertime

userid  2016-01-01  ..  2016-04-08  2016-04-09
----------------------------------------------
1       ..              4430        345
2       ..              11043       0
3       ..              0           12066
5       ..              13045       0
9       ..              0           15344

我知道我需要动态 SQL 以每次循环使用不同的日期和 stuff() 从来自 #usertime 的结果集中的行创建多个列。但我不明白如何使用这些功能。谁能帮帮我?

【问题讨论】:

  • SQL Server dynamic PIVOT query? 的可能重复项。这个问题已经被问了上百次了。
  • [dbo].[usertime]('2016-04-08') 还必须接收用户 ID。不是吗?
  • @RuslanK。 - 不,一定不能。唯一的输入是日期。该函数首先检索当天登录的用户的不同列表,然后从另一个表中检索在网站上花费的总时间:)
  • @Pr0no,抱歉疏忽,现在我看到该函数不是标量而是表值。
  • 从同一用户复制,略有改动....stackoverflow.com/questions/36363932/…

标签: sql sql-server


【解决方案1】:

不需要任何循环(在 SQL 中几乎总是应该避免的)。

SELECT
    T.userid,
    D._date,
    T.totaltime
FROM
    #dates D   -- Probably no need for a temporary table either...
CROSS APPLY dbo.usertime(D._date) T

如果您需要转置这些结果,那么您也可以这样做。

【讨论】:

  • 不错!但是,根据帖子,“日期”是永久表,而不是临时表。
【解决方案2】:

由于临时表的作用域,动态表结构更容易使用永久表。如果出于某种原因必须使用#usertime 临时表,则需要嵌套动态 SQL,这非常难看。

下面是一个示例,说明如何动态地将结果从行旋转到列。

SET NOCOUNT ON;

IF OBJECT_ID(N'dbo.TempUserTime', 'U') IS NOT NULL
    DROP TABLE dbo.TempUserTime;
IF OBJECT_ID(N'tempdb..#UnpivitedUserTime', 'U') IS NOT NULL
    DROP TABLE #UnpivitedUserTime;

--load temp table with unpivoted data
SELECT date, userid, totaltime
INTO #UnpivitedUserTime
FROM dates
CROSS APPLY dbo.userTime(date)
WHERE date BETWEEN '2016-01-01' AND '2016-04-09';

--create pivot table structure with userid and one column per date
DECLARE @SQL nvarchar(MAX) = 'CREATE TABLE dbo.TempUserTime(userid int NOT NULL';
SELECT @SQL += ',' + QUOTENAME(CONVERT(char(10), date, 121)) + ' int NULL'
FROM dates
WHERE date BETWEEN '2016-01-01' AND '2016-04-09';
SELECT @SQL += ');'
EXEC(@SQL);

--insert a row into pivot table for each user
INSERT INTO dbo.TempUserTime (userid)
SELECT DISTINCT userid FROM #UnpivitedUserTime;

--generate an update statement for each date to update all users
SET @SQL = N'';
SELECT @SQL += N'UPDATE dbo.TempUserTime
SET ' + QUOTENAME(CONVERT(char(10), date, 121)) + N' = (
    SELECT totaltime
    FROM #UnpivitedUserTime AS u
    WHERE
        u.date = ''' +  + CONVERT(char(10), date, 121) +  + N'''
        AND u.userid = TempUserTime.userid
    );
'
FROM dates
CROSS APPLY dbo.userTime(date)
WHERE date BETWEEN '2016-01-01' AND '2016-04-09';

--execute update batch
EXEC(@SQL);

--return results
SELECT *
FROM dbo.TempUserTime
ORDER BY userid;

IF OBJECT_ID(N'dbo.TempUserTime', 'U') IS NOT NULL
    DROP TABLE dbo.TempUserTime;
IF OBJECT_ID(N'tempdb..#UnpivitedUserTime', 'U') IS NOT NULL
    DROP TABLE #UnpivitedUserTime;
GO

【讨论】:

    【解决方案3】:

    正如 Tom H 所说,您应该避免循环,并且应该能够通过交叉应用来做到这一点。动态 SQL 是根据日期表中的内容来构建列。

    DECLARE @SearchList     varchar(1000)
    DECLARE @sql            varchar(MAX)
    
    SELECT @SearchList = COALESCE(@SearchList, '') + ',['  + CAST([date] AS VARCHAR(100)) + ']' FROM dates 
    select @SearchList
    
    SET @sql = 'SELECT userid' + @SearchList +'
        FROM
        (SELECT d.[date], U.userid, U.totaltime FROM dates d
            CROSS APPLY [dbo].[usertime](d.[date]) U) AS t
        PIVOT
        (
            SUM(seconds)
            FOR [date] IN (' + RIGHT(@SearchList, LEN(@SearchList)-1) + ') 
        ) AS pvt'   
    
    EXEC(@sql)
    

    【讨论】:

      【解决方案4】:

      这看起来你需要一个游标来调用你的函数,并使用从 [dates] 表中读取的值。 你可以从:

      CREATE TABLE #usertime
      (
          userid int
          ,date date
          ,seconds int
      )
      DECLARE @date nvarchar(16)
      DECLARE @sql nvarchar(max)
      DECLARE curs CURSOR FOR SELECT * FROM dates
      OPEN curs
      FETCH NEXT FROM curs INTO @date
      WHILE @@FETCH_STATUS = 0
      BEGIN
          SET @sql = 'INSERT INTO #usertime SELECT userid,'''+@date+''',totaltime from [dbo].[usertime]('''+@date+''')'
          --print @sql
          exec (@sql)
          FETCH NEXT FROM curs INTO @date
      END
      CLOSE curs
      DEALLOCATE curs
      
      SELECT * FROM #usertime
      

      这应该返回(除非我的表名有语法错误)这样的结果:

      userid  date        seconds
      ----------------------------------------------
      1       2016-04-08  4430
      1       2016-04-09  345
      2       2016-04-08  11043
      3       2016-04-09  12066
      

      在此之后,您可以在该表上添加一个枢轴,如果您希望它旋转

      【讨论】:

        【解决方案5】:

        希望,我理解你的问题!

        DECLARE @userstring AS nvarchar(max),
                @sql AS nvarchar(max)
        
        CREATE TABLE #usertime (
            userid int,
            totaltime int,
            dateof date
        )
        
        INSERT INTO #usertime VALUES
        (1, 4430, '2016-04-08'),
        (2, 11043, '2016-04-08'),
        (5, 13045, '2016-04-08'),
        (1, 345, '2016-04-09'),
        (3, 12066, '2016-04-09'),
        (9, 15344, '2016-04-09')
        
        
        SELECT @userstring = stuff((select distinct ',' + quotename(dateof) from  #usertime for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '');
        
        SELECT @sql = '
        select *
        from (select userid,
                     totaltime,
                     dateof
                from #usertime) src 
        pivot (SUM(totaltime) for [dateof] in ('+@userstring+')
        ) pvt'
        
        EXECUTE(@sql)
        
        DROP TABLE #usertime
        

        输出:

        userid      2016-04-08  2016-04-09
        ----------- ----------- -----------
        1           4430        345
        2           11043       NULL
        3           NULL        12066
        5           13045       NULL
        9           NULL        15344
        
        (5 row(s) affected)
        

        【讨论】:

          猜你喜欢
          • 2018-08-24
          • 2021-12-28
          • 2013-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-30
          相关资源
          最近更新 更多