Optimizing query to show repeat customer - mysql

I am working with prestashop 1.5 and have the following query that shows repeat customer (previous year vs next year).
SELECT prev.id_customer, prev.name, prev.email, prev.count AS 'prev', next.count AS 'next'
FROM(
SELECT o.id_customer, CONCAT(c.firstname, ' ', c.lastname) AS name, c.email, COUNT(1) AS 'count'
FROM ps_orders AS o
LEFT JOIN ps_order_history AS h ON o.id_order = h.id_order
LEFT JOIN ps_customer AS c on o.id_customer = c.id_customer
WHERE o.invoice_date BETWEEN '2014-01-01' AND '2014-12-31'
AND h.id_order_state = 2
GROUP BY o.id_customer
ORDER BY count DESC
) AS prev,
(
SELECT o.id_customer, CONCAT(c.firstname, ' ', c.lastname) AS name, c.email, COUNT(1) AS 'count'
FROM ps_orders AS o
LEFT JOIN ps_order_history AS h ON o.id_order = h.id_order
LEFT JOIN ps_customer AS c on o.id_customer = c.id_customer
WHERE o.invoice_date BETWEEN '2015-01-01' AND '2015-12-31'
AND h.id_order_state = 2
GROUP BY o.id_customer
ORDER BY count DESC
) AS next
WHERE prev.id_customer = next.id_customer
ORDER BY prev.name ASC;
I would like to know whether there is better way to optimize this query ? I am thinking of creating a VIEW for each year thus minimizing the number of lines. But i am not sure whether it is better (performance wise).
Can anyone give me a better solution to this?
EDIT 1

I think you can use conditional aggregation:
SELECT o.id_customer, CONCAT(c.firstname, ' ', c.lastname) AS name, c.email,
SUM(o.invoice_date BETWEEN '2014-01-01' AND '2014-12-31') as cnt_2014,
SUM(o.invoice_date BETWEEN '2015-01-01' AND '2015-12-31') as cnt_2015
FROM ps_orders o LEFT JOIN
ps_order_history h
ON o.id_order = h.id_order LEFT JOIN
ps_customer c
on o.id_customer = c.id_customer
WHERE h.id_order_state = 2
GROUP BY o.id_customer;

Related

Partition over or Group By best for Mysql

SELECT P.FirstName, d.Name, EDH.StartDate
FROM HumanResources.EmployeeDepartmentHistory edh INNER JOIN HumanResources.Department d
ON EDH.DepartmentID=D.DepartmentID
INNER JOIN HumanResources.Employee e
ON EDH.BusinessEntityID=E.BusinessEntityID
INNER JOIN Person.Person P
ON E.BusinessEntityID=P.BusinessEntityID
WHERE EndDate IS NULL
ORDER BY D.NAME;
I am trying to list the person in each department that has worked there the longest. I know that using the top(1) for each department would most likely be the best option but I can figure if using partition or group by is going to be the better option in this case. Anybody with more skill than me have any ideas?
Working the longest means the earliest start date. That suggests:
SELECT ed.*
FROM (SELECT P.FirstName, d.Name, EDH.StartDate,
RANK() OVER (PARTITION BY D.DepartmentID ORDER BY EDH.StartDate) as seqnum
FROM HumanResources.EmployeeDepartmentHistory edh JOIN
HumanResources.Department d
ON EDH.DepartmentID = D.DepartmentID JOIN
HumanResources.Employee e
ON EDH.BusinessEntityID = E.BusinessEntityID JOIN
Person.Person P
ON E.BusinessEntityID = P.BusinessEntityID
WHERE EndDate IS NULL
) ed
WHERE seqnum = 1
ORDER BY D.NAME;

Display results which have no count/zero as well

