You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy-DbaDbTableData - Exclude generated always columns from the writable destination columns
The period columns of a temporal table and the ledger metadata columns are
GENERATED ALWAYS: not computed, not rowversion, but just as unwritable, and
SMO reports them with Computed = false. The positional mapping counted them,
mapped query columns onto them and the server rejected the insert with error
13536. The lookup is guarded by the version because SMO only supports
GeneratedAlwaysType on SQL Server 2016 and later.
(do Copy-DbaDbTableData)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: public/Copy-DbaDbTableData.ps1
+12-10Lines changed: 12 additions & 10 deletions
Original file line number
Diff line number
Diff line change
@@ -56,14 +56,14 @@ function Copy-DbaDbTableData {
56
56
Custom SQL SELECT query to use as the data source instead of copying the entire table or view. Supports 3 or 4-part object names.
57
57
Use this when you need to filter rows, join multiple tables, or transform data during the copy operation. Still requires specifying a Table or View parameter for metadata purposes.
58
58
59
-
Note: Columns are mapped by position onto the writable columns of the destination table, so computedand rowversion columns of the destination do not count and must not have a counterpart in the SELECT list.
59
+
Note: Columns are mapped by position onto the writable columns of the destination table, so computed, rowversion and generated always columns of the destination do not count and must not have a counterpart in the SELECT list.
60
60
If the destination table has an identity column, include a placeholder value (e.g., 0) in your SELECT list at that position.
61
61
The placeholder will be ignored and the identity value auto-generated unless -KeepIdentity is specified.
62
62
63
63
.PARAMETERForceExplicitMapping
64
64
When used together with Query parameter, force the use of explicit column mapping (name-based) instead of switching over to ordinal position mapping. Use with care if query contains aliases.
65
65
Default behaviour when using Query parameter is to use ordinal position mapping, due to the possibility of the query including aliases (SELECT x AS y) which could lead to column mismatching and data not copying.
66
-
Positional mapping skips the computedand rowversion columns of the destination table, so the SELECT list only has to match its writable columns.
66
+
Positional mapping skips the computed, rowversion and generated always columns of the destination table, so the SELECT list only has to match its writable columns.
67
67
68
68
.PARAMETERAutoCreateTable
69
69
Automatically creates the destination table if it doesn't exist, using the same structure as the source table.
@@ -673,13 +673,15 @@ function Copy-DbaDbTableData {
673
673
$bulkCopy.NotifyAfter=$NotifyAfter
674
674
$bulkCopy.BulkCopyTimeout=$BulkCopyTimeout
675
675
676
-
# Get list of writable columns from destination table to avoid insert failures. Computed and rowversion
677
-
# columns cannot be written, and they are also what breaks the implicit positional mapping of SqlBulkCopy:
678
-
# it counts a computed column (the server then rejects the insert) and silently drops the source column
679
-
# that lands on a rowversion column, shifting every column behind it by one (see #10661).
676
+
# Get list of writable columns from destination table to avoid insert failures. Computed, rowversion
677
+
# and generated always columns (temporal periods, ledger metadata) cannot be written, and they are also
678
+
# what breaks the implicit positional mapping of SqlBulkCopy: it counts a computed column (the server
679
+
# then rejects the insert) and silently drops the source column that lands on a rowversion column,
680
+
# shifting every column behind it by one (see #10661). GeneratedAlwaysType is only supported by SMO on
681
+
# SQL Server 2016 and later, so it has to be guarded by the version.
680
682
# Refresh the columns collection to ensure it's populated
Write-Message-Level Verbose -Message "Destination table has $($destColumns.Count) writable columns"
684
686
685
687
# The legacy bulk copy library uses a 4 byte integer to track the RowsCopied, so the only option is to use
@@ -712,7 +714,7 @@ function Copy-DbaDbTableData {
712
714
# Custom queries may have different column names/aliases, so they are mapped by position
713
715
# Appending -ForceExplicitMapping will override this behaviour and keep explicit column mapping
714
716
if (-not (Test-Bound-ParameterName Query) -or$ForceExplicitMapping) {
715
-
# Map only columns that exist in both source and destination (excluding computedand rowversion columns)
717
+
# Map only columns that exist in both source and destination (excluding computed, rowversion and generated always columns)
716
718
for ($i=0; $i-lt$reader.FieldCount; $i++) {
717
719
$sourceColumn=$reader.GetName($i)
718
720
if ($destColumns-contains$sourceColumn) {
@@ -723,9 +725,9 @@ function Copy-DbaDbTableData {
723
725
}
724
726
} else {
725
727
# Map the query columns by position onto the writable destination columns. This is what SqlBulkCopy does
726
-
# on its own, except that its list also contains the computedand rowversion columns (see above).
728
+
# on its own, except that its list also contains the computed, rowversion and generated always columns (see above).
727
729
if ($reader.FieldCount-gt$destColumns.Count) {
728
-
$columnCountMessage="The query returns $($reader.FieldCount) columns but $fqtndest has only $($destColumns.Count) writable columns. Computedand rowversion columns cannot be written and do not count."
730
+
$columnCountMessage="The query returns $($reader.FieldCount) columns but $fqtndest has only $($destColumns.Count) writable columns. Computed, rowversion and generated always columns cannot be written and do not count."
$null=$destinationDb.Query("CREATE TABLE dbo.dbatoolsci_positional_dest (Id INT, A INT, Computed AS (A * 10), RV ROWVERSION, B INT, C INT)")
228
236
$null=$destinationDb.Query("CREATE TABLE dbo.dbatoolsci_positional_identity (Id INT IDENTITY(1, 1), A INT, B INT, C INT)")
229
237
$null=$destinationDb.Query("CREATE TABLE dbo.dbatoolsci_positional_rowversion (Id INT, A INT, RV ROWVERSION, B INT, C INT)")
238
+
if ($destinationDb.Parent.VersionMajor-ge13) {
239
+
# The period columns of a temporal table are GENERATED ALWAYS: not computed, not rowversion,
240
+
# but just as unwritable, and interleaved with the writable columns here on purpose.
241
+
$null=$destinationDb.Query("CREATE TABLE dbo.dbatoolsci_positional_temporal (Id INT PRIMARY KEY, A INT, ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL, B INT, ValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL, C INT, PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.dbatoolsci_positional_temporal_history))")
0 commit comments