Oracle
 sql >> Teknologi Basis Data >  >> RDS >> Oracle

SQL Query untuk Menciutkan Nilai Duplikat Berdasarkan Rentang Tanggal

Saya akan mengembangkan solusi saya secara bertahap, menguraikan setiap transformasi menjadi tampilan. Ini membantu menjelaskan apa yang sedang dilakukan, dan membantu dalam debugging dan pengujian. Ini pada dasarnya menerapkan prinsip dekomposisi fungsional ke kueri basis data.

Saya juga akan melakukannya tanpa menggunakan ekstensi Oracle, dengan SQL yang seharusnya berjalan pada RBDMS modern apa pun. Jadi tidak ada keep, over, partisi, hanya subquery dan group bys. (Beri tahu saya di komentar jika tidak berfungsi di RDBMS Anda.)

Pertama, tabel, yang karena saya tidak kreatif, saya sebut month_value. Karena id sebenarnya bukan id unik, saya akan menyebutnya "idul fitri". Kolom lainnya adalah "bln", "tahun", dan "v"alue:

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

Setelah memasukkan data, untuk dua lebaran, saya memiliki:

> 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)

Selanjutnya, kita memiliki satu entitas, bulan, yang direpresentasikan sebagai dua variabel. Itu harus benar-benar satu kolom (baik tanggal atau waktu, atau bahkan mungkin kunci asing ke tabel tanggal), jadi kami akan menjadikannya satu kolom. Kami akan melakukannya sebagai transformasi linier, sehingga pengurutannya sama dengan (y, m), dan untuk setiap tupel (y,m) hanya ada satu nilai, dan semua nilai berurutan:

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

Itu memberi kita:

> 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)

Sekarang kita akan menggunakan self-join dalam subkueri berkorelasi untuk menemukan, untuk setiap baris, bulan penerus paling awal di mana nilainya berubah. Kami akan mendasarkan tampilan ini pada tampilan sebelumnya yang kami buat:

> 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 sekarang adalah "bulan absolut" dari bulan pertama (paling awal) (setelah bulan dari baris saat ini) di mana nilai, v, berubah. Ini nol di mana tidak ada bulan berikutnya, untuk Idul Fitri itu, di tabel.

Karena last_am adalah sama untuk semua bulan menjelang perubahan v (yang terjadi pada last_am), kita dapat mengelompokkan last_am dan v (dan eid, tentu saja), dan di grup mana pun, min(am) adalah yang mutlak bulan pertama bulan berturut-turut yang memiliki nilai tersebut:

> 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)

Sekarang ini adalah kumpulan hasil yang kita inginkan, itulah sebabnya tampilan ini disebut cm_result_data. Yang kurang hanyalah sesuatu untuk mengubah bulan absolut kembali menjadi tupel (y,m).

Untuk melakukan itu, kita hanya akan bergabung ke tabel month_value.

Hanya ada dua masalah:1) kita ingin bulan sebelum last_am dalam output kami, dan2) kami memiliki nol di mana tidak ada bulan depan dalam data kami; untuk memenuhi spesifikasi OP, itu harus dalam rentang satu bulan.

EDIT:Ini sebenarnya bisa menjadi rentang yang lebih lama dari satu bulan, tetapi dalam setiap kasus itu berarti kita perlu menemukan bulan terakhir untuk Idul Fitri, yaitu:

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

Karena tampilan menguraikan masalah, kita dapat menambahkan "batas akhir" ini bulan sebelumnya, dengan menambahkan tampilan lain, tetapi saya hanya akan memasukkan ini ke dalam penggabungan. Mana yang paling efisien tergantung pada bagaimana RDBMS Anda mengoptimalkan kueri.

Untuk mendapatkan bulan sebelumnya, kita akan bergabung (cm_result_data.last_am - 1 =cm_abs_month.am)

Di mana pun kami memiliki nol, OP ingin bulan "ke" sama dengan bulan "dari", jadi kami hanya akan menggunakan coalesce pada itu:coalesce( last_am, am). Karena last menghilangkan null, gabungan kita tidak perlu menjadi gabungan luar.

> 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 |
+-----+------+------+------+------+------+

Dengan bergabung kembali, kami mendapatkan hasil yang diinginkan OP.

Bukan berarti kita harus bergabung kembali. Seperti yang terjadi, fungsi absolute_month kami adalah dua arah, jadi kami hanya dapat menghitung ulang tahun dan mengimbangi bulan darinya.

Pertama, mari kita berhati-hati dalam menambahkan bulan "batas akhir":

> 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;

Dan sekarang kita mendapatkan data, diformat per 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 |
+-----+------+------+------+------+------+

Dan ada data yang diinginkan OP. Semua dalam SQL yang harus dijalankan pada RDBMS apa pun, dan didekomposisi menjadi tampilan yang sederhana, mudah dipahami, dan mudah diuji.

Apakah lebih baik bergabung kembali atau menghitung ulang? Saya akan menyerahkan itu (itu pertanyaan jebakan) kepada pembaca.

(Jika RDBMS Anda tidak mengizinkan tampilan bys grup, Anda harus bergabung terlebih dahulu lalu grup, atau grup, lalu menarik bulan dan tahun dengan subkueri terkait. Ini dibiarkan sebagai latihan untuk pembaca.)

Jonathan Leffler bertanya di komentar,

Apa yang terjadi dengan kueri Anda jika ada celah dalam data (misalnya ada entri untuk 2007-12 dengan nilai 80, dan satu lagi untuk 2007-10, tetapi tidak satu untuk 2007-11? Pertanyaannya tidak jelas apa yang harus terjadi di sana.

Yah, Anda benar sekali, OP tidak menentukan. Mungkin ada prasyarat (yang tidak disebutkan) bahwa tidak ada celah. Dengan tidak adanya persyaratan, kita tidak boleh mencoba membuat kode di sekitar sesuatu yang mungkin tidak ada. Namun faktanya, kesenjangan membuat strategi "bergabung kembali" gagal; strategi "menghitung ulang" tidak gagal dalam kondisi tersebut. Saya akan mengatakan lebih banyak, tetapi itu akan mengungkapkan trik dalam pertanyaan jebakan yang saya singgung di atas.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Pertanyaan Berguna tentang kedaluwarsa kata sandi oracle EBS/Kebijakan/Pengaturan

  2. Contoh Transaksi Otonom Oracle

  3. Bagaimana Anda menjalankan SQL dari dalam skrip bash?

  4. Memeriksa apakah suatu item tidak ada di tabel lain

  5. Apa yang dimaksud dengan <> di Oracle