@Matt @DSakura:所以,我在这件事上撞了墙很长时间,但最终想通了……有点。我什至反编译了包含 Write-SqlTableData cmdlet (Microsoft.SqlServer.Management.PSSnapins) 的程序集。
这是我发现的:
这是一个错误。
在代码中,cmdlet 会尝试猜测您尝试向其发送的内容,然后尝试将您发送的内容重新格式化为与 SQL 期望接收的内容一致的内容。
它在这个过程中进行:
1 哪个 .Net 对象正在发送给我?
- 是 SQL 数据集吗?
- 它是 SQL 数据表吗?
- 它是 SQL 数据行吗?
- 是其他类型的时髦对象吗?
2 如果是时髦的对象,我可以把它变成我可以放入 SQL 表的东西吗?
.Net 对象不与 SQL 列类型对齐([System.String] 不是有效的 SQL 类型),因此 cmdlet 使用转换表进行最佳猜测(例如 nvarchar(MAX))然后在它转换类型后,它会尝试将其转储到表中。
当您使用“-force”参数创建一个全新的表时,这会更容易,因为它不必将您尝试输入的内容与现有表中已经存在的列相匹配。它只是做出最好的猜测,并将数据类型更改为 SQL 可以理解的类型。
例子:
哈希表
Write-SQLTableData -TableName TableHash -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData @{col1="SomeString"} -force
生产:
--------------------------
| |钥匙 |价值 |
--------------------------
| 1 | col1 |一些字符串 |
--------------------------
有类型:
------------------------------------------
|列名 |数据类型 |允许空值 |
------------------------------------------
| [钥匙] | sql_varient | |
------------------------------------------
|价值 | sql_varient | |
------------------------------------------
PSObject
$Obj = New-Object PSObject -Property @{col1="SomeString"}
Write-SQLTableData -TableName TablePSObject -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj -force
生产:
------------------
| | col1 |
------------------
| 1 |一些字符串 |
------------------
有类型:
---------------------------------------------
|列名 |数据类型 |允许空值 |
---------------------------------------------
| col1 | nvarchar(MAX) |真的 |
---------------------------------------------
所以,这就是它变得复杂的地方。当您有一个现有表时,cmdlet 会尽最大努力将您的数据与表列对齐。它还不是很擅长。文档提供的默认示例显示了如何通过将您的值放入哈希表中来执行此操作,其中列名作为哈希键。 这不适用于现有表
官方文档在这里:https://docs.microsoft.com/en-us/powershell/module/sqlserver/write-sqltabledata?view=sqlserver-ps
现有表失败示例
现有表:
---------------------------------------------
|列名 |数据类型 |允许空值 |
---------------------------------------------
| col1 | varchar(MAX) |错误 |
---------------------------------------------
| col2 |正文 |错误 |
---------------------------------------------
PowerShell 命令:
Write-SQLTableData -TableName TableExistsAlready -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData @{col1="SomeText";col2="SomeMoreText"}
Write-SQLTableData -TableName TableExistsAlready -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData @{col1="SomeText2";col2="SomeMoreText2"}
生产:
-----------------------------------------
| | col1 | col2 |
-----------------------------------------
| 1 | col2 | SomeMoreText |
-----------------------------------------
| 2 | col1 |一些文本 |
-----------------------------------------
| 3 | col2 |更多文本2 |
-----------------------------------------
| 4 | col1 |一些文本2 |
-----------------------------------------
不用说,这不是我们所期望的。
当您尝试将更复杂的数据类型放入表中时,情况会变得更糟,即使您在哈希表中排列了正确的类型(字符串与字符串,整数与整数等),在写入 SQL 阶段,它永远不会正确排列列。
那么,解决办法是什么?
在这里... 代码实际上是为了处理类型[PSCustomObject]而编写的。
如果您想了解 PSObject、Hash 和 PSCustomObject 类型之间的详细区别,请参阅以下内容:
Difference between PSObject, Hashtable and PSCustomObject
所以,既然我们知道了我们需要什么,同样重要的是要注意哈希表不设置项目的顺序(至少默认情况下不设置)。但是,您可以指定 PSObject 类型的顺序,但不能使用快速“散列”创建方法。您必须单独定义每个 NoteProperty。见:Change order of columns in the object
但是,从 PowerShell V3 开始,Microsoft 为 [PSCustomObject] 添加了一个类型加速器,该加速器使用有序哈希表创建对象,因此属性保持声明它们的顺序。幸运的是,这正是我们想要的类型。
那么,让我们再做一次:
现有表工作示例
现有表(注意,我原封不动地保留了原始数据):
---------------------------------------------
|列名 |数据类型 |允许空值 |
---------------------------------------------
| col1 | varchar(MAX) |错误 |
---------------------------------------------
| col2 |正文 |错误 |
---------------------------------------------
PowerShell 命令:
$Obj = [PSCustomObject] @{col1="SomeText";col2="SomeMoreText"}
Write-SQLTableData -TableName TableExistsAlready -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
$Obj2 = [PSCustomObject] @{col1="SomeText2";col2="SomeMoreText2"}
Write-SQLTableData -TableName TableExistsAlready -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj2
生产:
----------------------------------
| | col1 | col2 |
----------------------------------
| 1 | col2 | SomeMoreText |
----------------------------------
| 2 | col1 |一些文本 |
----------------------------------
| 3 | col2 |更多文本2 |
----------------------------------
| 4 | col1 |一些文本2 |
----------------------------------
| 5 |一些文本 | SomeMoreText |
----------------------------------
| 6 |一些文本2 |更多文本2 |
----------------------------------
万岁!它实际上是在正确的顺序!现在,不要忘记,如果你有一个需要 int 的列,你需要确保你的输入与它所期望的一致。我将把 cmdlet 用于比较类型的转换表放在底部。我从编译的 dll 中反汇编了代码,所以不能保证准确度。
所以,现在让我们转到“id”(自动递增标识规范)列问题。如果您未在 PSCustomObject 中添加该列,您的 SQL 写入将被拒绝。但是,没问题!事实证明,我们可以添加 id 列名并将值留为空白,然后它会以 SQL 将下一个值放入的方式进行转换。
ID 列示例
现有表(注意,这是一个新的空白表,只有列,没有行数据):
---------------------------------------------
|列名 |数据类型 |允许空值 |
---------------------------------------------
|编号 |整数 |错误 |
---------------------------------------------
| col1 | varchar(50) |错误 |
---------------------------------------------
| col2 |正文 |错误 |
---------------------------------------------
注意:“id”列的身份规范(是身份)变为“是”,身份增量设置为 1,身份种子设置为 1。
PowerShell 命令:
$Obj = [PSCustomObject] @{
id=''
col1="SomeString"
col2="SomeMoreString"
}
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
生产:
--------------------------------------
| |编号 | col1 | col2 |
--------------------------------------
| 1 | 1 |一些文本 | SomeMoreText |
--------------------------------------
你说什么?当您再执行四次命令时会发生什么?我很高兴你问:
PowerShell 命令:
$Obj = [PSCustomObject] @{
id=''
col1="SomeString"
col2="SomeMoreString"
}
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
生产:
--------------------------------------
| |编号 | col1 | col2 |
--------------------------------------
| 1 | 1 |一些文本 | SomeMoreText |
--------------------------------------
| 2 | 2 |一些文本 | SomeMoreText |
--------------------------------------
| 3 | 3 |一些文本 | SomeMoreText |
--------------------------------------
| 4 | 4 |一些文本 | SomeMoreText |
--------------------------------------
| 5 | 5 |一些文本 | SomeMoreText |
--------------------------------------
嗯...这比我想象的要长。我希望这对其他人有所帮助。
这是转换表:
类型转换表
case "System.String":
return DataType.NVarCharMax;
case "System.Int64":
return DataType.BigInt;
case "System.Byte[]":
return DataType.VarBinaryMax;
case "System.Boolean":
return DataType.Bit;
case "System.Char":
return DataType.NChar(1);
case "System.DateTime":
return DataType.DateTime2(7);
case "System.DateTimeOffset":
return DataType.DateTimeOffset(7);
case "System.Decimal":
return DataType.Decimal(14, 0x1c);
case "System.Double":
return DataType.Float;
case "System.Int32":
return DataType.Int;
case "System.Char[]":
return DataType.NVarCharMax;
case "System.Single":
return DataType.Real;
case "System.Int16":
return DataType.SmallInt;
case "System.Object":
return DataType.Variant;
case "System.TimeSpan":
return DataType.Timestamp;
case "System.Byte":
return DataType.TinyInt;
case "System.Guid":
return DataType.UniqueIdentifier;
case "System.UInt64":
return DataType.Numeric(0, 20);
case "System.UInt32":
return DataType.BigInt;
case "System.UInt16":
return DataType.Int;
case "System.SByte":
return DataType.SmallInt;
case "Microsoft.SqlServer.Types.SqlHierarchyId":
return DataType.HierarchyId;
case "Microsoft.SqlServer.Types.SqlGeography":
return DataType.Geography;
case "Microsoft.SqlServer.Types.SqlGeometry":
return DataType.Geometry;