Skip to content

Commit 245d6ed

Browse files
Copy-DbaDbTableData - Fix data ordering issue when copying tables without explicit ORDER BY (#9927)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent a437ea9 commit 245d6ed

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

public/Copy-DbaDbTableData.ps1

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,58 @@ function Copy-DbaDbTableData {
412412

413413

414414
if (Test-Bound -ParameterName Query -Not) {
415-
$Query = "SELECT * FROM $fqtnfrom"
415+
# Build ORDER BY clause to ensure consistent row order
416+
# This prevents data misalignment when copying tables without explicit ordering
417+
$orderByClause = ""
418+
419+
# Refresh indexes to ensure we have current metadata
420+
$sqlObject.Indexes.Refresh()
421+
422+
# Option 1: Use clustered index columns for ordering (most common and performant)
423+
$clusteredIndex = $sqlObject.Indexes | Where-Object IsClustered -eq $true | Select-Object -First 1
424+
if ($clusteredIndex) {
425+
$orderColumns = $clusteredIndex.IndexedColumns | Sort-Object IndexKeyPosition | ForEach-Object {
426+
$colName = $_.Name
427+
$descending = if ($_.Descending) { " DESC" } else { "" }
428+
"[$colName]$descending"
429+
}
430+
if ($orderColumns) {
431+
$orderByClause = " ORDER BY " + ($orderColumns -join ", ")
432+
Write-Message -Level Verbose -Message "Using clustered index for ordering: $orderByClause"
433+
}
434+
}
435+
436+
# Option 2: If no clustered index, try primary key
437+
if (-not $orderByClause) {
438+
$primaryKey = $sqlObject.Indexes | Where-Object IndexKeyType -eq "DriPrimaryKey" | Select-Object -First 1
439+
if ($primaryKey) {
440+
$orderColumns = $primaryKey.IndexedColumns | Sort-Object IndexKeyPosition | ForEach-Object {
441+
$colName = $_.Name
442+
$descending = if ($_.Descending) { " DESC" } else { "" }
443+
"[$colName]$descending"
444+
}
445+
if ($orderColumns) {
446+
$orderByClause = " ORDER BY " + ($orderColumns -join ", ")
447+
Write-Message -Level Verbose -Message "Using primary key for ordering: $orderByClause"
448+
}
449+
}
450+
}
451+
452+
# Option 3: If using KeepIdentity and an identity column exists, order by it
453+
if (-not $orderByClause -and $KeepIdentity) {
454+
$identityColumn = $sqlObject.Columns | Where-Object Identity -eq $true | Select-Object -First 1
455+
if ($identityColumn) {
456+
$orderByClause = " ORDER BY [$($identityColumn.Name)]"
457+
Write-Message -Level Verbose -Message "Using identity column for ordering: $orderByClause"
458+
}
459+
}
460+
461+
# If no ordering found, log a warning for tables without proper keys
462+
if (-not $orderByClause) {
463+
Write-Message -Level Verbose -Message "No clustered index, primary key, or identity column found for ordering. Row order is not guaranteed."
464+
}
465+
466+
$Query = "SELECT * FROM $fqtnfrom$orderByClause"
416467
$sourceLabel = $fqtnfrom
417468
} else {
418469
$sourceLabel = "Query"

tests/Copy-DbaDbTableData.Tests.ps1

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,45 @@ Describe $CommandName -Tag IntegrationTests {
197197
$destCount.Count | Should -Be 2
198198
}
199199
}
200+
201+
Context "Regression tests" {
202+
BeforeAll {
203+
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
204+
205+
$null = $sourceDb.Query("CREATE TABLE dbo.dbatoolsci_ordering_test (id INT IDENTITY(1,1) PRIMARY KEY, data_hash VARBINARY(32))")
206+
$null = $sourceDb.Query("INSERT INTO dbo.dbatoolsci_ordering_test (data_hash) VALUES (0x0102030405), (0x0607080910), (0x1112131415)")
207+
208+
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
209+
}
210+
211+
AfterAll {
212+
$PSDefaultParameterValues["*-Dba*:EnableException"] = $true
213+
214+
$null = $sourceDb.Query("DROP TABLE IF EXISTS dbo.dbatoolsci_ordering_test")
215+
$null = $destinationDb.Query("DROP TABLE IF EXISTS dbo.dbatoolsci_ordering_test_dest")
216+
217+
$PSDefaultParameterValues.Remove("*-Dba*:EnableException")
218+
}
219+
220+
It "Should maintain correct row order when copying tables with varbinary fields (issue #9610)" {
221+
$splatCopy = @{
222+
SqlInstance = $TestConfig.instance1
223+
Destination = $TestConfig.instance2
224+
Database = "tempdb"
225+
Table = "dbatoolsci_ordering_test"
226+
DestinationTable = "dbatoolsci_ordering_test_dest"
227+
AutoCreateTable = $true
228+
}
229+
$result = Copy-DbaDbTableData @splatCopy
230+
$result.RowsCopied | Should -Be 3
231+
232+
$sourceData = $sourceDb.Query("SELECT id, data_hash FROM dbo.dbatoolsci_ordering_test ORDER BY id")
233+
$destData = $destinationDb.Query("SELECT id, data_hash FROM dbo.dbatoolsci_ordering_test_dest ORDER BY id")
234+
235+
for ($i = 0; $i -lt $sourceData.Count; $i++) {
236+
$sourceData[$i].id | Should -Be $destData[$i].id
237+
$sourceData[$i].data_hash | Should -Be $destData[$i].data_hash
238+
}
239+
}
240+
}
200241
}

0 commit comments

Comments
 (0)