Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/74.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
(SQL Server)正在尝试计算从今天开始的连续会计年度的计算结果_Sql_Sql Server - Fatal编程技术网

(SQL Server)正在尝试计算从今天开始的连续会计年度的计算结果

(SQL Server)正在尝试计算从今天开始的连续会计年度的计算结果,sql,sql-server,Sql,Sql Server,我是SQL Server新手,这是我在stackoverflow上发布的第一个问题,希望我做得对 好的,假设我有一个表,它有一个ID、POSTEDDATE和一个AMOUNT字段。我想用今天的日期算出我们的会计年度是从每年的7月1日到6月31日。从今天开始,如何创建只提供连续会计年度中提供的ID的SQL?而且,它只会给我那些只连续地,换句话说,只持续了2年或更长时间的人的记录 例如,我指的是: ID POSTEDDATE AMOUNT 1 6/15/2016 100.0

我是SQL Server新手,这是我在stackoverflow上发布的第一个问题,希望我做得对

好的,假设我有一个表,它有一个ID、POSTEDDATE和一个AMOUNT字段。我想用今天的日期算出我们的会计年度是从每年的7月1日到6月31日。从今天开始,如何创建只提供连续会计年度中提供的ID的SQL?而且,它只会给我那些只连续地,换句话说,只持续了2年或更长时间的人的记录

例如,我指的是:

ID    POSTEDDATE    AMOUNT
1     6/15/2016     100.00
1     2/10/2015     5.00
2     6/15/2016     10.00
2     1/15/2016     50.00
3     1/10/2013     10.00
3     1/10/2012     60.00
因此,我从该数据集获得的唯一记录是: 身份证颁发年份 1 2

ID2在同一财政年度内给出了2次,ID3在连续两年内给出了2次,但不是从今天开始

这有意义吗?如果没有,我可以更好地澄清任何需要的东西,请尽管问

谢谢你的帮助,
GrimReaper

这里是一种完全不同的方式,它有一点令人讨厌,因为它有选择子选择等。我用常用的表表达式编写了它,但没有给你关于差异和结构的概念。我从另一篇文章中得到了这项技术,我只需要生成一个会计年度并将其与之结合使用

我仍然认为有一种更优雅的方式来面对滞后和领先,我喜欢我的方向,但我需要回到其他事情上。我的猜测是,如果你在一个更大的数据环境中,你可能需要优化这个

DECLARE @Revenue AS TABLE (Id INT, POSTEDDATE DATE, Amount Money)
INSERT INTO @Revenue (Id, POSTEDDATE, Amount)
VALUES (1,'6/15/2016',100.00)
,(1,'2/10/2015',5.00)
,(2,'6/15/2016',10.00)
,(2,'1/15/2016',50.00)
,(3,'1/10/2013',10.00)
,(3,'1/10/2012',60.00)
,(1,'2/10/2012',5.00)
,(1,'2/10/2011',5.00)
,(1,'2/10/2014',5.00)

DECLARE @CurrentFiscalYear INT
SET @CurrentFiscalYear = CASE
             WHEN MONTH(GETDATE()) < 7 THEN YEAR(GETDATE()) - 1
             ELSE YEAR(GETDATE())
       END


SELECT
    Id
    ,ConsecutiveYears = YearsDifference
FROM
    (
       SELECT
          a.Id
          ,AFY = a.FiscalYear
          ,BFY = b.FiscalYear
          ,YearsDifference = b.FiscalYear - a.FiscalYear + 1
          ,Ranking = DENSE_RANK() OVER (PARTITION BY a.id ORDER BY (b.FiscalYear - a.FiscalYear) DESC)
       FROM

          (SELECT FiscalYear = CASE
              WHEN MONTH(POSTEDDATE) < 7 THEN YEAR(POSTEDDATE) - 1
              ELSE YEAR(POSTEDDATE)
            END
            ,*
          FROM
            @Revenue)  a

          INNER JOIN 

          (SELECT FiscalYear = CASE
              WHEN MONTH(POSTEDDATE) < 7 THEN YEAR(POSTEDDATE) - 1
              ELSE YEAR(POSTEDDATE)
            END
            ,*
          FROM
            @Revenue)  b       

          ON a.Id = b.Id
          AND b.FiscalYear > a.FiscalYear
       WHERE
          (b.FiscalYear - a.FiscalYear) = (
             SELECT COUNT(*) - 1
             FROM

                (SELECT FiscalYear = CASE
                        WHEN MONTH(POSTEDDATE) < 7 THEN YEAR(POSTEDDATE) - 1
                        ELSE YEAR(POSTEDDATE)
                      END
                ,*
             FROM
                @Revenue)  a1

             WHERE a.Id = a1.Id AND a1.FiscalYear BETWEEN a.FiscalYear AND b.FiscalYear
             )
    ) final
