【问题标题】:Creating duplicate temp tables inside nested stored procedures在嵌套存储过程中创建重复的临时表
【发布时间】:2015-10-29 04:09:34
【问题描述】:

情况如下:

过程 1 创建一个临时表 (#MYTABLE) 并调用过程 2。过程 2 还尝试创建具有不同列的 #MYTABLE。当过程 2 尝试将数据插入 #MYTABLE 时,会发生错误,提示“列名无效”。我对此有两个问题:

1) 在过程 2 中创建 #MYTABLE 时系统不应该抱怨吗?我明白为什么它在编译时不能反对,但在运行时我会期待一个错误。

2) 鉴于它不会抱怨创建,实际上当您在过程 2 中从 #MYTABLE 中选择时,您会看到新列,为什么它会抱怨 INSERT?

下面是代码。取消注释任一 INSERT 语句都会出现错误。

(我知道很多方法可以解决这种情况,所以我不需要对此作出回应。我只是想了解发生了什么。)

IF OBJECT_ID(N'dbo.MYPROC1', N'P') IS NOT NULL
    DROP PROCEDURE dbo.MYPROC1;
GO

CREATE PROCEDURE dbo.MYPROC1
AS
    CREATE TABLE dbo.#MYTABLE ( Name VARCHAR(256) );

    SELECT
        'DO NOTHING 1' AS TABLENAME;

    EXEC dbo.MYPROC2;

GO

IF OBJECT_ID(N'dbo.MYPROC2', N'P') IS NOT NULL
    DROP PROCEDURE dbo.MYPROC2;
GO

CREATE PROCEDURE dbo.MYPROC2
AS
    SELECT
        'INSIDE PROC 2 BEFOREHAND' AS TABLENAME
       ,*
    FROM
        dbo.#MYTABLE;

    CREATE TABLE dbo.#MYTABLE
        (
         Name VARCHAR(256)
        ,LastName VARCHAR(256)
        );

    --INSERT  INTO dbo.#MYTABLE
    --        ( Name, LastName )
    --        SELECT
    --            'BARACK'
    --           ,'OBAMA';

    SELECT
        'INSIDE PROC 2 AFTERWARDS' AS TABLENAME
       ,*
    FROM
        dbo.#MYTABLE;

    --INSERT  INTO dbo.#MYTABLE
    --        ( Name, LastName )
    --        SELECT
    --            'BARACK'
    --           ,'OBAMA';

    SELECT
        'DO NOTHING 2' AS TABLENAME;

GO

EXEC MYPROC1;

