【问题标题】:How to BULK INSERT a file into a *temporary* table where the filename is a variable?如何将文件批量插入到文件名是变量的 *temporary* 表中?
【发布时间】:2011-01-23 19:16:41
【问题描述】:

我有一些这样的代码,用于将数据文件批量插入到表中,其中数据文件和表名是变量:

DECLARE @sql AS NVARCHAR(1000)
SET @sql = 'BULK INSERT ' + @tableName + ' FROM ''' + @filename + ''' WITH (CODEPAGE=''ACP'', FIELDTERMINATOR=''|'')'

EXEC (@sql)

标准表的工作正常,但现在我需要做同样的事情来将数据加载到 临时 表中(例如,#MyTable)。但是当我尝试这个时,我得到了错误:

Invalid Object Name: #MyTable

我认为问题在于BULK INSERT 语句是即时构造的,然后使用EXEC 执行,而#MyTableEXEC 调用的上下文中不可访问。

我需要像这样构造BULK INSERT 语句的原因是我需要将文件名插入到语句中,而这似乎是唯一的方法。所以,我似乎可以或者有一个变量文件名,或者使用一个临时表,但不能同时使用。

还有其他方法可以实现这一点 - 或许是使用OPENROWSET(BULK...)


更新: 好的,所以我听到的是 BULK INSERT 和临时表对我不起作用。感谢您的建议,但在我的情况下,将我的更多代码移动到动态 SQL 部分是不切实际的。

尝试过OPENROWSET(BULK...),似乎遇到了同样的问题,即它无法处理变量文件名,我需要像以前一样动态构造 SQL 语句(因此无法访问临时表)。

所以,这让我只有一个选择,即使用非临时表并以不同的方式实现进程隔离(通过确保任何时候只有一个进程可以使用这些表 - 我能想到几种方法来做到这一点)。

这很烦人。按照我原本打算的方式来做会方便得多。只是其中一件应该是微不足道的事情,但最终会浪费你一整天的时间......

【问题讨论】:

  • 您是否尝试过将静态批量插入临时表?
  • @Andrey:老实说我没有,但我现在有了 - 它按我的预期工作。
  • 我知道这已经解决了,但只是提一下:您可以编写一个 SQLCLR 存储过程来接收任何这些参数(例如文件名)并吐出结果。此时您只需创建临时表,然后创建INSERT INTO #mytemp EXEC SQLCLRproc @filename='path.txt';SQL# 库(我创建的)中已经存在这样的过程。该过程称为File_SplitIntoFields,并允许指定字段名称和数据类型(虽然不确定CODEPAGE='ACP')。我应该提到它在免费版本中不是,但它使这变得非常容易。

标签: sql sql-server tsql bulkinsert


【解决方案1】:

您始终可以在动态 SQL 中构造 #temp 表。例如,现在我猜你一直在尝试:

CREATE TABLE #tmp(a INT, b INT, c INT);

DECLARE @sql NVARCHAR(1000);

SET @sql = N'BULK INSERT #tmp ...' + @variables;

EXEC master.sys.sp_executesql @sql;

SELECT * FROM #tmp;

这使得维护(可读性)变得更加困难,但解决了范围问题:

DECLARE @sql NVARCHAR(MAX);

SET @sql = N'CREATE TABLE #tmp(a INT, b INT, c INT);

BULK INSERT #tmp ...' + @variables + ';

SELECT * FROM #tmp;';

EXEC master.sys.sp_executesql @sql;

编辑 2011-01-12

鉴于我将近 2 岁的答案突然被同样不完整的人认为是不完整和不可接受的,如何:

CREATE TABLE #outer(a INT, b INT, c INT);

DECLARE @sql NVARCHAR(MAX);

SET @sql = N'SET NOCOUNT ON; 

CREATE TABLE #inner(a INT, b INT, c INT);

BULK INSERT #inner ...' + @variables + ';

SELECT * FROM #inner;';

INSERT #outer EXEC master.sys.sp_executesql @sql;

【讨论】:

  • @Aaron:不幸的是,在我完成数据之前,我还有大量工作要做,所以让所有代码“动态”是不切实际的。
  • @Kermit 你是什么意思?
  • @AaronBertrand 我写信给 2013 Kermit。我会在他回复后立即通知您。
  • @Kermit 这像未来的德怀特吗?对不起,我昨天才第一次看到评论。
  • @AaronBertrand 那是一个很棒的插曲。该评论来自 3 个职位之前。
【解决方案2】:

你可以做任何你想做的事。 Aaron 的回答并不完整。

他的方法是正确的,直到在内部查询中创建临时表。然后,您需要将结果插入到外部查询中的表中。

以下代码 sn -p 抓取文件的第一行并将其插入到@Lines 表中:

declare @fieldsep char(1) = ',';
declare @recordsep char(1) = char(10);

declare @Lines table (
    line varchar(8000)
);

declare @sql varchar(8000) = ' 
    create table #tmp (
        line varchar(8000)
    );

    bulk insert #tmp
        from '''+@filename+'''
        with (FirstRow = 1, FieldTerminator = '''+@fieldsep+''', RowTerminator = '''+@recordsep+''');

    select * from #tmp';

insert into @Lines
    exec(@sql);

select * from @lines

【讨论】:

  • 你已经破解了!好吧,几乎:您需要从 #tmp 中选择作为动态 SQL 中的最后一条语句,但除此之外 - 成功!谢谢。 (对于那些希望在 SQL Server 2005 及更早版本上使用此解决方案的人,您会发现您需要进行一些语法更改,但原则是合理的)。
  • 哇,缺少外部插入(鉴于我不知道@tablename 应该是什么,并且目标是插入#tmp 表,而不是表变量)使我的答案不完整?对于大约两年前的问题?欢迎来到 StackOverflow。加里,答案仍然不完整,但因为它更新你改变了你的接受?我很困惑。
  • 您好,我有同样的问题,但它对我不起作用。我正在使用带有 Azure SQL Server 的 SQL Server。
  • 两者都不适合我,至少在 SQL Server 2012 上,字段分隔符被完全忽略。
【解决方案3】:

很抱歉挖掘了一个老问题,但万一有人偶然发现这个线程并想要一个更快的解决方案。

将带有 \n 行终止符的未知宽度文件批量插入在 EXEC 语句之外创建的临时表中。

DECLARE     @SQL VARCHAR(8000)

IF OBJECT_ID('TempDB..#BulkInsert') IS NOT NULL
BEGIN
    DROP TABLE #BulkInsert
END

CREATE TABLE #BulkInsert
(
    Line    VARCHAR(MAX)
)

SET @SQL = 'BULK INSERT #BulkInser FROM ''##FILEPATH##'' WITH (ROWTERMINATOR = ''\n'')'
EXEC (@SQL)

SELECT * FROM #BulkInsert

进一步支持 EXEC 语句中的动态 SQL 可以访问 EXEC 语句之外的临时表。 http://sqlfiddle.com/#!3/d41d8/19343

DECLARE     @SQL VARCHAR(8000)

IF OBJECT_ID('TempDB..#BulkInsert') IS NOT NULL
BEGIN
    DROP TABLE #BulkInsert
END

CREATE TABLE #BulkInsert
(
    Line    VARCHAR(MAX)
)
INSERT INTO #BulkInsert
(
    Line
)
SELECT 1
UNION SELECT 2
UNION SELECT 3

SET @SQL = 'SELECT * FROM #BulkInsert'
EXEC (@SQL)

进一步支持,为 MSSQL2000 编写 http://technet.microsoft.com/en-us/library/aa175921(v=sql.80).aspx

链接底部的示例

DECLARE @cmd VARCHAR(1000), @ExecError INT
CREATE TABLE #ErrFile (ExecError INT)
SET @cmd = 'EXEC GetTableCount ' + 
'''pubs.dbo.authors''' + 
'INSERT #ErrFile VALUES(@@ERROR)'
EXEC(@cmd)
SET @ExecError = (SELECT * FROM #ErrFile)
SELECT @ExecError AS '@@ERROR'

【讨论】:

    【解决方案4】:

    http://msdn.microsoft.com/en-us/library/ms191503.aspx

    我建议在批量插入之前创建具有唯一名称的表。

    【讨论】:

    • 谢谢,但我不确定您的建议是什么?如果我创建具有唯一名称的表,因此直到运行时才知道,那么我仍然需要动态构造 BULK INSERT 语句吗?等等 - 你的意思是我应该创建一个 non-temporary 表(我可以稍后删除)?或者一个全局临时表,我想。嗯...我想可能可以工作,但这意味着一旦插入数据,处理表格的所有其他代码 i> 必须动态构造才能使用正确的表名。这听起来像是一场噩梦......
    • 你回答了你的问题 :) “其他代码”也必须注意临时表。你可以这样做:创建非临时表,批量插入,从非临时复制到临时,删除非临时,玩得开心
    • 不,不要使用全局临时表。所有这一切将确保两个不同的用户在大致相同的时间运行将他们的结果混合在一起(或者当他们尝试创建一个已经存在的##table 时会出错)。我还没有找到一个真正实用的全局临时表用例。
    • 我认为你的计划有缺陷......为了“从非临时复制到临时”,我需要使用动态 SQL(这样我就可以插入非临时表的名称使用生成的唯一名称)- 再一次,动态 SQL 将无法访问临时表。
    • @Aaron:我的意思是,如果我要创建一个具有唯一名称的非临时表,那么它同样可以是一个具有唯一名称的全局临时表我>。唯一的好处是表会在某个时候自动删除,因此不会有数据库被我创建但未能显式删除的表弄乱的风险。我同意 - 就其本身而言 - 全局临时表实际上并不能帮助解决手头的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多