Avoid duplicate rows when join multiple tables - mysql

I have problem with 3 tables: first (User) contains car dealers, second and third (Order_new, Car_demo) contains two types of cars sold by dealers. I need to sum number of sales and profit for a dealer.
Unfortunately it doesn't work correctly. One of dealer sold 11 cars from Order_new and 1 from Car_demo, but when I using COUNT a get 11 from Order_new (good) and 11 from Car_demo (not good...). The same with SUM - from Car_demo I get profit x 11.
This is important fragment of code:
SELECT
d.name AS name,
COUNT(cn_renault.id) AS ren_p,
COUNT(cd_renault.id) AS ren_demo_p,
SUM(cn_renault.profit) AS cn_profit,
SUM(cd_renault.profit) AS cd_profit,
FROM User d
LEFT OUTER JOIN (
SELECT DISTINCT
cd2.id AS id,
cd2.dealer AS sale_by,
cd2.price AS price,
FROM Order_new cd2
LEFT OUTER JOIN Car car ON car.id = cd2.car_brand
WHERE DATE(cd2.' . $by . ') >= \'' . $date . '\' AND DATE(cd2.' . $by . ') < \'' . $date_end . '\' AND car.brand = \'Renault\'
GROUP BY cd2.id
) cn_renault ON cn_renault.sale_by = d.id
LEFT OUTER JOIN (
SELECT DISTINCT
cd2.id AS id,
cd2.sale_by AS sale_by,
cd2.selling_price AS price,
FROM Car_demo cd2
LEFT OUTER JOIN Car car ON car.id = cd2.car_brand
WHERE DATE(cd2.' . $by_d . ') >= \'' . $date . '\' AND DATE(cd2.' . $by_d . ') < \'' . $date_end . '\' AND cd2.sale_status >= 2 AND car.brand = \'Renault\'
GROUP BY cd2.id
) cd_renault ON cd_renault.sale_by = d.id
WHERE d.id = ' . $dealer_id . '
GROUP BY d.id
I know I can do it this way:
LEFT OUTER JOIN (
SELECT DISTINCT
COUNT(cd2.id) AS number_of,
SUM(cd2.price) AS price,
FROM Order_new cd2
LEFT OUTER JOIN Car car ON car.id = cd2.car_brand
WHERE DATE(cd2.' . $by . ') >= \'' . $date . '\' AND DATE(cd2.' . $by . ') < \'' . $date_end . '\' AND car.brand = \'Renault\'
GROUP BY cd2.dealer
) cn_renault ON cn_renault.sale_by = d.id
and it works, but I need cars id to join another tables in query...
Any ideas?

Related

Dormant users with filter days in prestashop 1.6

