8

以下是我两年前开始使用的模式,它在我的遗留代码中一遍又一遍地重复。

它使用不同的时间段有效地对相同的数据进行分组。

有没有我应该接近这个的标准方法,或者这种冗长的方法和我能得到的一样好?

提出这个问题的另一种方式是如何使以下内容更简洁?
所有 4 个查询都来自同一个数据源,并且所有四个查询都进入同一个输出表,这 4 个查询可以合并为 1 个较短的脚本吗?

DECLARE @myDate DATETIME = CONVERT(DATETIME,CONVERT(VARCHAR(11),GETDATE(),106)); 
DECLARE @myFirstDateLastMth CHAR(8) =CONVERT(CHAR(6),DATEADD(mm,-1,@myDate-1),112) + '01';
DECLARE @myFirstDateCurrentMth CHAR(8) =CONVERT(CHAR(6),DATEADD(mm,0,@myDate-1),112) + '01'; 

DELETE FROM WH.dbo.tb_myTable

--day on day==========
INSERT INTO WH.dbo.tb_myTable
SELECT 
  TimePeriod =
    CASE 
        WHEN x.DateKey = CONVERT(VARCHAR(11),@myDate - 1,112) THEN 'Day'                    
        WHEN x.DateKey = CONVERT(VARCHAR(11),@myDate - 2,112) THEN 'Day-1'  
    END,
  Game              = x.Name,
  Score             = SUM(x.Score),
  Ticks             = SUM(x.Ticks),
  ScorePerTick = SUM(x.Score)/SUM(x.Ticks)
FROM #LimitedBetinfo x
WHEREx.DateKey >= CONVERT(VARCHAR(11),@myDate - 2,112)
GROUP BY
  CASE 
    WHEN x.DateKey = CONVERT(VARCHAR(11),@myDate - 1,112) THEN 'Day'                    
    WHEN x.DateKey = CONVERT(VARCHAR(11),@myDate - 2,112) THEN 'Day-1'  
  END,
  x.Name;

--wk on wk==========
INSERT INTO WH.dbo.tb_myTable
SELECT 
  TimePeriod =
        CASE 
        WHEN x.DateKey >= CONVERT(VARCHAR(11),@myDate - 7,112) THEN 'Week'                  
        WHEN x.DateKey < CONVERT(VARCHAR(11),@myDate - 7,112) 
                    AND x.DateKey >= CONVERT(VARCHAR(11),@myDate - 14,112)  
                            THEN 'Week-1'
    END,
  Game               = x.Name,
  Score              = SUM(x.Score),
  Ticks              = SUM(x.Ticks),
  ScorePerTick = SUM(x.Score)/SUM(x.Ticks)
FROM #LimitedBetinfo x
WHERE   x.DateKey >= CONVERT(VARCHAR(11),@myDate - 14,112)
GROUP BY
  CASE 
    WHEN x.DateKey >= CONVERT(VARCHAR(11),@myDate - 7,112) THEN 'Week'                  
    WHEN x.DateKey < CONVERT(VARCHAR(11),@myDate - 7,112) 
                AND x.DateKey >= CONVERT(VARCHAR(11),@myDate - 14,112)  
                        THEN 'Week-1'
    END,
  g.Name;                   


--mth on mth==========
INSERT INTO WH.dbo.tb_myTable
SELECT 
  TimePeriod =
    CASE 
        WHEN x.DateKey >= CONVERT(VARCHAR(11),@myDate - 28,112) THEN 'Month'                    
        WHEN x.DateKey < CONVERT(VARCHAR(11),@myDate - 28,112) 
                        AND x.DateKey >= CONVERT(VARCHAR(11),@myDate - 56,112)  
                                THEN 'Month-1'
    END,
  Game               = x.Name,
  Score              = SUM(x.Score),
  Ticks              = SUM(x.Ticks),
  ScorePerTick = SUM(x.Score)/SUM(x.Ticks)
FROM #LimitedBetinfo x
WHERE   x.DateKey >=  CONVERT(VARCHAR(11),@myDate - 56,112)
GROUP BY
  CASE 
    WHEN x.DateKey >= CONVERT(VARCHAR(11),@myDate - 28,112) THEN 'Month'                    
    WHEN x.DateKey < CONVERT(VARCHAR(11),@myDate - 28,112) 
                AND x.DateKey >= CONVERT(VARCHAR(11),@myDate - 56,112)  
                        THEN 'Month-1'
  END,
  g.Name;                   


