Transponieren hierachischer Daten von einem VARCHAR auf zwei INTs

7

DAS PROBLEM

Die Tabelle, in der die Daten aufgezeichnet werden, ist

CREATE TABLE [dbo].[Almoxarifado](
    [idAlmoxarifado] [varchar](20) NOT NULL,
    [tipoAlmoxarifadoId] [varchar](30) NOT NULL,
    [entidadeId] [bigint] NOT NULL,
    [dtInclusao] [smalldatetime] NOT NULL,
    [dtUltimaAlteracao] [smalldatetime] NULL,
    [descricao] [varchar](255) NOT NULL,
    [terceiro] [bit] NOT NULL
) ON [PRIMARY]

Und dies ist ein SELECT TOP 10 *Beispiel (die Tabelle enthält mehr als 1 Million Einträge):

Ergebnis auswählen

Wie Sie sehen können, idAlmoxarifadospeichert das Feld hierarchische Daten, die keine hierarchische Beziehung haben, wie es die meisten hierarchischen Tabellen haben.

Jetzt möchte ich diese Daten in eine übliche hierarchische Tabelle bringen:

CREATE TABLE [dbo].[Almoxarifado2](
    [idMaster] [bigint] IDENTITY(1,1) NOT NULL,
    [idAlmoxarifado] [int] NOT NULL,
    [idAlmoxPai] [int] NOT NULL DEFAULT ((0)),
    [entidadeId] [bigint] NOT NULL,
    [tipoAlmoxarifadoId] [varchar](30) NOT NULL 
    [dtInclusao] [datetime] NULL DEFAULT (getdate()),
    [dtUltimaAlteracao] [datetime] NULL,
    [descricao] [varchar](255) NOT NULL,
    [terceiro] [bit] NULL DEFAULT ((0)),
    PRIMARY KEY ([idMaster])
)

Auf dieser Tabelle habe ich eine übliche Vater-Sohn-Beziehung, in der der "Vater" eines bestimmten Feldes immer sein idMasterKorrespondent ist (es sei denn, es befindet sich im ersten Grad der hierarchischen Kette; dann wäre es "Vater" 0).

Wenn wir also der im obigen Bild gezeigten Reihenfolge folgen, sollten wir (nach dem Einfügen der transponierten Daten in die neue Tabelle) Folgendes haben:

Geben Sie hier die Bildbeschreibung ein

DIE FRAGE

Wie kann ich diese Umsetzung auf sichere und schmerzlose Weise durchführen, da die ursprüngliche Tabelle mehr als 1 mi Einträge enthält?

DIE REGELN

Es gibt keine Einschränkungen hinsichtlich der Anzahl der Top-Levels. keine Kinder Ebenen. Unsere Idee ist es, mit einer Maske zu arbeiten, um die Daten in unserer Anwendung nützlich zu machen (wir haben mit Motorola Handheld Computer gearbeitet; überprüfen Sie MC45 und MC3190). Angenommen, der Kunde, der unsere Lösung kauft, verfügt über mehrere Lager, in denen er seine Produkte lagert. In diesem Fall kann er eine Maske definieren (z. B. 9.99.99.999 oder 9.9.99.99.99.999), und unser System muss diese Maske verwenden können, damit er seine Lager in diesen Ebenen registrieren kann.

Man kann also grundsätzlich sagen, dass unser System in gewisser Weise auf Logistik ausgerichtet ist.

Wo die 1. Ebene "NY South Warehouse" sein könnte, der 2. "Raum # 2", 3. "Korridor A", 4. "Schließfach 2" und 5. "Regal 3".

Doch menschlich
quelle

Antworten:

10

Da Ihre Daten ein bisschen wie hierarchyId aussehen, habe ich darüber nachgedacht, sie zu verwenden. Die ersten Versionen waren nicht gut auf 1 Million Zeilen skalierbar, jedoch haben einige Indizierungsoptionen in der temporären Haupttabelle geholfen. Probleme können jedoch auch auf meine Testdaten zurückzuführen sein. Können Sie mir also etwas mehr über Ihre Hierarchie erzählen? Zum Beispiel, wie viele Eltern der obersten Ebene gibt es, wie viele Ebenen gibt es durchschnittlich, wie viele Kinder könnte jede Ebene haben?

