【问题标题】:Sql Server temporary table disappearsSql Server 临时表消失
【发布时间】:2020-08-15 22:30:22
【问题描述】:

我正在创建一个临时表#ua_temp,它是常规表的子集。我没有收到错误,但是当我在第二步中尝试从 #ua_temp 进行 SELECT 时,找不到它。如果我删除 #,则会创建一个名为 ua_temp 的表。

我使用了与在其他地方使用 SELECT INTO 创建表时完全相同的技术。它运行良好,所以我认为它与数据库设置没有任何关系。谁能看出问题?

        // Create temporary table 
        q = new StringBuilder(200);
        q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
        q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
        q.Append("into #ua_temp from elig_ua_response ");
        q.Append("where [filename] = @fn1 or [filename] = @fn2 ");
        sc = new SqlCommand(q.ToString(), db);
        sc.Parameters.Add(new SqlParameter("@fn1", sFn));
        sc.Parameters.Add(new SqlParameter("@fn2", sFn2));
        int r = sc.ExecuteNonQuery();
        MessageBox.Show(r.ToString() + " rows");

        // Rosters
        q = new StringBuilder(200);
        q.Append("select policy_no,name,amt_due,due_date,hic,grp,eff_dt,");
        q.Append("lis_prem,lis_grp,lis_co_pay_lvl,lep_prem,lapsed,dn_code,[filename] ");
        q.Append("from #ua_temp where (lis_prem > 0.00 or lep_prem > 0.00) ");
        q.Append("and [filename] = @fn order by name");
        sc.CommandText = q.ToString();
        sc.Parameters.Clear();
        sc.Parameters.Add(new SqlParameter("@fn", sFn));
        sda = new SqlDataAdapter(sc);
        sda.Fill(ds, "LIS LEP Roster");

回答一些显而易见的问题:这个程序使用源表 elig_ua_response 运行良好。引入临时表的原因是我想删除这个特定报告的一些行。我在测试时在 [filename] 列周围加上括号,以确保它不是关键字问题。如果将 #ua_temp 替换为 elig_ua_response,则第二个 SELECT 可以正常工作。我为临时表尝试了不同的名称。显示行数的 MessageBox 仅用于调试目的;不影响问题。

【问题讨论】:

  • 运行 SQL Server Profiler,有一个列 SPID。如果这两个语句具有不同的 SPID,那么您就知道这是一个范围问题。
  • 客户端进程ID相同,SPID相同。
  • 能否将文本从跟踪中复制出来并在 management studio 中运行这两个语句?
  • 我将它们复制到 Sql 查询分析器中。第一个没有错误,但没有创建表,所以第二个被炸毁了。它与##global_temp 一起工作正常。
  • 好像是create步骤的问题,我试着把第一个命令的SqlParameters去掉,程序运行OK了。

标签: c# sql-server


【解决方案1】:

Joe Zack 的评论帮助我理解了这里发生的事情。一个非常清晰和简洁的解释。这应该是一个答案,以便通过 google 搜索到达这里的人更容易看到它。

当有参数时,SqlCommand 通过 sp_executesql 使用参数调用 sql,这意味着您的临时表是在存储过程中创建(然后在其中清理),因此以后的调用无法使用它 - 即使它们共享相同的连接也是如此

p>

【讨论】:

  • 是的 - 在不带参数的命令中创建您的临时表。然后你可以在带参数的命令中任意查询。
【解决方案2】:

我认为解决您的问题的方法是将临时表的创建和从该临时表中进行选择合并到一个查询中(参见下面的代码 sn-p #3)。如果您不使用命令参数,则执行两次命令(就像您在问题中的代码中所做的那样)似乎可以正常工作,但如果引入它们则会失败。我测试了几种不同的方法,这就是我的发现。

1) WORKS OK:使用相同的命令对象,没有命令参数,执行两次命令:

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        const string query = @"
            CREATE TABLE #temp 
                ([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
            INSERT INTO #temp VALUES(1, 'User 1')
            INSERT INTO #temp VALUES(2, 'User 2')";
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = query;
        cmd.ExecuteNonQuery();

        cmd.CommandText = "SELECT * FROM #temp";
        using (var sda = new SqlDataAdapter(cmd))
        {
            var ds = new DataSet();
            sda.Fill(ds);
            foreach (DataRow row in ds.Tables[0].Rows)
                Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
        }
    }
}

