Статьи

Нулевые и трехмерные помощники упорядочения в Java

Когда мы имеем дело с наборами данных, извлеченными из базы данных, если мы хотим, чтобы они упорядочивались, мы обычно хотим упорядочивать их прямо в SQL, а не упорядочивать их после извлечения. Наша база данных, как правило, будет более эффективной из-за доступной вычислительной мощности, потенциального использования доступных индексов и общей эффективности алгоритма в современных СУБД. У вас также есть большая гибкость, чтобы иметь сложные критерии заказа при использовании этих предложений ORDER BY. Например, предположим, что у вас есть запрос, который извлекает информацию о сотруднике, включая зарплату и относительные шаги (должность), из верхней позиции. Вы могли бы легко иметь порядок первого уровня, где зарплаты группируются по категориям (<= 50 000 долларов, от 50 001 до 100 000 долларов,> 100 001 долларов), а следующий уровень — по относительной позиции. Если бы вы могли предположить, что зарплаты были подходящими для всех сотрудников,это может дать вам хорошее представление о том, где в компании слишком много или слишком мало менеджмента — очень грубый подход, который я бы не рекомендовал, это всего лишь пример использования.

Вы получаете свободное поведение из своей базы данных, когда дело доходит до заказа, понимаете ли вы это или нет. При работе с NULL база данных должна принять решение, как их упорядочить. Каждая база данных, с которой я когда-либо работал, и, вероятно, все реляционные базы данных имеют поведение по умолчанию. В порядке возрастания MySQL и SQL Server ставят NULL перед реальными значениями, они «меньше», чем ненулевое значение. Oracle и Postgres ставят NULL после реальных значений, они «больше, чем» ненулевые значения. Oracle и Postgres приятно дают вам инструкции NULLS FIRST и NULLS LAST, чтобы вы могли переопределить значения по умолчанию. Даже в MySQL и SQLServer вы можете найти способы переопределить значения по умолчанию, используя функции в вашем заказе по выражению. В MySQL я использую IFNULL . В SQL Server вы можете использовать ISNULL, Они оба дают вам возможность заменить нуль определенным значением. Просто замените соответствующее значение для типа, который вы сортируете.

Вся сортировка, поддерживаемая в этих типах запросов, является двумерной. Вы выбираете столбцы, и строки упорядочены по ним. Когда вам нужно отсортировать данные по дополнительным измерениям, вы, вероятно, попадаете в области, которые рассматриваются в других связанных технологиях, таких как хранилище данных и кубы OLAP. Если это подходит и доступно для вашего случая, во что бы то ни стало используйте эти мощные функции.

Однако во многих случаях у нас либо нет доступа к этим технологиям, либо нам необходимо, чтобы наши операции были связаны с текущими данными. Например, предположим, что вы работаете над инвестиционной системой, счета инвесторов, сделки, позиции и т. Д. Все поддерживаются. Вам нужно написать запрос, чтобы помочь извлечь торговую активность за данный период времени. Наши данные возвращаются в виде двумерных наборов данных, хотя у нас больше измерений. Наш запрос вернет данные об аккаунте (ах) и сделке (ах) по аккаунту. Нам нужны наши результаты для тех счетов, чьи совершенные сделки имеют наибольшее значение. Но нам нужно вести сделки со своими счетами. Простое упорядочение нашего запроса по стоимости совершенной сделки, скорее всего, разбило бы строки одного и того же счета.

У нас есть выбор, мы можем либо заказать в базе данных и контролировать нашу реконструкцию возвращаемых данных, чтобы поддерживать состояние и порядок восстановленных объектов, либо мы можем отсортировать по факту. В большинстве случаев мы, вероятно, не хотим писать новый код каждый раз, когда сталкиваемся с этой проблемой, которая конкретно касается восстановления данных из этого запроса в представление нашей объектной модели. Надеемся, что наш ORM поможет или у нас есть какой-то ранее существующий, функциональный и хорошо протестированный код, который мы можем использовать для этого.

Другим вариантом является сортировка в нашем коде. На самом деле мы получаем большую гибкость, делая это. Возможно, у нас есть некоторые финансовые функции, написанные в нашем приложении, которые мы теперь можем использовать. Нам также не нужно выполнять всю сортировку самостоятельно, поскольку мы можем использовать функции JDK для сортировки по компараторам и коллекциям.

Во-первых, давайте разберемся с нашей проблемой нулевого порядка. Допустим, у нашего Торгового объекта есть несколько открытых публичных констант. Это позволяет нам использовать коллекцию сделок вместе с java.util.Collections.sort (List <Trade>, Comparator <Trade>). Trade.TRADE_VALUE_NULL_FIRST — это то, что мы хотим использовать. Этот Comparator — не что иное, как проход к глобальному помощнику Null Comparator.

