понедельник, 22 декабря 2008 г.

Сортировка IP адресов на MySQL

Во многих web-приложениях приходится сталкиваться с сортировками данных. Это является несложной задачей, если в качестве данных для сортировки выступают простые фамилии, наименования, даты, суммы, ну и другие строково-числовые данные. Но когда требуется упорядочить более сложные структурные данные, с учётом их логического разбиения, то иногда приходится поломать голову в поисках эффективного и не ресурсоёмкого алгоритма.

Я столкнулся с такой задачей. Нужно было отсортировать ряд IP адресов в порядке убывания или возрастания. Было два инструментария для сортировки, либо сделать это средствами PHP, либо переложить это всё на MySQL сервер. Второй вариант более предпочтителен, так он быстрее и эффективее, потому что выборка всё-равно происходит из базы данных.

Если решать сортировку в лоб, и отсортировать просто в алфавитно-числовом порядке, то IP адрес 135.x.x.x будет стоять перед 88.x.x.x, хотя это логически неправильно. Здесь надо учитывать не строку целиком, а разбивать на четыре отдельных байта – составляющие IP адреса.

Первое что пришло на ум – поискать специализированные функции для работы с IP адресами. Нашёл в manual по MySQL функцию для перевода строки с IP address-ом в числовое значение, то есть в integer, – это функция inet_aton().

SELECT ip, inet_aton(ip) as `IPvINT`
FROM ip_address
ORDER BY `IPvINT` ASC;

Этот метод работает безотказно, но если у вас данные в таблице были проверены и не содержат никаких больше символов, не относящихся к IP адресу.

Передо мной стояла задача немного сложнее, у меня мало того значением выступает IP адрес, он может содержать и маску вида 192.168.1.*, и даже диапазон адресов вида 192.168.1.0-192.168.230.14. Тогда выше приведенный метод не будет работать, потому что функция inet_aton() всегда будет возвращать неправильное значение для масок и диапазонов, и эти адреса будут кучей сбиваться в конце или начале отсортированного списка.

Приведённый ниже SQL запрос сможет отсортировать список значений IP адресов содержащих маски адресов вида 192.168.1.*, или диапазон адресов вида 192.168.1.0-192.168.230.14:

SELECT ip, (SUBSTRING_INDEX(ip, '.', 1)<<24) +
  (SUBSTRING_INDEX(SUBSTRING_INDEX(ip, '.', 2), '.', -1)<<16) +
  (SUBSTRING_INDEX(SUBSTRING_INDEX(ip, '.', -2), '.', 1)<<8) +
  SUBSTRING_INDEX(ip, '.', -1) as `IPvINT`
FROM ip_address
ORDER BY `IPvINT` ASC;

Я думаю, что эта реализация выглядит довольно элегантно и при этом будет быстро вычисляться, если кто-то знает более мудрое и оптимальное решение, с радостью выслушаю в комментариях.

2 комментария:

Рекоммендую

Попробуйте надёжный хостинг от Scala Hosting