2010-10-05 1 views
3

Je suis confronté à un problème conceptuel que j'ai du mal à surmonter. J'espère que les gens de SO pourront m'aider à surmonter le problème avec un coup de pouce dans la bonne direction.J'ai besoin d'une approche pour travailler avec de petits sous-ensembles d'un grand ensemble de données

Je suis en train de faire un travail ETL avec les données sources étant très similaires et très grandes. Je le charge dans une table qui est destinée à la réplication et je veux seulement les informations les plus basiques dans cette table cible.

Ma table source ressemble à ceci:

alt text

J'ai besoin de ma table cible pour refléter comme tel:

alt text

Comme vous pouvez le voir, je ne l'ai pas dupliquer le statut InTransit où il a été dupliqué dans la table source. Les étapes que j'essaie de comprendre sont

  1. Afficher toutes les nouvelles lignes distinctes entrées depuis la dernière exécution de la requête. (Facile)
  2. Pour chaque TrackingId, j'ai besoin de vérifier si chaque nouveau statut est déjà le statut le plus récent dans la cible et si tel est le cas, sinon continuez et insérez-le. Ce qui signifie que je dois aussi commencer au plus tôt des nouveaux statuts et partir de là. (Je n'ai pas * (! # Dans la moindre idée comment je vais le faire)
  3. Pour ce faire, toutes les 15 minutes afin que les statuts sont conservés très récent si l'étape 2 doit être performant.

Ma table source pourrait facilement constitué de 100k + lignes, mais ayant besoin de courir toutes les 15 minutes, il faut que je m'assure que c'est très performant, donc pourquoi j'essaie vraiment d'éviter les curseurs

Maintenant, la seule façon de voir cela est en utilisant un sprl CLR mais je pense qu'il peut y avoir de meilleurs moyens donc j'espère que vous pourrez me pousser dans la bonne direction. Je suis sûr que je laisse probablement quelque chose que vous pourriez avoir besoin alors s'il vous plaît laissez-moi savoir quelles informations vous pouvez avoir besoin et je serai heureux de fournir.

Merci d'avance!

EDIT: OK Je n'étais pas assez explicite dans ma question. Ma table source va contenir plusieurs ID de suivi. Il peut y avoir jusqu'à 100k + lignes contenant plusieurs TrackingId et plusieurs statuts pour chaque trackingId. Je dois mettre à jour la table cible comme ci-dessus pour chaque identifiant de suivi individuel, mais ma source sera un amalgame de trackingId.

Répondre

1

Et voilà. Je vais vous laisser le nettoyer et faire des optimisations. une des sous-requêtes peut entrer dans une vue et la comparaison de date désordonnée peut être nettoyée. Si vous utilisez SQL 2008 R2, utilisez plutôt CAST comme DATE.

declare @tbl1 table(
id int, Trackingid int, Status varchar(50), StatusDate datetime 
) 

declare @tbl2 table(
id int, Trackingid int, Status varchar(50), StatusDate datetime 
) 

----Source data 
insert into @tbl1 (id, trackingid, status, statusdate) values(1,1,'PickedUp','10/01/10 1:00') -- 
insert into @tbl1 (id, trackingid, status, statusdate) values(2,1,'InTransit','10/02/10 1:00') -- 
insert into @tbl1 (id, trackingid, status, statusdate) values(8,1,'InTransit','10/02/10 3:00') 
insert into @tbl1 (id, trackingid, status, statusdate) values(4,1,'Delayed','10/03/10 1:00') 
insert into @tbl1 (id, trackingid, status, statusdate) values(5,1,'InTransit','10/03/10 1:01') 
insert into @tbl1 (id, trackingid, status, statusdate) values(6,1,'AtDest','10/03/10 2:00') 
insert into @tbl1 (id, trackingid, status, statusdate) values(7,1,'Deliv','10/03/10 3:00') -- 
insert into @tbl1 (id, trackingid, status, statusdate) values(3,2,'InTransit','10/03/10 1:00') 
insert into @tbl1 (id, trackingid, status, statusdate) values(9,2,'AtDest','10/04/10 1:00') 
insert into @tbl1 (id, trackingid, status, statusdate) values(10,2,'Deliv','10/04/10 1:05') 
insert into @tbl1 (id, trackingid, status, statusdate) values(11,1,'Delayed','10/02/10 2:05') 

----Target data 
insert into @tbl2 (id, trackingid, status, statusdate) values(1,1,'PickedUp','10/01/10 1:00') 
insert into @tbl2 (id, trackingid, status, statusdate) values(2,1,'InTransit','10/02/10 1:00') 
insert into @tbl2 (id, trackingid, status, statusdate) values(3,1,'Deliv','10/03/10 3:00') 


select d.* from 
(
    select 
    * , 
    ROW_NUMBER() OVER(PARTITION BY trackingid, CAST((STR(YEAR(statusdate)) + '/' +STR(MONTH(statusdate)) + '/' +STR(DAY(statusdate))) AS DATETIME) ORDER BY statusdate) AS 'RN' 
    from @tbl1 
) d 

where 
not exists 
(
    select RN from 
    (
     select 
     * , 
     ROW_NUMBER() OVER(PARTITION BY trackingid, CAST((STR(YEAR(statusdate)) + '/' +STR(MONTH(statusdate)) + '/' +STR(DAY(statusdate))) AS DATETIME) ORDER BY statusdate) AS 'RN' 
     from @tbl1 
    )f where f.RN = d.RN + 1 and d.status = f.status and f.trackingid = d.trackingid and 
    CAST((STR(YEAR(f.statusdate)) + '/' +STR(MONTH(f.statusdate)) + '/' +STR(DAY(f.statusdate))) AS DATETIME) = 
      CAST((STR(YEAR(d.statusdate)) + '/' +STR(MONTH(d.statusdate)) + '/' +STR(DAY(d.statusdate))) AS DATETIME) 
) 

and 
not exists 
(
    select 1 from @tbl2 t2 
    where (t2.trackingid = d.trackingid 
    and t2.statusdate = d.statusdate 
    and t2.status = d.status) 
) 
and (
    not exists 
    (
     select 1 from 
     (
      select top 1 * from @tbl2 t2 
      where t2.trackingid = d.trackingid 
      order by t2.statusdate desc 
     ) g 
     where g.status = d.status 
    ) 
    or not exists 
    (
     select 1 from 
     (
      select top 1 * from @tbl2 t2 
      where t2.trackingid = d.trackingid 
      and t2.statusdate <= d.statusdate 
      order by t2.statusdate desc 
     ) g 
     where g.status = d.status 
    ) 
) 
order by trackingid,statusdate 
+0

Cela réalise tout ce dont j'avais besoin, merci beaucoup! – joshlrogers

1

Comment bien Effectue dépendra des indices, et surtout si vous ciblez un seul TrackingID à la fois, mais cela est une façon d'utiliser un CTE et autojointure pour obtenir les résultats souhaités:

CREATE TABLE #foo 
(
    TrackingID INT, 
    [Status] VARCHAR(32), 
    StatusDate SMALLDATETIME 
); 

INSERT #foo SELECT 1, 'PickedUp', '2010-10-01 08:15'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-02 03:07'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-02 10:28'; 
INSERT #foo SELECT 1, 'Delayed', '2010-10-03 09:52'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-03 20:09'; 
INSERT #foo SELECT 1, 'AtDest', '2010-10-04 13:42'; 
INSERT #foo SELECT 1, 'Deliv',  '2010-10-04 17:05'; 

WITH src AS 
(
    SELECT 
     TrackingID, 
     [Status], 
     StatusDate, 
     ab = ROW_NUMBER() OVER (ORDER BY [StatusDate]) 
    FROM #foo 
    WHERE TrackingID = 1 
), 
realsrc AS 
(
    SELECT 
     a.TrackingID, 
     leftrow   = a.ab, 
     rightrow  = b.ab, 
     leftstatus  = a.[Status], 
     leftstatusdate = a.StatusDate, 
     rightstatus  = b.[Status], 
     rightstatusdate = b.StatusDate 
    FROM src AS a 
    LEFT OUTER JOIN src AS b 
    ON a.ab = b.ab - 1 
) 
SELECT 
    Id = ROW_NUMBER() OVER (ORDER BY [leftstatusdate]), 
    TrackingID, 
    [Status] = leftstatus, 
    [StatusDate] = leftstatusdate 
FROM 
    realsrc 
WHERE 
    rightrow IS NULL 
    OR (leftrow = rightrow - 1 AND leftstatus <> rightstatus) 
ORDER BY 
    [StatusDate]; 
GO 
DROP TABLE #foo; 

Si vous devez prendre en charge plusieurs TrackingIDs dans la même requête:

CREATE TABLE #foo 
(
    TrackingID INT, 
    [Status] VARCHAR(32), 
    StatusDate SMALLDATETIME 
); 

INSERT #foo SELECT 1, 'PickedUp', '2010-10-01 08:15'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-02 03:07'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-02 10:28'; 
INSERT #foo SELECT 1, 'Delayed', '2010-10-03 09:52'; 
INSERT #foo SELECT 1, 'InTransit', '2010-10-03 20:09'; 
INSERT #foo SELECT 1, 'AtDest', '2010-10-04 13:42'; 
INSERT #foo SELECT 1, 'Deliv',  '2010-10-04 17:05'; 
INSERT #foo SELECT 2, 'InTransit', '2010-10-02 10:28'; 
INSERT #foo SELECT 2, 'Delayed', '2010-10-03 09:52'; 
INSERT #foo SELECT 2, 'InTransit', '2010-10-03 20:09'; 
INSERT #foo SELECT 2, 'AtDest', '2010-10-04 13:42'; 

WITH src AS 
(
    SELECT 
     TrackingID, 
     [Status], 
     StatusDate, 
     ab = ROW_NUMBER() OVER (ORDER BY [StatusDate]) 
    FROM #foo 
), 
realsrc AS 
(
    SELECT 
     a.TrackingID, 
     leftrow   = a.ab, 
     rightrow  = b.ab, 
     leftstatus  = a.[Status], 
     leftstatusdate = a.StatusDate, 
     rightstatus  = b.[Status], 
     rightstatusdate = b.StatusDate 
    FROM src AS a 
    LEFT OUTER JOIN src AS b 
    ON a.ab = b.ab - 1 
    AND a.TrackingID = b.TrackingID 
) 
SELECT 
    Id = ROW_NUMBER() OVER (ORDER BY TrackingID, [leftstatusdate]), 
    TrackingID, 
    [Status] = leftstatus, 
    [StatusDate] = leftstatusdate 
FROM 
    realsrc 
WHERE 
    rightrow IS NULL 
    OR (leftrow = rightrow - 1 AND leftstatus <> rightstatus) 
ORDER BY 
    TrackingID, 
    [StatusDate]; 
GO 
DROP TABLE #foo; 
+0

cela fonctionne pour la moitié de mon problème. Vous étiez en mesure de filtrer les statuts séquentiels de la source, mais cela ne règle finalement pas les problèmes que j'aurais avec la cible. Comme je l'ai dit dans ma question d'origine à l'étape 2, je dois aussi m'inquiéter de l'insertion d'un statut séquentiel en double dans ma cible. Cela ne m'assure pas que cela n'arriverait pas. Je vous donne un +1 pour une solution élégante dans la récupération des données source. – joshlrogers

0

Si cela est SQL 2005, vous pouvez ensuite utiliser ROW_NUMBER avec une requête secondaire ou CTE: Si l'ensemble de données est vraiment énorme que et la performance est un question alors l'un des ci-dessus que J'ai collé pendant que j'essayais de faire fonctionner le bloc de code pourrait bien être plus efficace.

/** 
* This is just to create a sample table to use in the test query 
**/ 

DECLARE @test TABLE(ID INT, TrackingID INT, Status VARCHAR(20), StatusDate DATETIME) 
INSERT @test 
SELECT 1,1,'PickedUp', '01 jan 2010 08:00' UNION 
SELECT 2,1,'InTransit', '01 jan 2010 08:01' UNION 
SELECT 3,1,'InTransit', '01 jan 2010 08:02' UNION 
SELECT 4,1,'Delayed', '01 jan 2010 08:03' UNION 
SELECT 5,1,'InTransit', '01 jan 2010 08:04' UNION 
SELECT 6,1,'AtDest', '01 jan 2010 08:05' UNION 
SELECT 7,1,'Deliv', '01 jan 2010 08:06' 


/** 
* This would be the select code to exclude the duplicate entries. 
* Sorting desc in row_number would get latest instead of first 
**/ 
;WITH n AS 
(
    SELECT ID, 
      TrackingID, 
      Status, 
      StatusDate, 
      --For each Status for a tracking ID number by ID (could use date but 2 may be the same) 
      ROW_NUMBER() OVER(PARTITION BY TrackingID, Status ORDER BY ID) AS [StatusNumber] 
    FROM @test 
) 
SELECT ID, 
     TrackingID, 
     Status, 
     StatusDate 
FROM n 
WHERE StatusNumber = 1 
ORDER BY ID 
+0

Vous requête ne renvoie pas l'entrée '5' de la table d'origine. – Quassnoi

2

est ici une solution sans Autojointures:

WITH q AS 
     (
     SELECT *, 
       ROW_NUMBER() OVER (ORDER BY statusDate) AS rn, 
       ROW_NUMBER() OVER (PARTITION BY status ORDER BY statusDate) AS rns 
     FROM tracking 
     WHERE tackingId = @id 
     ), 
     qs AS 
     (
     SELECT *, 
       ROW_NUMBER() OVER (PARTITION BY rn - rns ORDER BY statusDate) AS rnn 
     FROM q 
     ) 
SELECT * 
FROM qs 
WHERE rnn = 1 
ORDER BY 
     statusDate 

Voici un script pour vérifier:

DECLARE @tracking TABLE 
     (
     id INT NOT NULL PRIMARY KEY, 
     trackingId INT NOT NULL, 
     status INT, 
     statusDate DATETIME 
     ) 

INSERT 
INTO @tracking 
SELECT 1, 1, 1, DATEADD(d, 1, '2010-01-01') 
UNION ALL 
SELECT 2, 1, 2, DATEADD(d, 2, '2010-01-01') 
UNION ALL 
SELECT 3, 1, 2, DATEADD(d, 3, '2010-01-01') 
UNION ALL 
SELECT 4, 1, 2, DATEADD(d, 4, '2010-01-01') 
UNION ALL 
SELECT 5, 1, 3, DATEADD(d, 5, '2010-01-01') 
UNION ALL 
SELECT 6, 1, 3, DATEADD(d, 6, '2010-01-01') 
UNION ALL 
SELECT 7, 1, 4, DATEADD(d, 7, '2010-01-01') 
UNION ALL 
SELECT 8, 1, 2, DATEADD(d, 8, '2010-01-01') 
UNION ALL 
SELECT 9, 1, 2, DATEADD(d, 9, '2010-01-01') 
UNION ALL 
SELECT 10, 1, 1, DATEADD(d, 10, '2010-01-01') 
; 
WITH q AS 
     (
     SELECT *, 
       ROW_NUMBER() OVER (ORDER BY statusDate) AS rn, 
       ROW_NUMBER() OVER (PARTITION BY status ORDER BY statusDate) AS rns 
     FROM @tracking 
     ), 
     qs AS 
     (
     SELECT *, 
       ROW_NUMBER() OVER (PARTITION BY rn - rns ORDER BY statusDate) AS rnn 
     FROM q 
     ) 
SELECT * 
FROM qs 
WHERE rnn = 1 
ORDER BY 
     statusDate 
+0

Pensez-vous que cela sera équivalent en performance à une auto-jointure, ou vous attendez-vous à ce qu'il fonctionne plus vite? – JNK

+0

+1 - Je n'arrive pas à faire travailler le mien! – JNK

0

Je pense que cet exemple fera ce que vous cherchez:

CREATE TABLE dbo.srcStatus (
Id INT IDENTITY(1,1), 
TrackingId INT NOT NULL, 
[Status] VARCHAR(10) NOT NULL, 
StatusDate DATETIME NOT NULL 
); 

CREATE TABLE dbo.tgtStatus (
Id INT IDENTITY(1,1), 
TrackingId INT NOT NULL, 
[Status] VARCHAR(10) NOT NULL, 
StatusDate DATETIME NOT NULL 
); 

INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'PickedUp','10/1/2010 8:15 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'InTransit','10/2/2010 3:07 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'InTransit','10/2/2010 10:28 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'PickedUp','10/1/2010 8:15 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'InTransit','10/2/2010 3:07 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'Delayed','10/2/2010 10:28 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'Delayed','10/3/2010 9:52 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'InTransit','10/3/2010 8:09 PM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'AtDest','10/4/2010 1:42 PM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (1,'Deliv','10/4/2010 5:05 PM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'InTransit','10/3/2010 9:52 AM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'InTransit','10/3/2010 8:09 PM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'AtDest','10/4/2010 1:42 PM'); 
INSERT INTO dbo.srcStatus (TrackingId, [Status], StatusDate) VALUES (2,'Deliv','10/4/2010 5:05 PM'); 