I am trying to add below query in $this_select with left join but not working properly
Below is my working query which works fine :
select a.id_customer as id_customer,
a.id_shop,
a.email,
a.lastname,
a.firstname,
max(c.date_add) as last_visit,
IFNULL(max(b.date_add),'1001-01-01 00:00:00') as Last_order_date
from ps_customer a
left join ps_orders b
on a.id_customer = b.id_customer
left join ps_guest g
on a.id_customer = g.id_customer
left join ps_connections c
on g.id_guest = c.id_guest
group by a.id_customer
having to_days(Last_order_date) < to_days(now())- '30'
But my problem is that when I placed below query code in my controller it is not taking the first and the second left join:
$this->_select='
a.id_shop,
a.email,
a.lastname,
a.firstname,
max(c.date_add) as last_visit,
IFNULL(max(b.date_add),"'.$default_date.'") as Last_order_date
';
$this->_join = '
LEFT JOIN `'._DB_PREFIX_.'orders` b ON (a.`id_customer` =b.`id_customer`)';
$this->_join ='left join ps_guest g
on (a.id_customer = g.id_customer)';
$this->_join ='left join ps_connections c
ON ( g.id_guest = c.id_guest)
group by a.id_customer
having to_days(Last_order_date) < to_days(now())- '.$dormant_filter_days.'';
Am I doing anything wrong in the above $this_select or $this_join ??
Bleow is db exception which I get the problem is that I am not seeing my first two joins here ie it is not taking the first two joins
You're overriding the _join value on each call to $this->_join =. You should use $this->_join .= for the second and last join.
$this->_select = '
a.id_shop,
a.email,
a.lastname,
a.firstname,
MAX(c.date_add) AS last_visit,
IFNULL(MAX(b.date_add), "' . $default_date . '") AS Last_order_date';
$this->_join = 'LEFT JOIN `' . _DB_PREFIX_ . 'orders` b
ON (a.`id_customer` = b.`id_customer`)';
$this->_join .= ' LEFT JOIN `' . _DB_PREFIX_ . 'guest` g
ON (a.id_customer = g.id_customer)';
$this->_join .= ' LEFT JOIN `' . _DB_PREFIX_ . 'connections` c
ON (g.id_guest = c.id_guest)';
$this->_group = 'GROUP BY a.id_customer';
$this->_having = 'HAVING TO_DAYS(Last_order_date) < TO_DAYS(NOW()) - ' . $dormant_filter_days;
I tried this way which worked for me :
$this->_select='
a.id_shop,
a.email,
a.lastname,
a.firstname,
a.date_add as registered_date,
g.id_customer as guest_id,
max(c.date_add) as last_visit,
IFNULL(max(b.date_add),"'.$default_date.'") as Last_order_date
';
$this->_join = '
LEFT JOIN `'._DB_PREFIX_.'orders` b ON (a.`id_customer` =b.`id_customer`)
LEFT JOIN ps_guest g on (a.id_customer = g.id_customer)
LEFT JOIN ps_connections c
ON ( g.id_guest = c.id_guest)
';
$this->_where = 'group by a.id_customer having to_days(Last_order_date) < to_days(now())- '.$dormant_filter_days.' AND to_days(a.date_add) < to_days(now())- '.$dormant_filter_days.' ';
$this->_orderBy = 'id_customer';
$this->_orderWay = 'DESC';

Why the below query returns all the records instead of one?

