sql >> Database teknologi >  >> RDS >> Oracle

SQL-forespørgsel for at skjule duplikerede værdier efter datointerval

Jeg vil udvikle min løsning trinvist, og dekomponere hver transformation til en visning. Dette hjælper både med at forklare, hvad der bliver gjort, og hjælper med at fejlfinde og teste. Det er i bund og grund at anvende princippet om funktionel dekomponering på databaseforespørgsler.

Jeg vil også gøre det uden at bruge Oracle-udvidelser, med SQL, der burde køre på enhver moderne RBDMS. Så ingen keep, over, partition, kun underforespørgsler og gruppe-bys. (Informér mig i kommentarerne, hvis det ikke virker på dit RDBMS.)

Først tabellen, som da jeg er ukreativ, vil jeg kalde månedsværdi. Da id'et faktisk ikke er et unikt id, vil jeg kalde det "eid". De andre kolonner er "m"onth, "y"ear og "v"alue:

create table month_value( 
   eid int not null, m int, y int,  v int );
 

Efter at have indsat dataene, for to eids, har jeg:

> select * from month_value; +-----+------+------+------+ | eid | m | y | v | +-----+------+------+------+ | 100 | 1 | 2008 | 80 | | 100 | 2 | 2008 | 80 | | 100 | 3 | 2008 | 90 | | 100 | 4 | 2008 | 80 | | 200 | 1 | 2008 | 80 | | 200 | 2 | 2008 | 80 | | 200 | 3 | 2008 | 90 | | 200 | 4 | 2008 | 80 | +-----+------+------+------+ 8 rows in set (0.00 sec)

Dernæst har vi en enhed, måneden, der er repræsenteret som to variable. Det burde virkelig være én kolonne (enten en dato eller et datetime, eller måske endda en fremmednøgle til en tabel med datoer), så vi laver det til én kolonne. Vi vil gøre det som en lineær transformation, sådan at den sorterer det samme som (y, m), og sådan at der for enhver (y,m) tupel er én og eneste værdi, og alle værdier er fortløbende:

> create view cm_abs_month as 
select *, y * 12 + m as am from month_value;
 

Det giver os:

> select * from cm_abs_month;
+-----+------+------+------+-------+
| eid | m    | y    | v    | am    |
+-----+------+------+------+-------+
| 100 |    1 | 2008 |   80 | 24097 |
| 100 |    2 | 2008 |   80 | 24098 |
| 100 |    3 | 2008 |   90 | 24099 |
| 100 |    4 | 2008 |   80 | 24100 |
| 200 |    1 | 2008 |   80 | 24097 |
| 200 |    2 | 2008 |   80 | 24098 |
| 200 |    3 | 2008 |   90 | 24099 |
| 200 |    4 | 2008 |   80 | 24100 |
+-----+------+------+------+-------+
8 rows in set (0.00 sec)
 

Nu vil vi bruge en selv-join i en korreleret underforespørgsel til for hver række at finde den tidligste efterfølgende måned, hvor værdien ændres. Vi baserer denne visning på den tidligere visning, vi oprettede:

> create view cm_last_am as 
   select a.*, 
    ( select min(b.am) from cm_abs_month b 
      where b.eid = a.eid and b.am > a.am and b.v <> a.v) 
   as last_am 
   from cm_abs_month a;

> select * from cm_last_am;
+-----+------+------+------+-------+---------+
| eid | m    | y    | v    | am    | last_am |
+-----+------+------+------+-------+---------+
| 100 |    1 | 2008 |   80 | 24097 |   24099 |
| 100 |    2 | 2008 |   80 | 24098 |   24099 |
| 100 |    3 | 2008 |   90 | 24099 |   24100 |
| 100 |    4 | 2008 |   80 | 24100 |    NULL |
| 200 |    1 | 2008 |   80 | 24097 |   24099 |
| 200 |    2 | 2008 |   80 | 24098 |   24099 |
| 200 |    3 | 2008 |   90 | 24099 |   24100 |
| 200 |    4 | 2008 |   80 | 24100 |    NULL |
+-----+------+------+------+-------+---------+
8 rows in set (0.01 sec)
 

last_am er nu den "absolutte måned" i den første (tidligste) måned (efter måneden i den aktuelle række), hvor værdien v ændres. Den er null, hvor der ikke er en senere måned, for den eid, i tabellen.

Da last_am er den samme i alle måneder op til ændringen i v (som sker ved last_am), kan vi gruppere på last_am og v (og eid, selvfølgelig), og i enhver gruppe er min(am) det absolutte måned af den første på hinanden følgende måned, der havde denne værdi:

> create view cm_result_data as 
  select eid, min(am) as am , last_am, v 
  from cm_last_am group by eid, last_am, v;

> select * from cm_result_data;
+-----+-------+---------+------+
| eid | am    | last_am | v    |
+-----+-------+---------+------+
| 100 | 24100 |    NULL |   80 |
| 100 | 24097 |   24099 |   80 |
| 100 | 24099 |   24100 |   90 |
| 200 | 24100 |    NULL |   80 |
| 200 | 24097 |   24099 |   80 |
| 200 | 24099 |   24100 |   90 |
+-----+-------+---------+------+
6 rows in set (0.00 sec)
 

Nu er dette det resultatsæt, vi ønsker, hvorfor denne visning kaldes cm_result_data. Det eneste, der mangler, er noget til at forvandle absolutte måneder tilbage til (y,m) tupler.