WHERE
    Ranking = 1
    AND BFY = @CurrentFiscalYear


------ and organized as a common table expressions

;WITH cteRevenue AS (
    SELECT
       FiscalYear = CASE
             WHEN MONTH(POSTEDDATE) < 7 THEN YEAR(POSTEDDATE) - 1
             ELSE YEAR(POSTEDDATE)
       END
          ,*
       FROM
          @Revenue
)

, cteConsecutiveYears AS (
    SELECT
          a.Id
          --,AFY = a.FiscalYear
          ,BFY = b.FiscalYear
          ,YearsDifference = b.FiscalYear - a.FiscalYear + 1
          ,Ranking = DENSE_RANK() OVER (PARTITION BY a.id ORDER BY (b.FiscalYear - a.FiscalYear) DESC)
       FROM
          cteRevenue a
          INNER JOIN cteRevenue b      
          ON a.Id = b.Id
          AND b.FiscalYear > a.FiscalYear
       WHERE
          (b.FiscalYear - a.FiscalYear) = (
             SELECT COUNT(*) - 1
             FROM
                cteRevenue a1
             WHERE a.Id = a1.Id AND a1.FiscalYear BETWEEN a.FiscalYear AND b.FiscalYear
             )
) 

SELECT
    Id
    ,ConsecutiveYears = YearsDifference
FROM
    cteConsecutiveYears
WHERE
    Ranking = 1
    AND BFY = @CurrentFiscalYear

这里有一个非常类似于@Matt的解决方案,但在我的测试中执行得更快。我看到您的评论,这将在一个包含大量数据的表上进行,因此我在数据库中创建了一个名为dbo.generation的表,并将66666 7个样本数据集副本加载到该表中,每个副本增加ID值,总共有4000002条记录。然后,我将ID>200000的每个记录的PostedDate向后移了一年,这有效地将匹配捐赠者的数量限制为66667个,是您数据集副本数量的十分之一。我不知道这与您期望从这个查询中返回的实际捐赠者数量有多接近;我只是选择了一个似乎合理的数字

在我的机器上,查询在大约两秒钟内返回了正确的结果集。所以我希望它会对你有用。查询中的注释解释了它是如何工作的

顺便说一句,我也看到了你的评论,你的老板不想要一个CTE,我应该指出,我使用的CTE并没有让它们本质上比另一个公式慢。事实上,编写使用子查询代替CTE的替代公式很简单,当我这样做时,我发现两个查询生成的执行计划完全相同。CTE版本更易于阅读,因此更易于维护,因为您可以从上到下而不是从内到外对其进行解释。如果你有任何问题,请告诉我

-- Compute the current fiscal year.
declare @ThisFiscalYear int = year(getdate()) + case when month(getdate()) <= 6 then 0 else 1 end;

-- This CTE computes the fiscal year of each donation and gets the set of unique fiscal years in
-- which each donor (ID) made a contribution.
with IDYearComboCTE as
(
    select distinct
        D.ID,
        FiscalYear = year(D.PostedDate) + case when month(D.PostedDate) <= 6 then 0 else 1 end
    from
        dbo.Donation D
),

-- The second CTE produces two values for each fiscal year: 
--
--     1. FiscalYearOffset is the number of fiscal years that lie between the current fiscal year
--        and the fiscal year in which the donation was posted, inclusive. So a donation from the
--        current fiscal year will have FiscalYearOffset = 1, one from the previous fiscal year
--        will have FiscalYearOffset = 2, and so on.
--
--     2. FiscalYearIndex is a reverse chronological ordering of the unique fiscal years for each
--        donor. In other words, the most recent donation for every donor, regardless of how long
--        ago it was, will have FiscalYearOffset = 1.
--
-- The important point to realize here is that FiscalYearOffset and FiscalYearIndex will be the
-- same for a given donation IF AND ONLY IF the donor has posted a donation in that year and in
-- every subsequent year up to and including the current fiscal year. If the donor has skipped a
-- year, then FiscalYearOffset will increase by more than one while FiscalYearIndex increases by
-- only one (because its values are always contiguous), and if the donor hasn't given in the
-- current fiscal year, then the record with FiscalYearIndex = 1 will have FiscalYearOffset > 1.
--
FiscalYearOrderingCTE as
(
    select
        C.ID,
        FiscalYearOffset = @ThisFiscalYear - C.FiscalYear + 1,
        FiscalYearIndex = row_number() over (partition by C.ID order by C.FiscalYear desc)
    from
        IDYearComboCTE C
    where
        C.FiscalYear <= @ThisFiscalYear
)

