简体   繁体   English

MySQL / PHP,ForEach循环慢

[英]MySQL / PHP, ForEach Loops slow

I am new at stackoverflow, please accept my apologies for the long question. 我是stackoverflow的新手,对于长的问题,请接受我的歉意。 I have a large database of a retail outlet. 我有一个大型的零售商店数据库。 We wanted to prepare a report to get the opening and closing balances of products, products have batch number and every batch has unique serial number which are assigned to multiple locations. 我们希望准备一份报告,以获取产品的期初和期末余额,产品具有批次编号,每批次具有唯一的序列号,这些序列号分配给多个位置。 The challenge is to get the opening balances of each batch of the selected product for each warehouse location. 面临的挑战是如何在每个仓库位置获取所选产品的每批次的期初余额。 If location isn't specified in report filter then the report should give the result for each location's selected product and all of its batches. 如果未在报告过滤器中指定位置,则报告应提供每个位置的所选产品及其所有批次的结果。

I get all the locations using PHP / PDO and loop through it, with in the first loop i get all the batches against the selected product and with in the batches loop i first get the opening balance (which is calculated like, sum of quantity before the from date filter), i then calculate the quantity received of that product's batch in that location (sum of quantity received within the from and to date filter), then quantity sold or quantity moved to other warehouses (sum of quantity moved to other warehouse within the selected from and to date filter). 我使用PHP / PDO获取所有位置并循环遍历,在第一个循环中我获取了针对所选产品的所有批次,在批次循环中我首先获取了期初余额(其计算方式如下:从日期过滤器),然后我计算该位置该产品批次的接收数量(从日期过滤器接收的数量之和),然后计算已售出的数量或移至其他仓库的数量(移至其他仓库的数量之和)在选定的自和日期过滤器中)。

I can now calculate the closing balance as i have all the values. 我现在可以计算期末余额,因为我拥有所有值。 This runs fine but when the query runs for 20 warehouses and there are almost 20+ batches for a single product, the report takes ages to process and MySQL CPU usage goes upto 200%. 这运行得很好,但是当查询针对20个仓库运行并且单个产品有将近20多个批次时,该报告将花费很多时间来处理,并且MySQL CPU使用率高达200%。

I tried to optimize the queries, my tables are properly indexed. 我试图优化查询,我的表已正确索引。 Tables have alot of data. 表有很多数据。 millions of records. 数百万条记录。 I need advise on how to improve my code or methods, what am I doing wrong and how can I make the report faster. 我需要有关如何改进代码或方法,我在做什么错以及如何使报告更快的建议。

I am using code igniter for my application and following is the code. 我正在为我的应用程序使用代码点火器,以下是代码。