--MTD and PrevCalMonth==========
INSERT INTO WH.dbo.tb_myTable
SELECT 
  TimePeriod
  = CASE 
        WHEN  x.DateKey >= @myFirstDateCurrentMth   THEN 'MTD'
        WHEN  x.DateKey < @myFirstDateCurrentMth  
                AND  x.DateKey >=@myFirstDateLastMth THEN 'PrevCalMonth'                                      
  END,
  Game              = x.Name,
  Score             = SUM(x.Score),
  Ticks             = SUM(x.Ticks),
  ScorePerTick = SUM(x.Score)/SUM(x.Ticks)
FROM #LimitedBetinfo x
WHERE   x.DateKey >=  CONVERT(CHAR(6),DATEADD(mm,-1,@myDate-1),112) + '01'
GROUP BY
  CASE 
    WHEN  x.DateKey >= @myFirstDateCurrentMth   THEN 'MTD'
    WHEN  x.DateKey < @myFirstDateCurrentMth  
            AND  x.DateKey >=@myFirstDateLastMth THEN 'PrevCalMonth'            
  END,
  g.Name;   
4

5 回答 5

7

我会将其设为单个插入语句。

现在宁愿不要通过对集合、多维数据集或汇总进行分组来使用该组,因为我不知道如何限制在单个日期组上计算的行数少于在较大时间段组上计算的行数。

因此,为了防止这种情况发生,您可以创建一个包含时间段的公共表表达式 (;WITH mycte AS (...subquery...))、临时表、表变量或 XML 格式的文本对象,每个行/元素。

此脚本也可以在定义更多或更少的时间段的情况下运行,以便从应用程序到服务器只需一次行程即可获得所有结果。

这是一个临时表的例子,也可以很容易地做成一个表变量:

--Define time periods
CREATE TABLE #TempTimePeriods (
    TimePeriod VARCHAR(20) PRIMARY KEY,
    TPBegin VARCHAR(11) NOT NULL,
    TPEnd VARCHAR(11) NULL
);

DECLARE @myDate DATETIME = '2012-10-10';
DECLARE @myDateMinusOne DATETIME = DATEADD(dd, -1, @myDate);
INSERT INTO #TempTimePeriods ( TimePeriod, TPBegin, TPEnd )
SELECT [TimePeriod], CONVERT(VARCHAR(11), TPBegin, 112) TPBegin, CONVERT(VARCHAR(11), TPEnd, 112) TPEnd
FROM (
    SELECT 'Day'          [TimePeriod], @myDate - 1 TPBegin, @myDate -  1 TPEnd UNION ALL
    SELECT 'Day-1'        [TimePeriod], @myDate - 2 TPBegin, @myDate -  2 TPEnd UNION ALL
    SELECT 'Week'         [TimePeriod], @myDate - 7 TPBegin,                                  NULL TPEnd UNION ALL
    SELECT 'Week-1'       [TimePeriod], @myDate - 14 TPBegin, @myDate -  8 TPEnd UNION ALL
    SELECT 'Month'        [TimePeriod], @myDate - 28 TPBegin,                                  NULL TPEnd UNION ALL
    SELECT 'Month-1'      [TimePeriod], @myDate - 56 TPBegin, @myDate - 29 TPEnd UNION ALL
    SELECT 'MTD'          [TimePeriod], DATEADD(dd, -1 * DAY(@myDateMinusOne) + 1, @myDateMinusOne) TPBegin, NULL TPEnd UNION ALL
    SELECT 'PrevCalMonth' [TimePeriod], DATEADD(mm,-1,DATEADD(dd, -1 * DAY(@myDateMinusOne) + 1, @myDateMinusOne)) TPBegin, DATEADD(dd, -1 * DAY(@myDateMinusOne), @myDateMinusOne) TPEnd
) TT;  

这是主要查询...

--compute/insert results
INSERT INTO WH.dbo.tb_myTable
SELECT TimePeriods.TimePeriod,
x.Name Game,
SUM(x.Score) Score,
SUM(x.Ticks) Ticks,
CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick
FROM #TempTimePeriods TimePeriods
--for periods with no data use left outer join to return 0-value results, otherwise inner join
LEFT OUTER JOIN #LimitedBetInfo x 
ON x.DateKey >= [TimePeriods].TPBegin
AND (
    [TimePeriods].TPEnd IS NULL
    OR x.DateKey <= [TimePeriods].TPEnd
)
GROUP BY TimePeriods.TimePeriod, x.Name

