Programming bit index support

It seems to me that sometimes using hash tables is not required and thus no need to make another expensive JOINs. Especially, when the number of attributes can be foreseen, you can simply set some bit to on.
Imagine, we have a product which has two attributes: new, promotion. Usually, we create 2 tables, `Product` and `Product_To_Attribute` (as hash table), then while searching products by attributes we prepare query as below:

SELECT *
FROM `product` `Prod` INNER JOIN Product_To_Attribute `Prta`
WHERE `Prta`.`Attribute_Id` in (1,2);

Everything is fine untill your data are small. Therefore, I prefer to remove table `Product_To_Attribute` replacing it by “bit index” in `product` table. I name it “bit representation”.
How it works? Suppose, that we have only two attributes new (represented by first bit), promotion (represented by second bit). As I said, if product has an attribute its position is ON, therefore the following states are possible:
00 (no attributes);
01 (is new);
10 (is promoted);
11 (is new & promoted);

How to search through attributes

As you probably know, a/m bit states are numbers, 00 is 0, 01 is 1, 10 is 2, etc. So if we would like to remove JOIN from our first query simply you must store attributes as integer. Our product is new and promoted, so in bits it is 11, as integer it is 3. Now, we add a column `Attribute_in_bits` and update this column at our product to 3 (Update `product` SET `Attribute_bit_idx`= 3 WHERE Id = ‘OurProduct’; And now simple query can be used:

-- This collects all products with attributes new or promoted
SELECT * FROM `product` WHERE `Attribute_bit_idx` in (1, 2 ,3);
-- This collects all promoted products.
SELECT * FROM `product` WHERE `Attribute_bit_idx` in (2 ,3);

Obviuosly, you can use native MySQL functions to find adequate rows, however “EXPLAIN” (in case of MySQL) will explain you the costs.

-- This collects all products with attributes new or promoted
SELECT * FROM `product` WHERE (`Attribute_bit_idx` & 1|2) ;
-- This collects all promoted products.
SELECT * FROM `product` WHERE (`Attribute_bit_idx` & 2) ;

How to prepare data

All a/m operations are rather simple, more difficult is to prepare data. Here is example of my own way (in PHP).

Update index

‘OurProduct’ obtains new attribute – ‘new’; integer representation of this attribute is 1 (first bit ON).

UPDATE `product` SET `Attribute_bit_idx` = `Attribute_bit_idx` + 1
 WHERE Id = 'OurProduct'
   AND /* check whether attribute exists */  (`Attribute_bit_idx` & 1)=0; 

‘OurProduct’ obtains new attribute – ‘bestseller’; integer representation of this attribute is 8 (fourth bit ON).

UPDATE `product` SET `Attribute_bit_idx` = `Attribute_bit_idx` + 8
  WHERE Id = 'OurProduct'
   AND /* check whether attribute exists */  (`Attribute_bit_idx` & 8)=0; 
Using index

For this purpose I wrote class Shop_Bitwise which is kind of helper for bit operations. Additionaly, at the bottom you will find class Shop_Product which contains attributes as consts. I do not translate integers into bits cause I assume that attribute number conforms to bit position. (int 1 is 1, int 2 is 2, int 3 is 4, int 4 is 8, etc.).
So, I want to update product ‘OurProduct’ to set new attribute. Identity number of new attribute is 1. I must know the bit which represents this number:


How to find rows by chosen attributes?

Numbers();
$Query = "SELECT Id, Attribute_bit_idx FROM `product` 
           WHERE `Attribute_bit_idx` IN (".implode(",", $SearchedIntegers ).");";

How to translate attributes to integers?

Having instance of class Shop_Bitwise we can use method ExtractAttributesFromInt() which returns array containing identifier of attributes.

ExtractAttributesFromInt($row['Attribute_bit_idx']);

PHP Classes:

_setAttributes($attr);
		$this->_setSearched($searched);
	}
	
	public function Numbers()
	{
		if ($this->_numbers == null):
			$this->_generate();
		endif;
		return $this->_numbers;
	}

	public function ExtractAttributesFromInt($i)
	{
		$attributes = array();
		foreach ($this->_attr as $attr):
			if ($i & $attr):
				$attributes[] = $attr;
			endif;
		endforeach;
		return $attributes;
	}
	
	public static function GetAttributeBitFromInt($i)
	{
		return 0 | $i;
	}
	
	public static function GetAttributeIntFromBit($i)
	{ 
		return pow(2,$x-1);
	}	

	private function _setSearched(array $a)
	{
		$this->_searched = $a;
	}
	
	private function _searched()
	{
		return $this->_searched;
	}

	private function _setAttributes(array $a)
	{
		$this->_attr = $a;
	}
	
	private function _attributes()
	{
		return $this->_attr;
	}
		
	private function _generate()
	{
		$searched = $this->_searched();
		$dostepneAtrybuty = $this->_attributes();
		sort($dostepneAtrybuty);
		sort($searched);
		$highestSearch = end($searched);
		
		$iterator  		= 0;
		$subIterator 	= 0 ;
		$result 		= array();
		
		foreach ($dostepneAtrybuty as $attr):
			if ($attr > $highestSearch):
				break;
			endif;
			$attrPassed = array();
			$attrPassed[] = $attr;
		
			$subIterator = 0 ;
			foreach ($dostepneAtrybuty as $attr2):
				// zabezpieczenie przed powtarzalnoscia
				if ( $attr2 == $attr):
					continue;
				endif;				
				// zabezpieczenie przed powtarzalnoscia 2
				if ($subIterator < $iterator):
					$subIterator++;
					continue;
				else :
					$subIterator++;
				endif;
				
				$attrPassed [] 	= $attr2;
			endforeach;
			
			$variations = $attrPassed;
			$result [] = array($variations[0]);
			$variations[0] = false;
			$variations = array_filter($variations);
			sort($variations);
		
			$digitQty = count($variations); // number of digits
			for ($i=0; $i < $digitQty ; $i++ ):
				$elements = $digitQty-$i;
				$hop = 0;
				$first = current ($variations);
				while(true):
					$newAr = array();
					$cnt = $hop;
					for ($i2=0; $i2 < $elements;$i2++):
						$cnt = $hop + $i2;
						if ($cnt >=	 $digitQty){
							$cnt = abs ( ($digitQty - $cnt) );
						}
						$newAr [] = $variations[$cnt];
					endfor;
					if (  current($newAr) == $first  &&  $hop > 0  ):
						break;
					endif;
					$result [] = array_merge_recursive( array($attrPassed[0]), $newAr);
					$hop++;
				endwhile;	
			endfor;	
			$iterator++;
		endforeach;
		
		$unique = array();
		foreach ( $result as $n ):
			foreach ($searched as $searchedValue):
				if (in_array($searchedValue, $n)):
					$unique [] =  array_sum( $n );
					continue;
				endif;
			endforeach;
		endforeach;		
		sort($unique);
		$this->_numbers = array_unique($unique);
	}
}

             
  1. No comments yet.

  1. No trackbacks yet.