In der Zwischenzeit finden Sie hier eine Version, damit Sie sie durchsehen und sehen können, ob hierarchyIdsie für Sie funktionieren könnte.

Die Grundidee besteht darin, eine temporäre Tabelle mit den ursprünglichen IDs zu erstellen, in die konvertiert wurde hierarchyIds, und dann die Hierarchie zu durchlaufen, um die Abstammungslinie zu ermitteln. Diese temporäre Tabelle (die permanent sein könnte) könnte dann für die Migration verwendet werden:

USE tempdb
GO

SET NOCOUNT ON
GO

IF OBJECT_ID('[dbo].[Almoxarifado]') IS NOT NULL DROP TABLE [dbo].[Almoxarifado]
IF OBJECT_ID('[dbo].[Almoxarifado2]') IS NOT NULL DROP TABLE [dbo].[Almoxarifado2]
GO


CREATE TABLE [dbo].[Almoxarifado](
    [idAlmoxarifado] [varchar](20) NOT NULL,
    [tipoAlmoxarifadoId] [varchar](30) NOT NULL,
    [entidadeId] [bigint] NOT NULL,
    [dtInclusao] [smalldatetime] NOT NULL,
    [dtUltimaAlteracao] [smalldatetime] NULL,
    [descricao] [varchar](255) NOT NULL,
    [terceiro] [bit] NOT NULL
) ON [PRIMARY]
GO



CREATE TABLE [dbo].[Almoxarifado2](
    [idMaster] [bigint] IDENTITY(1,1) NOT NULL,
    [idAlmoxarifado] [int] NOT NULL,
    [idAlmoxPai] [int] NOT NULL DEFAULT ((0)),
    [entidadeId] [bigint] NOT NULL,
    [tipoAlmoxarifadoId] [varchar](30) NOT NULL ,
    [dtInclusao] [datetime] NULL DEFAULT (getdate()),
    [dtUltimaAlteracao] [datetime] NULL,
    [descricao] [varchar](255) NOT NULL,
    [terceiro] [bit] NULL DEFAULT ((0)),
    PRIMARY KEY ([idMaster])
)
GO

/*
-- Test data
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
VALUES
    ( '1', 'TESTE', 12, '29 Apr 2016', NULL, '0000000', 0 ),
    ( '1.1', 'TESTE', 12, '29 Apr 2016', NULL, '0000001', 0 ),
    ( '1.1.01', 'TESTE', 12, '29 Apr 2016', NULL, '0000002', 0 ),
    ( '1.1.01.01', 'TESTE', 12, '29 Apr 2016', NULL, '0000003', 0 ),
    ( '1.1.01.01.001', 'TESTE', 12, '29 Apr 2016', NULL, '0000004', 0 ),

    ( '1.1.01.01.002', 'TESTE', 12, '29 Apr 2016', NULL, '0000005', 0 ),
    ( '1.1.01.01.003', 'TESTE', 12, '29 Apr 2016', NULL, '0000006', 0 ),
    ( '1.1.01.01.004', 'TESTE', 12, '29 Apr 2016', NULL, '0000007', 0 ),
    ( '1.1.01.01.005', 'TESTE', 12, '29 Apr 2016', NULL, '0000008', 0 ),
    ( '1.1.01.01.006', 'TESTE', 12, '29 Apr 2016', NULL, '0000009', 0 )
GO
*/

-- Add 10 parent levels
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
VALUES
    ( '1', 'TESTE', 1, '1 Jan 2016', NULL, '00000001', 0 ),
    ( '2', 'TESTE', 1, '1 Feb 2016', NULL, '00000002', 0 ),
    ( '3', 'TESTE', 1, '1 Mar 2016', NULL, '00000003', 0 ),
    ( '4', 'TESTE', 1, '1 Apr 2016', NULL, '00000004', 0 ),
    ( '5', 'TESTE', 1, '1 May 2016', NULL, '00000005', 0 ),
    ( '6', 'TESTE', 1, '1 Jun 2016', NULL, '00000006', 0 ),
    ( '7', 'TESTE', 1, '1 Jul 2016', NULL, '00000007', 0 ),
    ( '8', 'TESTE', 1, '1 Aug 2016', NULL, '00000008', 0 ),
    ( '9', 'TESTE', 1, '1 Sep 2016', NULL, '00000008', 0 ),
    ( '10', 'TESTE', 1, '1 Oct 2016', NULL, '00000010', 0 )


