How to store a value in an array only if unique - php

I'm writing a PHP report which is designed to be exported purely as a CSV file, using commma delimiters.
There are three columns relating to product_id, these three columns are as follows:
SKU Parent / Child Parent SKU
12345 parent 12345
12345_1 child 12345
12345_2 child 12345
12345_3 child 12345
12345_4 child 12345
18099 parent 18099
18099_1 child 18099
At the moment the code looks like this:
<?php
$con_size = array (35,355,36,37,375,38,385,39,395,40,405,41,415,42,425,43,435,44,445,45,455,46,465,47,475,48,485);
$arrlength=count($con_size);
for($x=0;$x<$arrlength;$x++) {
// check if size is available
if($line['quantity_c_size_'.$con_size[$x].'_chain'] > 0 ) {
?>
<? echo 'Shoes'; ?>,
<?=$line['product_id']?>,
,
,
So at the moment this is simply echoing out the product_ID into the SKU column. How would I create an array which tests whether the 'product_id' is unique and if not suffixes the ID with _1, _2, etc.
Thanks,

Test whether it's in the array, and add it if it's not.
if (!in_array($line['product_id'], $SKU)) {
$SKU[] = $line['product_id'];
echo $line['product_id'];
} else {
echo $line['product_id'] . "_1";
}
If you need the suffixes to increment each time, you can do:
$SKU_counters = array();
if (isset($SKU_counters[$line['product_id']])) {
echo $line['product_id'] . "_" . $SKU_counters[$line['product_id']]++;
} else {
echo $line['product_id'];
$SKU_counters[$line['product_id']] = 1;
}
At the end of the loop you do:
$SKU = array_keys($SKU_counters);

Possibly putting most of the work onto the database (not tested):-
<?php
$con_size = array (35,355,36,37,375,38,385,39,395,40,405,41,415,42,425,43,435,44,445,45,455,46,465,47,475,48,485);
$con_check = 'AND (quantity_c_size_'.implode('_chain > 0 OR quantity_c_size_',$con_size).'_chain > 0)';
$sql = "SELECT DISTINCT product_id, #cnt AS out_counter, #cnt:=IF(#prev_product_id=product_id, #cnt + 1, 0), #prev_product_id:=product_id
FROM product
CROSS JOIN (SELECT #prev_product_id:=0, #cnt:=0) sub1
WHERE on_amazon = 'on'
AND active = 'on'
$con_check
ORDER BY product_id";
$result = mysql_query($sql) or die ( mysql_error() );
while ($line = mysql_fetch_assoc($result) )
{
if ($line['out_counter'] == 1)
{
$SKU[] = $line['product_id'];
}
else
{
echo $line['product_id'].'_'.$line['out_counter']." \r\n";
}
}
?>
Takes your list of $con size and builds up a load of WHERE clauses to check each value is greater than 0. This should save you bringing back values which you don't care about.
The SELECT then brings back items in product_id order, but adds a sequence number (which resets to 0 when the product_id is different from the one on the previous row).
Then when looping around the results it saves the value of product_id if the sequence is 1 (ie the first occurrence of that product_id), else it just outputs the product id with the sequence number concatenated onto the end.

Related

mySql select multiple IDs with possible repeating IDs for shopping cart

I am trying to get the cost of all the items by the IDs and then add them up for a shopping cart. I have two issues:
1.) The users may have more than one item with the same ID. Using the method below mySql will not select the item twice. In the example below I have THREE items with the ID of '01' and TWO items with the ID of '07'. However my select statement will only select each ID once.
2.) The method I am using below adds a comma at the end of the last ID so my statement will not work. I need the comma's in between each item for the select but not on the last one. How can I resolve this issue?
if($_SESSION[InCart])
{
foreach($_SESSION[InCart] as $result)
{
$Items .= $result . ',';
}
}
include('../connect.php');
// EXAMPLE RESULT MAY LOOK LIKE THIS
// echo $Items (would print: 01,09,07,01,01,23,07,)
$sql = "SELECT Cost FROM cartItems WHERE itemNumber IN ('$Items')";
$result = $conn->query($sql);
$Cost = 0;
if ($result->num_rows > 0)
{
// output data of each row
while($row = $result->fetch_assoc()) {
$Cost = $Cost + $row["Cost"];
}
}
else {
echo "0 results";
}
$conn->close();
You should keep track of how many items of each type in your cart.
so in the cart you have now like
$items = [1,1,1,2,6];
you should have them like this
$items = [
'item-1' => 3,
'item-2' => 1,
'item-6' => 1
];
I never use integers for associate keys, if you sort ( or some other array functions ) an array like that it will mess it up by reordering or resetting the numeric keys, [1=>3,2=>1,6=>1] could become [0=>3,1=>1,2=>1] for example. You can make it more readable and protect them by prefixing them with a string like item-, So then you just do something like:
$itemIDs = [];
foreach( $items as $itemID => $Quantity ){
//replace item- with empty, you could also use substr, or even str_replace.
$itemIDs[] = (int)preg_replace('/^item-/', '', $itemID);
}
That will give you a list of ids like you have ( minus the duplicates )
$itemIDs = [1,2,6];
Do your search and in the while loop
while($row = $result->fetch_assoc()) {
//correlate it back to original $items using the key [item-{ID}] to get the quantity
$Cost += ( $row["Cost"] * $items['item-'.$row['ID']] ); //cost + ( item cost * quantity )
}
Multiply the items cost by the quantity in the cart, add that to your sum....
For this bit in your sql:
$sql = "SELECT Cost FROM cartItems WHERE itemNumber IN ('$Items')";
instead do this
$sql = 'SELECT Cost FROM cartItems WHERE itemNumber IN ('.implode(',', $itemIDs) .')';
You wont need to quote them because we cast them to int in the foreach loop. This is a regular expression that matches ^ starts with item-, so it matches the item- and removes it. ( see it in action here https://regex101.com/r/pLqDWw/1 )
(int)preg_replace('/^item-/', '', $itemID);
So that will come out like ( I think, just doing it in my head )
$sql = 'SELECT Cost FROM cartItems WHERE itemNumber IN (1,2,6)';
Which is fine because generally integers don't need to be quoted in MySql. This also has the benefit of sanitizing them to prevent any Sql Injection, because casting them wont allow any alphabet or special characters though.
I changed the " to single quotes ', it just makes it look better when used as a query without variable interpolation "$var" for example.
UPDATE: to add items
$items = isset($_SESSION['items']) ? $_SESSION['items'] : []; //check if items array is saved in the session.
$key = 'item-'.$_POST['itemID'];
if( !isset( $items[$key] ) ){
$items[$key] = $_POST['quantity'];
}else{
$items[$key] += $_POST['quantity'];
}
$_SESSION['items'] = $items;
Assuming $_POST['itemID'] is the items id and $_POST['quantity'] is the number to add. Same goes for subtracting except for that I would add
if( $items[$key] <= 0 ){
unset( $items[$key] ); //remove item from list.
}
You can use a join for this calculation -- because you explicit want duplicates. However, the query is a bit more challenging to generate:
SELECT SUM(cost) as cost
FROM cartItems c JOIN
(SELECT 1 as itemNumber UNION ALL
SELECT 1 UNION ALL
SELECT 2
) i
ON i.itemNumber = c.itemNumber;
Note: You should use aggregation in the query to add all the values together. That is the best way to use SQL for this purpose.

PHP how to echo the first value of each group in a mysql query list

To better explain my situation, please take a look at this mokeup query result:
Category Item
cat1 Item 1
cat1 Item 2
cat1 Item 3
cat23 Item x
cat23 Item y
cat23 Item z
X apples
X oranges
X bananas
and so on....
I am pulling the data off a mysql database and would like to display the results like this:
Category----Item
cat1 Item 1
Item 2
Item 3
cat23 Item x
Item y
Item z
X apples
oranges
bananas
and so on....
I tried different ways but I am coming up empty. This is the latest attempt:
PHP
//Headers
echo 'Category';
echo ' | ';
echo 'Item';
echo "\n";
$sql = "SELECT `table1`.*, `table2`.* FROM table1 INNER JOIN table2 ON `table1`.`Category` = `table2`.`Category` ORDER BY `table1`.`Category`, `table2`.`Item`";
$dbq = mysql_query( $sql );
$cat = '';
while( $data = mysql_fetch_assoc( $dbq ) ) {
if( !$cat == $data['category'] ) {
$cat = $data['category'];
echo $cat;
echo ' | ';
echo $data['item'];
echo "\n";
}
else {
echo ' ';
echo ' | ';
echo $data['item'];
echo "\n";
}
}
With this code, the current output is:
Category----Item
cat1 Item 1
Item 2
Item 3
Item x
Item y
Item z
apples
oranges
bananas
...rather than the desired output.
I am looking for the most efficient and simple way to echo each category 1 time only. Perhaps this code is not the best way to approach it, but I tried in different ways and my brain right now is shut S_S .
Took me quite some time...
Change
if( !$cat == $data['category'] ) {
To
if( $cat != $data['category'] ) {
Better to concatenate the Items of same category and store it like as Item1,Item2,Item3.
When you want to use this Items use explode() to split and
when ever you want to search in this items for a particular item use mysql's FIND-IN-SET
Not sure what you're looking for as that solution seems like it solves what you need it to do but if you want to be more tolerant of the result orders you can try this:
$concated_data = array();
while( $data = mysql_fetch_assoc( $dbq ) )
{
if ( ! array_key_exists( $data['category'], $concated_data ) )
$concated_data[$data['category']] = array();
array_push($concated_data[$data['category']], $data['item']);
}
foreach ($concated_data as $category => $items)
{
echo $category;
$count = count($items);
for ($i = 0; $i < $count; $i++)
{
if ($i == 0)
echo "\t";
else
echo "\t\t";
echo $items[$i]."\n"; // Items
}
}
Formatting up to you at this point I guess but I think putting the data into an array of arrays is the best way so the top level keys are categories and then the arrays they point to now hold an array of your items. This also doesn't assume that you get results in the same category consecutively.
Sorry code might contain bugs as I haven't coded in PHP in awhile. But basically you should accumulate the items first then go through the accumulated list and output the data.

How to fetch query where sub/child create new UL

I am trying to make un-order list for parent child categories where if there is any child category than it will create another un-order list ( like indented text) so user can understand properly.
I have fetch sql but with foreach I don't understand how to set so where child category only will display under parent category by creating another un-order list under the parent category.
Here is my code
$query_cat = "SELECT * FROM ^categories";
$query = qa_db_query_sub($query_cat);
$catsid = qa_db_read_all_assoc($query);
echo '<UL>';
foreach ($catsid as $catid){
echo '<LI>'. $catid['title'].' '. $catid['categoryid'].'</LI>';
}
echo '</UL>';
So final result would be
First Category
Sub Category1
Second Category
EDIT:
After modified code with #vlcekmi3 answer https://stackoverflow.com/a/13451136/1053190 I am getting this result
Now how to exclude subcategory from parent list?
There's no really easy solution for this with your design. The most effective way would be to add column like order_in_list (and maybe depth_in_list).
They would be pre calculated in loop (pseudocode):
START TRANSACTION
UPDATE t1 SET order_in_list = 0 // Restart whole loop
$ids = array(0);
while $id = array_shift($ids){
$record = SELECT * FROM t1 WHERE id = $id // Get id details, order_in_list is important
$children = SELECT * FROM t1 WHERE parent_id = $id // get list of all childs
// If it's root element, start indexing from 0
$root_order = ($record ? $record->order_in_list : 1)
$child_no = count($children) // How many child will be adding
// No children, nothing to do:
if $child_no < 1{
continue;
}
append_to_array($ids, $children) // Store ids to process
// Shift all later records, we'll be creating gap in order_in_list 1,2,3,4,5
// To 1,2,5,6,7 to insert items on places 3,4
UPDATE t1 SET order_in_list = (order_in_list + $child_no)
WHERE order_in_list > $record->order_in_list
// Okay, set IDs for direct children
foreach( $children as $child){
UPDATE t1 SET order_in_list = $root_order, depth_in_list = $record->depth_in_list+1
WHERE id = $child->id
$root_order++;
}
}
COMMIT
This way you'll get records like:
First category, 1, 1
Second category 3, 1
Sub category, 2, 2
Which you could display with simple loop:
$last_depth = 0;
foreach( (SELECT * FROM t1 ORDER by `order_in_list`) as $row){
if( $last_detph > $row['depth_in_list'])){
// Close level </ul>
} else if($last_detph < $row['depth_in_list']){
// Opening level <ul>
} else {
// The same depth
}
$last_depth = $row['depth_in_list'];
}
Without modifying database
It would be probably most effective to build two arrays containing root elements and all elements:
$root_elements = array();
$all_elements = array();
foreach( (SELECT * FROM t1) as $row){
// Store details into all_elements, note that entry may have already be created when
// processing child node
if( isset( $all_elements[$row['id']])){
// set details
} else {
$all_elements[$row['id']] = $row;
$all_elements[$row['id']]['children'] = array(); // Array of child elements
}
if( $row['parent_id'] == NULL){
$all_elements[] = $row['id']; // Add row element
} else {
if( isset( $all_elements[ $row[ 'parent_id']])){
$all_elements[ $row[ 'parent_id']]['children'][] = $row['id'];
} else {
// Create new record:
$all_elements[ $row[ 'parent_id']] = array();
$all_elements[ $row[ 'parent_id']]['children'] = array($row['id']);
}
}
}
And then write it as:
foreach( $root_elements as $element_id){
write_recursive( $all_elements[ $element_id]);
}
// And display
function write_recursive( $element)
{
echo '<ul>...';
if( count( $element['children'])){
foreach( $element['children'] as $child){
write_recursive( $all_elements[ $child]);
}
}
echo '</ul>';
}
You better create class for that (to replace using global variables), but you should have a solid way to do this. Anyway try avoid using this with large number of records (I wouldn't go past 2000-5000 menu entries), try to at least cache it.
Note: solutions are oriented towards minimal number of requests on database when displaying list.
you can use complicated query or something like this
foreach ($catsid as $catid) {
...
$subquery_cat = "SELECT * FROM ^categories WHERE parentid='".$catid['categoryid']."'";
$query = qa_db_query_sub($subquery_cat);
$subcatsid = qa_db_read_all_assoc($query);
// wrap into html
...
}

How do I extract and display hierarchical data from my database?

I have two tables.
The chapters table has the columns id and name.
The chapters_chapter table has columns id, master_id, and slave_id.
Lets say that the chapters table has 7 records:
id name
1 test01
2 test02
3 test03
4 test04
5 test05
6 test06
7 test07
And in the chapters_chapters table I have these records:
id master_id slave_id
1 1 5
2 1 6
3 6 7
4 7 2
Given that data, how can I extract the hierarchy of that data so that it looks like this?
test01
test05
test06
test07
test02
test03
test04
So this was kind of a pain because of the fact that we had to have the hierarchy stored in the DB. Because of this, each item can have multiple children, and each child can have multiple parents.
This second part means we cannot simply loop through the list once and be done with it. We might have to insert an item in multiple places in the hierarchy. While you probably won't actually structure your data that way, the database schema you've described supports this scenario, so the code must support it too.
Here's a high-level version of the algorithm:
Query both tables
Create a map (array) of a parent (number) to its children (another array)
Create a set of items that are children (array of numbers)
Create a function that displays a single item, indenting it to the desired depth.
If that item has children, this function increases the depth by one, and calls itself recursively
Loop through all items that aren't children (root items).
Call the function for each of those items, with a desired depth of 0 (no indent).
Here's two hours work. Enjoy :)
Note that I stuck it within a <pre> block, so you might have to mess with how the indentation is done (output something other than two spaces, mess with the style of the divs, etc).
<?php
$con = mysql_connect("localhost", "test_user", "your_password");
if(!$con)
{
die("could not connect to DB: " . mysql_error());
}
mysql_select_db("your_db", $con);
// get chapters
$chapters = array();
$result = mysql_query("SELECT * FROM chapters");
while($row = mysql_fetch_array($result))
{
$id = $row["id"];
$name = $row["name"];
$chapters[$id] = $name;
}
// get chapters_chapters - We'll call it "parent/child" instead of "master/slave"
$parent_child_map = array();
$is_child = array();
$result = mysql_query("SELECT master_id, slave_id FROM chapters_chapters");
while($row = mysql_fetch_array($result))
{
$parent_id = $row["master_id"];
$child_id = $row["slave_id"];
$children = $parent_child_map[$parent_id];
if($children == null)
{
$children = array();
}
$children[] = $child_id;
$parent_child_map[$parent_id] = $children;
$is_child[$child_id] = true;
}
// display item hierarchically
$display_item_and_children = function($id, $name, $depth)
use ($chapters, $parent_child_map, &$display_item_and_children)
{
echo "<div><pre>";
// indent up to depth
for($i = 0; $i < $depth; $i++)
{
echo " ";
}
echo "id: " . $id
. " name: " . $name
. "</pre></div>";
// if there are children, display them recursively
$children = $parent_child_map[$id];
if($children != null)
{
foreach($children as $child_id)
{
$child_name = $chapters[$child_id];
$display_item_and_children($child_id, $child_name, $depth + 1);
}
}
};
// display all top-level items hierarchically
foreach($chapters as $id => $name)
{
// if it is a top-level item, display it
if($is_child[$id] != true)
{
$display_item_and_children($id, $name, 0);
}
}
mysql_close($con);
?>
And here's a screenshot:
The question becomes how complex you want your solution to be. I'd do it with the following pseudo code.
SELECT all the chapters
SELECT all the *chapters_chapters*
loop over the chapters to create an array chapter objects
loop over the `chapters_chapters* and create the relationships using the chapter objects
Essentially you're creating a link-list.

Simple PHP Array Issue with Key Comparison

I've been pulling my hair out on this one all afternoon. Basically, I have a long table of values (stored in SQL) and I want to go through the entire table and count the number of times each value shows up. I've called the values "pid" integers.
The best way I thought of to do this was to create an array with the PIDs as the key of the array, and the number of times each PID has occured in the table as the value at that key. Then go through the entire list and either add the PID to the array if it didn't already exist, or increment the click value if it already exists. The goal is to then figure out which PID has the highest number of clicks.
It sounds straightforward, and it is! I think I must have an error in my syntax somewhere because everything seems right. This is my first time working with arrays in PHP so be nice :)
Thanks so much!
$tabulation = array();
while ($row = mysql_fetch_array($result)) {
$pid = $row[1];
//if this post isn't in the tabulation array, add it w a click value of 1
if ( !isset( $tabulation[$pid] ) ){ array_push( $tabulation[$pid], 1 ); }
//if this post is already in the tabulation array, incrment its click value by 1
else {
$t = $tabulation[$pid]; $t++; $tabulation[$pid] = $t;
}
}
$highestClicksValue = -1;
$highestClicksPID = -1;
foreach ($tabulation as $pid => $clicks){
if ($clicks > $highestClicksValue){ $highestClicksPID = $pid; }
printf("PID: ". $tabulation[$pid] . " clicks: " . $tabulation[$clicks] . "<br />");
}
I know you're looking for a PHP answer, but have you considered that this is what SQL is best at?
select pid,count(*)
from theTable
group by pid
order by count(*) desc
Just a thought...
Why are you using the array key and value as keys for $tabulation in the last foreach?
This should work...
$tabulation = array();
while ($row = mysql_fetch_array($result)) {
$pid = $row[1];
//if this post isn't in the tabulation array, add it w a click value of 1
if ( ! isset( $tabulation[$pid] ))
$tabulation[$pid] = 1;
//if this post is already in the tabulation array, incrment its click value by 1
else
$tabulation[$pid]++;
}
arsort($tabulation);
$highestClicksValue = reset($tabulation);
$highestClicksPID = key($tabulation);
foreach ($tabulation as $pid => $clicks){
print("PID: ". $pid . " clicks: " . $clicks . "<br />");
}

Categories