MySQL: fetch a row and multiple related rows - possible? - php

Let's say I have one table: "cars" with 3 fields: id, brand, cost.
There's a second table: "models" with 3 fields: id, brand, model_name.
Each "cars" row can have multiple related "models" rows.
Is it possible to do an sql-select whose output looks like this?
edit: I use PHP for the database querys
array(
[0] => array(
[id] => 1
[brand] => mercedes
[cost] => 1000
[models] => array(
[0] => array(
[id] => 1
[brand] => mercedes
[model_name] => slk
)
[1] => array(
[id] => 2
[brand] => mercedes
[model_name] => clk
)
[2] => array(
[id] => 3
[brand] => mercedes
[model_name] => whatever
)
)
)
)

You need to add a foreign key relation to the models table, say car_id. Then:
SELECT * FROM cars JOIN models ON car_id = models.id;
This will output something similar to what you are looking for.
Assuming you are using PHP, using the output:
$query= "SELECT * FROM cars JOIN models ON car_id = models.id";
$r= #mysqli_query($dbc, $query);
while ($row= mysqli_fetch_array($r, MYSQLI_ASSOC)) {
$carstuff['id']=$row[id];
$carstuff['brand']=$row[brand];
$carstuff['cost']=$row[cost];
$carstuff[$row['models']][]=$row['model_name'];
}
var_dump($carstuff);
Note, that the id, brand and cost are repeatedly overwritten, but that is okay because they are overwritten with the same information. I'm not too sure about the cleanliness of the code, but that is the basic idea.

