Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Wybierz CIDR, który jest w zasięgu IP

Przechowywanie adresów IP w kropkowanej notacji quad w VARCHAR nie jest najbardziej optymalnym sposobem ich przechowywania, ponieważ kropka-kwadrat to przyjazna dla człowieka reprezentacja 32-bitowej liczby całkowitej bez znaku, która nie nadaje się do indeksowania w bazie danych. Ale czasami jest to zasadniczo wygodniejsze, a na małą skalę fakt, że zapytania wymagają skanowania tabeli, zwykle nie stanowi problemu.

Funkcje przechowywane w MySQL to dobry sposób na hermetyzację stosunkowo złożonej logiki za prostą funkcją, do której można się odwoływać w zapytaniu, co potencjalnie prowadzi do łatwiejszych do zrozumienia zapytań i zmniejsza liczbę błędów kopiowania/wklejania.

Oto przechowywana funkcja, którą napisałem o nazwie find_ip4_in_cidr4() . Działa nieco podobnie do wbudowanej funkcji FIND_IN_SET() -- podajesz mu wartość i nadajesz mu „zestaw” (specyfikacja CIDR), a zwraca wartość wskazującą, czy wartość jest w zestawie.

Najpierw ilustracja funkcji w działaniu:

Jeśli adres znajduje się wewnątrz bloku, zwróć długość prefiksu. Po co zwracać długość prefiksu? Liczby niezerowe są „prawdą”, więc możemy po prostu zwrócić 1 , ale jeśli chcesz posortować pasujące wyniki, aby znaleźć najkrótszy lub najdłuższy z wielu pasujących przedrostków, możesz ORDER BY wartość zwracana przez funkcję.

mysql> SELECT find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                  24 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16') |
+-----------------------------------------------------+
|                                                  16 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Nie w bloku? To zwraca 0 (fałsz).

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Jest specjalny przypadek dla adresu zawierającego same zera, zwracamy -1 (nadal "prawda", ale zachowuje porządek sortowania):

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0');
+------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0') |
+------------------------------------------------+
|                                             -1 |
+------------------------------------------------+
1 row in set (0.00 sec)

Bezsensowne argumenty zwracają wartość null:

mysql> SELECT find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                NULL |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Teraz kod:

DELIMITER $$

DROP FUNCTION IF EXISTS `find_ip4_in_cidr4` $$
CREATE DEFINER=`mezzell`@`%` FUNCTION `find_ip4_in_cidr4`(
  _address VARCHAR(15), 
  _block VARCHAR(18)
) RETURNS TINYINT
DETERMINISTIC /* for a given input, this function always returns the same output */
CONTAINS SQL /* the function does not read from or write to tables */
BEGIN

-- given an IPv4 address and a cidr spec,
-- return -1 for a valid address inside 0.0.0.0/0
-- return prefix length if the address is within the block,
-- return 0 if the address is outside the block,
-- otherwise return null

DECLARE _ip_aton INT UNSIGNED DEFAULT INET_ATON(_address);
DECLARE _cidr_aton INT UNSIGNED DEFAULT INET_ATON(SUBSTRING_INDEX(_block,'/',1));
DECLARE _prefix TINYINT UNSIGNED DEFAULT SUBSTRING_INDEX(_block,'/',-1);
DECLARE _bitmask INT UNSIGNED DEFAULT (0xFFFFFFFF << (32 - _prefix)) & 0xFFFFFFFF;

RETURN CASE /* the first match, not "best" match is used in a CASE expression */
  WHEN _ip_aton IS NULL OR _cidr_aton IS NULL OR /* sanity checks */
       _prefix  IS NULL OR _bitmask IS NULL OR
       _prefix NOT BETWEEN 0 AND 32 OR
       (_prefix = 0 AND _cidr_aton != 0) THEN NULL
  WHEN _cidr_aton = 0 AND _bitmask = 0 THEN -1
  WHEN _ip_aton & _bitmask = _cidr_aton & _bitmask THEN _prefix /* here's the only actual test needed */
  ELSE 0 END;

END $$
DELIMITER ;

Problem, który nie jest specyficzny dla funkcji przechowywanych, ale raczej dotyczy większości funkcji na większości platform RDBMS, polega na tym, że gdy kolumna jest używana jako argument funkcji w WHERE , serwer nie może "spojrzeć wstecz" przez funkcję, aby użyć indeksu do optymalizacji zapytania.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Demon MySQL odmawia startu z Can't start server:Bind na porcie TCP/IP:Adres jest już używany (nie jest).

  2. Python MySQLdb iteruje po tabeli

  3. aktualizacja listy użytkowników w grupie:UPDATE lub DELETE + INSERT

  4. Jak uniknąć błędów funkcje agregujące nie są dozwolone w WHERE

  5. Rekursywny wybór mysql?