您还可以使用下面的 Common-Table-Expression 消除 #TempTimePeriods 表:

DECLARE @myDate DATETIME = '2012-10-10';
DECLARE @myDateMinusOne DATETIME = DATEADD(dd, -1, @myDate);
;WITH TimePeriods AS (
    SELECT [TimePeriod], CONVERT(VARCHAR(11), TPBegin, 112) TPBegin, CONVERT(VARCHAR(11), TPEnd, 112) TPEnd
    FROM (
        SELECT 'Day'          [TimePeriod], @myDate - 1 TPBegin, @myDate -  1 TPEnd UNION ALL
        SELECT 'Day-1'        [TimePeriod], @myDate - 2 TPBegin, @myDate -  2 TPEnd UNION ALL
        SELECT 'Week'         [TimePeriod], @myDate - 7 TPBegin,                                  NULL TPEnd UNION ALL
        SELECT 'Week-1'       [TimePeriod], @myDate - 14 TPBegin, @myDate -  8 TPEnd UNION ALL
        SELECT 'Month'        [TimePeriod], @myDate - 28 TPBegin,                                  NULL TPEnd UNION ALL
        SELECT 'Month-1'      [TimePeriod], @myDate - 56 TPBegin, @myDate - 29 TPEnd UNION ALL
        SELECT 'MTD'          [TimePeriod], DATEADD(dd, -1 * DAY(@myDateMinusOne) + 1, @myDateMinusOne) TPBegin, NULL TPEnd UNION ALL
        SELECT 'PrevCalMonth' [TimePeriod], DATEADD(mm,-1,DATEADD(dd, -1 * DAY(@myDateMinusOne) + 1, @myDateMinusOne)) TPBegin, DATEADD(dd, -1 * DAY(@myDateMinusOne), @myDateMinusOne) TPEnd
    ) TT
)
INSERT INTO WH.dbo.tb_myTable
SELECT TimePeriods.TimePeriod,
x.Name Game,
SUM(x.Score) Score,
SUM(x.Ticks) Ticks,
CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick
FROM [TimePeriods]
--for periods with no data use left outer join to return 0-value results, otherwise inner join
LEFT OUTER JOIN #LimitedBetInfo x
ON x.DateKey >= [TimePeriods].TPBegin
AND (
     [TimePeriods].TPEnd IS NULL
     OR x.DateKey <= [TimePeriods].TPEnd
)
GROUP BY [TimePeriods].TimePeriod, x.Name

最后,您可以在 XML 字符串中定义时间段,以便传递给存储过程(如果这是您的偏好),然后按以下步骤操作:

--example XML string with time period definitions
DECLARE @TimePeriodsXml NVARCHAR(MAX) = '
<TimePeriod name="Day" tpbegin="20121010" tpend="20121010" />
<TimePeriod name="Day-1" tpbegin="20121009" tpend="20121009" />
<TimePeriod name="Week" tpbegin="20121004"/>
<TimePeriod name="Week-1" tpbegin="20120927" tpend="20121004" />
<TimePeriod name="Month" tpbegin="20120913" />
<TimePeriod name="Month-1" tpbegin="20120815" tpend="20120912" />
<TimePeriod name="MTD" tpbegin="20121001" />
<TimePeriod name="PrevCalMonth" tpbegin="20120901" tpend="20120930" />
';

并将主查询修改为读取 XML:

SELECT TimePeriods.TimePeriod,
x.Name Game,
SUM(x.Score) Score,
SUM(x.Ticks) Ticks,
CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick
FROM (
    SELECT
    E.TimePeriod.value('./@name', 'VARCHAR(20)') TimePeriod,
    E.TimePeriod.value('./@tpbegin', 'VARCHAR(20)') TPBegin,
    E.TimePeriod.value('./@tpend', 'VARCHAR(20)') TPEnd
    FROM (
        SELECT CAST(@TimePeriodsXml AS XML) tpxml
    ) TT
    CROSS APPLY tpxml.nodes('/TimePeriod') AS E(TimePeriod)
) TimePeriods
--for periods with no data use left outer join to return 0-value results, otherwise inner join
LEFT OUTER JOIN #LimitedBetInfo x
ON x.DateKey >= [TimePeriods].TPBegin
AND (
     [TimePeriods].TPEnd IS NULL
     OR x.DateKey <= [TimePeriods].TPEnd
)
GROUP BY TimePeriods.TimePeriod, x.Name

