Zielony Smok - logo witryny

Apache Derby: funkcja użytkownika MEDIAN

Zadanie programistyczne

Stworzyć funkcję kolumnową umożliwiającą obliczanie mediany dla kolumny wyników

Mediana to środkowa wartość z kolumny wyników. W przypadku gdy liczba rekordów jest nieparzysta jest to wartość środkowa. W przypadku, gdy liczba rekordów jest nieparzysta jest to suma wyrazów środkowych podzielona przez 2.

Przykład w dokumentacji Derby oblicza błędną wartość mediany.

Rozwiązanie

Napisać klasę Median(Median.java). Pamiętaj o wyeksportowaniu pakietu, w którym znajduje się ta klasa.

Opis klasy

Klasa implementuje interfejs Aggregator, a więc i cztery metody tego interfejsu.

Typ podany jako środkowy (Double) w linii 9 musi odpowiadać typowi zwracanemu przez metodę terminate() w linii 31

Typ podany jako trzeci (Median<p>) w linii 9 musi odpowiadać typowi podanemu jako typ argumentu 'otherData’ w metodzie merge. Nie musi to być typ tej klasy. Może być inny typ agregacyjny, ale z parametrem T.

Zadeklarować funkcję w bazie Derby (Plik r091.sql)

Napisać klasę testującą (R091_MEDIAN.java)

Klasa Median.java
package aderby.functions.custom;

import java.util.*;

import org.apache.derby.agg.*;

public class Median<T extends Comparable<T>> implements Aggregator<T, Double, Median<T>> {
    private static final long serialVersionUID = 6089118633594167438L;
    private ArrayList<T> data;

    public Median() {
    }

    @Override
    public void init() {
        data = new ArrayList<>();
    }

    @Override
    public void accumulate(T value) {
        data.add(value);
    }

    @Override
    public void merge(Median<T> otherData) {
        data.addAll(otherData.data);
    }

    @Override
    public Double terminate() {
        Collections.sort(data);
        int n = data.size();
        if (n == 0) {
            return null;
        }
        if (n % 2 != 0) {
            return (Double) data.get((n + 1) / 2);
        } else {
            return (Double) data.get(n / 2) / (Double) data.get((n / 2) + 1);
        }
    }
}
Klasa R091_MEDIAN.java
package aderby.functions.custom;

import aderby.DerbyUtil;

import java.io.File;
import java.sql.*;

public class R091_MEDIAN {
    private static final String baza = "C:/Przyklady/r091_median";
    private static final String skrypt = "aderby/src/resources/sqls/r091.sql";
    private static final String sqlQuery = "SELECT czujnik, MEDIAN(val) FROM temperatura GROUP BY czujnik";
    public static void main(String[] args) {
        DerbyUtil.startDerbyEngine("EmbeddedDriver");
        Connection con = DerbyUtil.connectEmbeddedDB(baza, ";create=true");
        DerbyUtil.jdbcRunScript(skrypt, con);
        Statement stat = null;
        ResultSet rs = null;
        try {
            stat = con.createStatement();
            rs = stat.executeQuery(sqlQuery);
            while (rs.next()) {
                System.out.println(rs.getInt(1) + " " + rs.getDouble(2));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        DerbyUtil.close(rs);
        DerbyUtil.close(stat);
        DerbyUtil.close(con);
        DerbyUtil.shutdownDerbyEngine();
        DerbyUtil.dropDatabase(new File(baza));
    }
}
Plik r091.sql

CREATE TABLE temperatura(
            id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS 
                    IDENTITY(START WITH 1, INCREMENT BY 1),
            czujnik INTEGER,
            val DOUBLE
);

CREATE INDEX i_czujnik ON temperatura (czujnik ASC);
CREATE INDEX i_val ON temperatura (val ASC);

CREATE DERBY AGGREGATE median   
	FOR DOUBLE  
	RETURNS DOUBLE 
	EXTERNAL NAME 'aderby.functions.custom.Median';

INSERT INTO temperatura (czujnik, val) VALUES (2, 37.8);
INSERT INTO temperatura (czujnik, val) VALUES (1, 39.7);
INSERT INTO temperatura (czujnik, val) VALUES (3, 36.6);
INSERT INTO temperatura (czujnik, val) VALUES (3, 33.2);
INSERT INTO temperatura (czujnik, val) VALUES (2, 39.2);
INSERT INTO temperatura (czujnik, val) VALUES (2, 40.6);
INSERT INTO temperatura (czujnik, val) VALUES (2, 41.2);
INSERT INTO temperatura (czujnik, val) VALUES (1, 40.1);
INSERT INTO temperatura (czujnik, val) VALUES (1, 39.8);
INSERT INTO temperatura (czujnik, val) VALUES (2, 38.9);
INSERT INTO temperatura (czujnik, val) VALUES (1, 38.3);
INSERT INTO temperatura (czujnik, val) VALUES (2, 37.9);
INSERT INTO temperatura (czujnik, val) VALUES (2, 37.4);
INSERT INTO temperatura (czujnik, val) VALUES (3, 37.1);
INSERT INTO temperatura (czujnik, val) VALUES (3, 36.8);
INSERT INTO temperatura (czujnik, val) VALUES (3, 36.7);
INSERT INTO temperatura (czujnik, val) VALUES (1, 36.6);

Przetestować przykład

1 39.8
2 39.2
3 36.8

Pliki do ściągnięcia

Aktualny (tworzony narastająco) plik module-info.java

Aktualny (tworzony narastająco) plik DerbyUtil.java

Pliki tworzone narastająco zastępują poprzednie pliki o tej samej nazwie i działają dla wszystkich wcześniej opublikowanych przykładów we wszystkich wpisach w projekcie. W przypadku pliku module-info.java może być potrzebne skreślenie niepotrzebnych wpisów.