如何迭代填充临时表并将结果填充()到列中?

时间:2016-04-08 13:31:06

标签: sql sql-server

如果我想知道每个用户在某一天他们在内联网上花了多少时间,我可以使用自定义功能 - 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

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

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。但我不明白如何使用这些功能。有人可以帮助我吗?

5 个答案:

答案 0 :(得分:13)

不需要任何循环(在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

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

答案 1 :(得分:3)

由于临时表范围的限制,使用永久表作为动态表结构更容易。如果由于某种原因必须使用#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

答案 2 :(得分:2)

正如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)

答案 3 :(得分:1)

这看起来你需要一个游标来调用你的函数,其中包含从[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

在此之后,如果您想要旋转

,可以在该表上添加一个数据透视表

答案 4 :(得分:1)

希望,我理解你的问题吧!

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)