有关如何将 XML 字符串查询转换为过程以支持 1 个或多个时间段的单个参数的示例:

CREATE PROCEDURE dbo.GetTimePeriodAggregates
@TimePeriodsXmlString NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT TimePeriods.TimePeriod,
x.Name Game,
SUM(x.Score) Score,
SUM(x.Ticks) Ticks,
CASE WHEN SUM(x.Ticks) != 0 THEN SUM(x.Score)/SUM(x.Ticks) END ScorePerTick
FROM (
    SELECT
    E.TimePeriod.value('./@name', 'VARCHAR(20)') TimePeriod,
    E.TimePeriod.value('./@tpbegin', 'VARCHAR(20)') TPBegin,
    E.TimePeriod.value('./@tpend', 'VARCHAR(20)') TPEnd
    FROM (
        SELECT CAST(@TimePeriodsXml AS XML) tpxml
    ) TT
    CROSS APPLY tpxml.nodes('/TimePeriod') AS E(TimePeriod)
) TimePeriods
LEFT OUTER JOIN #LimitedBetInfo x
ON x.DateKey BETWEEN TimePeriods.TPBegin AND TimePeriods.TPEnd
GROUP BY TimePeriods.TimePeriod, x.Name
END

可以运行为:

--This declare is just an example, it could be instead a parameter passed from an application
DECLARE @ThisExecutionsXmlString NVARCHAR(MAX) = N'
<TimePeriod name="Day" tpbegin="20121010" tpend="20121010" />
<TimePeriod name="Day-1" tpbegin="20121009" tpend="20121009" />
<TimePeriod name="Week" tpbegin="20121004"/>
<TimePeriod name="Week-1" tpbegin="20120927" tpend="20121004" />
<TimePeriod name="Month" tpbegin="20120913" />
<TimePeriod name="Month-1" tpbegin="20120815" tpend="20120912" />
<TimePeriod name="MTD" tpbegin="20121001" />
<TimePeriod name="PrevCalMonth" tpbegin="20120901" tpend="20120930" />
';

INSERT INTO WH.dbo.tb_myTable
EXEC dbo.GetTimePeriodAggregates @TimePeriodsXmlString=@ThisExecutionsXmlString
于 2013-01-04T14:20:52.460 回答
6

您可以创建此存储过程

CREATE PROCEDURE InsertData
    @minLimit date,
    @maxLimit date,
    @minTerm nvarchar(50),
    @maxTerm nvarchar(50)
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO tb_myTable
    SELECT 
        [TimePeriod]        = CASE WHEN x.DateKey >= @maxLimit THEN @maxTerm ELSE @minTerm END,
        [Game]              = x.Name,
        [Score]             = SUM(x.[Score]),
        [Ticks]             = SUM(x.[Ticks]),
        [ScorePerTick]      = SUM(x.[Score])/SUM(x.[Ticks])

    FROM #LimitedBetinfo x
    WHERE x.DateKey >= @minLimit
    GROUP BY 
        CASE WHEN x.DateKey >= @maxLimit THEN @maxTerm ELSE @minTerm END,
        x.Name

END
GO

并像这样使用

TRUNCATE TABLE tb_myTable

DECLARE @today date = cast(getdate() as date)
DECLARE @yesterday date = dateadd(day, -1, @today)
EXECUTE dbo.InsertData @yesterday, @today, N'Day-1', N'Day' 

DECLARE @thisweek date = DATEADD(ww, DATEDIFF(ww,0,GETDATE()), 0)
DECLARE @lastweek date = DATEADD(ww, -1, @thisweek)
EXECUTE dbo.InsertData @lastweek, @thisweek, N'Week-1', N'Week' 

DECLARE @prev28 date = dateadd(day, -28, @today)
DECLARE @prev56 date = dateadd(day, -56, @today)
EXECUTE dbo.InsertData @prev56, @prev28, N'Month-1', N'Month' 

DECLARE @thismonth date = DATEADD(mm, DATEDIFF(mm,0,GETDATE()), 0)
DECLARE @lastmonth date = DATEADD(mm, -1, @thismonth)
EXECUTE dbo.InsertData @lastmonth, @thismonth, N'PrevCalMonth', N'MTD' 
于 2012-12-30T22:40:34.447 回答
3

使用参数 - VALUES 作为表源并将它们作为参数应用到 CROSS APPLY 与派生表

