【问题标题】:Struggling with SQL BCP (uniqidentifier, default values on columns...)SQL BCP 苦苦挣扎(唯一标识符,列上的默认值......)
【发布时间】:2011-09-01 07:56:10
【问题描述】:

编辑:我唯一悬而未决的问题是 c)(文件中的 True 和 False,数据库中的位,我既不能更改文件也不能更改数据库方案,有数百 TB 的数据我无法触及)。

系统接收具有特定格式的文件(实际上是数十万个)。事情是: a) 第一种类型是 uniqidentifier(稍后会详细介绍) b) 在数据库中,表的前 4 个值是由数据库生成的(它们与日期相关),这意味着在文件中找不到这 4 个值(其余的都是 - 并且按顺序排列 - 即使它是总是将它们表示为文本或者它们是空的) c) 位值在文件上用 False/True 表示。

因此,1 的问题是在我收到的输入文本文件中,uniqidentifier 使用方括号。当我尝试使用 bcp 命令工具生成具有格式 nul 选项的文件时,它会使其成为具有 37 个字符的 sqlchar(这对我来说没有意义,因为它可能是 36 或 38)。

行分隔符为“+++\r\n”,列分隔符为“©®©”。

我将如何生成格式文件?我已经被这个问题困扰了一段时间,我以前从未使用过 bcp,而且我遇到的错误并不能说明太多(“在 BCP 数据文件中遇到意外的 EOF”)

我应该指定格式文件中的所有列还是只指定我希望从获得的文件中读取的列?

谢谢!

注意:我无法提供 SQL 架构,因为它是针对我工作的公司的。但它几乎是:smalldate、tinyint tinyint tinyint(这四个是由 db 生成的)、uniqidentifier、chars、chars、更多 varchars、一些位、更多 varchars、一些 nvarchar。除了 db 生成的值之外,所有值都接受 null。

我目前的问题是跳过前 4 列。

http://msdn.microsoft.com/en-us/library/ms179250(v=SQL.105).aspx

我遵循了该指南,但不知何故它不起作用。这是更改(我只是硬更改列名以保护项目的隐私,即使听起来很愚蠢)

这是用 bcp 生成的(格式为 nul -c)-注意我把它作为链接,因为它不是那么短- http://pastebin.com/4UkpPp1n

第二个应该做同样的事情但忽略前 4 列在下一个 pastebin 中: http://pastebin.com/Lqj6XSbW

但它不起作用。错误是“Error = [Microsoft][SQL Native Client]The number of fields provided for bcp operation is less than the number of columns on the server.”,这应该是所有这些的目的。

任何帮助将不胜感激。

