【问题标题】:VB.Net Importing CSV File to SQL DatabaseVB.Net 将 CSV 文件导入 SQL 数据库
【发布时间】:2016-05-14 11:14:33
【问题描述】:

所以,我需要帮助弄清楚如何完成这项工作,并且很想与其他 VB 专家展开对话。

我有一个 CSV 文件,每小时都会转储到导入文件夹中。我编写了一个 VB 应用程序,它扫描该文件夹,获取 CSV 文件并将其导入 SQL 数据库。没有问题。

尝试在 SQL 中查询数据时出现问题。我需要的所有重要数据都在一个字段中,这是字段内容的修改示例:

Successful Write by DOMAIN\USERLOGIN on /SOMENETWORKSHARE/FOLDER/MyFolder/USERLOGIN/DesktopBackUp/log.txt

所以基本上我需要一种方法将上述行拆分为多个字段,并且需要在 SQL 导入时发生。

Successful Write“是事件”字段1

by "doesnever change 可以用作分隔符"

DOMAIN/USERLOGIN "DOMAIN 不会改变,但 userlogin 会" 字段 2

关于“永远不会改变可以用作分隔符”

/SOMENETWORKSHARE/FOLDER/MyFolder/USERLOGIN/DesktopBackUp/“这是文件路径”字段3

log.txt“已修改的文件名”字段 4

有时没有文件名,只有文件路径。

我愿意接受建议,但是,CSV 文件不会更改,我也无法更改文件传递给我的方式。

提前致谢。