I am trying to get a count of the number of logins during a given timeframe, currently my SQL query displays only results that had at least one login, I'd like it to display even those which have zero logins.
Query i'm using:
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l JOIN UsersTable u ON u.Email = l.Email JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
You have a few issues. Firstly, you either need to use a RIGHT JOIN from LoginsTable or reorder the JOINs to put the JOIN to LoginsTable last and use a LEFT JOIN. Given the nature of your query the latter probably makes more sense.
Secondly, you need to put any conditions on fields from a table which has been LEFT JOINed into the join condition, otherwise MySQL converts the LEFT JOIN into an INNER JOIN (see the manual). Finally, you should GROUP BY the same fields as specified in your SELECT. This should work:
SELECT c.FullName, COUNT(l.Id)
FROM Organisations c
JOIN UsersTable u ON u.OrganisationId = c.Id
LEFT JOIN LoginsTable l ON u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName
I found 2 issues here:
your group by column is not listed on your column
date condition is using double quotes.
try below query.
SELECT c.FullName, COUNT(l.Id)
FROM LoginsTable l
LEFT JOIN UsersTable u ON u.Email = l.Email
LEFT JOIN Organisations c ON c.Id = u.OrganisationId
WHERE l.AttemptTime between '2019-10-01' AND '2019-11-01' AND l.Success = 1
GROUP BY c.FullName
ORDER BY c.FullName ASC;
As Roman Hocke said you need to use left join as below :
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON u.Email = l.Email
WHERE l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1
GROUP BY c.Name
ORDER BY c.Name ASC;
Moreover, you should fix your group by or select using the same field : SELECT c.Name or GROUP BY c.FullName ORDER BY c.FullName
EDIT : Nick's answer is the one. As he said perfectly well, you need to put your conditions in the on clause of your left join.
SELECT c.FullName, COUNT(l.Id)
FROM UsersTable u
JOIN Organisations c ON c.Id = u.OrganisationId
LEFT JOIN LoginsTable l ON (u.Email = l.Email AND l.AttemptTime > "2019-10-01" AND l.AttemptTime < "2019-11-01" AND l.Success = 1)
GROUP BY c.FullName
ORDER BY c.FullName ASC;

Retrieve customer who bought more than 13 different products who never purchased same product

I tried this. But I feel this gives people who ordered same product
SELECT DISTINCT Count(od.orderqty) OrderQty,
c.customerid,
od.productid
FROM sales.customer c
INNER JOIN sales.salesorderheader oh
ON c.customerid = oh.customerid
INNER JOIN sales.salesorderdetail od
ON oh.salesorderid = od.salesorderid
GROUP BY od.productid,
c.customerid
HAVING Count(od.productid) > 10
ORDER BY c.customerid
Not sure what flavor of SQL you're using but try this:
select t.CustomerID
from (
select c.CustomerID
, count(distinct od.ProductID) as DistinctCount
, count(od.ProductID) as Count
from Sales.Customer c
join Sales.SalesOrderHeader oh
on c.customerid = oh.customerid
join Sales.SalesOrderDetail od
on oh.SalesOrderID = od.SalesOrderID
group
by c.CustomerID
) as t
where t.DistinctCount = t.Count
and t.DistinctCount > 13
order
by t.CustomerID

Select an sql field and don't show an specific word of the selection

