在嵌套存储过程中创建重复的临时表

时间:2015-10-28 19:49:22

标签: sql-server stored-procedures

情况如下:

过程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;

4 个答案:

答案 0 :(得分:5)

来自Create Table文档:

  

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

答案 1 :(得分:2)

  

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

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

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

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

答案 2 :(得分:2)

  

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

期间收到错误消息

答案 3 :(得分:-1)

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

当您创建名为MyTable的临时表时,SQL Server在TEMPDB中创建实际表,其名称类似于“MyTable _____________...._____ 01D”,因此当任何其他代码创建具有相同名称但不同的表时范围,服务器可以区分它们。

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

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

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