Write-SqlTableData:来自数据源的String类型的给定值不能转换为指定目标列的类型位

DSa*_*ura 2 sql-server powershell

我正在尝试使用写入表Write-SqlTableData。由于它是公司数据库,因此名称已更改。

环境:

  • Windows 8.1和Powershell 5.0,SqlServer模块21.0.17099

命令:

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.

表格栏:

  • ID:int身份
  • 范围:int
  • ContextID:ntext
  • FeatureEnabled:位
  • 类型:ntext

我不知道为什么会收到错误消息。四个值就足够了,我按列顺序给它们。

我也尝试使用哈希表,但是错误更加令人困惑。

  • 哈希表: @{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)。版本可能重要吗?

Jer*_*ton 5

@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
Run Code Online (Sandbox Code Playgroud)

产生:

--------------------------
| | 关键 价值|
--------------------------
| 1 | col1 | SomeString |
--------------------------

带有类型:

-------------------------------------------
| 列名| 数据类型 允许为空
-------------------------------------------
| [关键] | sql_varient | |
-------------------------------------------
| 价值| sql_varient | |
-------------------------------------------

PS对象

$Obj = New-Object PSObject -Property @{col1="SomeString"}

Write-SQLTableData -TableName TablePSObject -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj -force
Run Code Online (Sandbox Code Playgroud)

产生:

-------------------
| | col1 |
-------------------
| 1 | SomeString |
-------------------

带类型:

---------------------------------------------
| 列名| 数据类型 允许为空
---------------------------------------------
| col1 | nvarchar(MAX)| TRUE |
---------------------------------------------

所以,这就是复杂的地方。当您具有现有表时,该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"}
Run Code Online (Sandbox Code Playgroud)

产生:

-----------------------------
| | col1 | col2 |
-----------------------------
| 1 | col2 | SomeMoreText |
-----------------------------
| 2 | col1 | SomeText |
-----------------------------
| 3 | col2 | SomeMoreText2 |
-----------------------------
| 4 | col1 | SomeText2 |
-----------------------------

不用说,这不是我们所期望的。

当您尝试将更复杂的数据类型放入表中时,情况变得更糟,因为即使您在哈希表中排列了正确的类型(带字符串的字符串,带整数的int等),也永远不会在写入SQL阶段正确排列列。

那么,有什么解决方案?

这里是... 该代码实际上是为处理[PSCustomObject]类型而编写的。

如果您想查看PSObject,Hash和PSCustomObject类型之间的详细区别,请参考此内容: PSObject,Hashtable和PSCustomObject之间的区别

因此,既然我们知道需要什么,那么还需要注意的是,哈希表未设置项目的顺序(至少默认情况下未设置)。但是,您可以指定PSObject类型的顺序,但是不能使用快速的“哈希”创建方法。您必须分别定义每个NoteProperty。请参阅:更改对象中列的顺序

但是,自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
Run Code Online (Sandbox Code Playgroud)

产生:

----------------------------------
| | col1 | col2 |
----------------------------------
| 1 | col2 | SomeMoreText |
----------------------------------
| 2 | col1 | SomeText |
----------------------------------
| 3 | col2 | SomeMoreText2 |
----------------------------------
| 4 | col1 | SomeText2 |
----------------------------------
| 5 | SomeText | SomeMoreText |
----------------------------------
| 6 | SomeText2 | SomeMoreText2 |
----------------------------------

霍雷!实际上顺序正确!现在,不要忘记,如果您有一个需要整数的列,则需要确保您的输入与期望的对齐。我将cmdlet用于比较类型的转换表放在底部。我从编译的dll中反汇编了代码,因此无法保证精确性。

因此,现在让我们转到“ id”(自动递增身份规范)列问题。如果您未在PSCustomObject中添加该列,则SQL写操作将被拒绝。但是,没有问题!事实证明,我们可以添加id列名称,并将该值保留为空白,然后将其转换为SQL将只放入下一个值的方式。

ID列示例

现有表(请注意,这是一个新的空白表,只有列,没有行数据):

---------------------------------------------
| 列名| 数据类型 允许为空
---------------------------------------------
| id | int | 假|
---------------------------------------------
| col1 | varchar(50)| 假|
---------------------------------------------
| col2 | 文字| 假|
---------------------------------------------
注意:“ id”列的“身份规范”(Is Identity)设置为“是”,并且“身份增量”设置为1,“身份种子”设置为1。

PowerShell命令:

$Obj = [PSCustomObject] @{
                    id=''
                    col1="SomeString"
                    col2="SomeMoreString"
}

Write-SQLTableData -TableName TableWithID -SchemaName dbo -DatabaseName DB1 -ServerInstance localhost\SQLExpress -InputData $Obj
Run Code Online (Sandbox Code Playgroud)

产生:

--------------------------------------
| | 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
Run Code Online (Sandbox Code Playgroud)

产生:

--------------------------------------
| | 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;
Run Code Online (Sandbox Code Playgroud)