【问题标题】:MS SQL Server: Access a local temporary table in a prepared statementMS SQL Server:在准备好的语句中访问本地临时表
【发布时间】:2015-05-22 22:07:33
【问题描述】:

问题陈述

我正在尝试创建并填充一个本地临时表,它现在运行良好,但之后我无法从 pepared 查询中访问该表及其内容。

这个问题的一个非常简化的版本,已经重现了这个问题(C++0x with Qt5 framework):

// fyi: this constructor overload already executes the given query string
QSqlQuery query1("IF EXISTS(SELECT * FROM SYSOBJECTS WHERE ID=OBJECT_ID('#tmp') AND XTYPE='U') DROP TABLE #tmp", _db);
QSqlQuery query2("SELECT * INTO #tmp FROM mytable WHERE 1 = 0", _db);

// so far, so good: the table gets dropped if it was already existing and gets recreated with the schema of 'mytable'; it is empty due to the '1 = 0'

// this works: some code for inserting data
QSqlQuery query3("INSERT INTO #tmp SELECT mytable WHERE ...", _db);

// this also works: i can select some data
QSqlQuery query4("SELECT count(*) FROM #tmp WHERE col = 1234", _db);
Q_ASSERT(query4.hasNext());

// fails at exec() with error: "unkown object name '#tmp'"
QSqlQuery query5(_db);
query5.prepare("SELECT count(*) FROM #tmp WHERE col = :boundVar");
query5.bindValue(":boundVar", 1234);
query5.exec();

我在这里想念什么?我已经很难让创建临时表的查询运行起来,直到我在SQL Server documentation 中读到这个:

在 SQL Server 2005 中,准备好的语句不能用于创建 临时对象 [...],例如临时表。这些程序 必须直接执行。

这已经很令人困惑了,因为我使用的是 SQL Server 2008r2 - 而为 Server 2012 编写的文档只提到了 2005 和更早的版本。尽管如此,我尝试在不准备的情况下执行创建表的查询并且它工作(参见代码示例)。然而,这句话并没有说明通过准备好的语句访问临时表。

要求

由于性能原因,我必须使用本地临时表:相当大的数据集将被过滤和转换并插入到临时表中,大约 10 个查询将跟随并使用这些物化的中间数据。 TVP 不是一个好的选择,因为它会大大减慢以下查询的速度。在我的情况下,创建临时表的开销确实是好几倍。此外,本地临时表将比其他解决方案更受欢迎,因为它们与其他连接隔离,并且在断开连接后会被清理,这正是我想要的。

我不能没有准备好的从表格中阅读的语句有很多原因,所以我真的希望有人可以帮助我。

实际问题

如何使用准备好的查询从本地临时表(当然是在创建它的同一连接中)读取数据?

非常感谢任何想法/解决方案!

【问题讨论】:

  • 不是从您的应用程序运行 5 个单独的查询,您可以将它们全部放在一个存储过程中,然后从应用程序运行存储过程吗?这样一来,它们都将在数据库上的相同上下文中运行,并且临时表将持续存在。
  • 您必须在同一个 SQL Server 会话中,而不是客户端会话中。如果将其更改为##tmp(这将持续更长时间-它是一个全局临时表),其他会话可以看到它,但是一旦创建它的会话结束,它将没有更多会话使用它,因此它也会消失。我会按照 Tab 的建议去做。
  • @Tab Alleman:这 10 个查询将分别从不同的表中选择大量数据集,只是它们使用临时表中相同的会话本地预过滤数据,我将如何在单个存储过程?
  • @benjamin moskovits:那么其他用户和连接不会也看到##tmp 表吗?它们必须相互隔离,不能删除另一个进程的表。
  • @PhilipDaubmeier 存储过程可以返回多个结果集

标签: c++ sql-server qt sql-server-2008-r2 prepared-statement


【解决方案1】:

如何将所有这些都放在一个存储过程中,该存储过程会创建一个以日期和时间以及用户 ID 为前缀的真实表。

大致如此

创建表 someuser_190320151830....

如上构建动态 sql 语句以填充表

从表中选择任何内容,以便您的应用可以使用它

删除表 someuser_190320151830

最后一步可能是选择数据然后删除表格。

除非同一个人设法在完全相同的时间运行它,否则它可以工作。我使用了类似的方法,但它很少运行。

【讨论】:

  • 如果他使用的是真实表而不是临时表,那么他是调用存储过程还是实际执行来自 c++ 的所有查询都无关紧要。该表将持续存在,直到显式删除。如果他使用 GUID 生成表名,则不会有任何并发​​问题。
  • 感谢您的想法。但是,我真的不喜欢必须手动清理这些表的事实。如果连接丢失或程序在执行期间终止,则表将被卡住,并且数据库会挤满孤立的表。更重要的是,出于这个确切原因引入了临时表,所以我真的很想使用这个功能。
  • 接受您的回答 - 感谢您的帮助。请参阅我现在实施的具体解决方案的答案。
【解决方案2】:

具体解决方案

经过大量的测试和试验,以及user1683641Tab Alleman 的想法,我将所有东西结合在一起,最终完成了这项工作并满足了我的所有要求:

  • 我现在正在使用全局临时表,因为它们在未准备好的查询和准备好的查询范围之间是可见的
  • 当客户端应用程序在没有显式 DROP 的情况下关闭时,即使它被终止或连接丢失,全局临时表也会被清理
  • 我用唯一的 GUID 对这些表进行后缀,这样可以避免用户和时间戳组合的冲突

小例子:

QString const tableName(QString("##tmp_%1").arg(QUuid::createUuid().toString()));

// clean up, prepare empty table and fill with intermediate data
QSqlQuery query1(QString("IF EXISTS(SELECT * FROM SYSOBJECTS WHERE ID=OBJECT_ID('%1') AND XTYPE='U') DROP TABLE %1").arg(tableName), _db);
QSqlQuery query2(QString("SELECT * INTO %1 FROM mytable WHERE 1 = 0").arg(tableName), _db);
QSqlQuery query3(QString("INSERT INTO %1 SELECT mytable WHERE ...").arg(tableName), _db);

// works now! I can now select the temporary data perfectly fine from prepared statements
QSqlQuery query4(_db);
query4.prepare(QString("SELECT count(*) FROM %1 WHERE col = :boundVar").arg(tableName));
query4.bindValue(":boundVar", 1234);
query4.exec();

感谢大家的意见!我会接受user1683641s 的回答,因为它引导我这样做。

【讨论】:

  • 我遇到了类似的问题。我非常想使用您的解决方案。但是,我仍然不清楚使用全局临时表如何解决问题。即,即使您使用唯一的 GUID 对这些表进行后缀,其他连接和用户是否仍然可以看到它?其他用户不能对其进行查询吗? (如果它是一个本地临时表,情况就不会如此)。如果我遗漏了什么,请纠正我?
  • @Mohammed Raqeeb:是的,这些表对其他连接和用户可见,但这不是问题,因为 GUID 不会发生冲突,因此任何用户的其他应用程序实例都不会使用/查询桌子。此外,其中没有机密值,只有经过过滤的一组表,该表已对数据库的所有用户可见。
猜你喜欢
  • 2013-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多