-- Since, as described above, records with FiscalYearOffset = FiscalYearIndex represent contiguous
-- years in which the same donor made a contribution, we select only those records, then limit the
-- result set to those IDs with two or more records (i.e. with donations in two or more contiguous 
-- fiscal years starting with the current fiscal year). 
select
    O.ID,
    ConsecYears = max(FiscalYearIndex)
from
    FiscalYearOrderingCTE O
where
    O.FiscalYearOffset = O.FiscalYearIndex
group by
    O.ID
having
    max(FiscalYearIndex) >= 2;
编辑:我将尝试更透彻地解释第二个CTE在做什么,因为这就是魔法发生的地方

首先,确保您了解SQL Server的功能。由于按C.ID划分子句,对于具有相同ID的每组记录,此函数生成的行号从1开始。并且由于按C.FiscalYear desc子句排序,我们知道,对于具有相同ID的任何两个记录,其会计年度较晚的记录的FiscalYearIndex值较小。还请注意,我们正在操作的记录是由上一个CTE IDYearComboCTE选择的记录,并且由于该查询使用distinct,因此没有两个具有相同ID的记录也具有相同的会计年度。因此,对于具有相同ID的任何记录集,如果按FiscalYear按时间顺序递减排列,您会发现它们的FiscalYearIndex值形成一个始终以值1开始的连续整数序列

第二,考虑我使用的公式来计算财政赤字:我只是从一个常数中减去财政年度,这个常数等于我们预期在结果集中出现的最新会计年度,然后加上1。以下是一些重要的观察结果:

因为我们只考虑在当前财政年度或之前发生的捐赠,所以可能发生的最低财政支出抵消值为1,并且它将恰好发生在当前财政年度发生的捐赠上

由于IDYearComboCTE中没有两个具有相同ID的记录具有上述相同的FiscalYear,因此也没有两个具有相同ID的记录具有相同的FiscalYearOffset

由于第2点的原因,以及较低的FiscalYear值将产生较高的FiscalYearOffset值,请注意 对于具有相同ID的任何一组记录,如果按FiscalYear按时间顺序递减排列,您会发现它们的FiscalYearOffset值形成严格递增的序列。这与我在上面对FiscalYearIndex的观察非常相似,有一个重要的区别,即特定ID的FiscalYearIndex值序列总是从1开始,并且不包含任何间隙。这两种情况都不适用于一系列的财政收支平衡值

现在,假设我们有一个来自IDYearComboCTE的记录,它代表了某个特定捐赠者最近的捐赠。因为这是最近的一次,我们知道它的财政指数将是1,因为这就是行数的工作原理。我们还知道,如果且仅当捐赠发生在当前财政年度时,其财政支出将为1。如果最近的捐赠来自上一个财政年度,那么您可以通过FiscalYearOffset的公式看到,您将获得大于1的值。因此,对于任何捐赠者最近的捐赠,我们知道当且仅当捐赠在当前财政年度时,FiscalYearOffset=FiscalYearIndex

接下来,考虑一个来自IdEnCuMoBcTe的记录,它代表了一些捐赠者最近的第二次捐赠。由于它是第二个最近的,我们知道它的财政指数将是2,因为这也是行数的工作原理。至关重要的是,我们也知道其财政支出抵消值将大于或等于2。它不能是1,因为这是捐赠者最近的第二个捐赠年,我们在上面说过,在IDYearComboCTE中,同一个ID不会发生两次相同的财政年度。如果捐款来自上一个财政年度,则为2;如果捐款早于上一个财政年度,则为大于2的数字