WITH cteSrcTrackingIds 
      AS (SELECT DISTINCT 
         TrackingId 
       FROM  dbo.srcStatus 
      ), 
     cteAllTrackingIds 
      AS (SELECT TrackingId , 
         [Status] , 
         StatusDate 
       FROM  dbo.srcStatus 
       UNION 
       SELECT tgtStatus.TrackingId , 
         tgtStatuS.[Status] , 
         tgtStatus.StatusDate 
       FROM  cteSrcTrackingIds 
         INNER JOIN dbo.tgtStatus ON cteSrcTrackingIds.TrackingId = tgtStatus.TrackingId 
      ), 
     cteAllTrackingIdsWithRownums 
      AS (SELECT TrackingId , 
         [Status] , 
         StatusDate , 
         ROW_NUMBER() OVER (PARTITION BY TrackingId ORDER BY StatusDate) AS rownum 
       FROM  cteAllTrackingIds 
      ), 
     cteTrackingIdsWorkingSet 
      AS (SELECT src.rownum AS [id] , 
         src2.rownum AS [id2] , 
         src.TrackingId , 
         src.[Status] , 
         src.StatusDate , 
         ROW_NUMBER() OVER (PARTITION BY src.TrackingId, 
              src.rownum ORDER BY src.StatusDate) AS rownum 
       FROM  cteAllTrackingIdsWithRownums AS [src] 
         LEFT OUTER JOIN cteAllTrackingIdsWithRownums AS [src2] ON src.TrackingId = src2.TrackingId 
                   AND src.rownum < src2.rownum 
                   AND src.[Status] != src2.[Status] 
      ), 
     cteTrackingIdsSubset 
      AS (SELECT id , 
         TrackingId , 
         [Status] , 
         StatusDate , 
         ROW_NUMBER() OVER (PARTITION BY TrackingId, id2 ORDER BY id) AS rownum 
       FROM  cteTrackingIdsWorkingSet 
       WHERE rownum = 1 
      ) 
    INSERT INTO dbo.tgtStatus 
      (TrackingId , 
       [status] , 
       StatusDate 
      ) 
      SELECT cteTrackingIdsSubset.TrackingId , 
        cteTrackingIdsSubset.[status] , 
        cteTrackingIdsSubset.StatusDate 
      FROM cteTrackingIdsSubset 
        LEFT OUTER JOIN dbo.tgtStatus ON cteTrackingIdsSubset.TrackingId = tgtStatus.TrackingId 
                AND cteTrackingIdsSubset.[status] = tgtStatus.[status] 
                AND cteTrackingIdsSubset.StatusDate = tgtStatus.StatusDate 
      WHERE cteTrackingIdsSubset.rownum = 1 
        AND tgtStatus.id IS NULL 
      ORDER BY cteTrackingIdsSubset.TrackingId , 
        cteTrackingIdsSubset.StatusDate;