2) FAILS:使用相同的命令对象,命令参数,执行两次命令:

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        const string query = @"
            CREATE TABLE #temp 
                ([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
            INSERT INTO #temp VALUES(1, @username1)
            INSERT INTO #temp VALUES(2, @username2)
        ";
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = query;
        cmd.Parameters.Add("@username1", SqlDbType.VarChar).Value ="First User";
        cmd.Parameters.Add("@username2", SqlDbType.VarChar).Value ="Second User";
        cmd.ExecuteNonQuery();

        cmd.Parameters.Clear();
        cmd.CommandText = "SELECT * FROM #temp";
        using(var sda = new SqlDataAdapter(cmd))
        {
            var ds = new DataSet();
            sda.Fill(ds);
            foreach(DataRow row in ds.Tables[0].Rows)
                Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
        }
    }
}

3) WORKS OK:使用相同的命令对象,命令参数,只执行一次命令:

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        const string query = @"
            CREATE TABLE #temp 
                ([ID] INT NOT NULL, [Name] VARCHAR(20) NOT NULL)
            INSERT INTO #temp VALUES(1, @username1)
            INSERT INTO #temp VALUES(2, @username2)
            SELECT * FROM #temp
        ";
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = query;
        cmd.Parameters.Add("@username1", SqlDbType.VarChar).Value ="First User";
        cmd.Parameters.Add("@username2", SqlDbType.VarChar).Value ="Second User";
        using (var sda = new SqlDataAdapter(cmd))
        {
            var ds = new DataSet();
            sda.Fill(ds);
            foreach (DataRow row in ds.Tables[0].Rows)
                Console.WriteLine("{0} - {1}", row["ID"], row["Name"]);
        }
    }
}

【讨论】:

  • 感谢您的努力。我要试试你的第三个建议。我将创建一个小临时表来保存这两个参数,然后使用连接将大表限制为所需的行。
  • @SeaDrive 您还可以考虑在 WHERE 子句中使用 EXISTS 以避免完全使用临时表。我没有把它放在答案中,因为我只是想按要求回答问题。祝你好运。
  • 事实证明选项 3 不能满足我的需要,因为临时表不可用于其他 SELECT。我把它分成三个步骤来工作。
【解决方案3】:

这行得通。显然,如果 SqlParameters 在创建表的步骤中,则不会为下一步留下该表。创建表后,可以在单独的步骤中为 INSERT 使用 SqlParameters。

        // Create temporary file dropping members from termed groups.
        q = new StringBuilder(500);
        q.Append("create table #ua_param ");
        q.Append("([ID] int not null, fn varchar(50) not null) ");
        sc = new SqlCommand(q.ToString(), db);
        sc.ExecuteNonQuery();

        q = new StringBuilder(500);
        q.Append("insert into #ua_param values(1,@fn1) ");
        q.Append("insert into #ua_param values(2,@fn2) ");
        sc = new SqlCommand(q.ToString(), db);
        sc.Parameters.Add(new SqlParameter("@fn1", sFn));
        sc.Parameters.Add(new SqlParameter("@fn2", sFn2));
        sc.ExecuteNonQuery();

        q = new StringBuilder(500);
        q.Append("select policy_no, name, amt_due, due_date, hic, grp, eff_dt, lis_prem, lis_grp, lis_co_pay_lvl, ");
        q.Append("lep_prem, lapsed, dn_code, [filename], created_dt, created_by ");
        q.Append("into #ua_temp from elig_ua_response inner join #ua_param on [filename] = fn ");
        sc.Parameters.Clear();
        sc.CommandText = q.ToString();
        sc.CommandTimeout = 1800;
        sc.ExecuteNonQuery();

【讨论】:

  • SqlCommand 在有参数时通过 sp_executesql 调用带有参数的 sql,这意味着您的临时表是在存储过程中创建(然后在其中清理),因此它对未来的调用不可用 - 即使它们共享相同的连接
【解决方案4】:

因为临时表就是这样。暂时的。您可以考虑在存储过程中进行操作。