When this query returns me the "Center" register, there is a word at all the fields with the same name that i don't want to display. I could make it via PHP but I need to use it on the db.
Basics I know, but i haven't done sql for a long time
SELECT
o.id_order,
o.reference AS Ref,
c.firstname AS Name,
c.lastname AS Last Name,
pl.`name` AS Center,
od.product_name AS Product,
od.product_quantity AS Quant,
ROUND(od.product_price * 1.21,2) AS Price,
o.date_add AS `Date`
FROM ps_orders AS o
INNER JOIN ps_order_detail AS od ON od.id_order = o.id_order
INNER JOIN ps_customer AS c ON c.id_customer = o.id_customer
INNER JOIN ps_product_lang AS pl ON pl.id_product = od.product_id
WHERE pl.id_lang = 1
ORDER BY od.id_order_detail DESC
When this returns me Center data, all fields have the preposition "The" in front, returning something like:
Center
The Odoo Team Center
The Dev house
Then I need to show something like
Center
Odoo Team Center
Dev house
You can use REPLACE:
SELECT
o.id_order,
o.reference AS Ref,
c.firstname AS Name,
c.lastname AS Last Name,
REPLACE(pl.`name`,"The ","") AS Center,
od.product_name AS Product,
od.product_quantity AS Quant,
ROUND(od.product_price * 1.21,2) AS Price,
o.date_add AS `Date`
FROM ps_orders AS o
INNER JOIN ps_order_detail AS od ON od.id_order = o.id_order
INNER JOIN ps_customer AS c ON c.id_customer = o.id_customer
INNER JOIN ps_product_lang AS pl ON pl.id_product = od.product_id
WHERE pl.id_lang = 1
ORDER BY od.id_order_detail DESC
Documentation: https://dev.mysql.com/doc/refman/5.7/en/replace.html
Demo: http://sqlfiddle.com/#!9/a5640/91
Something like this should work if you always wants to remove the first four chars:
SELECT SUBSTRING(center, 4, LENGTH(center)-3) FROM YOURTABLE;

Group by id having max(date_field)

To build a report, I must select some information on the last transaction status of all my customers. Until now, this is what I got:
SELECT c.firstname, c.lastname, d.product_name, o.payment, s.name, h.date_add
FROM ps_orders o
INNER JOIN ps_order_detail d ON d.id_order = o.id_order
INNER JOIN ps_customer c ON c.id_customer = o.id_customer
INNER JOIN ps_order_history h ON o.id_order = h.id_order
INNER JOIN ps_order_state_lang s ON s.id_order_state = h.id_order_state
WHERE s.id_lang =6
GROUP BY c.id_customer
HAVING MAX(h.date_add)
For each customer, this query is selecting the first date (the field h.date_add) when I need of the last one. It seems the MySQL is ignoring the HAVING.
I tried to make a sub-select, but it doesn't work too.
Thanks any answer.
Here, you need to have a subquery which gets the latest date_add for every id_order on table ps_order_history. The result of the subquery is then joined back on the original table ps_order_history provided that it macth on two columns: date_add and id_order.
SELECT c.firstname,
c.lastname,
d.product_name,
o.payment,
s.name,
h.date_add
FROM ps_orders o
INNER JOIN ps_order_detail d ON d.id_order = o.id_order
INNER JOIN ps_customer c ON c.id_customer = o.id_customer
INNER JOIN ps_order_history h ON o.id_order = h.id_order
INNER JOIN
(
SELECT id_order, MAX(date_add) max_date
FROM ps_order_history
GROUP BY id_order
) x ON h.id_order = x.id_order AND
h.date_add = x.max_date
INNER JOIN ps_order_state_lang s ON s.id_order_state = h.id_order_state
WHERE s.id_lang =6
GROUP BY c.id_customer
To get the last date, you need to join it in:
SELECT c.firstname, c.lastname, d.product_name, o.payment, s.name, h.date_add
FROM ps_orders o
INNER JOIN ps_order_detail d ON d.id_order = o.id_order
INNER JOIN ps_customer c ON c.id_customer = o.id_customer
INNER JOIN ps_order_history h ON o.id_order = h.id_order
INNER JOIN ps_order_state_lang s ON s.id_order_state = h.id_order_state
inner join (select o.id_customer, max(oh.date_add) as maxdate from ps_order_history h join ps_order o on h.id_order = o.id_order group by o.id_customer) omax on omax.id_customer = o.id_customer and o.date_add = omax.maxdate
WHERE s.id_lang =6
GROUP BY c.id_customer
Your having clause calculates the maximum date and then succeeds when it is not equal to 0 (which would be most of the time).