【问题标题】:How can I write 2 select statements in CTE?如何在 CTE 中编写 2 个选择语句?
【发布时间】:2013-02-22 05:49:18
【问题描述】:

这是我的查询不正确:

;With normal As
    (   
        Select  ROW_NUMBER() Over (Order by RecordingDateTime) as RowNum, 
                DocumentID, 
                CreatedByAccountID, 
                JurisdictionID,
                InstrumentID
            --  Cast(InstrumentID as decimal(18)) as InstrumentID
            From Documents  Where RecordingDateTime IS NOT NULL
    )
    Select Top 1 @PreviousInstrumentID = InstrumentID,
                @PreviousDocumentID = DocumentID,
                @PreviousCreatedByAccountID = CreatedByAccountID,
                @PreviousBelongsToJurisdictionID = JurisdictionID
            From normal
                Where RowNum = @RowNum - 1

    Select Top 1 @NextInstrumentID = InstrumentID,
                @NextDocumentID = DocumentID,
                @NextCreatedByAccountID = CreatedByAccountID,
                @NextBelongsToJurisdictionID = JurisdictionID
            From normal
                Where RowNum = @RowNum + 1

我希望两个 select 语句都在 CTE 的上下文中。我如何做到这一点?

【问题讨论】:

  • 不能这样做。 CTE 仅适用于一个语句。如果您需要保留结果 - 您需要将其放入临时表(或表变量)中,然后针对该结果运行您的两个语句
  • @marc_s:谢谢!表变量不会低效吗?我目前在表中有 10 万条记录,并且会随着时间的推移而增加。请提出您的想法。
  • 这个查询的输出是如何消费的?你真的需要那些变量赋值吗?
  • 是的,否则我不会编写查询。
  • 表变量适用于少数几行 - 但如果有 100K,我会选择临时表

标签: sql sql-server-2008 tsql common-table-expression


【解决方案1】:

您可以创建另外两个 CTE,并在您的 select 语句中使用它们:

;With normal As
    (   
        Select  ROW_NUMBER() Over (Order by RecordingDateTime) as RowNum, 
                DocumentID, 
                CreatedByAccountID, 
                JurisdictionID,
                InstrumentID
            --  Cast(InstrumentID as decimal(18)) as InstrumentID
            From Documents  Where RecordingDateTime IS NOT NULL
    )
, PrevRow AS (
SELECT InstrumentID, DocumentID, CreatedByAccountID, JurisdictionID
FROM normal
WHERE RowNum = @RowNum - 1
), NextRow AS (
SELECT InstrumentID, DocumentID, CreatedByAccountID, JurisdictionID
FROM normal
WHERE RowNum = @RowNum + 1
)
Select Top 1    @PreviousInstrumentID = PrevRow.InstrumentID,
                @PreviousDocumentID = PrevRow.DocumentID,
                @PreviousCreatedByAccountID = PrevRow.CreatedByAccountID,
                @PreviousBelongsToJurisdictionID = PrevRow.JurisdictionID,
                @NextInstrumentID = NextRow.InstrumentID,
                @NextDocumentID = NextRow.DocumentID,
                @NextCreatedByAccountID = NextRow.CreatedByAccountID,
                @NextBelongsToJurisdictionID = NextRow.JurisdictionID

FROM PrevRow 
FULL OUTER JOIN NextRow ON 1=1

【讨论】:

  • 优秀。这看起来很有希望。
  • @GiiM:这种方法在正常情况下效果很好,但在边缘情况下会失败。说如果我在第 0 条记录并且没有以前的记录。因为您已经完成了交叉连接,甚至下一条记录也不会返回,因为由于交叉连接而未执行 Select :(
  • @Jack:啊,但这很容易修改。尝试将CROSS JOIN 替换为FULL JOIN ... ON 1=1。 (如果这确实解决了问题,您可能还想查看您选择的答案。)
  • @AndriyM,我们都在争论对方的解决方案,这很有趣。我儿子将此称为加拿大对峙(就像没有人在 4 路停靠站上,因为他们都试图让其他人先行)。
  • 好图,哈哈! :) 但我确实对我的建议的效率表示怀疑。它使用分组,这必须会影响性能。我只是不确定到什么程度,因为它只是聚合了两行。或许,在这种情况下,这是一个带有转折的加拿大对峙:一名司机试图说服路人帮助他们决定让哪个司机先走更好。 :)