-- For each parent, add 3 sublevels
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
SELECT 
    idAlmoxarifado + '.' + CAST( x.y AS VARCHAR(10) ),
    tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro
FROM [dbo].[Almoxarifado] a
    CROSS JOIN ( SELECT TOP 5 idAlmoxarifado y FROM [dbo].[Almoxarifado] ) x


-- add n sublevels
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
SELECT 
    idAlmoxarifado + '.0' + CAST( x.y AS VARCHAR(10) ),
    tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro
FROM [dbo].[Almoxarifado] a
    CROSS JOIN ( SELECT TOP 8 idAlmoxarifado y FROM [dbo].[Almoxarifado] WHERE idAlmoxarifado Not Like '%.%' ) x
WHERE idAlmoxarifado Like '%.%'


-- Add 8 sublevels
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
SELECT 
    idAlmoxarifado + '.0' + CAST( x.y AS VARCHAR(10) ),
    tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro
FROM [dbo].[Almoxarifado] a
    CROSS JOIN ( SELECT TOP 13 ROW_NUMBER() OVER( ORDER BY idAlmoxarifado ) y FROM [dbo].[Almoxarifado] ) x
WHERE idAlmoxarifado Like '%.%.%'


-- Add 9 sublevels
INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
SELECT 
    idAlmoxarifado + '.0' + CAST( x.y AS VARCHAR(10) ),
    tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro
FROM [dbo].[Almoxarifado] a
    CROSS JOIN ( SELECT TOP 21 ROW_NUMBER() OVER( ORDER BY idAlmoxarifado ) y FROM [dbo].[Almoxarifado] ) x
WHERE idAlmoxarifado Like '%.%.%.%'
GO

INSERT INTO [dbo].[Almoxarifado] ( idAlmoxarifado, tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro )
SELECT 
    idAlmoxarifado + '.0' + CAST( x.y AS VARCHAR(10) ),
    tipoAlmoxarifadoId, entidadeId, dtInclusao, dtUltimaAlteracao, descricao, terceiro
FROM [dbo].[Almoxarifado] a
    CROSS JOIN ( SELECT TOP 9 ROW_NUMBER() OVER( ORDER BY idAlmoxarifado ) y FROM [dbo].[Almoxarifado] ) x
WHERE idAlmoxarifado Like '%.%.%.%.%'
GO



-- Main hierarchyId processing
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL DROP TABLE #tmp

SELECT
    IDENTITY( INT, 1, 1 ) AS idMaster,
    idAlmoxarifado,
    CAST( '/' + REPLACE( REPLACE( REPLACE( idAlmoxarifado, '.0', '.' ), '.0', '.' ), '.', '/' ) + '/' AS hierarchyid ) hId,
    CAST( '/' + REPLACE( REPLACE( REPLACE( idAlmoxarifado, '.0', '.' ), '.0', '.' ), '.', '/' ) + '/' AS hierarchyid ).ToString() hString,
    CAST( NULL AS INT ) AS idAlmoxarifado2,
    CAST( NULL AS INT ) AS idAlmoxPai

INTO #tmp
FROM [dbo].[Almoxarifado]
GO


-- Index temp table to help with recursive CTE
ALTER TABLE #tmp ADD PRIMARY KEY ( idMaster )
ALTER TABLE #tmp ALTER COLUMN hId hierarchyId NOT NULL
CREATE UNIQUE INDEX _idx ON #tmp ( hId ) INCLUDE ( idAlmoxarifado, hString )
GO