foreach($locations as $loc){
    //if batch is selected put it in array
    if($query_array['batch_no'] != ''){
        $batches[] = $query_array['batch_no'];
    }else{
    // or get all batches from the inventory table for the location
        $this->db->select('distinct(batch_no)');
        $this->db->from('product_details');
        $this->db->where('product_id',$query_array ['product']);
        $this->db->where('warehouse_id',$loc);
        $query = $this->db->get();
        foreach($query->result() as $row){
            $batches[] =  $row->batch_no;
        }
        $query->free_result();
    }

    foreach($batches as $batch){

        //GIN IN Opening Balance
        $this->db->select('IFNULL(SUM(gi.qty),0) as gin_in',FALSE);
        $this->db->from('gin as g');
        $this->db->join('gin_items as gi','gi.gin_id=g.id');
        $this->db->where('gi.product_id',$query_array ['product']);
        $this->db->where('DATE(g.creation_date) < ',$query_array ['date_from']);
        $this->db->where('g.to_location_id', $loc);
        $this->db->where_in('gi.batch_no',$batch);
        $this->db->where_in('gi.status',2);
        $this->db->where('g.status',3);
        $query = $this->db->get();

        if($query->num_rows()==1)
        {
            $var1=$query->row()->gin_in;
            $query->free_result();
        }

        // SUM (Return Invoices for a specific product, for this particular store, 
        // before this date range) -> $var2
        $this->db->select('IFNULL(SUM(ii.qty),0) as sale_return_in',FALSE);
        $this->db->from('return_sales_invoice rs');
        $this->db->join('return_invoice_items as ii','ii.invoice_id=rs.id');
        $this->db->where('ii.medicine_id',$query_array ['product']);
        $this->db->where_in('ii.batch_no',$batch);
        $this->db->where_in('rs.location_id',$loc);
        $this->db->where('rs.dated < ',$query_array ['date_from']);

        $query = $this->db->get();

        if($query->num_rows()==1){
            $var2=$query->row()->sale_return_in;
            $query->free_result();
        }


        //SUM (Sales Invoices of a specific product, from this particular store, 
        // before this date range) -> $var3
        $this->db->select('IFNULL(SUM(ii.qty),0) as sale_out',FALSE);
        $this->db->from('sales_invoice si');
        $this->db->join('invoice_items as ii','ii.invoice_id=si.id');

        $this->db->where('ii.medicine_id',$query_array ['product']);
        $this->db->where_in('ii.batch_no',$batch);
        $this->db->where_in('si.location_id',$loc);
        $this->db->where('si.dated < ',$query_array ['date_from']);

        $query = $this->db->get();

        if($query->num_rows()==1){
            $var3=$query->row()->sale_out;
            $query->free_result();
        }

        // SUM (GIN of a specific product, from this particular store, 
        // before this date range) -> $var4
        // if from location then minis stock 
        $this->db->select('IFNULL(SUM(gi.qty),0) as gin_out',FALSE);
        $this->db->from('gin as g');
        $this->db->join('gin_items as gi','gi.gin_id=g.id');
        $this->db->where('DATE(g.creation_date) < ',$query_array ['date_from']);
        $this->db->where('gi.product_id',$query_array ['product']);
        $this->db->where_in('gi.batch_no',$batch);
        $this->db->where_in('g.from_location_id',$loc);
        $this->db->where('gi.status',2);
        $this->db->where('g.status',3);
        $query = $this->db->get();

        if($query->num_rows()==1){
            $var4=$query->row()->gin_out;
            $query->free_result();
        }
        $op_bal = ($var1 + $var2) - ($var3 + $var4);

        //---------------------------------------------------------------------------------

        $where_from = "DATE(g.creation_date) >='" . $query_array ['date_from'] . "'";
        $where_to = "DATE(g.creation_date)  <='" . $query_array ['date_to'] . "'";

        $rs_where_from = "DATE(rs.creation_date) >='" . $query_array ['date_from'] . "'";
        $rs_where_to = "DATE(rs.creation_date)  <='" . $query_array ['date_to'] . "'";

        $si_where_from = "DATE(si.creation_date) >='" . $query_array ['date_from'] . "'";
        $si_where_to = "DATE(si.creation_date)  <='" . $query_array ['date_to'] . "'";

        //GIN IN Opening Balance
        $this->db->select('IFNULL(SUM(gi.qty),0) as gin_in',FALSE);
        $this->db->from('gin as g');
        $this->db->join('gin_items as gi','gi.gin_id=g.id');
        $this->db->where($where_from);
        $this->db->where($where_to);
        $this->db->where('gi.product_id',$query_array ['product']);
        $this->db->where_in('gi.batch_no',$batch);
        $this->db->where_in('g.to_location_id', $loc);
        $this->db->where('g.status',3);
        $this->db->where('gi.status',2);
        $query = $this->db->get();

        if($query->num_rows()==1){
            $g_stock_in=$query->row()->gin_in;
            $query->free_result();
        }

        // SUM (Return Invoices for a specific product, for this particular store, 
        // before this date range) -> $var2
        $this->db->select('IFNULL(SUM(ii.qty),0) as sale_return_in',FALSE);
        $this->db->from('return_sales_invoice rs');
        $this->db->join('return_invoice_items as ii','ii.invoice_id=rs.id');
        $this->db->where('ii.medicine_id',$query_array ['product']);
        $this->db->where($rs_where_from);
        $this->db->where($rs_where_to);                 
        $this->db->where_in('ii.batch_no',$batch);
        $this->db->where_in('rs.location_id',$loc);
        $query = $this->db->get();

        if($query->num_rows()==1){
            $s_stock_in=$query->row()->sale_return_in;
            $query->free_result();
        }


        //SUM (Sales Invoices of a specific product, from this particular store, 
        // before this date range) -> $var3
        $this->db->select('IFNULL(SUM(ii.qty),0) as sale_out',FALSE);
        $this->db->from('sales_invoice si');
        $this->db->join('invoice_items as ii','ii.invoice_id=si.id');
        $this->db->where('ii.medicine_id',$query_array ['product']);
        $this->db->where_in('ii.batch_no',$batch);
        $this->db->where_in('si.location_id',$loc);
        $this->db->where($si_where_from);
        $this->db->where($si_where_to);

        $query = $this->db->get();

        if($query->num_rows()==1){
            $s_stock_out=$query->row()->sale_out;
            $query->free_result();
        }

        // SUM (GIN of a specific product, from this particular store, 
        // before this date range) -> $var4
        // if from location then minis stock 
        $this->db->select('IFNULL(SUM(gi.qty),0) as gin_out',FALSE);
        $this->db->from('gin as g');
        $this->db->join('gin_items as gi','gi.gin_id=g.id');
        $this->db->where($where_from);
        $this->db->where($where_to);
        $this->db->where('gi.product_id',$query_array ['product']);
        $this->db->where_in('gi.batch_no',$batch);
        $this->db->where_in('g.from_location_id',$loc);
        $this->db->where('g.status',3);
        $this->db->where('gi.status',2);
        $query = $this->db->get();

        if($query->num_rows()==1){
            $g_stock_out=$query->row()->gin_out;
            $query->free_result();
        }
        $qty_in=$g_stock_in+$s_stock_in;
        $qty_out=$g_stock_out+$s_stock_out;

        $productName=$this->getProductName($query_array ['product']);
        $locationName=$this->getLocationName($loc);

        $data [] = array (
            'location' => $locationName,
            'product' => $productName, 
            'batchno' => $batch , 
            'op_bal' => $res['op_bal'] , 
            'qty_in' => $qty_in, 
            'qty_out' => $qty_out,
            'cl_bal' => ($res['op_bal'] + $qty_in ) - $qty_out
        );      

    }
}
return $data;

