Request with two different dates ranges - mysql

I've a little issue with MySQL for making a request with two different dates ranges.
I need to have nb_sales and last_sales until 2014 but frequence only for the past year.
The result I want :
customer_id | nb_sales | last_sales | frequence
---------------------------------------------------------------
Customer ID | Sales make by | How many days| How many sales
| the customer | since the | has been made
| | last sales? | this year?
Column 1-3 are in the first date range : today to 2014
Column 4 is in a seconde date range : today to y-1
So I tried to :
Create a temporary table and insert frequence
SELECT customer_id, nb_sales, last_sales and frequence with LEFT OUTER JOIN
The first step is ok but for the second one I don't have any result or error message... And this happened when I wanted to LEFT OUTER JOIN my temporary table:
LEFT OUTER JOIN tmp_frequence
ON tmp_frequence.client_id = sales_flat_order.customer_id
Maybe you have a better idea?
CREATE TEMPORARY TABLE tmp_frequence (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
client_id INT,
frequence INT,
PRIMARY KEY (id)
);
INSERT INTO tmp_frequence (client_id, frequence)
SELECT sales_flat_order.customer_id, COUNT(sales_flat_order.entity_id)
FROM sales_flat_order
WHERE sales_flat_order.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
GROUP BY sales_flat_order.customer_id;
/* ------------------------------ */
SELECT
-- * ,
sales_flat_order.customer_id customer_id,
COUNT(sales_flat_order.entity_id) nb_sales,
DATEDIFF("2017-07-31",DATE_FORMAT(MAX(sales_flat_order_item.created_at),"%Y-%m-%d")) last_sales,
tmp_frequence.frequence frequence
FROM adl_ec.sales_flat_order_item
LEFT OUTER JOIN sales_flat_order
ON sales_flat_order.entity_id = sales_flat_order_item.order_id
LEFT OUTER JOIN tmp_frequence
ON tmp_frequence.client_id=sales_flat_order.customer_id
WHERE sales_flat_order_item.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
GROUP BY customer_id;
DROP TABLE tmp_frequence ;

I've Finally found a solution.
Thank you really for your help :)
DROP TABLE IF EXISTS tmp_frequence;
CREATE TEMPORARY TABLE tmp_frequence (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
client_id INT,
recence INT,
frequence INT,
montant INT,
PRIMARY KEY (id)
);
INSERT INTO tmp_frequence (client_id, frequence)
SELECT sales_flat_order.customer_id, COUNT(sales_flat_order.entity_id)
FROM sales_flat_order
WHERE sales_flat_order.created_at BETWEEN '2016-07-31 00:00:00' and '2017-07-31 23:59:59'
GROUP BY sales_flat_order.customer_id;
INSERT INTO tmp_frequence (client_id, recence, montant)
SELECT
sales_flat_order.customer_id,
DATEDIFF("2017-07-31",DATE_FORMAT(MAX(sales_flat_order_item.created_at),"%Y-%m-%d")) recence,
COUNT(sales_flat_order.grand_total) montant
FROM adl_ec.sales_flat_order_item
LEFT OUTER JOIN sales_flat_order ON sales_flat_order.entity_id = sales_flat_order_item.order_id
AND sales_flat_order_item.created_at BETWEEN '2014-05-22 00:00:00' and '2017-07-31 23:59:59'
AND qty_invoiced >0
AND sales_flat_order_item.sku NOT LIKE '%abo%'
AND sales_flat_order.total_qty_ordered < 5
GROUP BY customer_id;
SELECT tmp_frequence.client_id,MAX(tmp_frequence.recence) Recence,MAX(tmp_frequence.frequence) Frequence,MAX(tmp_frequence.montant) Montant
FROM tmp_frequence
GROUP BY tmp_frequence.client_id;

Related

Mysql Why when I use group by Id the sum function doesn't calculate the summation of a column

We have employees in our company, the employees borrow some money from their salaries & this borrowed money is to be know for us. also we need to know the remaining money from the salary in each transaction,and the sum of borrowed money.
I created table employees which includes Salary column
as
CREATE TABLE `employees` (
`Employee_Id` int(11) NOT NULL AUTO_INCREMENT,
`Employee_Name` varchar(100) NOT NULL,
`Salary` decimal(10,0) NOT NULL,
PRIMARY KEY (`Employee_Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2
And salary_transaction table to save each borrowed money in money_amount column
CREATE TABLE salary_transaction (
Salary_Transaction_Id int(11) NOT NULL AUTO_INCREMENT,
Employee_Id int(11) NOT NULL,
money_amount decimal(10,0) NOT NULL,
PRIMARY KEY (Salary_Transaction_Id)
) ENGINE=InnoDB AUTO_INCREMENT=3
This is my query and the problem it doesn't calculate the cumulative sum of money_amount borrowed by specific employee.
SELECT t.Salary_Transaction_Id,
t.Employee_Id,t.money_amount,
sum(t.money_amount) as Total_borrowed,
e.salary-sum(t.money_amount) as remaining from salary_transaction t
JOIN
(SELECT salary,Employee_Id,Employee_Name from employees ) e
ON
t.Employee_Id = e.Employee_Id GROUP by t.salary_transaction_id
Edit
I provided my questions with scripts
All scripts here
Edit 2
Expected total_borrowed values
select t.salary_transaction_id,
t.employee_id,
t.money_amount,
sum(t.money_amount) over (partition by employee_id order by t.salary_transaction_id) as total_borrowed,
e.salary - sum(t.money_amount) over (partition by e.employee_id order by t.salary_transaction_id) remaining
from employees e
inner join salary_transaction t on t.employee_id = e.employee_id
group by t.employee_id, t.salary_transaction_id, t.money_amount
You can use the OVER () function.
salary_transaction_id | employee_id | money_amount | total_borrowed | remaining
1 | 1 | 3000 | 3000 | 4000
2 | 1 | 1000 | 4000 | 3000
3 | 1 | 500 | 4500 | 2500
You want to substract the total amount borrowed by each employee from their salary. I would recommend a left join with pre-aggregation:
select e.*,
coalesce(st.total_borrowed, 0) as total_borrowed,
e.salary - coalesce(st.total_borrowed, 0) as remaining
from employee e
left join (
select employee_id, sum(money_amount) as total_borrowed
from salary_transaction
group by employee_id
) st on st.employee_id = e.employee_id
You could also use a correlated subquery. In very recent versions of MySQL, lateral joins come handy:
select e.*, st.total_borrowed, e.salary - st.total_borrowed as remaining
from employee e
cross join lateral (
select coalesce(sum(money_amount), 0) as total_borrowed
from salary_transaction st
where st.employee_id = e.employee_id
) st
Edit: both queries give you one row per employee. If you actually want one row per transaction, with a running sum of borrowed money and remaining salary, then it is different.
In MySQL 8.0, you can use window functions:
select e.*,
st.salary_transaction_id, st.money_amount,
coalesce(sum(st.money_amount) over(partition by employee_id order by st.salary_transaction_id), 0) as total_borrowed,
e.salary - coalesce(sum(st.money_amount) over(partition by employee_id order by st.salary_transaction_id), 0) as remaining
from employee e
left join salary_transaction st using(employee_id)
In earlier versions, an alternative is a correlated subquery:
select t.*, salary - total_borrowed
from (
select e.*,
st.salary_transaction_id, st.money_amount,
(
select coalesce(sum(st.money_amount), 0)
from salary_transaction st1
where st.employee_id = e.employee_id and st1.salary_transaction_id <= st.salary_transaction_id
) as total_borrowed
from employee e
left join salary_transaction using(employee_id)
) t

MySQL get records beetween tables with conditions

I've got a big problem in my hands, I have the following SQL structure, where the contracts tables are dinamically generated, with random names, like _xyz, _xxx, etc:
CREATE TABLE contract_xyz(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
created_at DATETIME NOT NULL
);
CREATE TABLE contract_events(
id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
id_contract INT(11) NOT NULL,
table_contract VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL
);
INSERT INTO contract_xyz (id,created_at) VALUES (1,'2016-11-01');
INSERT INTO contract_xyz (id,created_at) VALUES (2,'2016-10-21');
INSERT INTO contract_xyz (id,created_at) VALUES (3,'2016-11-04');
INSERT INTO contract_events(id,id_contract,table_contract,created_at) VALUES (1,1,'contract_xyz','2016-11-03');
INSERT INTO contract_events(id,id_contract,table_contract,created_at) VALUES (2,3,'contract_xyz','2016-11-04');
Each contract can have his own events. I need to solve the following issue:
Get all contracts that don't have new events in 2 days, or don't have any event at all, and was created over 2 days ago.
I've tried with LET JOIN but it wasn't the correct result. The nearest I get was the following query:
SELECT `contract_xyz`.*
FROM `contract_xyz`
WHERE EXISTS(SELECT 1
FROM `contract_events`
WHERE
`contract_events`.id_contract = `contract_xyz`.id AND `contract_events`.table_contract = 'contract_xyz'
AND DATEDIFF(CURDATE(), `contract_events`.created_at) >= 2
ORDER BY `contract_events`.created_at DESC
LIMIT 1)
OR (NOT EXISTS(SELECT 1
FROM `contract_events`
WHERE `contract_events`.id_contract = `contract_xyz`.id AND
`contract_events`.table_contract = 'contract_xyz') AND
DATEDIFF(CURDATE(), `contract_xyz`.created_at) >= 2);
But I still can't find the contracts that doesn't have any events, and was created over 2 days ago.
I would create a subquery with the max event date for each contract. I would left join the contracts table on this subquery. You can filter based on the max event date and the created date fields to achieve the expected outcome:
select c.*
from contract_xyz c
left join
(select id_contract,
max(created_at) max_event_date
from contract_events
group by id_contract) t on c.id-t.id_contract
where
DATEDIFF(CURDATE(), t.max_event_date) >= 2
or (t.max_event_date is null and DATEDIFF(CURDATE(), c.created_at) >= 2)
Alternatively, you do not use a subquery, but join the 2 tables directly with group by and do the filtering in the having clause.
LEFT OUTER JOIN with an ON condition could help here:
select c.id, c.created_at,count(e.id) as contract_events_less_than_2_days_old
from contract_xyz c
left outer join contract_events e on e.id_contract = c.id
and e.table_contract = 'contract_xyz'
and e.created_at > now() - interval 2 day
where c.created_at < now() - interval 2 day
and e.id is null
group by c.id, c.created_at;
If you have any control over it I would advise against dynamically-generated table names!

Wrong data output in SQL request

I have a table named payments
CREATE TABLE payments (
`id` INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
`student_id` INT NOT NULL,
`datetime` DATETIME NOT NULL,
`amount` FLOAT DEFAULT 0,
INDEX `student_id` (`student_id`)
);
It is necessary to create a query that is find all student_id whose sum payment is less than the biggest one. (it can be more than one user with the same biggest amount of payments)
Let assume for instance this is a test data:
== Dumping data for table payments
id-student_id-datetime-amount
|1|4|2015-06-11 00:00:00|2
|2|5|2015-06-01 00:00:00|6
|3|1|2015-06-03 00:00:00|8
|4|2|2015-06-02 00:00:00|9
|5|4|2015-06-09 00:00:00|6
|6|5|2015-06-06 00:00:00|3
|7|2|2015-06-05 00:00:00|6
|8|3|2015-06-09 00:00:00|12
|14|1|2015-06-01 00:00:00|0
|15|1|2015-06-03 00:00:00|7
|16|6|2015-06-02 00:00:00|0
|17|6|2015-06-07 00:00:00|0
|18|6|2015-06-05 00:00:00|0
Next query shows all students with their sum payments
SELECT `student_id`, SUM(amount) as `sumamount`
FROM `payments`
GROUP BY `student_id`
ORDER BY `sumamount` DESC
Here is write output of this query ordered by sumamount
student_id sumamount
1 15
2 15
3 12
5 9
4 8
6 0
BUT the problem is when I try to get the user who paid less than the biggest one it gives me the wrong answer
Here is the query to get the second user:
SELECT `student_id`, SUM(amount) as `sumamount`
FROM `payments`
GROUP BY `student_id`
HAVING `sumamount` < MAX(sumamount)
ORDER BY `sumamount` DESC
Here is the result
student_id sumamount
3 12
4 8
6 0
As we can see student_id = 5 missed and I have no idea why.
You need to calcualate MAX(sumamount) in a subquery, so that MAX is not grouped by student_id.
SELECT `student_id`, SUM(amount) as `sumamount`, maxsum
FROM `payments`
CROSS JOIN (SELECT MAX(sumamount) AS maxsum
FROM (SELECT SUM(amount) AS sumamount
FROM payments
GROUP BY student_id) t1) t2
GROUP BY `student_id`
HAVING `sumamount` < maxsum
ORDER BY `sumamount` DESC
DEMO

Limit count in sql

I have a query that looks like the below
SELECT
venueid as VENUES, venue2.venue AS LOCATION,
(SELECT COUNT(*) FROM events WHERE (VENUES = venueid) AND eventdate < CURDATE()) AS number
FROM events
INNER JOIN venues as venue2 ON events.venueid=venue2.id
GROUP BY VENUES
ORDER BY number DESC
I want to limit the count to count the last 5 rows in the table (sorting by id) however when I add a limt 0,5 the results don't seem to change. When counting where do you add in the limit to limit the amount of rows that are being counted?
CREATE TABLE venues (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
venue VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE categories (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
category VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE events (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
eventdate DATE NOT NULL,
title VARCHAR(255),
venueid INT,
categoryid INT
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
INSERT INTO venues (id, venue) VALUES
(1, 'USA'),
(2, 'UK'),
(3, 'Japan');
INSERT INTO categories (id, category) VALUES
(1, 'Jazz'),
(2, 'Rock'),
(3, 'Pop');
INSERT INTO events (id, eventdate, title, venueid, categoryid) VALUES
(1,20121003,'Title number 1',1,3),
(2,20121010,'Title number 2',2,1),
(3,20121015,'Title number 3',3,2),
(4,20121020,'Title number 4',1,3),
(5,20121022,'Title number 5',2,1),
(6,20121025,'Title number 6',3,2),
(7,20121030,'Title number 7',1,3),
(8,20121130,'Title number 8',1,1),
(9,20121230,'Title number 9',1,2),
(10,20130130,'Title number 10',1,3);
The expected result should look like the below
|VENUES |LOCATION |NUMBER |
|1 | USA | 3 |
|2 | UK | 1 |
|3 | Japan | 1 |
As of the time of posting id 9,8,7,6,5 are the last 5 events before the current date.
See SQL Fiddle link below for full table details.
http://sqlfiddle.com/#!2/21ad85/32
This query gives you the five rows that you are trying to group and count:
SELECT *
FROM events
WHERE eventdate < CURDATE()
ORDER BY eventdate DESC
LIMIT 5
Now you can use this query as a subquery. You can join with the result of a subquery just as if it were an ordinary table:
SELECT
venueid as VENUES,
venue2.venue AS LOCATION,
COUNT(*) AS number
FROM
(
SELECT *
FROM events
WHERE eventdate < CURDATE()
ORDER BY eventdate DESC
LIMIT 5
) AS events
INNER JOIN venues as venue2 ON events.venueid=venue2.id
GROUP BY VENUES
ORDER BY number DESC
http://sqlfiddle.com/#!2/21ad85/37

SELECT newest record of any GROUP of records (ignoring records with one record)

Having trouble with a query to return the newest order of any grouped set of orders having more than 1 order. CREATE & INSERTs for the test data are below.
This query returns the unique customer id's I want to work with, along with the grouped order_id's. Of these records, I only need the most recent order (based on date_added).
SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
mysql> SELECT COUNT(customer_id), customer_id, GROUP_CONCAT(order_id) FROM orderTable GROUP BY customer_id HAVING COUNT(customer_id)>1 LIMIT 10;
+--------------------+-------------+------------------------+
| COUNT(customer_id) | customer_id | GROUP_CONCAT(order_id) |
+--------------------+-------------+------------------------+
| 2 | 0487 | F9,Z33 |
| 3 | 1234 | 3A,5A,88B |
+--------------------+-------------+------------------------+
2 rows in set (0.00 sec)
I'm looking for order Z33 (customer_id 0487) and 3A (customer_id 1234).
For clarification, I do not want orders for customers that have only ordered once.
Any help or tips to get me pointed in the right direction appreciated.
Sample table data:
--
-- Table structure for table orderTable
CREATE TABLE IF NOT EXISTS orderTable (
customer_id varchar(10) NOT NULL,
order_id varchar(4) NOT NULL,
date_added date NOT NULL,
PRIMARY KEY (customer_id,order_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Dumping data for table orderTable
INSERT INTO orderTable (customer_id, order_id, date_added) VALUES
('1234', '5A', '1997-01-22'),
('1234', '88B', '1992-05-09'),
('0487', 'F9', '2002-01-23'),
('5799', 'A12F', '2007-01-23'),
('1234', '3A', '2009-01-22'),
('3333', '7FHS', '2009-01-22'),
('0487', 'Z33', '2004-06-23');
==========================================================
Clarification of the query.
The question was to only include those customers that had more... hence my query has it INSIDE with the GROUP BY... This way it ONLY GIVES the customer in question that HAD multiple orders, but at the same time, only gives the most recent date OF the last order for the person... Then the PreQuery is re-joined to the orders table by the common customer ID, but only for the order that matches the last date as detected in the prequery. If a customer only had a single order, its inner PreQuery count would have only been 1 and thus excluded from the final PreQuery result set.
select ot.*
from
( select
customer_id,
max( date_added ) as LastOrderDate,
from
orderTable
having
count(*) > 1
group by
customer_id ) PreQuery
join orderTable ot
on PreQuery.Customer_ID = ot.Customer_ID
and PreQuery.LastOrderDate = ot.date_added