DECLARE @myDate datetime = CAST(GETDATE() AS date);
IF OBJECT_ID('WH.dbo.tb_myTable') IS NOT NULL DROP TABLE WH.dbo.tb_myTable
SELECT TimePeriod, Game, Score, Ticks, ScorePerTicks
INTO WH.dbo.tb_myTable
FROM (VALUES('Day', DATEADD(day, -1, @myDate), @myDate),
            ('Day-1', DATEADD(day, -2, @myDate), DATEADD(day, -2, @myDate)),
            ('Week', DATEADD(day, -7, @myDate), @myDate),
            ('Week-1', DATEADD(day, -14, @myDate), DATEADD(day, -8, @myDate)),
            ('Month', DATEADD(day, -28, @myDate), @myDate),
            ('Month-1', DATEADD(day, -56, @myDate), DATEADD(day, -29, @myDate)),
            ('MTD', DATEADD(DAY, 1 - DAY(@myDate), @myDate), @myDate), 
            ('PrevCalMonth', DATEADD(DAY, 1 - DAY(@myDate), DATEADD(MONTH, -1, @myDate)), DATEADD(DAY,  - DAY(@myDate), @myDate)))
RParameters(TimePeriod, BDate, EDate)
  CROSS APPLY (SELECT x.Name AS Game,
                      SUM(x.Score) AS Score,
                      SUM(x.Ticks) AS Ticks,
                      SUM(x.Score) / SUM(x.Ticks) AS ScorePerTicks
               FROM #LimitedBetinfo x
               WHERE DateKey BETWEEN RParameters.BDate AND RParameters.EDate
               GROUP BY Name) AS o

SQLFiddle上的演示

于 2013-01-05T12:32:00.600 回答
2

弗雷德的答案可能会有所改进。不是在速度方面,只是通过删除额外的CASE. 作为一个建议,我还用一个字符串替换了两个字符串(例如DAYDAY-1)的传递,并让另一个只是一个连接;然而,这将导致PrevCalMonth显示为MTD-1(尽管有一些解决方法)。

CREATE PROCEDURE InsertData
  @minLimit date, @maxLimit date, @string nvarchar(50)
AS
  INSERT INTO tb_myTable
  SELECT TimePeriod, Name, SUM(Score) Score, SUM(Ticks) Ticks, 
    SUM(Score)/SUM(Ticks) ScorePerTick 
  FROM
  (
    SELECT *, /* or 'Name, Score, Ticks,' */
      TimePeriod = CASE WHEN x.DateKey >= @maxLimit THEN @string ELSE @string+'-1' END
    FROM #LimitedBetinfo x
    WHERE x.DateKey >= @minLimit
  ) A
  GROUP BY TimePeriod, Name
GO

并像这样使用:

TRUNCATE TABLE tb_myTable

DECLARE @today date = cast(getdate() as date)
DECLARE @yesterday date = dateadd(day, -1, @today)
EXECUTE dbo.InsertData @yesterday, @today, N'Day' 

DECLARE @thisweek date = DATEADD(ww, DATEDIFF(ww,0,GETDATE()), 0)
DECLARE @lastweek date = DATEADD(ww, -1, @thisweek)
EXECUTE dbo.InsertData @lastweek, @thisweek, N'Week' 

DECLARE @prev28 date = dateadd(day, -28, @today)
DECLARE @prev56 date = dateadd(day, -56, @today)
EXECUTE dbo.InsertData @prev56, @prev28, N'Month' 

DECLARE @thismonth date = DATEADD(mm, DATEDIFF(mm,0,GETDATE()), 0)
DECLARE @lastmonth date = DATEADD(mm, -1, @thismonth)
EXECUTE dbo.InsertData @lastmonth, @thismonth, N'MTD' 
于 2013-01-03T09:57:35.367 回答
2

看来这可能是CUBE 分组的工作。

抱歉,我不会为你的问题提供确切的解决方案,但是 select 的MOCKUP形式应该是这样的:

select * from
(
    select *,count(*) amount from
    (
        select datepart(HOUR, login_time) as hour, 
               datepart(MINUTE, login_time) as minute, 
               cmd as name
        from sys.sysprocesses
    ) tmp
    group by cube(tmp.hour, tmp.minute, tmp.name)
) tmp2
where tmp2.name is not null and
      (
      (tmp2.hour is not null and tmp2.minute is null) or
      (tmp2.hour is null and tmp2.minute is not null)
      )

减一 -cube在这里为您的问题生成太多数据。所以需要过滤掉。一个很大的优点是您只需要一个选择到临时表中。

于 2013-01-04T13:25:27.053 回答