Try this:
Query:
SELECT c.ID, c.brand,c.cost, GROUP_CONCAT(model_name SEPARATOR '","') as models
, GROUP_CONCAT(m.ID SEPARATOR ',') as MID
, GROUP_CONCAT(m.brand SEPARATOR '","') as mbrand
FROM cars c
LEFT OUTER JOIN model m
ON m.brand = c.brand
GROUP BY brand;
Output:
ID BRAND COST MODELS MID MBRAND
1 audi 1000 m11","m22 4,5 audi","audi
1 mercedes 1200 m1","m2","m3 1,2,3 mercedes","mercedes","mercedes
Now in your php code you can process the MODEL,MID and MBrand
(By using explode)
$modelArray = explode(" ,", $row["MODELS");
SQLFIDDLE

Related

SQL how do I join two tables using prepared statements based on values found in an array

I have the table products which contains the following:
product_id | name | category | img
1 car toy tcar.jpg
2 boat toy tboat.jpg
3 plane actual plane.jpg
I also have the table product which contains the following:
product_id | size | price
1 large 5
2 small 3
2 medium 4
3 small 7
Finally, I have a multidimensional array called $products_in_cart, with key equal to product_id. It stores the following values:
Array ( [1] => Array ( [Quantity] => 1 [Size] => large) [2] => Array ( [Quantity] => 5 [Size] => medium ) )
I want to inner join products and product and then fetch the products which are found in the array $products_in_cart. (so I will fetch based on the product_id (the keys in $products_in_cart) as well as the size). This has to be done using prepared statements.
I managed to fetch products which are found in $products_in_cart using these statements:
$array_to_question_marks = implode(',', array_fill(0, count($products_in_cart), '?'));
$stmt = $pdo->prepare('SELECT * FROM products WHERE product_id IN (' . $array_to_question_marks . ')');
$stmt->execute(array_keys($products_in_cart));
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
This gives the output:
Array ( [0] => Array ( [product_id] => 1 [name] => car [category] => toy [img] => tcar.jpg) [1] => Array ( [product_id] => 2 [name] => boat [category] => toy [img] => tboat.jpg))
However, what I want is to get this output:
Array ( [0] => Array ( [product_id] => 1 [name] => car [category] => toy [img] => tcar.jpg [size] => large [price] => 5) [1] => Array ( [product_id] => 2 [name] => boat [category] => toy [img] => tboat.jpg [size] => medium [price] => 4))
I expect that I should inner join products and product and then pick those with id's and size that match the ones in $products_in_cart. However I got no clue on how to do that (prepared statements are making the process complicated for me).
I'm sorry for my poor description of the problem, but I hope that the example of what I managed to do makes it clear, thanks!
Well, if you want to do a inner join, just... do an inner join. Nothing says you can't do one within a prepared statement!
Replace this line
$stmt = $pdo->prepare('SELECT * FROM products WHERE product_id IN (' . $array_to_question_marks . ')');
With this one (I'm spliting it in lines for better clarity):
$stmt = $pdo->prepare('SELECT * FROM products
INNER JOIN product
ON (products.product_id = product.product_id)
WHERE products.product_id IN (' . $array_to_question_marks . ')');
You could also use the "shorthand" version if you prefer:
$stmt = $pdo->prepare('SELECT * FROM products, product
WHERE products.product_id = product.product_id
AND products.product_id IN (' . $array_to_question_marks . ')');
As a side note, using two similar named tables is confusing. Since the "product" table contains sizes and prices and no product information, you could rename it to something like "prices_sizes" or something like that.

How to get all categories of a product in PHP/SQL

I have an sql request that retrieves all the products in the database with it's categories using mysqli_fetch_all, problem arises when a product has two categories I have twice the product coming back in the result or n times as many categories the product has and I cain't get it to output properly in the html template if someone explained to me in php or sql how to do what i wish thanks.
products
--------
id
name
etc ...
categories
----------
id
name
category_product
----------------
category_id
product_id
$sql = "SELECT
p.id,
p.name,
p.price,
categories.name as category_name
FROM products as p
JOIN category_product
ON p.id = category_product.product_id
JOIN categories
ON category_product.category_id = categories.id";
output => [0] => Array (
[id] => 3
[name] => product 3
[price] => 1.00
[category_name] => cat 4 )
[1] => Array (
[id] => 3
[name] => product 3
[price] => 1.00
[category_name] => cat 3 )
expected => [0] => Array (
[id] => 3
[name] => product 3
[price] => 1.00
[category_name] => Array(
[0] => cat 3
[1] => cat 4 ]))
Change your query to generate a GROUP_CONCAT of category names, then after fetching you can split that value using explode in PHP. For example (assuming you are using MySQLi with a connection $conn):
$sql = "SELECT
p.id,
p.name,
p.price,
GROUP_CONCAT(categories.name SEPARATOR '|') as category_name
FROM products as p
JOIN category_product ON p.id = category_product.product_id
JOIN categories ON category_product.category_id = categories.id
GROUP BY p.id, p.name, p.price";
$result = $conn->query($sql) or die($conn->error);
while ($row = $result->fetch_assoc()) {
// split the category names into an array
$row['category_name'] = explode('|', $row['category_name']);
// do something with the row value
}
Note that you need to specify a separator to GROUP_CONCAT (in this case I've used |) which will not occur in a category name.

Pushing pointers to followers with the metadata (MySQL Query)

I’ve seen the following question on StackOverflow, Intelligent MySQL GROUP BY for Activity Streams posted by Christian Owens 12/12/12.
So I decided to try out the same approach, make two tables similar to those of his. And then I pretty much copied his query which I do understand.
This is what I get out from my sandbox:
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[action] => published_post
[object_id] => 776286559146635
[object_type] => post
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 0
)
)
I am curious, since looking at the results in Owens question, I am not able to fully get something, and does he perform additional queries to grab the actual metadata? And if yes, does this mean that one can do it from that single query or does one need to run different optimized sub-queries and then loop through the arrays of data to render the stream itself.
Thanks a lot in advanced.
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[action] => hearted
[object_id] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[object_type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
In "pseudo" code you need something like this
$result = $pdo->query('
SELECT stream.*,
object.*,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON stream.user_id = follows.following_user
LEFT JOIN object ON stream.object_id = object.id
WHERE follows.user_id = '0'
GROUP BY stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC
');
then parse the result and convert it in php
$data = array(); // this will store the end result
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
// here for each row you get the keys and put it in a sub-array
// first copy the selected `object` data into a sub array
$row['object_data']['id'] = $row['object.id'];
$row['object_data']['title'] = $row['object.title'];
// remove the flat selected keys
unset($row['object.id']);
unset($row['object.title']);
...
$data[] = $row; // move to the desired array
}
you should get
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[verb] => hearted
[object_data] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
It seems that you want a query where you can return the data you're actually able to get plus the user fullname and the data related to the object_id.
I think that the best effort would be to include some subqueries in your query to extract these data:
Fullname: something like (SELECT fullname FROM users WHERE id = stream.user_id) AS fullname... or some modified version using the stream.user_id, as we can't identify in your schema where this fullname comes from;
Object Data: something like (SELECT CONCAT_WS(';', id, title, category_name) FROM objects WHERE id = stream.object_id) AS object_data. Just as the fullname, we can't identify in your schema where these object data comes from, but I'm assuming it's an objects table.
One object may have just one title and may have just one category. In this case, the Object Data subquery works great. I don't think an object can have more than one title, but it's possible to have more than one category. In this case, you should GROUP_CONCAT the category names and take one of the two paths:
Replace the category_name in the CONCAT_WS for the GROUP_CONCAT of all categories names;
Select a new column categories (just a name suggestion) with the subquery which GROUP_CONCAT all categories names;
If your tables were like te first two points of my answer, a query like this may select the data, just needing a proper parse (split) in PHP:
SELECT
MAX(stream.id) as id,
stream.user_id,
(select fullname from users where id = stream.user_id) as fullname,
stream.verb,
stream.object_id,
(select concat_ws(';', id, title, category_name) from objects where id = stream.object_id) as object_data,
stream.type,
date(stream.stream_date) as stream_date,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON 1=1
AND stream.user_id = follows.following_user
WHERE 1=1
AND follows.user_id = '0'
GROUP BY
stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC;
In ANSI SQL you can't reference columns not listed in your GROUP BY, unless they're in aggregate functions. So, I included the id as an aggregation.

Update multiple rows in database based on multiple checkboxes and inputs

I have array below and I need to update database according to this.
It should be something like example code below but I don't know how to do it correctly:
UPDATE productPercent SET percent="$percent" WHERE
store="$store" AND
startDate>"$start_date" AND
endDate<"$end_date" AND
storeGroup="$storeGroup" AND
productGroup="$product_group" AND
productName LIKE '$search%'
I need to check for each store, store group, product (if contains word) and product group and then update productPercent table. Percent, product group, store group, product name and store are in different tables so some kind of inner join is needed.
I need some directions regarding this because I don't know how to start, thank you.
Array
(
[percent] => 3
[store] => Array
(
[0] => 36
[1] => 45
[2] => 56
)
[start_date] => 2015-02-09
[end_date] => 2015-03-31
[storeGroup] => Array
(
[0] => 2
[1] => 4
)
[product_group] => Array
(
[0] => 13
[1] => 31
[2] => 32
)
[search] => iphone
[setPercent] => Submit
)
UPDATED: data model - tableName: columns(connected tables)
store: id,name,startDate,endDate
storeGroup: id,storeGroupID(in table storeGroupName: id,name),storeID
productGroup: id,productID(in table productName: id,name),groupID(in table productGroupName: id,name)
productName: id,name
productPercent: id,productID,storeID,percent
$pdoHandle = $this->getPDOHandle();
$searchword = 'iphone';
$sql = "UPDATE
productPercent
inner join store on productPercent.storeID=store.id
inner join storeGroup on storeGroup.storeID=store.id
inner join productGroup on productGroup.id=storeGroup.groupID
inner join productName on productPercent.productID=productName.id and productGroup.productID=productName.id
SET percent=:percent
WHERE productName.name like :searchword";
$pdo->prepare($sql);
$pdo->setAttribute('percent', floatval($percent/100));
$pdo->setAttribute('searchword', $searchword . '%');

PHP/MySQL - Analyzing common sets across multiple sets

Let's say I have two tables, people and families.
families has two fields - id and name. The name field contains the family surname.
people has three fields - id, family_id and name - The family_id is the id of the family that that person belongs to. The name field is that person's first name.
It's basically a one to many relationship with one family having many people.
I want to get a lists of name sets, ordered by the highest occurrence of the largest set of names across families.
That probably doesn't make much sense...
To explain what I want further, we can score each set of names. The 'score' is the array size * number of occurrences across families.
For example, let's say two names, 'John' and 'Jane' both existed in three families - That set's 'score' would be 2*3 = 6.
How could I get an array of sets of names, and the set's 'score', ordered by each set's score?
Sample Result Set (I've put it in a table layout, but this could be a multi-dimensional array in PHP) - Note this is just randomly thought up and doesn't reflect any statistical name data.
names | occurrences | score
Ben, Lucy | 4 | 8
Jane, John | 3 | 6
James, Rosie, Jack | 2 | 6
Charlie, Jane | 2 | 4
Just to clarify, I'm not interested in sets where:
The number of occurrences is 1 (obviously, just one family).
The set size is 1 (just a common name).
I hope I have explained my somewhat complex problem - if anyone needs clarification please say.
OK, got it:
<?php
require_once('query.lib.php');
$db=new database(DB_TYPE,DB_HOST,DB_USER,DB_PASS,DB_MISC);
$qry=new query('set names utf8',$db);
//Base query, this filters out names that are in just one family
$sql='select name, cast(group_concat(family order by family) as char) as famlist, count(*) as num from people group by name having num>0 order by num desc';
$qry=new query($sql,$db);
//$qry->result is something like
/*
Array
(
[name] => Array
(
[0] => cathy
[1] => george
[2] => jack
[3] => john
[4] => jane
[5] => winston
[6] => peter
)
[famlist] => Array
(
[0] => 2,4,5,6,8
[1] => 2,3,4,5,8
[2] => 1,3,5,7,8
[3] => 1,2,3,6,7
[4] => 2,4,7,8
[5] => 1,2,6,8
[6] => 1,3,6
)
[num] => Array
(
[0] => 5
[1] => 5
[2] => 5
[3] => 5
[4] => 4
[5] => 4
[6] => 3
)
)
$qry->rows=7
*/
//Initialize
$names=$qry->result['name'];
$rows=$qry->rows;
$lists=array();
for ($i=0;$i<$rows;$i++) $lists[$i]=explode(',',$qry->result['famlist'][$i]);
//Walk the list and populate pairs - this filters out pairs, that are specific to only one family
$tuples=array();
for ($i=0;$i<$rows;$i++) {
for ($j=$i+1;$j<$rows;$j++) {
$isec=array_intersect($lists[$i],$lists[$j]);
if (sizeof($isec)>1) {
//Every tuple consists of the name-list, the family list, the length and the latest used name
$tuples[]=array($names[$i].'/'.$names[$j],$isec,2,$j);
}
}
}
//Now walk the tuples again rolling forward, until there is nothing left to do
//We do not use a for loop just for style
$i=0;
while ($i<sizeof($tuples)) {
$tuple=$tuples[$i];
//Try to combine this tuple with all later names
for ($j=$tuple[3]+1;$j<$rows;$j++) {
$isec=array_intersect($tuple[1],$lists[$j]);
if (sizeof($isec)>0) $tuples[]=array($tuple[0].'/'.$names[$j],$isec,$tuple[2]+1,$j);
}
$i++;
}
//We have all the tuples, now we just need to extract the info and prepare to sort - some dirty trick here!
$final=array();
while (sizeof($tuples)>0) {
$tuple=array_pop($tuples);
//name list is in $tuple[0]
$list=$tuple[0];
//count is sizeof($tuple[1])
$count=sizeof($tuple[1]);
//length is in $tuple[2]
$final[]=$tuple[2]*$count."\t$count\t$list";
}
//Sorting and output is all that is left
rsort($final);
print_r($final);
?>
I am sorry I just realized I use a query lib that I can't source in here, but from the comment you will easily be able to create the arrays as in the section "Initialize".
Basically what I do is starting with the pairs I keep an array of the families all the names in the current name list belong to, then intersect it with all not-yet tried names.
Will this work?
SELECT
f.name AS 'surname',
GROUP_CONCAT(DISTINCT p.name ORDER BY p.name) AS 'names',
COUNT(DISTINCT p.name) AS 'distinct_names',
COUNT(p.id) AS 'occurrences',
COUNT(DISTINCT p.name) * COUNT(p.id) AS 'score'
FROM
families f
LEFT JOIN people p ON ( f.id = p.family_id )
GROUP BY
f.id
ORDER BY
f.name

Categories