I have written below query to fetch the records from DB that matches the id value. But this query return all the records instead of only one record whose id =1.
$movie_id=$_GET['id']; //assume movie_id=1
//$sql ='select * from tbl_movie where movie_id='.$_GET["id"];
$sql='SELECT M.movie_name, MC.on_screen_name, R.role, C.celebrity_name, C.celebrity_id, MI.production, MI.director,'
. ' MI.screenplay, MI.music, MI.bgm_score, Col.movie_running_time, L.language, CC.censor_certificate,'
. ' MR.movie_review_comment, A.award_name'
. ' FROM tbl_movie M INNER JOIN tbl_movie_awards MA INNER JOIN tbl_movie_details MD'
. ' INNER JOIN tbl_movie_cast MC on MC.movie_id='.$movie_id
. ' INNER JOIN tbl_actor_role R on MC.movie_role_id=R.role_id AND MC.movie_id='.$movie_id
. ' INNER JOIN tbl_celebrity C on MC.movie_celebrity_id=C.celebrity_id AND MC.movie_id='.$movie_id
. ' INNER JOIN tbl_awards A on MA.award_id=A.award_id AND MA.award_movie_id='.$movie_id
. ' INNER JOIN tbl_language L on MD.movie_language_id=L.language_id'
. ' INNER JOIN tbl_censor_certificate CC on MD.censor_id=CC.censor_id AND MD.movie_id='.$movie_id
. ' INNER JOIN tbl_movie_info MI on MI.movie_info_id='.$movie_id
. ' INNER JOIN tbl_movie_collection Col on Col.movie_id='.$movie_id
. ' INNER JOIN tbl_tt_movie_review MR on MR.movie_review_id='.$movie_id.';';
$result = $db->getData($sql);
if(!empty($result))
{
while($row=$result->fetch_assoc())
{
$movie_name=$row['movie_name'];
}
}
getData function written in another file:
public function getData($query)
{
$result=$this->myconn->query($query);
if($result->num_rows>0)
{
return $result;
}
else
{
echo ' error in query execution'.$this->myconn->error;
}
}
I don't know what the problem is in the above query. Can any one suggest how to get correct answer?
Assuming the commented line ( //$sql ) at the top is working well, try this:
$sql='SELECT M.movie_name, MC.on_screen_name, R.role, C.celebrity_name, C.celebrity_id, MI.production, MI.director,'
. ' MI.screenplay, MI.music, MI.bgm_score, Col.movie_running_time, L.language, CC.censor_certificate,'
. ' MR.movie_review_comment, A.award_name'
. ' FROM tbl_movie M INNER JOIN tbl_movie_awards MA INNER JOIN tbl_movie_details MD'
. ' INNER JOIN tbl_movie_cast MC on MC.movie_id='.$movie_id
. ' INNER JOIN tbl_actor_role R on MC.movie_role_id=R.role_id AND MC.movie_id='.$movie_id
. ' INNER JOIN tbl_celebrity C on MC.movie_celebrity_id=C.celebrity_id AND MC.movie_id='.$movie_id
. ' INNER JOIN tbl_awards A on MA.award_id=A.award_id AND MA.award_movie_id='.$movie_id
. ' INNER JOIN tbl_language L on MD.movie_language_id=L.language_id'
. ' INNER JOIN tbl_censor_certificate CC on MD.censor_id=CC.censor_id AND MD.movie_id='.$movie_id
. ' INNER JOIN tbl_movie_info MI on MI.movie_info_id='.$movie_id
. ' INNER JOIN tbl_movie_collection Col on Col.movie_id='.$movie_id
. ' INNER JOIN tbl_tt_movie_review MR on MR.movie_review_id='.$movie_id
. ' WHERE M.movie_id = '.$movie_id.';';
You're filtering the tables related with movies (INNER JOIN ... ON id = $movie_id), but the WHERE sentence filters the retrieved result.
EDITED: Actually, I was reviewing your query, and the ON clause after the INNER JOIN is to tell which field is doing the relationship between the tables, without specify the ID, only telling "This table1.ID equals to this table2.ID". Then in the WHERE clause you can add your filtering. So, IMO, the query should be something like this:
$sql='SELECT M.movie_name, MC.on_screen_name, R.role, C.celebrity_name, C.celebrity_id, MI.production, MI.director,'
. ' MI.screenplay, MI.music, MI.bgm_score, Col.movie_running_time, L.language, CC.censor_certificate,'
. ' MR.movie_review_comment, A.award_name'
. ' FROM tbl_movie M '
. ' INNER JOIN tbl_movie_awards MA'
. ' INNER JOIN tbl_movie_details MD'
. ' INNER JOIN tbl_movie_cast MC ON MC.movie_id = M.movie_id'
. ' INNER JOIN tbl_actor_role R ON MC.movie_role_id = R.role_id AND MC.movie_id = M.movie_id'
. ' INNER JOIN tbl_celebrity C ON MC.movie_celebrity_id = C.celebrity_id AND MC.movie_id = M.movie_id'
. ' INNER JOIN tbl_awards A ON MA.award_id = A.award_id AND MA.award_movie_id = M.movie_id
. ' INNER JOIN tbl_language L ON MD.movie_language_id = L.language_id'
. ' INNER JOIN tbl_censor_certificate CC ON MD.censor_id = CC.censor_id AND MD.movie_id = M.movie_id'
. ' INNER JOIN tbl_movie_info MI ON MI.movie_info_id = M.movie_id'
. ' INNER JOIN tbl_movie_collection Col ON Col.movie_id = M.movie_id'
. ' INNER JOIN tbl_tt_movie_review MR ON MR.movie_review_id = M.movie_id'
. ' WHERE M.movie_id = '.$movie_id.';';

Concatenated column to be store in two different columns

I have a website and when customer register on it the data is stored in the admin panel, Now If you see in the below code at the first line emp_name and emp_ID is saving in a single column, and I want to save it in two different columns how to differentiate it.
$sql = "SELECT c.customer_id, CONCAT(c.emp_name, ' ', c.emp_ID) AS name,
c.email, c.mobile_no, CONCAT(oca.address_1,oca.address_2) AS address,
oca.city, oca.postcode, occ.name as Country, c.ip,
IF( c.status = 1, 'Enabled','Disabled' ) AS status,
IF( c.approved = 1, 'Yes', 'No' ) AS approved, c.date_added, cgd.name AS customer_group FROM " . DB_PREFIX . "customer c
LEFT JOIN " . DB_PREFIX . "customer_group_description cgd ON (c.customer_group_id = cgd.customer_group_id)
LEFT JOIN " . DB_PREFIX . "address oca ON (c.address_id=oca.address_id)
LEFT JOIN " . DB_PREFIX . "country occ ON (oca.country_id=occ.country_id)
WHERE cgd.language_id = '" . (int)$this->config->get('config_language_id') . "'";
Try this:
SELECT c.customer_id,
c.emp_id,
c.emp_name AS NAME,
c.email,
c.mobile_no,
Concat(oca.address_1,oca.address_2) AS address,
oca.city,
oca.postcode,
occ.NAME AS Country,
c.ip,IF( c.status = 1, 'Enabled', 'Disabled' ) as status,
IF( c.approved = 1, 'Yes', 'No' ) AS approved, c.date_added, cgd.NAME AS customer_group FROM " . DB_PREFIX . "customer c LEFT JOIN " . DB_PREFIX . "customer_group_description cgd ON (
c.customer_group_id = cgd.customer_group_id
)
LEFT JOIN " . DB_PREFIX . "address oca ON (
c.address_id=oca.address_id
)
LEFT JOIN " . DB_PREFIX . "country occ ON (
oca.country_id=occ.country_id
)
WHERE cgd.language_id = '" . (int)$this->config->get('config_language_id') . "'
MySQL CONCAT() function is used to concatenate two or more strings to form a single string. This is why you see the data in a single column. Simply remove the function, if you want to show the data in separate columns.

Limit query results to total results minus 10 results

guys I cant figure this out.. I red a lot but no luck...
I have the following query, and I need to limit it in order to show the total results minus a specific number os results, let's say 10.
I mean, if the query would return 1000 total results, I want it to return 990, and the last 10 results need to be excluded.
Is this possible?
see the query call:
$query = ' SELECT p.*,c.name as name_category,t.name as name_type,cy.name as name_country,s.name as name_state,l.name as name_locality,l.alias as locality_alias,pf.name as name_profile, '
. ' CASE WHEN CHAR_LENGTH(p.alias) THEN CONCAT_WS(":", p.id, p.alias) ELSE p.id END as Pslug,'
. ' CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(":", c.id, c.alias) ELSE c.id END as Cslug,'
. ' CASE WHEN CHAR_LENGTH(cy.alias) THEN CONCAT_WS(":", cy.id, cy.alias) ELSE cy.id END as CYslug,'
. ' CASE WHEN CHAR_LENGTH(s.alias) THEN CONCAT_WS(":", s.id, s.alias) ELSE s.id END as Sslug,'
. ' CASE WHEN CHAR_LENGTH(l.alias) THEN CONCAT_WS(":", l.id, l.alias) ELSE l.id END as Lslug, '
. ' CASE WHEN CHAR_LENGTH(t.alias) THEN CONCAT_WS(":", t.id, t.alias) ELSE t.id END as Tslug '
. ' FROM #__properties_products AS p '
. ' LEFT JOIN #__properties_country AS cy ON cy.id = p.cyid '
. ' LEFT JOIN #__properties_state AS s ON s.id = p.sid '
. ' LEFT JOIN #__properties_locality AS l ON l.id = p.lid '
. ' LEFT JOIN #__properties_profiles AS pf ON pf.mid = p.agent_id '
. ' LEFT JOIN #__properties_category AS c ON c.id = p.cid '
. ' LEFT JOIN #__properties_type AS t ON t.id = p.type '
. ' WHERE p.published = 1 '
.' ORDER BY p.id DESC '
;
If you are using MySQL, then use limit and offset:
limit 10, 9999999
This starts at the 10th record and continues to the end of the data (or to 9999999 rows, technically).
You are ordering by p.id desc, so this will remove the 10 rows with the highest id.
To really do what you seem want (remove the 10 with the lowest id), use a subquery and then sort again after the subquery:
select t.*
from (<most of your query here>
order by p.id asc
limit 10, 999999
) t
order by id desc
To remove the rows with the highest id, just add the limit statement:
$query = 'SELECT p.*,c.name as name_category,t.name as name_type,cy.name as name_country,s.name as name_state,l.name as name_locality,l.alias as locality_alias,pf.name as name_profile, '
. ' CASE WHEN CHAR_LENGTH(p.alias) THEN CONCAT_WS(":", p.id, p.alias) ELSE p.id END as Pslug,'
. ' CASE WHEN CHAR_LENGTH(c.alias) THEN CONCAT_WS(":", c.id, c.alias) ELSE c.id END as Cslug,'
. ' CASE WHEN CHAR_LENGTH(cy.alias) THEN CONCAT_WS(":", cy.id, cy.alias) ELSE cy.id END as CYslug,'
. ' CASE WHEN CHAR_LENGTH(s.alias) THEN CONCAT_WS(":", s.id, s.alias) ELSE s.id END as Sslug,'
. ' CASE WHEN CHAR_LENGTH(l.alias) THEN CONCAT_WS(":", l.id, l.alias) ELSE l.id END as Lslug, '
. ' CASE WHEN CHAR_LENGTH(t.alias) THEN CONCAT_WS(":", t.id, t.alias) ELSE t.id END as Tslug '
. ' FROM #__properties_products AS p '
. ' LEFT JOIN #__properties_country AS cy ON cy.id = p.cyid '
. ' LEFT JOIN #__properties_state AS s ON s.id = p.sid '
. ' LEFT JOIN #__properties_locality AS l ON l.id = p.lid '
. ' LEFT JOIN #__properties_profiles AS pf ON pf.mid = p.agent_id '
. ' LEFT JOIN #__properties_category AS c ON c.id = p.cid '
. ' LEFT JOIN #__properties_type AS t ON t.id = p.type '
. ' WHERE p.published = 1 '
.' ORDER BY p.id desc limit 10, 999999'
;

How to display rows from MySQL not older than a day?

I want to display news, for example from yesterday. Here's what my query looks like. Is there any magical line of code which would do that?
$query = '
SELECT a.*,
CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias)
ELSE a.id END as slug,
CASE WHEN CHAR_LENGTH(b.alias) THEN CONCAT_WS(":", b.id, b.alias)
ELSE b.id END as catslug,
b.title as category_title,
b.alias as categoryalias,
b.params as categoryparams,
u.name AS author
FROM #__content as a
LEFT JOIN #__categories as b ON a.catid = b.id
LEFT JOIN #__users AS u ON u.id = a.created_by
WHERE ' . $where. '
ORDER BY ' . $orderby. '
LIMIT ' . $limit;
The "magical" line of code should be in your WHERE clause.
e.g. if you date is only YYYY-MM-DD
$query .= 'WHERE b.created_date = '.$db->quote( date('Y-m-d', strtotime('-1 day')) ).'
e.g. if your date column is YYYY-MM-DD H:i:s
$query .= 'WHERE DATE_FORMAT(b.created_date,"%Y-%m-%d") = '.$db->quote( date('Y-m-d', strtotime('-1 day')) ).'