【问题讨论】:

    标签: sql sql-server-2008 bcp


    【解决方案1】:

    我将为 GUID 创建一个带有CHAR(38) 的新表。将您的数据导入到此暂存表中,然后使用CAST(SUBSTRING(GUID, 2, 36) AS UNIQUEIDENTIFIER) 将其转换为将暂存数据导入到您的永久表中。这种方法也适用于奇数格式的日期、带有货币符号的数字或任何格式不佳的输入。

    BCP 格式文件有点棘手,但基本上并不太复杂。如果该部分继续给您带来麻烦,一种选择是将整行作为单个 VARCHAR(1000) 字段导入,然后在 SQL 中将其拆分 - 如果您对 SQL 文本处理感到满意的话。

    或者,如果您熟悉其他一些编程语言,例如 Perl 或 C#,您可以创建一个脚本来将您的输入预处理为更友好的形式,例如制表符分隔。如果您不熟悉其他编程语言,我建议您选择一种并开始使用! SQL 是一门很棒的语言,但有时您需要不同的工具;它不适合文本处理。

    如果您熟悉 C#,这是我生成格式文件的代码。没有人会取笑我的白石缩进:P

    private static string  CreateFormatFile(string filePath, SqlConnection connection, string tableName, string[] sourceFields, string[] destFields, string fieldDelimiter, string fieldQuote)
        {
        string         formatFilePath = filePath + ".fmt";
        StreamWriter   formatFile     = null;
        SqlDataReader  data           = null;
        try
            {
            // Load the metadata for the destination table, so we can look up fields' ordinal positions
            SqlCommand  command = new SqlCommand("SELECT TOP 0 * FROM " + tableName, connection);
                        data    = command.ExecuteReader(CommandBehavior.SchemaOnly);
            DataTable   schema  = data.GetSchemaTable();
    
            Dictionary<string, Tuple<int, int>>  metadataByField = new Dictionary<string, Tuple<int, int>>();
            foreach (DataRow row in schema.Rows)
                {
                string  fieldName = (string)row["ColumnName"];
                int     ordinal   = (int)row["ColumnOrdinal"] + 1;
                int     maxLength = (int)row["ColumnSize"];
                metadataByField.Add(fieldName, new Tuple<int, int>(ordinal, maxLength));
                }
    
            // Begin the file, including its header rows
            formatFile = File.CreateText(formatFilePath);
            formatFile.WriteLine("10.0");
            formatFile.WriteLine(sourceFields.Length);
    
            // Certain strings need to be escaped to use them in a format file
            string  fieldQuoteEscaped     = fieldQuote     == "\"" ? "\\\"" : fieldQuote;
            string  fieldDelimiterEscaped = fieldDelimiter == "\t" ? "\\t"  : fieldDelimiter;
    
            // Write a row for each source field, defining its metadata and destination field
            for (int i = 1;  i <= sourceFields.Length;  i++)
                {
                // Each line contains (separated by tabs): the line number, the source type, the prefix length, the field length, the delimiter, the destination field number, the destination field name, and the collation set
                string  prefixLen   = i != 1 || fieldQuote == null ? "0" : fieldQuote.Length.ToString();
                string  fieldLen;
                string  delimiter   = i < sourceFields.Length ? fieldQuoteEscaped + fieldDelimiterEscaped + fieldQuoteEscaped : fieldQuoteEscaped + @"\r\n";
                string  destOrdinal;
                string  destField   = destFields[i - 1];
                string  collation;
                if (destField == null)
                    {
                    // If a field is not being imported, use ordinal position zero and a placeholder name
                    destOrdinal = "0";
                    fieldLen    = "32000";
                    destField   = "DUMMY";
                    collation   = "\"\"";
                    }
                else
                    {
                    Tuple<int, int>  metadata;
                    if (metadataByField.TryGetValue(destField, out metadata) == false)  throw new ApplicationException("Could not find field \"" + destField + "\" in table \"" + tableName + "\".");
                    destOrdinal = metadata.Item1.ToString();
                    fieldLen    = metadata.Item2.ToString();
                    collation   = "SQL_Latin1_General_CP1_CI_AS";
                    }
                string  line = String.Join("\t", i, "SQLCHAR", prefixLen, fieldLen, '"' + delimiter + '"', destOrdinal, destField, collation);
                formatFile.WriteLine(line);
                }
    
            return formatFilePath;
            }
        finally
            {
            if (data       != null)  data.Close();
            if (formatFile != null)  formatFile.Close();
            }
        }
    

    当时我没有为数据读取器使用using 块是有原因的。

    【讨论】:

    【解决方案2】:

    BCP 似乎无法将 True 和 False 理解为位值。最好使用 SSIS 或先替换文本的内容(创建视图或类似的东西不是一个好主意,开销更大)。

    【讨论】:

    • 已经很久了,但确实这是唯一的方法。即使您必须使用 C# 替换文本,BCP 仍然比 SSIS 快,而且它的扩展性更好。令人惊讶的是,考虑到 SSIS 已经存在了一段时间......它的功能要多得多,但速度较慢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 2013-02-20
    • 2018-07-20
    • 1970-01-01
    • 2019-01-20
    相关资源
    最近更新 更多