【问题讨论】:

    标签: sql-server stored-procedures


    【解决方案1】:

    来自Create Table 文档:

    在存储过程或触发器中创建的本地临时表可以与在调用存储过程或触发器之前创建的临时表同名。但是,如果一个查询引用了一个临时表,并且当时存在两个同名的临时表,则不会定义该查询是针对哪个表解析的。嵌套存储过程还可以创建与调用它的存储过程创建的临时表同名的临时表。但是,为了解析在嵌套过程中创建的表的修改,该表必须具有与调用过程中创建的表相同的结构和相同的列名。

    【讨论】:

      【解决方案2】:

      1) 在内部创建#MYTABLE 时系统不应该抱怨吗 程序 2?我明白为什么它不能在编译时反对,但是 在运行时,我预计会出现错误。

      确实在编译时抱怨。当它编译dbo.MYPROC2 时,它会看到该表存在于父范围内并且与您正在使用的列列表不兼容。如果没有该名称的可见父对象,则该语句的编译将被推迟到它被执行(在CREATE TABLE 之后)。

      如果您要从dbo.MYPROC2 中删除初始的SELECT,然后在dbo.MYPROC1 之前先执行dbo.MYPROC2,它可能会成功——因为它已经有dbo.MYPROC2 的缓存计划,无需重新编译.

      我不建议这样做,除非您在计划从缓存中删除并且程序以错误的顺序执行时遇到随机错误。最好使用唯一的名称。

      【讨论】:

      • Ack,感谢您指出我关于“编译时间”的草率语言。我应该说“当 CREATE PROCEDURE 发生时”。
      【解决方案3】:

      1) 在内部创建#MYTABLE 时系统不应该抱怨吗 程序 2?我明白为什么它不能在编译时反对,但是 在运行时,我预计会出现错误。

      不,不应该。您将获得 2 个本地临时表,查看它们的名称:

      CREATE PROCEDURE dbo.MYPROC1
      AS
          CREATE TABLE dbo.#MYTABLE ( Name VARCHAR(256) );
          EXEC dbo.MYPROC2;
      GO
      
      CREATE PROCEDURE dbo.MYPROC2
      AS
          CREATE TABLE dbo.#MYTABLE(
               Name VARCHAR(256)
              ,LastName VARCHAR(256));
      
          SELECT *
          FROM tempdb.INFORMATION_SCHEMA.TABLES
          WHERE [Table_name] LIKE '%MYTABLE%' 
      GO
      

      SqlFiddleDemo

      输出:

      ╔════════════════╦═══════════════╦═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╦════════════╗
      ║ TABLE_CATALOG  ║ TABLE_SCHEMA  ║                                                            TABLE_NAME                                                             ║ TABLE_TYPE ║
      ╠════════════════╬═══════════════╬═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╬════════════╣
      ║ tempdb         ║ dbo           ║ #MYTABLE____________________________________________________________________________________________________________000000000117  ║ BASE TABLE ║
      ║ tempdb         ║ dbo           ║ #MYTABLE____________________________________________________________________________________________________________000000000118  ║ BASE TABLE ║
      ╚════════════════╩═══════════════╩═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╩════════════╝
      

      2) 鉴于它不会抱怨创作,事实上,当 您从程序 2 中的 #MYTABLE 中选择,您会看到新列, 为什么它抱怨 INSERT?

      因为 SQL Server 从外部存储过程获取第一个表定义。它有不同的列,因此您在INSERT 期间会出错

      【讨论】:

        【解决方案4】:

        嗯,乍一看你的假设是可以的,但只是在第一个。

        当您创建名为 MyTable 的临时表时,SQL Server 会在 TEMPDB 中创建实际表,该表名为“MyTable_____________....范围,服务器可以对它们产生影响。

        在您的情况下,您在两个不同的范围内创建本地临时表 - 两个不同的过程,不要介意一个正在调用另一个,您无法访问在第一个过程中创建的第二个过程中的表。

        我建议您从 sys.objects 中选择数据,这样您就可以看到创建了两个实际且不同的表 - select name from tempdb..sysobjects where name like 'MYTABLE%'

        最后 - 您使用相同的名称并希望访问“最小”范围表,但实际上 Server 使用首先创建的表。假设 SQL server 只是从 sys.objects 中选择前 1 个,其中范围和名称与当前的匹配。

        【讨论】:

        • 这个答案很好地解释了问题 #1,谢谢。但我仍然不明白为什么 INSERT 和 SELECT 的行为不同。为什么 SELECT “看到” 较新的表而 INSERT “看到” 较旧的表?
        • 嗯,这是一个很好的问题,我无法回答(对不起,我在回答你的帖子时没有完整地阅读它)。它可能应该指向更有能力的团队,即那些了解 SQL Server 内部运行方式的团队。我将尝试在不同版本的 SQL Server 上运行相同的示例并查看它的行为方式。你用的是哪个版本?
        • “您无法访问在第一个过程中创建的第二个过程中的表。”这是不正确的。临时表具有动态范围。它们的作用域不像变量。创建表后,其他过程可以对其进行读写。虽然读取显然是阴影。
        • @ShannonSeverance,如果您在 tampdb 中直接通过其名称访问表,那么 - 是的。否则,您无法从父作用域的子作用域(在本例中为从另一个存储过程调用的存储过程)中创建的本地临时表中进行选择。
        • 父级不能访问子级的本地临时表,因为该表在退出子过程时被破坏。但这是一个孩子访问父母本地临时表的情况,它可以在不使用完整的 tempdb 名称的情况下完成。只要孩子不创建自己的冲突临时表。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多