-- Walk the hierarchy
;WITH cte AS (
SELECT 1 AS xlevel, idMaster, idAlmoxarifado, hId, hString, hId.GetLevel() getLevel, 0 AS parentId
FROM #tmp
WHERE hId.GetLevel() = 1

UNION ALL

SELECT xlevel + 1, t.idMaster, t.idAlmoxarifado, t.hId, t.hString, t.hId.GetLevel() getLevel, c.idMaster AS parentId
FROM cte c
    INNER JOIN #tmp t ON c.hId  = t.hId.GetAncestor(1)
)
UPDATE t
SET 
    -- Parse out the individual node, by taking its parent and stuffing it in front
    t.idAlmoxarifado2 = CAST( REPLACE( STUFF( c.hString, 1, LEN(c.hId.GetAncestor(1).ToString()), '' ), '/', '' ) AS INT ),
    t.idAlmoxPai = parentId
FROM cte c
    INNER JOIN #tmp t ON c.idMaster = T.idMaster


-- Check results
SELECT *
FROM #tmp

Dieses gesamte Skript (einschließlich der Erstellung von Testdaten) läuft auf meinem Laptop in nur wenigen Minuten.

wBob
quelle
5

Fügen Sie eine Spalte hinzu dbo.Almoxarifado2, die den ursprünglichen Wert von enthält idAlmoxarifado.

Fügen Sie alle Zeilen von hinzu dbo.Almoxarifadound idAlmoxarifadoOrgaktualisieren Sie sie dann idAlmoxPaimit dem Wert von idMasterfür die übergeordnete Zeile.

Code mit einigen Kommentaren:

-- Add a column to store original idAlmoxarifado
alter table dbo.Almoxarifado2 
    add idAlmoxarifadoOrg varchar(20) not null;

go

-- Add all rows to Almoxarifado2 with 0 to idAlmoxPai 
-- and the rightmost number of idAlmoxarifado to idAlmoxarifado.
-- Original value of idAlmoxarifado goes to idAlmoxarifadoOrg
insert into dbo.Almoxarifado2(idAlmoxarifado, idAlmoxPai, entidadeId, tipoAlmoxarifadoId, dtInclusao, dtUltimaAlteracao, descricao, terceiro, idAlmoxarifadoOrg)
select right(A.idAlmoxarifado, charindex('.', reverse('.'+A.idAlmoxarifado)) - 1), 0, A.entidadeId, A.tipoAlmoxarifadoId, A.dtInclusao, A.dtUltimaAlteracao, A.descricao, A.terceiro, A.idAlmoxarifado
from dbo.Almoxarifado as A;

go

-- Update idAlmoxPai with the parent idMaster
-- Don't update root nodes
update A
set idAlmoxPai = P.idMaster
from dbo.Almoxarifado2 as A
  inner join dbo.Almoxarifado2 as P
    on P.idAlmoxarifadoOrg = left(A.idAlmoxarifadoOrg, len(A.idAlmoxarifadoOrg) - charindex('.', reverse(A.idAlmoxarifadoOrg)))
where charindex('.', A.idAlmoxarifadoOrg) > 0;

go

-- Cleanup
alter table dbo.Almoxarifado2 drop column idAlmoxarifadoOrg;

Einige Teile könnten weitere Informationen benötigen.

Dies right(A.idAlmoxarifado, charindex('.', reverse('.'+A.idAlmoxarifado)) - 1)extrahiert die letzte Nummer in idAlmoxarifado. Also 1.1.10gib dir ein 10.

Und dieser Teil left(A.idAlmoxarifado, len(A.idAlmoxarifado) - charindex('.', reverse(A.idAlmoxarifado)))gibt alle bis auf die letzte Nummer zurück. Denn 1.1.10es wird zurückkehren 1.1.

Ich habe die von wBob generierten Testdaten schamlos gestohlen (danke und sehr geschätzt) und festgestellt, dass diese Version auf meinem Computer schneller ist. Ich benutze einen wirklich alten 2-Kern-Laptop für die Tests, so dass das Ergebnis auf einem echten Server unterschiedlich sein kann. Auf jeden Fall dauerte es 3 Minuten, bis der Code von wBob ausgeführt wurde, und meine Lösung betrug ungefähr 20 Sekunden.

Mikael Eriksson
quelle