【讨论】:

  • @Seadrive - 如果您将此代码放入存储过程中,问题将消失,因为存储过程将在执行时维护表
  • @SeaDrive - 如果它对您没有帮助,请继续 -1 我。但我坚持这个答案。
  • 这是问题的答案。这不是问题的答案。
  • 由数据库决定表会发生什么。
  • 使用相同的临时表来分隔命令是完全有效的,只要共享相同的连接即可。问题是当创建临时表的 sqlcommand 带有参数时,该 sql 会通过 sp_executesql 执行 - 它包含临时表并吃掉它。
【解决方案5】:

除了按照@Daniel A White 的建议将其转换为存储过程之外,您还可以查看 BOL 文章并搜索 global temporary tables。还有一个简短的写在Temporary Tables。任何一种方法都应该使临时表保持活动状态。

【讨论】:

  • 全局临时表将不起作用,因为启动连接在第二个连接访问它之前关闭。它仍然会在查询之间被删除。
  • 我的错,我想当我不编写代码来验证我的答案时会发生这种情况。
  • 实际上,全局临时表确实起作用了,这本身就很神秘。
  • @Bill - 如果您检查 BOL 关于 ##globaltemptables 它也在那里,但只要调用会话处于活动状态或正在访问它们,它们就会持续存在
【解决方案6】:

我遇到了同样的问题。我尝试了 SeaDrive 解决方案并且它有效,但是我的测试让我相信查询执行“刷新”了“ADO.NET/SQLDriver”和 MS SQL Server 之间的某些内容。

因此,您需要先隔离“CREATE TABLE”语句并将其提交到数据库,然后才能与“INSERT INTO”一起使用。在一个独特的语句中加入 CREATE 和 INSERT 的组合命令不起作用,除非您可以放弃参数。

【讨论】:

    【解决方案7】:

    #TEMP 表只能在同一会话或 SPID 中访问。所以如果你想重用它,你需要重用你用来生成它的连接。

    【讨论】:

    • 它使用相同的 SqlCommand 和相同的 SqlConnection,没有干预代码。
    • @Seadrive - 它没有重用连接,否则第二个命令可以看到该表。
    • 如果不是,那为什么不呢?我以前使用过这种技术没有问题。
    • @Seadrive - 我是 SQL 人而不是 C# 人,但如果我不得不猜测,我会说它正在第二个代码块中创建一个新的 SQLDataAdapter。
    • SqlDataAdapter 在技术工作的地方以相同的方式处理。我也试过不带 SqlParameters,这没什么区别。
    【解决方案8】:

    对这类事情使用存储过程是有意义的。

    如果由于某种原因不可行,请确保您使用与创建临时表相同的连接来创建临时表,否则临时表将不可见。 (如果您使用连接池,您可能会随机遇到此问题。)或者,使用真实的物理表甚至全局临时表(##global_tmp 与 #local_tmp),但无论哪种情况,您都需要设计一个方案/协议,使多个进程不会尝试创建/删除/写入该表。

    再次强调,如果可能的话,存储过程将是一个很好的途径。

    【讨论】:

    • 它确实适用于##global_temp。为什么会这样?我是开发机器(笔记本电脑)上的唯一用户。
    • 抱歉回复晚了。它可能与全局(而不是本地)临时表一起工作 b/c 全局临时表可以跨连接共享。
    【解决方案9】:

    Dapper 的工作示例:

    using (var conn = new SqlConnection(connectionString))
    {
        conn.Open();
        var expected = Guid.NewGuid();
    
        // creating the temp table with NO PARAMETERS PASSED IN is the key part.
        conn.Execute("CREATE TABLE #MyTemp (ID UNIQUEIDENTIFIER NOT NULL PRIMARY KEY);");
    
        // now that the temp table is created, you can run queries with params as
        // much as you want.
        conn.Execute("INSERT INTO #MyTemp (ID) VALUES (@ID)", new { ID = expected });
    
        var actual = conn.Query<Guid>("SELECT ID FROM #MyTemp;").Single();
    
        Assert.Equal(expected, actual); // proof it worked
    }
    

    【讨论】:

      猜你喜欢
      • 2010-09-07
      • 2011-02-14
      • 2011-09-06
      • 1970-01-01
      • 1970-01-01
      • 2011-06-01
      • 2011-04-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多