You recognize that you're doing a lot of stuff in this report program. 您意识到在此报告程序中您正在做很多事情。 In particular you're making lots of single-result-row queries. 特别是,您要进行很多单结果行查询。 That's often considered harmful to performance, compared with writing fewer queries that return a row for each item. 与编写较少为每个项目返回一行的查询相比,这通常被认为对性能有害。

One possible solution to your problem: Run the report program at night, and don't sweat the lousy performance. 一种可能的解决方案:晚上运行报告程序,而不用担心糟糕的性能。 You'll get the job done. 您会完成工作。 This sort of overnight batch run is fairly common in real-world situations. 这种隔夜批量运行在现实世界中非常普遍。

Another solution: treat the database as a machine to return tables, not just values. 另一个解决方案:将数据库视为返回表的机器,而不仅仅是返回值的机器。 Refactor your queries to return multiple results. 重构查询以返回多个结果。 You'll end up with far fewer queries and you'll be able to take advantage of the DBMS's query planner to pull lots of information at once. 您最终将获得更少的查询,并且可以利用DBMS的查询计划器立即提取大量信息。 For example, instead of looping on location in php, get MySQL to do that. 例如,与其在php中的位置上循环,不如让MySQL做到这一点。

This will give you a list of batches, warehouses and products, one row each. 这将为您提供批次,仓库和产品的清单,每列一行。

    SELECT DISTINCT batch_no, warehouse_id, product
      FROM product_details
     ORDER BY batch_no, warehouse_id, product

Next, I guess this query will give you one of your required results, but for all the batches, warehouses, and products. 接下来,我想此查询将为您提供所需的结果之一,但适用于所有批次,仓库和产品。 "I guess" because you haven't disclosed your schema or business rules. “我猜”是因为您尚未公开架构或业务规则。 This query comes from combining the first two queries in your program. 该查询来自程序中前两个查询的组合。

SELECT SUM(gi.qty) gin_in, p.batch_no, p.warehouse_id, p.product 
  FROM product_details p
  JOIN gin g        ON g.to_location_id = p.warehouse_id
  JOIN gin_items gi ON gi.gin_id = g.id AND gi.product_id = p.product
 WHERE DATE(g.creation_date) < $date_from
   AND g.status = 3
 GROUP BY p.batch_no, p.warehouse_id, p.product

You get the idea: make your queries return lots of rows, rather than running the query a gazillion times only to return a single row each time. 您有个主意:让查询返回很多行,而不是运行查询一次数以亿计的次数,而每次只返回一行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM