Gurnarok
Gurnarok

Reputation: 90

Joining tables and replacing null values with specified values

I am trying to make a printable page, where there is all the sales of a specified manufacturer, listing all the products, between specified dates. If there has not been any sales, it should display 0.

The tables

// Manufacturer table
// mid, manufacturer
// Products table
// pid, product, ref_manufacturer_id
// Orders table
// oid, orderPrice, orderDateTime, ref_product_id

And the query that works (without date limitation)

SELECT prod.product, COALESCE(COUNT(pord.oid),0) AS orderCount,
COALESCE(SUM(pord.orderPrice),0) AS orderSum
FROM product_manufacturer AS manu
JOIN product_list AS prod ON prod.ref_manufacturer_id = manu.mid
LEFT JOIN product_orders AS pord ON pord.ref_product_id = prod.pid
WHERE manu.mid = :manu_id
GROUP BY prod.product;

But as soon as I add into the WHERE-syntax this

WHERE manu.mid = :manu_id AND DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end

I am using PHP PDO on connecting and verifying that the manu_id is int and the orders_start/end is converted to MySQL date format.

But the question I am trying to fidn out is, what is causing the problem, that when I add the date restriction, every product that was not ordered, is not displayed on the output?

SQL on creating the tables

CREATE TABLE product_list (
  pid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  product varchar(255) NOT NULL,
  ref_manufacturer_id bigint(20) unsigned NOT NULL,
  PRIMARY KEY (pid),
  KEY ref_manufacturer_id (ref_manufacturer_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE product_manufacturer (
  mid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  manufacturer varchar(255) NOT NULL,
  PRIMARY KEY (mid),
  UNIQUE KEY manufacturer (manufacturer)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

CREATE TABLE product_orders (
  oid bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  orderPrice float(10,2) NOT NULL,
  orderDatetime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  red_product_id bigint(20) unsigned NOT NULL,
  PRIMARY KEY (oid),
  KEY red_product_id (red_product_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Upvotes: 1

Views: 346

Answers (4)

Michał Powaga
Michał Powaga

Reputation: 23183

Try:

SELECT p.product, 
    COALESCE(o.orderCount, 0) as orderCount, 
    COALESCE(o.orderSum,0) AS orderSum
FROM product_manufacturer AS m
JOIN product_list AS p ON p.ref_manufacturer_id = m.mid
LEFT JOIN (
    SELECT ref_product_id as pid, COUNT(oid) AS orderCount, SUM(orderPrice) AS orderSum
    FROM product_orders
    WHERE DATE(orderDateTime) BETWEEN :orders_start AND :orders_end
    GROUP BY ref_product_id
) AS o ON p.pid = o.pid
WHERE m.mid = :manu_id

Edit: Corrected after ypercube comment.

Upvotes: 2

Mahmoud Gamal
Mahmoud Gamal

Reputation: 79929

What you need is to move the orderDateTime criteria to the join clause instead of where clause like:

SELECT prod.product, COALESCE(COUNT(pord.oid),0) AS orderCount,
       COALESCE(SUM(pord.orderPrice),0) AS orderSum
FROM product_manufacturer AS manu
JOIN product_list AS prod ON prod.ref_manufacturer_id = manu.mid
LEFT JOIN product_orders AS pord 
          ON pord.ref_product_id = prod.pid
          AND DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end
WHERE manu.mid = :manu_id
GROUP BY prod.product;

The reason it does not work within the WHERE clause is because of the NULL values returned from the outer join. When you do not have a row in product_orders fot a product, the outer join returns a NULL for the date field orderDateTime and that row will be filtered out because a NULL is not equal to anything.

Upvotes: 2

cegfault
cegfault

Reputation: 6632

I don't know how your specific system works, but it may be orderDateTime is not set (ie, NULL or something else) until that product gets ordered. You may want to try:

WHERE manu.mid = :manu_id AND ((DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end) OR pord.orderDateTime=NULL)

If this is not the case, could you give an example of the orderDateTime value for something that is not showing up when you want it to?

Upvotes: 0

Nathan
Nathan

Reputation: 1520

try this on the where clause.

WHERE manu.mid = :manu_id AND (DATE(pord.orderDateTime) BETWEEN :orders_start AND :orders_end)

It might be reading the second AND function as another where clause that the statement should return true. Just a hunch on that. Let me know if this does the trick.

Upvotes: 0

Related Questions