我正在尝试使用Write-SqlTableData
写入表格。该名称已更改,因为它是公司数据库。
环境:
命令:
Write-SqlTableData -DatabaseName 'dbname' -ServerInstance sql1 -TableName tablename -InputData (1,'1346',$false,'CalendarInvite') -Force -SchemaName "dbo"
错误:Write-SqlTableData : Input array is longer than the number of columns in this table.
表格列:
我不知道为什么会收到错误。四个值应该足够了,我按列顺序给出它们。
我也尝试使用哈希表,但错误更令人困惑。
哈希表:@{Scope=1;ContextID='1346';FeatureEnabled=$false;Type='CalendarInvite'}
错误:Write-SqlTableData : The given value of type String from the data source cannot be converted to type int of the specified target column.
我完全糊涂了。我的命令出了什么问题?
编辑: 该命令适用于我的测试SQL(13.0.1601.5)但团队测试SQL(11.0.3156.0)。也许版本很重要?
答案 0 :(得分:4)
这是我发现的:
这是一个错误。 在代码内部,cmdlet会尝试猜测您尝试向其发送的内容,然后尝试将发送的内容重新格式化为与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
产地:
-------------------------- | | Key | Value | -------------------------- | 1 | col1 | SomeString | --------------------------
使用类型:
------------------------------------------- | Column Name | Data Type | Allow Nulls | ------------------------------------------- | [Key] | sql_varient | | ------------------------------------------- | Value | 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 | SomeString | -------------------
使用类型:
--------------------------------------------- | Column Name | Data Type | Allow Nulls | --------------------------------------------- | col1 | nvarchar(MAX) | TRUE | ---------------------------------------------
所以,这里变得复杂了。如果您有现有表,则cmdlet会尽力将数据与表列对齐。它还没那么好。文档提供的默认示例显示了如何通过将值放在具有列名作为哈希键的哈希表中来执行此操作。 这对现有表格不起作用 官方文档:https://docs.microsoft.com/en-us/powershell/module/sqlserver/write-sqltabledata?view=sqlserver-ps
现有表格失败示例
现有表格:
--------------------------------------------- | Column Name | Data Type | Allow Nulls | --------------------------------------------- | col1 | varchar(MAX) | FALSE | --------------------------------------------- | col2 | Text | FALSE | ---------------------------------------------
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 | SomeText | ----------------------------- | 3 | col2 | SomeMoreText2 | ----------------------------- | 4 | col1 | SomeText2 | -----------------------------
毋庸置疑,这不是我们所期待的。
当你有更复杂的数据类型时,即使你在哈希表中排列了正确的类型(带有字符串的字符串,带有整数的int等),情况会变得更糟在写入SQL阶段期间,它永远不会正确排列列。
那么,解决方案是什么?
这是... 代码实际上是为处理类型[PSCustomObject]而编写的。
如果您想了解PSObject,Hash和PSCustomObject类型之间的详细差异,请参阅: Difference between PSObject, Hashtable and PSCustomObject
所以,既然我们知道我们需要什么,那么注意Hash表不设置项的顺序(至少在默认情况下不是这样)也很重要。但是,您可以指定PSObject类型的顺序,但不能使用快速“哈希”创建方法。您必须单独定义每个NoteProperty。请参阅:Change order of columns in the object
但是,自PowerShell V3以来,Microsoft为[PSCustomObject]添加了一个类型加速器,它使用有序哈希表创建对象,因此属性保持在它们声明的顺序。幸运的是,这就是我们想要的类型。
所以,让我们再次这样做:
现有表格工作示例
现有表格(注意,我保留原始数据完整):
--------------------------------------------- | Column Name | Data Type | Allow Nulls | --------------------------------------------- | col1 | varchar(MAX) | FALSE | --------------------------------------------- | col2 | Text | FALSE | ---------------------------------------------
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 | SomeText | ---------------------------------- | 3 | col2 | SomeMoreText2 | ---------------------------------- | 4 | col1 | SomeText2 | ---------------------------------- | 5 | SomeText | SomeMoreText | ---------------------------------- | 6 | SomeText2 | SomeMoreText2 | ----------------------------------
Horray!它实际上是正确的顺序!现在,不要忘记,如果你有一个需要int的列,你需要确保你的输入符合它的预期。我将把cmdlet用来比较类型的转换表放在底部。我从已编译的dll中反汇编代码,因此无法保证准确的准确性。
所以,现在让我们转到“id”(自动递增标识规范)列问题。如果未在PSCustomObject中添加该列,则会拒绝SQL写入。但是,没有问题!事实证明,我们可以添加id列名称,只需将值保留为空白,然后以SQL将下一个值放入的方式进行翻译。
ID列示例
现有表(注意,这是一个只有列而没有行数据的新空白表):
--------------------------------------------- | Column Name | Data Type | Allow Nulls | --------------------------------------------- | id | int | FALSE | --------------------------------------------- | col1 | varchar(50) | FALSE | --------------------------------------------- | col2 | Text | FALSE | --------------------------------------------- Note: "id" column has Identity Specification (Is Identity) turned to "Yes" with Identity Increment set to 1 and Identity Seed set to 1.
PowerShell命令:
$Obj = [PSCustomObject] @{
id=''
col1="SomeString"
col2="SomeMoreString"
}
Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
产地:
-------------------------------------- | | id | col1 | col2 | -------------------------------------- | 1 | 1 | SomeText | 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
产地:
-------------------------------------- | | id | col1 | col2 | -------------------------------------- | 1 | 1 | SomeText | SomeMoreText | -------------------------------------- | 2 | 2 | SomeText | SomeMoreText | -------------------------------------- | 3 | 3 | SomeText | SomeMoreText | -------------------------------------- | 4 | 4 | SomeText | SomeMoreText | -------------------------------------- | 5 | 5 | SomeText | 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;