For at gøre det slutter vi os til tabellen month_value.

Der er kun to problemer:1) vi vil have måneden før last_am i vores output, og2) vi har nuller, hvor der ikke er nogen næste måned i vores data; for at opfylde OP's specifikation, bør disse være enkeltmånedersintervaller.

EDIT:Disse kan faktisk være længere intervaller end en måned, men i alle tilfælde betyder de, at vi skal finde den seneste måned for eid, som er:

(select max(am) from cm_abs_month d where d.eid = a.eid )
 

Fordi visningerne nedbryder problemet, kunne vi tilføje denne "end cap" måned tidligere ved at tilføje en anden visning, men jeg vil bare indsætte dette i sammensmeltningen. Hvilket der ville være mest effektivt afhænger af, hvordan dit RDBMS optimerer forespørgsler.

For at få måned før slutter vi os (cm_result_data.last_am - 1 =cm_abs_month.am)

Uanset hvor vi har en null, ønsker OP'en at "til"-måneden skal være den samme som "fra"-måneden, så vi bruger bare sammensmeltning på det:coalesce( last_am, am). Siden sidst eliminerer alle nuller, behøver vores joins ikke at være outer joins.

> select a.eid, b.m, b.y, c.m, c.y, a.v 
   from cm_result_data a 
    join cm_abs_month b 
      on ( a.eid = b.eid and a.am = b.am)  
    join cm_abs_month c 
      on ( a.eid = c.eid and 
      coalesce( a.last_am - 1, 
              (select max(am) from cm_abs_month d where d.eid = a.eid )
      ) = c.am)
    order by 1, 3, 2, 5, 4;
+-----+------+------+------+------+------+
| eid | m    | y    | m    | y    | v    |
+-----+------+------+------+------+------+
| 100 |    1 | 2008 |    2 | 2008 |   80 |
| 100 |    3 | 2008 |    3 | 2008 |   90 |
| 100 |    4 | 2008 |    4 | 2008 |   80 |
| 200 |    1 | 2008 |    2 | 2008 |   80 |
| 200 |    3 | 2008 |    3 | 2008 |   90 |
| 200 |    4 | 2008 |    4 | 2008 |   80 |
+-----+------+------+------+------+------+
 

Ved at slutte sig tilbage får vi det output, OP'en ønsker.

Ikke at vi skal være med igen. Som det sker, er vores absolute_month-funktion tovejs, så vi kan bare genberegne året og forskyde måneden fra det.

Lad os først tage os af at tilføje "end cap"-måneden:

> create or replace view cm_capped_result as 
select eid, am, 
  coalesce( 
   last_am - 1, 
   (select max(b.am) from cm_abs_month b where b.eid = a.eid)
  ) as last_am, v  
 from cm_result_data a;
 

Og nu får vi dataene, formateret efter OP:

select eid, 
 ( (am - 1) % 12 ) + 1 as sm, 
 floor( ( am - 1 ) / 12 ) as sy, 
 ( (last_am - 1) % 12 ) + 1 as em, 
 floor( ( last_am - 1 ) / 12 ) as ey, v    
from cm_capped_result 
order by 1, 3, 2, 5, 4;

+-----+------+------+------+------+------+
| eid | sm   | sy   | em   | ey   | v    |
+-----+------+------+------+------+------+
| 100 |    1 | 2008 |    2 | 2008 |   80 |
| 100 |    3 | 2008 |    3 | 2008 |   90 |
| 100 |    4 | 2008 |    4 | 2008 |   80 |
| 200 |    1 | 2008 |    2 | 2008 |   80 |
| 200 |    3 | 2008 |    3 | 2008 |   90 |
| 200 |    4 | 2008 |    4 | 2008 |   80 |
+-----+------+------+------+------+------+
 

Og der er de data, OP ønsker. Alt sammen i SQL, der burde køre på ethvert RDBMS og er opdelt i enkle, letforståelige og nemme at teste visninger.

Er det bedre at tilslutte sig igen eller at genberegne? Det overlader jeg (det er et trick-spørgsmål) til læseren.

(Hvis dit RDBMS ikke tillader gruppebys i visninger, skal du først deltage og derefter gruppere eller gruppere og derefter trække måneden og året ind med korrelerede underforespørgsler. Dette efterlades som en øvelse for læseren.)

spørger Jonathan Leffler i kommentarerne,

Hvad sker der med din forespørgsel, hvis der er huller i dataene (f.eks. er der en post for 2007-12 med værdi 80 og en anden for 2007-10, men ikke en for 2007-11? Spørgsmålet er ikke klart, hvad der skal ske der.

Nå, du har helt ret, OP specificerer det ikke. Måske er der en (unævnt) forudsætning, at der ikke er huller. I mangel af et krav, bør vi ikke forsøge at kode omkring noget, der måske ikke er der. Men faktum er, at huller får strategien til at "joining back" mislykkes; "genberegn"-strategien fejler ikke under disse forhold. Jeg ville sige mere, men det ville afsløre tricket i trickspørgsmålet, jeg hentydede til ovenfor.



  1. Sådan opgraderes postgresql-database fra 10 til 12 uden at miste data til openproject

  2. SCD Type 4

  3. Tjek om et objekt er en lagret procedure ved at bruge OBJECTPROPERTY() i SQL Server

  4. Sådan sikkerhedskopieres / eksporteres MySQL-database ved hjælp af PHP