这是数据库结构

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[SACL](
    [Unique ID] [varchar](max) NULL,
    [Event ID] [varchar](max) NULL,
    [Event Time] [datetime] NULL,
    [Severity] [varchar](max) NULL,
    [Workspace] [varchar](max) NULL,
    [Headline] [varchar](max) NULL,
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [Event] [varchar](max) NULL,
    [Username] [varchar](max) NULL,
    [Path] [varchar](max) NULL,
    [Filename] [varchar](max) NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

【问题讨论】:

  • Google VB Substring() 或 VB Regex。祝你好运
  • 我认为你没有提到 条你想要的数据。
  • 我想把那个长字段名拆分成 4 个字段,我在上面把它们分解了。
  • 感谢@mxix 的正则表达式建议,这可能会奏效。我将尝试使用它,看看我是否可以构建一个模式并发布它。
  • 您有多种选择: 1. 使用 VB 代码在中途拆分 2. 使用 SQL 表达式在中途拆分 3. 在表中创建计算列进行拆分它 4. 在拆分数据的表上创建一个视图。我的经验是这样拆分数据很容易出错,您需要能够轻松调整拆分算法。我首先建议您编写一些选择语句来使用 SQL 获取数据,并了解提取数据所需的复杂程度。

标签: sql-server vb.net csv import


【解决方案1】:

我希望这可以帮助你。调用此函数后,您可以使用结果将每个字段包含在数据库中另一个表或类似的东西中。

Public Function divideThis(CompleteString As String)

    Dim n As Integer = InStr(CompleteString, " by ")
    Dim Field1 As String = CompleteString.Substring(0, n - 1)

    Dim m As Integer = InStr(CompleteString, " on ")
    Dim Field2 As String = CompleteString.Substring(n + 3, m - n - 4)

    Dim o As Integer = InStrRev(CompleteString, "/")
    Dim Field3 As String = CompleteString.Substring(m + 3, o - m - 3)

    Dim Field4 As String = CompleteString.Substring(o, CompleteString.Length - o)

    Dim fragmented As String() = {Field1, Field2, Field3, Field4}

    Return (fragmented)

End Function

【讨论】:

  • 这个答案看起来很好
  • 这真的很棒,给了我测试时需要的 4 条信息。所以这是一个实施问题。我的应用程序读取 csv 文件并首先将其导入数据集,然后我使用 sqlbulkcopy 导入 SQL。在我将它传递给 sqlbulkcopy 之前,可以在数据集中的列上使用这个函数来添加 4 个额外的列吗?另外,对于具有 100K+ 记录的 CSV 文件,我是否会遇到速度问题?
【解决方案2】:

好的,试试这个...它将导入 CSV 文件,拆分必填字段并随时合并。

我已修改代码以在 LINQ 语句中包含 RegEx 以拆分该字段,如果没有文件名但末尾必须有一个正斜杠并且这必须是唯一的字段(在您的 CSV 文件)包含正斜杠...原因是如果正斜杠丢失并且没有文件名,您将丢失一个字段,或者如果您的 csv 文件中的其他字段包含正斜杠,您将获得太多字段斜线.....

Sub Main()  
    Dim regex As Text.RegularExpressions.Regex = New Text.RegularExpressions.Regex("\d+")  
    Dim readText() As String = File.ReadAllLines("<Path to your CSV file>")  

    Using connection As SqlConnection = New SqlConnection("<DB connection string>")  

        Dim command As SqlCommand = connection.CreateCommand()
        connection.Open()  

        command.CommandType = System.Data.CommandType.StoredProcedure  
        command.CommandText = "SP_InsertCSVData"  

        For i As Integer = 0 To readText.Count
            Dim readRecord() As String = readText.ToArray

            Dim CSVData As XElement = New XElement("Root",
                            From str In readText
                            Let fields = Text.RegularExpressions.Regex.Replace(str, "(by|on|\/(?=[^\/]*$))", ",").Split(",")
                            Select New XElement("CSVDataRecord",
                                New XAttribute("Field_1", fields(0)),
                                New XAttribute("Field_2", fields(1)),
                                New XAttribute("Field_3", fields(2)),
                                New XAttribute("Field_4", fields(3)),
                                New XAttribute("Field_5", fields(4)),
                                New XAttribute("Field_6", fields(5)),
                                New XAttribute("Field_7", fields(6)),
                                New XAttribute("Field_8", fields(7))
                            )
                        )

            command.Parameters.Clear()
            command.Parameters.Add(New SqlParameter With
            {
                .ParameterName = "@InputXML",
                .DbType = DbType.Xml,
                .Value = CSVData.CreateReader
            })

            command.ExecuteNonQuery()

        Next
    End Using

End Sub

这是新表……

/****** Object:  Table [dbo].[CSV_Import_Table]    Script Date: 06/02/2016 01:55:32 ******/   
SET ANSI_NULLS ON  
GO  

SET QUOTED_IDENTIFIER ON  
GO  

CREATE TABLE [dbo].[CSV_Import_Table](  
[Field_1] [nvarchar](max) NULL,  
[Field_2] [nvarchar](max) NULL,  
[Field_3] [nvarchar](max) NULL,  
[Field_4] [nvarchar](max) NULL,  
[Field_5] [nvarchar](max) NULL,  
[Field_6] [nvarchar](max) NULL,  
[Field_7] [nvarchar](max) NULL,  
[Field_8] [nvarchar](max) NULL  
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  

GO  

这里是 SP...

/****** Object:  StoredProcedure [dbo].[SP_InsertCSVData]    Script Date: 06/02/2016 01:56:20 ******/  
SET ANSI_NULLS ON  
GO  
SET QUOTED_IDENTIFIER ON  
GO  
CREATE PROCEDURE [dbo].[SP_InsertCSVData] (@InputXML xml)  
as  
begin  
MERGE  CSV_Import_Table AS main  
USING (select Row.id.value('@Field_1','[nvarchar](MAX)') as Field_1, Row.id.value('@Field_2','[nvarchar](MAX)') as Field_2, Row.id.value('@Field_3','[nvarchar](MAX)') as Field_3, Row.id.value('@Field_4','[nvarchar](MAX)') as Field_4,  Row.id.value('@Field_5','[nvarchar](MAX)') as Field_5, Row.id.value('@Field_6','[nvarchar](MAX)') as Field_6, Row.id.value('@Field_7','[nvarchar](MAX)') as Field_7, Row.id.value('@Field_8','[nvarchar](MAX)') as Field_8  
            from @InputXML.nodes('/Root/CSVDataRecord') as Row(id)) as stage  
ON main.Field_1=stage.Field_1  
WHEN MATCHED THEN  
    UPDATE SET main.Field_1=stage.Field_1, main.Field_2=stage.Field_2, main.Field_3=stage.Field_3, main.Field_4=stage.Field_4 ,   main.Field_5=stage.Field_5 , main.Field_6=stage.Field_6, main.Field_7=stage.Field_7, main.Field_8=stage.Field_8     
WHEN NOT MATCHED THEN   
    INSERT  (Field_1, Field_2, Field_3, Field_4, Field_5, Field_6, Field_7, Field_8) VALUES (stage.Field_1, stage.Field_2, stage.Field_3, stage.Field_4, stage.Field_5, stage.Field_6, stage.Field_7, stage.Field_8);  
end  

我猜你的 CSV 文件看起来像这样......

记录 1 字段 1,DOMAIN\USERLOGIN 成功写入 /SOMENETWORKSHARE/FOLDER/MyFolder/USERLOGIN/DesktopBackUp/Log.txt,字段 6,字段 7,字段 8

【讨论】:

  • 正如我在原始帖子中所述,我无法控制 CSV 文件。 CSV 文件包含 5 个已经用逗号分隔的字段。我的 Windows 窗体应用程序,在导入目录中检测到 CSV 文件,将其加载到处理队列中,将文件导入数据集并使用 SQLBulkCopy 导入数据库。我需要做的是将现有字段之一拆分为另外 4 个字段,因此最终数据库表在导入后将有 9 个字段。
  • 好吧,更清楚一点......好吧,在将其插入表格的单独列之前,您必须以任何方式拆分该字段的元素......为什么不只是简单地将数据拆分为 [以及何时] 您将其显示给用户....即,在 SQL 中查询数据时,只需使用 Miguel Baena 函数“divideThis”示例之类的方法将重新调整的字符串从该字段中拆分出来,传入那个字段作为他的“CompleteString”,然后将返回一个数组“碎片”,其中包含您需要的 4 个字段.....
【解决方案3】:

这是一个选择,您应该能够在您的表上运行以进行测试。

FROM ( .... ) as tbl 位替换为您的真实表

您可以看到选择中的表达式非常丑陋且笨拙,因此请考虑一下您是否愿意维护它

最后有一个WHERE来阻止无效数据进入。如果缺少分隔符的数据进入,那么索引最终将是负数,你会得到各种错误。

对于这些事情,数据质量始终是一个问题。因此,我们不想通过假设域将永远具有相同的名称,或者假设您只会从一个域中导入数据来复合这一点。

所以你需要对你的桌子运行这个,看看它是否有效。如果是这样,下一步是:

  • 从路径中取出文件名
  • 更改为更新

这应该很简单

SELECT 
SRC,
LEFT(SRC,CHARINDEX(' by ',SRC)) AS Operation,
SUBSTRING(
    SRC,
    CHARINDEX(' by ',SRC)+4,
    CHARINDEX(' on ',SRC) - CHARINDEX(' by ',SRC)-4
    ) AS LoginName,

RIGHT(
    SRC,  
    LEN(SRC) - CHARINDEX(' on ',SRC)-4
) AS FullPath,

REVERSE(LEFT(
    REVERSE(SRC),
    CHARINDEX('/',REVERSE(SRC))-1)
    ) AS PathAndFileName
FROM 
(
SELECT 'Successful Write by DOMAIN\USERLOGIN on /SOMENETWORKSHARE/FOLDER/MyFolder/USERLOGIN/DesktopBackUp/log.txt' AS SRC
UNION ALL
SELECT 'some invalid stuff' AS SRC
) as tbl
WHERE SRC LIKE '% by %\% on %/%'

适用于此的更新语句是:

UPDATE [dbo].[SACL]
SET 
[Event]=
LEFT(Headline,CHARINDEX(' by ',Headline)),
[Username] = 
SUBSTRING(
    Headline,
    CHARINDEX(' by ',Headline)+4,
    CHARINDEX(' on ',Headline) - CHARINDEX(' by ',Headline)-4
    ),
[Path] = 
SUBSTRING(
    Headline,
    CHARINDEX(' by ',Headline)+4,
    CHARINDEX(' on ',Headline) - CHARINDEX(' by ',Headline)-4
    ),

[FileName]=
REVERSE(LEFT(
    REVERSE(Headline),
    CHARINDEX('/',REVERSE(Headline))-1)
    )
WHERE 
-- Only update rows that fit the pattern
Headline LIKE '% by %\% on %/%'
-- Only update rows that haven't been processed
AND ([Event] IS NULL  OR [Event]  = '')

请注意:

  • 我不知道哪个字段包含完整的字符串。我假设Headline
  • 我建议您对Event 字段的内容进行标准化。 NULL = 未处理,空白表示其他内容(已处理但无法解决)
  • 我还建议您添加一个日期时间类型的ImportedDateProcessedDate 字段。 ImportedDatedefaultGETDATE(),这意味着这是导入记录的日期时间。 ProcessedDate 应由 UPDATE 语句更新为 GETDATE(),这会告诉您记录的更新时间。

【讨论】:

  • 这很漂亮,效果很好!!!但是有一个问题,在最后一个 PathandFileName 上,您让它根据最后一个“/”右侧的所有内容填充此字段,我的审核日志将为我提供目录路径或文件名,一行将是 '/PATH/ TO/Accounting”,下一个是“/PATH/TO/Accounting/Filename.xlsx”。有什么方法可以忽略此字段中除文件名之外的任何内容吗?
  • 很高兴它有帮助。如果您想从记录中排除路径名,请将您的位置更改为WHERE SRC LIKE '% by %\% on %/%.%'
  • 一切顺利后,您将SELECT 更改为UPDATE,并在导入数据后更新预先存在的字段。您可以将所有这些包装在一个存储过程中,并从您的导入应用程序中调用该存储过程。如果您的规则发生变化,您可以更改存储过程而不是重新编译您的应用程序
  • 优秀。我将添加更新语句,但您需要发布您的表 DDL(数据定义语言 - 创建表语句)
  • 那太好了。问题,如果我只想在 [Event] 为 Null 或 Empty 时更新数据库,我们该怎么做?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多