private static final Comparator<Trade> TRADE_VALUE_NULL_FIRST = new Comparator<Trade>(){
  public int compare(Trade o1, Trade o2) {
    return ComparatorUtil.DECIMAL_NULL_FIRST_COMPARATOR.compare(
        o1.getTradeValue(),
        o2.getTradeValue());
}};

... ComparatorUtil ...

public static NullFirstComparator<BigDecimal> DECIMAL_NULL_FIRST_COMPARATOR =
  new NullFirstComparator<BigDecimal>();
public static NullLastComparator<BigDecimal> DECIMAL_NULL_LAST_COMPARATOR =
  new NullLastComparator<BigDecimal>();
...snip...
public static NullLastComparator<String> STRING_NULL_LAST_COMPARATOR =
  new NullLastComparator<String>();

public static class NullFirstComparator<T extends Comparable<T>> implements Comparator<T> {
  public int compare(T o1, T o2) {
    if (o1 == null && o2 == null) {
	return 0;
    } else if (o1 == null) {
	return -1;
    } else if (o2 == null) {
	return 1;
    } else {
	return o1.compareTo(o2);
    }
  }
}
public static class NullLastComparator<T extends Comparable<T>> implements Comparator<T> {
  public int compare(T o1, T o2) {
    if (o1 == null && o2 == null) {
	return 0;
    } else if (o1 == null) {
	return 1;
    } else if (o2 == null) {
	return -1;
    } else {
	return o1.compareTo(o2);
    }
  }
}

Теперь у нас есть простое, повторно используемое решение, которое мы можем использовать с любым классом и любым значением NULL в сортировке JDK. Теперь мы выставляем любые константы заказа, подходящие для бизнес-использования в нашем классе Теперь давайте разберемся с более сложной проблемой иерархического упорядочения значений. Мы не хотим писать новый код каждый раз, когда нам нужно что-то подобное. Так что давайте просто расширим нашу идею заказа помощников для высокотехнологичных организаций.

public interface Parent<C> {
  public List<C> getChildren();
}
public class ParentChildPropertiesComparator<P extends Parent<C>, C> implements Comparator<P> {
  private List<Comparator<C>> mChildComparators;
  public ParentChildPropertiesComparator(List<Comparator<C>> childComparators) {
    mChildComparators = Collections.unmodifiableList(childComparators);
  }
  public List<Comparator<C>> getChildComparators() {
    return mChildComparators;
  }
  public int compare(P o1, P o2) {
    int compareTo = 0;
    for (int i=0; i < mChildComparators.size() && compareTo == 0; i++) {
	Comparator<C> cc = mChildComparators.get(i);
	List<C> children1 = o1.getChildren();
	List<C> children2 = o2.getChildren();
	Collections.sort(children1, cc);
	Collections.sort(children2, cc);
	int max = Math.min(children1.size(), children2.size());
	for (int j=0; j < max && compareTo == 0; j++) {
	  compareTo = cc.compare(children1.get(j), children2.get(j));
	}
    }
    return compareTo;
  }
}

Это немного сложнее, но все же достаточно просто, чтобы легко понять и использовать повторно. У нас есть идея родителя. Это не ОО отношения. Это отношение композиции или агрегации. Родитель может существовать в любом месте иерархии, то есть родитель также может быть дочерним. Но в нашем примере у нас есть простые отношения родитель / ребенок — счет / торговля. Этот новый класс ParentChildPropertiesComparator поддерживает идею получения списка упорядоченных компараторов для дочерних объектов, но сортировки по родителям. В нашем сценарии мы сортируем только по одному дочернему значению, но это может быть несколько, точно так же, как можно отсортировать более двух уровней данных.

В нашем случае мы предполагаем, что Account уже реализует интерфейс Parent для учетных записей. Если нет, вы всегда можете использовать шаблон проектирования адаптера . Наша сортировка счетов / сделок теперь будет выглядеть следующим образом.

List<Account> accounts = fetchPreviousMonthsTradeActivityByAccount();
List<Comparator<Trade>> comparators = Arrays.asList(Trade.TRADE_VALUE_NULL_FIRST);
ParentChildPropertiesComparator<Account, Trade> childComparator =
  new ParentChildPropertiesComparator<Account, Trade>(comparators);
Collections.sort(accounts, childComparator);

В самом деле! Вот и все. Наша досадная проблема сортировки счетов по тем, у кого самые высокие торговые значения, где некоторые из этих торговых значений могут быть нулевыми, решается всего несколькими строками кода. Наши аккаунты теперь отсортированы по желанию. Я придумал этот подход, и он успешно используется как часть построителя запросов для крупномасштабной системы финансовой выверки. Введение новых сортируемых типов и значений требует лишь минимальных дополнений. Возьмите этот подход в движение и посмотрите, насколько он невероятно силен для работы с требованиями сортировки в сложных иерархиях данных. И напишите нам, если вам нужна помощь в реализации или есть какие-либо комментарии.