【解决方案2】:

您可以在单个语句中选择两行并使用条件聚合获取相应的值:

With normal As
    (   
        Select  ROW_NUMBER() Over (Order by RecordingDateTime) as RowNum, 
                DocumentID, 
                CreatedByAccountID, 
                JurisdictionID,
                InstrumentID
            --  Cast(InstrumentID as decimal(18)) as InstrumentID
            From Documents  Where RecordingDateTime IS NOT NULL
    )
SELECT
  @PreviousInstrumentID            = MAX(CASE RowNum WHEN @RowNum - 1 THEN InstrumentID       END),
  @PreviousDocumentID              = MAX(CASE RowNum WHEN @RowNum - 1 THEN DocumentID         END),
  @PreviousCreatedByAccountID      = MAX(CASE RowNum WHEN @RowNum - 1 THEN CreatedByAccountID END),
  @PreviousBelongsToJurisdictionID = MAX(CASE RowNum WHEN @RowNum - 1 THEN JurisdictionID     END),
  @NextInstrumentID                = MAX(CASE RowNum WHEN @RowNum + 1 THEN InstrumentID       END),
  @NextDocumentID                  = MAX(CASE RowNum WHEN @RowNum + 1 THEN DocumentID         END),
  @NextCreatedByAccountID          = MAX(CASE RowNum WHEN @RowNum + 1 THEN CreatedByAccountID END),
  @NextBelongsToJurisdictionID     = MAX(CASE RowNum WHEN @RowNum + 1 THEN JurisdictionID     END)
FROM normal
WHERE RowNum IN (@RowNum - 1, @RowNum + 1)
;

【讨论】:

  • 不错。我打算提出这样的建议,但认为它不如额外的 CTE 方法清晰,并且更难维护。但是,查看先前的评论表明行数可能很大,那么也许这是一种更好的方法,因为它可以节省对结果的扫描。
  • @GilM:另一方面,如果他们需要一次性获得超过 @RowNum 的 RowNum-1 和 RowNum+1,那么您的建议会更容易转换:您只需需要将 CROSS JOIN 替换为 INNER JOIN ON next.RowNum = prev.RowNum + 2。我不确定我的方法在这方面是否具有可扩展性。
  • 呃,当然,他们可能不再使用变量赋值了。但他们可能希望返回这些成对的行。好吧,没关系,这可能是一个过于牵强的假设。
  • 这就像一个魅力,可能也很有效:)。谢谢安德烈。
  • @Jack:谢谢。我不确定它在性能方面是否真的比 GilM 的解决方案好得多。也许您可以在按照我在另一条评论中的建议修复 GilM 后比较这两种解决方案?
【解决方案3】:

如果您不需要这些变量分配,因为您在数据读取器中以某种方式使用此查询的输出,您可以只运行一个 CTE:

RowNum = @Row + 1 或 RowNum = @Row - 1

这将为您提供两行的结果,您可以在使用代码中随意使用它。

【讨论】:

  • 这不是答案。如果表只有 1 行怎么办。然后我如何决定该行是上一个还是下一个?
  • 我不明白......你如何填充 rownum 变量?如果你有一排,那么你不会总是有这个问题。假设有一行,rownum() 为 1。您将传递 RowNum 的值 1,所有查询都不起作用。如果您将 RowNum 的值传递给 2,则将返回 1。您可以查看行号列,如果小于您发送的值,则为上一个,如果大于则为下一个。
  • 抱歉,不是我想要的。 @RowNum 不是 1 的常量值。它可以是 30000 或 27677 或任何您能想到的数字。
  • 是的,我明白了。假设该表有一行,并且您传入一个值“@RowNum = 27677”,那么预期的输出是什么?您如何获得 @RowNum 的价值?
猜你喜欢
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-31
  • 2010-10-29
  • 2016-07-10
相关资源
最近更新 更多