sql >> Databáze >  >> RDS >> Mysql

Vyberte CIDR, který je v rozsahu IP

Ukládání IP adres v tečkovaném čtvercovém zápisu do VARCHAR není nejoptimálnějším způsobem jejich ukládání, protože tečkovaná quad je pro člověka přátelská reprezentace 32bitového celého čísla bez znaménka, které se nehodí k indexování databáze. Někdy je to ale zásadně pohodlnější a v malém měřítku nebývá problém, že dotazy vyžadují skenování tabulky.

Uložené funkce MySQL jsou dobrým způsobem, jak zapouzdřit relativně složitou logiku za jednoduchou funkcí, na kterou lze odkazovat v dotazu, což potenciálně vede ke snazším dotazům a snižuje chyby při kopírování a vkládání.

Zde je tedy uložená funkce, kterou jsem napsal, s názvem find_ip4_in_cidr4() . Funguje poněkud podobně jako vestavěná funkce FIND_IN_SET() -- dáte mu hodnotu a dáte mu "set" (CIDR spec) a vrátí hodnotu, která označuje, zda je hodnota v sadě.

Nejprve ilustrace funkce v akci:

Pokud je adresa uvnitř bloku, vraťte délku prefixu. Proč vracet délku předpony? Nenulová celá čísla jsou "pravda", takže bychom mohli vrátit 1 , ale pokud chcete seřadit odpovídající výsledky tak, abyste našli nejkratší nebo nejdelší z více odpovídajících předpon, můžete ORDER BY návratovou hodnotu funkce.

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)

Nejste v bloku? To vrátí 0 (nepravda).

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)

Pro adresu s nulami existuje speciální případ, vracíme -1 (stále "pravda", ale zachovává pořadí řazení):

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)

Nesmyslné argumenty vrátí hodnotu 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)

Nyní kód:

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 ;

Problém, který není specifický pro uložené funkce, ale vztahuje se spíše na většinu funkcí na většině platforem RDBMS, je, že když je sloupec použit jako argument funkce v WHERE , server se nemůže "dívat zpět" prostřednictvím funkce, aby použil index k optimalizaci dotazu.



  1. Volání uložené procedury MySQL pomocí VB6 s parametrem OUT

  2. Rozsah dočasných tabulek v SQL Server

  3. Hodnoty zaškrtávacího políčka do dotazu mysql

  4. MySQL Errno 150