I have products table which has id, price, name , and I am receiving an array of products id and quantity from front-end, what I want is to calculate the sum of price*quantity.
I know that I can use PHP foreach, but I am looking for database way
//Laravel php way
$sum = 0;
foreach($request->items as $item)
{
product = Product::find($item['product_id']);
$sum += $product->price*$item['quantity']
}
what I want is to pass the array to mysql and make mysql handle the calculation.
Do you keep the quantity of the item in the database? If you are happy, you can reach the result you want after a small query, but you need to tell us the details of the structure you have prepared.
Related
I have a table in MySQL called bundles, this table has a column called attractions_array which is a comma separated list of "attraction ids" e.g 41,13,60
attractions is another table in the database that has all the info about each of these attractions.
I have figured out how to get the first id from the array using: substring_index(attractions_array,',',1) but this isn't too helpful.
I somehow need to check through each of these ids from their own attractions table to do things like:
Average & total price for all of the attractions
Make sure the attraction is still available
etc...
How would this be possible in MySQL? I don't even know where to start looking.
If MySQL was PHP I would probably write something like this to get the job done:
foreach ($bundles as $bundle) {
// init pricing
$bundle->price_count = 0;
$bundle->price_total = 0;
// each of the attractions
$attractions = explode(",", $bundle->attractions_array);
foreach ($attractions as $attraction) {
// no longer available
if (!$attraction->available) {
continue 2;
}
// count pricing
$bundle->price_count++;
$bundle->price_total += $attraction->price;
}
// average pricing
$bundle->price_average = $bundle->price_total / $bundle->price_count;
}
My current statement
SELECT * FROM `bundles` LIMIT 0,10
Is something like this even possible in MySQL?
Following relational-model approach, you should create a relation between the table 'bundles' and the table 'attractions' that is not based on a list of ids in a field like you did. If the relation between 'bundles' and 'attractions' is many to many, that means bundles can have many attractions and attractions can be related to many bundles, you need to do something like this:
table bundles pivot table table attractions
id name id_bundles id_attract id name
where in your pivot table the id_bundles and id_attract have foreign key constraint relative to bundles and attractions.
With a simple join you will be able at this point to retrieve what you need easily.
A MySQL query example to retrieve all the attractions related to one bundle could be:
SELECT * FROM attractions JOIN pivot_table ON (pivot_table.id = attractions.id) where pivot_table.id = customIdbundles
where customIdbundles is the id of the bundle you need info about.
I try to sum mutliple count fields with JOOQ and a MySQL database.
At the moment my code looks like this:
int userId = 1;
Field<Object> newField = DSL.select(DSL.count()).from(
DSL.select(DSL.count())
.from(REQUIREMENT)
.where(REQUIREMENT.CREATOR_ID.equal(userId))
.unionAll(DSL.select(DSL.count())
.from(REQUIREMENT)
.where(REQUIREMENT.LEAD_DEVELOPER_ID.equal(userId)))
which always returns 2 as newField. But I want know how many times an user is the creator of a requirement PLUS the lead developer of a requirement.
You say "sum over multiple count", but that's not what you're doing. You do "count the number of counts". The solution is, of course, something like this:
// Assuming this to avoid referencing DSL all the time:
import static org.jooq.impl.DSL.*;
select(sum(field(name("c"), Integer.class)))
.from(
select(count().as("c"))
.from(REQUIREMENT)
.where(REQUIREMENT.CREATOR_ID.equal(userId))
.unionAll(
select(count().as("c"))
.from(REQUIREMENT)
.where(REQUIREMENT.LEAD_DEVELOPER_ID.equal(userId)))
);
Alternatively, if you plan to add many more of these counts to the sum, this might be a faster option:
select(sum(choose()
.when(REQUIREMENT.CREATOR_ID.eq(userId)
.and(REQUIREMENT.LEAD_DEVELOPER_ID.eq(userId)), inline(2))
.when(REQUIREMENT.CREATOR_ID.eq(userId), inline(1))
.when(REQUIREMENT.LEAD_DEVELOPER_ID.eq(userId), inline(1))
.otherwise(inline(0))
))
.from(REQUIREMENT);
More details about the second technique in this blog post
I found this in the guide, but have no idea how to implement the same
yii\db\Query::count(); returns the result of a COUNT query. Other
similar methods include sum($q), average($q), max($q), min($q), which
support the so-called aggregational data query. $q parameter is mandatory
for these methods and can be either the column name or expression.
Say for example I have a table name 'billing' with columns:
name amount
charge1 110.00
charge2 510.00
Total - 620.00
How I implement using
yii\db\Query::sum('amount');
I have also tried like
$command = Yii::$app->db->createCommand("SELECT sum(amount) FROM billing");
yii\db\Query::sum($command);
but page generates error.
Thanks.
The first part of code you tried appears to be attempting to use Query Builder. In this case, you must create an instance of a query, set the target table, and then compute the sum:
Via Query Builder
(http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html):
$query = (new \yii\db\Query())->from('billing');
$sum = $query->sum('amount');
echo $sum;
The second part of code you tried appears to be attempting to use Data Access Objects. In this case, you can write raw SQL to query the database, but must use queryOne(), queryAll(), queryColumn(), or queryScalar() to execute the query. queryScalar() is appropriate for an aggregate query such as this one.
Via Data Access Objects
(http://www.yiiframework.com/doc-2.0/guide-db-dao.html):
$command = Yii::$app->db->createCommand("SELECT sum(amount) FROM billing");
$sum = $command->queryScalar();
echo $sum;
Within a model the sum could also be fetched with:
$this->find()->where(...)->sum('column');
You can directly use yii query concept in Search Model
$this->find()->from('billing')->where(['column'=>value])->sum('amount');
OR
$this->find()->where(['column'=>value])->sum('amount');
Outside the Model (Billing is model name)
Billing::find()->where(['column'=>value])->sum('amount');
i hope your model name is Billing
inside Billing model use
$this->find()->sum('amount');
in other models
Billing::find()->sum('amount');
This is an efficiency/best practice question. Hoping to receive some feed back on performance. Any advice is greatly appreciated.
So here is a little background in what i have setup. I'm using codeigniter, the basic setup is pretty similar to any other product relationships. Basic tables are: Brands, products, categories. On top of these tables there is a need for install sheets, marketing materials, and colors.
I created some relationship tables:
Brands_Products
Products_Colors
Products_Images
Products_Sheets
I also have a Categories_Relationships table that holds all of the relationships to categories. Install sheets etc can have their own categories but i didn't want to define a different category relationship table for each type because i didn't think that would be very expandable.
On the front end I am sorting by brands, and categories.
I think that covers the background now to the efficiency part. I guess my question pertains mostly to weather it would be better to use joins or to make separate calls to return individual parts of each item (colors, images, etc)
What I currently have coded is working, and sorting fine but I think i can improve the performance, as it take some time to return the query. Right now its returning about 45 items. Here is my first function it grabs all the products and its info.
It works by first selecting all the products and joining it's brand information. then looping through the result i set up the basic information, but for the categories images and installs i am using functions that returns each of respected items.
public function all()
{
$q = $this->db
->select('*')
->from('Products')
->join('Brands_Products', 'Brands_Products.product_id = Products.id')
->join('Brands', 'Brands.id = Brands_Products.brand_id')
->get();
foreach($q->result() as $row)
{
// Set Regular Data
$data['Id'] = $row->product_id;
$data['Name'] = $row->product_name;
$data['Description'] = $row->description;
$data['Brand'] = $row->brand_name;
$data['Category'] = $this->categories($row->product_id);
$data['Product_Images'] = $this->product_images($row->product_id);
$data['Product_Installs'] = $this->product_installs($row->product_id);
$data['Slug'] = $row->slug;
// Set new item in return object with created data
$r[] = (object)$data;
}
return $r;
}
Here is an example of one of the functions used to get the individual parts.
private function product_installs($id)
{
// Select Install Images
$install_images = $this->db
->select('*')
->where('product_id', $id)
->from('Products_Installs')
->join('Files', 'Files.id = Products_Installs.file_id')
->get();
// Add categories to category object
foreach($install_images->result() as $pImage)
{
$data[] = array(
'id' => $pImage->file_id,
'src' => $pImage->src,
'title' => $pImage->title,
'alt' => $pImage->alt
);
}
// Make sure data exists
if(!isset($data))
{
$data = array();
}
return $data;
}
So again really just looking on advice on what is the most efficient, best practice way of doing this. I really appreciate any advice, or information.
I think your approach is correct. There are only a couple of options: 1) load your product list first, then loop, and load required data for each product row. 2) create a big join on all tables first, then loop through (possibly massive) cartesian product. The second might get rather ugly to parse. For example, if you got Product A and Product B, and Product A has Install 1, Install 2, Install 3, and product B has Install 1, and Install 2,t hen your result is
Product A Install 1
Product A Install 2
Product A Install 3
Product B Install 1
Product B Install 2
Now, add your images and categories to the join and it might become huge.
I am not sure what the sizes of your tables are but returning 45 rows shouldn't take long. The obvious thing to ensure (and you probably did that already) is that product_id is indexed in all tables as well as your brands_products tables and others. Otherwise, you'll do a table scan.
The next question is how you're displaying your data on the screen. So you're getting all products. Do you need to load categories, images, installs when you're getting a list of products? If you're simply listing products on the screen, you might want to wait to load that data until user picks a products they are viewing.
On a side note, any reason you're converting your array to object
$r[] = (object)$data;
Also, in the second function, you can simply add
$data = array();
before the foreach, instead of
// Make sure data exists
if(!isset($data))
{
$data = array();
}
You can try this:
Query all of the products
Get all of the product IDs from step 1
Query all of the install images that has a product ID from step 2, sorted by product ID
Iterate through the products from step 1, and add the results from step 3
That takes you from 46 queries (for 45 products) to 2 queries, without any additional joins.
You can also use CodeIgniter's Query Caching to increase performance even further, if it's worth the time to write the code to reset the cache when data is updated.
Doing more work in PHP is generally better than doing the work in MySQL in terms of scalability. You can scale PHP easily with load balancers and web servers. Scaling MySQL isn't as easy due to concurrency issues.
I have a datamodel, let's say: invoices (m:n) invoice_items
and currently I store the invoice total, calculated in PHP by totalling invoice_items, in a column in invoices. I don't like storing derived data as it paves the way for errors later.
How can I create a logical column in the invoices table in MySql? Is this something I would be better handling in the PHP (in this case CakePHP)?
There's something called Virtual Fields in CakePHP which allows you to achieve the same result from within your Model instead of relying on support from MySQL. Virtual Fields allow you to "mashup" various data within your model and provide that as an additional column in your record. It's cleaner than the other approaches here...(no afterFind() hacking).
Read more here: http://book.cakephp.org/view/1608/Virtual-fields
Leo,
One thing you could do is to modify the afterFind() method in your model. This would recalculate the total any time you retrieve an invoice (costing runtime processing), but would mean you're not storing it in the invoices table, which is apparently what you want to avoid (correct if I'm wrong).
Try this:
class Invoice extends AppModel {
// .. other stuff
function afterFind() {
parent::afterFind();
$total = 0;
foreach( $this->data['Invoice']['InvoiceItems'] as $item )
$total += ($item['cost'] * $item['quantity']);
$this->data['Invoice']['total'] = $total;
}
}
I may have messed up the arrays on the hasMany relationship (the foreach line), but I hope you get the jist of it. HTH,
Travis
Either you can return the derived one when you want it via
SELECT COUNT(1) as total FROM invoice_items
Or if invoices can be multiple,
//assuming that invoice_items.num is how many there are per row
SELECT SUM(num) as total FROM invoice_items
Or you can use a VIEW, if you have a certain way you want it represented all the time.
http://forge.mysql.com/wiki/MySQL_virtual_columns_preview
It's not implemented yet, but it should be implemented in mysql 6.0
Currently you could create a view.