关键的一点是:假设在第二个最近的捐赠记录中,FiscalYearOffset大于2,这意味着它大于FiscalYearIndex。当我们进入第三个最近的捐赠年度、第四个最近的捐赠年度,依此类推,对于特定的ID,FiscalYearOffset和FiscalYearIndex将不再相等。这是因为对于每个连续记录,我们知道FiscalYearIndex的值将比前一个记录的FiscalYearIndex大一个。正因为如此,并且由于第二次最新捐赠的FiscalYearOffset>FiscalYearIndex,为了使其他记录的FiscalYearIndex赶上FiscalYearOffset,FiscalYearOffset必须增加一些小于1的数字。但是不存在小于1的正整数,我们已经在上面展示了给定ID的FiscalYearOffset值集是一个严格递增的序列。因此,财政收支差额增加不到1是不可能的

因此,第二个CTE(其中FiscalYearOffset=FiscalYearIndex)中的唯一记录是那些表示从当前财政年度开始并向后运行的连续年份序列中的捐赠,以及对于给定ID,该连续序列中的捐赠数量等于第二个CTE中具有该ID的记录数量,该相等性适用于第二个CTE

这是很多可以接受的。如果仍然不清楚,我建议将一些示例数据放在一起,然后将查询中的最终选择更改为仅从FiscalYearOrderingCTE中选择*,以便您可以看到第二个CTE的原始输出


ǂ-在我撰写这篇编辑时,我意识到我最初的查询无法解释以下事实:您的数据库可能包含未来日期的捐赠,特别是在当前财政年度之后发生的捐赠。这将打破最初编写的查询,因此我对其进行了修改,包括where C.FiscalYear以下是经典的差距和孤岛方法:

with
    y as (select distinct ID, year(dateadd(month, -6, POSTEDDATE)) as FY from T),
    r as (select ID, FY, row_number() over (partition by ID order by FY desc) as RN from y)
select ID, min(FY), max(FY)
from r
group by ID, FY + RN
having max(FY) = year(dateadd(month, -6, getdate())) and count(*) > 1;
获取不同ID和会计年度的列表。 为行编号,按ID重置。我使用降序,因为你可能会这样想,但这真的不重要。 使用间隙和孤岛分组查找连续的年份块。由于降序排序,它使用FY加RN而不是减法。当数据块的跨度超过一年时,仅将其固定到当前会计年度。 优化方法

您已经对表的大小以及性能如何成为一个关注点发表了评论。这在某种程度上取决于你的数据的性质,但我认为你可能没有太多的重复捐赠者,因此尽早挑选潜在捐赠者名单可能是一个很好的改进方法。我在这里看到两种可能的可能性:首先找到当前财政年度的捐赠者,然后在过去两个财政年度内找到重复捐赠者。当你在财政年度的早期,第一个可能是更好的选择

with
    f1 as (
        select ID from T
        where POSTEDDATE >= datefromparts(year(dateadd(month, -6, getdate())), 7, 1)
    )
    f2 as (
        select ID from T
        where POSTEDDATE >= datefromparts(year(dateadd(month, -6, getdate())) - 1, 7, 1)
        group by ID having count(distinct year(dateadd(month, -6, POSTEDDATE))) = 2
    ),
    y as (
        select distinct ID, year(dateadd(month, -6, POSTEDDATE)) as FY from T
        where ID in (select ID from fN) -- choose f1 or f2
    ), 
    r as (
        select ID, FY, row_number() over (partition by ID order by FY desc) as RN from y
    )
select ID, min(FY), max(FY)
from r
group by ID, FY + RN
having max(FY) = year(dateadd(month, -6, getdate())) and count(*) > 1;
如果没有datefromparts,则可以使用dateaddmonth,-6,castyeargetdate作为日期,dateaddmonth,-6,castyeargetdate-1作为日期,或者使用其他等效表达式作为会计年度的开始

我在试着 g想办法让它利用ID和POSTEDDATE上的索引,但我不认为SQL Server知道从转换日期提取年份不会改变顺序。如果你有稠密的等级,你可以尝试结合前两个步骤,完全消除y。顺便说一句,y是指年,r是排名

with r as (
    select distinct ID, year(dateadd(month, -6, POSTEDDATE)) as FY,
        dense_rank() over
            (partition by ID order by year(dateadd(month, -6, POSTEDDATE)) desc) as RN
    from T
) ...
过时的错误方法

下面是我最初为在过去两个财政年度内取得成果而写的答案。我重新阅读了这个问题,意识到它没有解决全部问题。尽管如此,它仍然会有所帮助:

select
    ID,
    /* the rest of these values are just for verification */
    min(year(dateadd(month, -6, POSTEDDATE))) as FY1,
    max(year(dateadd(month, -6, POSTEDDATE))) as FY2,
    min(POSTEDDATE) as firstDate,
    max(POSTEDDATE) as lastDate
from T
where
        /* look at two most recent fiscal years */
        year(dateadd(month, -6, POSTEDDATE)) between
            year(dateadd(month, -6, getdate())) - 1 and year(dateadd(month, -6, getdate()))
        /* sargable filter - never need more than two full years plus one leap day */
    and POSTEDDATE >= dateadd(day, -731, getdate())
group by ID
having count(distinct year(dateadd(month, -6, POSTEDDATE))) = 2;
这个查询说:

给我所有从731天前开始向前的行。似乎没有 对我来说,你可以有任何未来日期,但在下一步之间处理它,如果你这样做。如果性能是一个问题,您可以计算更严格的上限和下限

将日期向后调整六个月,去掉年份以计算会计年度数。只有 保留属于当前日期或当前日期的会计年度的行 一个先例

对结果进行分组,并在ID中保留每两个不同的会计年度 小组


好的,当我从今天开始提到的时候,我指的是一份精选声明,我可以用今天的日期开始追溯,因为ID3不在当前财政年度内,所以它不会被计算在内。因此,由于财政年度为7/1至6/30,本财政年度为2016财年至2017财年的7/1财年。因此,它将从当前财政年度开始倒数,直到出现中断,如果它大于1,则将被视为连续。谢谢,GrimGot It,如果您想从当前财政年度向后计算,您首先需要创建一个包含所有所需财政年度的财政年度表,然后计算财政年度列relate并开始向后计算,但滞后仍然很有用。为了引用我使用的格式,它是常见的表表达式,在sql server中非常流行,这是一种不必编写子选择的方法。我将根据这些新信息编辑我的答案。事实上,这是我问的第一个问题!上一个我工作的地方有一个会计年度表,但我被告知,应该可以通过使用SQL而不使用会计年度表来完成,因为我工作的模式中不存在该表。此外,我还要承认一件事,我使用Oracle有几年了,所以我也在尝试适应SQL Server。我没有用过。。。在甲骨文之前。但我会弄明白的。因此,我尝试了你上面的建议,看看它是否有效,但我得到了一个错误。表名为Revenue,列为ID、POSTDATE和AMOUNT。我得到的错误是,这是我尝试的FROM语句.SQL的语法错误,如下所示:当MONTHPOSTDATE<7时,cteFiscalYear为SELECT FISCALEAR=CASE,然后CASTDATEFROMPARTSYEARPOSTDATE-1,7,1为DATETIME,否则CASTDATEFROMPARTSYEARPOSTDATE,7,1为DATETIME END,*从REVENUE SELECT*,ConcertiveYearNot=IIFLAGFiscalYear,按Id划分的1,0按FiscalYear=DATEADDYEAR,-1,FiscalYear,1,0,IdRowNum=按Id划分的行数按FiscalYear划分的行数,从CTEFiscalYear发布SQL Server的哪个版本?非常感谢,Joe。这真是太好了!!!!在第二次CTE中,我已经一遍又一遍地尝试去理解它在做什么,但我希望,我会很快理解它。我想我想的是,你必须分组,然后订购FiscalYearOrderingCTE,以获得适当的FiscalYearOffset,因为如果它不符合顺序,那么年份和ID将不符合顺序,并且不会给你适当的连续年份。因此,我试图了解财政支出补偿和财政支出指数是如何相互作用的,以给出正确的连续年数。@GrimReaper,我更新了我的帖子,对财政支出订购CTE的逻辑进行了大幅度的解释。我希望它能帮助你。如果这个查询对您有效,请帮我一个忙并接受答案:因此,如果我需要修改它,使其不包括当前财政年度。换句话说,它从上一个财政年度开始倒计时,我希望记录的连续计数为1或更大,而不是2。这就是我要做的改变吗?FiscalYearOffset=@ThisFiscalYear-C.FiscalYear,删除“+1”maxFiscalYearIndex>=1,使其变为1而不是2;你建议对末尾HAVING条款的修改是正确的。但是,与其更改FiscalYearOffset的公式,我建议您将@ThisFiscalYear的值更改为上一个财政年度,而不是当前财政年度,并重命名该变量以更准确地描述其内容。这应该是你所需要的。事实上,maxFiscalYearIndex ll始终大于或等于1,因此如果愿意,可以完全删除该子句。