SAP HANA: days between two Orders - mysql

Here is the table ihave, i was trying days between by joining the same table with left join and group by with min difference. I was not so successful.
Customer|Order|Date
1 | 1 |Date1
1 | 2 |Date2
1 | 3 |Date3
1 | 4 |Date4
2 | 1 |Date1
2 | 2 |Date3
2 | 3 |Date6
3 | 1 |Date3
3 | 2 |Date5
Required is:
Customer|Order|Date |diff
1 | 1 |Date1| 0
1 | 2 |Date2| days_betwen(Date2, Date1)
1 | 3 |Date3| days_betwen(Date3, Date2)
1 | 4 |Date4| days_betwen(Date4, Date3)
2 | 1 |Date1| 0
2 | 2 |Date3| days_betwen(Date3, Date1)
2 | 3 |Date6| days_betwen(Date6, Date3)
3 | 1 |Date3| 0
3 | 2 |Date5| days_betwen(Date5, Date3)
I need suggestion with the logic part!
EDIT: What if the order numbers are not sequential?

In first, you need to join the table to itself by Customer and Order fields. Then use DATEDIFF() function to get days number between two dates.
If the Order column is numbered sequentially then solution is simplest:
SELECT
cur.`Customer` AS `Customer`,
cur.`Order` AS `Order`,
cur.`Date` AS `Date`,
DATEDIFF(cur.`Date`, IFNULL(prv.`Date`, cur.`Date`)) AS `DaysPassed`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` = prv.`Order`+ 1;
If the Order column is not numbered sequentially, but next Order value is greater than previous, then you could use greater than or less than operators. Use GROUP BY clause and an aggregate function to return single row for each order. Note, maybe it will be long!
SELECT
comb.`Customer` AS `Customer`,
comb.`curOrder` AS `Order`,
comb.`curDate` AS `Date`,
DATEDIFF(comb.`curDate`, IFNULL(pr.`Date`, comb.`curDate`)) AS `DaysPassed`
FROM
(SELECT
cur.`Customer` AS `Customer`, cur.`Order` AS curOrder, cur.`curDate` AS `Date`, max(prv.`Order`) AS `prvOrder`
FROM
MyTable cur
LEFT JOIN
MyTable prv
ON cur.`Customer` = prv.`Customer` AND cur.`Order` > prv.`Order`
GROUP BY cur.`Order`, cur.`Customer`) comb
LEFT JOIN
MyTable pr
ON pr.`Customer` = comb.`Customer` AND pr.`Order` = comb.prvOrder;
If you use random order number, then it is possible to use Date column instead of Order in the comb subquery to join records by nearest order dates of same customer.
Good luck!

Related

How to fetch rows from which sum of a single integer/float column sums upto a certain value

I have a table. It has the following structure
goods_receiving_items
id
item_id
quantity
created_at
I am trying to fetch rows against which have the following conditions
Has one item_id
When the sum of the quantity column equals a certain value
So for example I have the following data
+----+---------+----------+------------+
| id | item_id | quantity | created_at |
+----+---------+----------+------------+
| 1 | 2 | 11 | 2019-10-10 |
| 2 | 3 | 110 | 2019-10-11 |
| 3 | 2 | 20 | 2019-11-09 |
| 4 | 2 | 5 | 2019-11-10 |
| 5 | 2 | 1 | 2019-11-11 |
+----+---------+----------+------------+
I have tried the following query:
SET #sum:= 0;
SELECT item_id, created_at, (#sum:= #sum + quantity) AS SUM, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
ORDER BY created_at DESC
If I don't use ORDER BY, then the query will give me ID '1'. But if I use ORDER BY it will return all the rows with item_id = 2.
What should be returned are IDs '5' and '4' exclusively in this order
I can't seem to resolve this and ORDER BY is essential to my task.
Any help would be appreciated
You should use the order by on the resulting set
you could do this using a subquery
SET #sum:= 0;
select t.*
from t (
SELECT item_id
, created_at
, (#sum:= #sum + quantity) as sum
, quantity
FROM goods_receiving_items
WHERE item_id = 2 AND #sum<= 6
) t
ORDER BY created_at DESC
You should try an INNER JOIN with SELECT min(created_at) or SELECT max(created_at)
From MYSQL docs:
...the selection of values from each group cannot be influenced by
adding an ORDER BY clause. Sorting of the result set occurs after
values have been chosen, and ORDER BY does not affect which values the
server chooses.
The answers on the following might help in more detail: MYSQL GROUP BY and ORDER BY not working together as expected
After searching around, I have made up the following query
SELECT
t.id, t.quantity, t.created_at, t.sum
FROM
( SELECT
*,
#bal := #bal + quantity AS sum,
IF(#bal >= $search_number, #doneHere := #doneHere + 1 , #doneHere) AS whereToStop
FROM goods_receiving_items
CROSS JOIN (SELECT #bal := 0.0 , #doneHere := 0) var
WHERE item_id = $item_id
ORDER BY created_at DESC) AS t
WHERE t.whereToStop <= 1
ORDER BY t.created_at ASC
In the above query, $search_number is a variable that holds the value that has to be reached. $item_id is the item we are searching against.
This will return all rows for which the sum of the column quantity makes up the required sum. The sum will be made with rows in descending order by created_at and then will be rearranged in ascending order.
I was using this query to calculate the cost when a certain amount of items are being used in an inventory management system; so this might help someone else do the same. I took most of the query from another question here on StackOverflow

Finding time difference based on distinct ids in mySQL

I would like to find the day difference between the latest and the 2nd latest distinct order_id for each user.
The intended output would be:
user_id | order_diff
1 | 1
3 | 7
8 | 1
order_diff represents the difference in days between 2 distinct order_id. In the event that there are no two distinct order_id (as in the case for user id 9), the result is not returned.
In this case, the order_diff for user_id 1 is 1 since the day difference between his 2 distinct order_id is 1. However, there is no order_diff for user_id 9 since he has no 2 distinct `order_id'.
This is the dataset:
user_id order_id order_time
1 208965785 2016-12-15 17:14:13
1 201765785 2016-12-14 17:19:05
1 203932785 2016-12-13 20:41:30
1 209612785 2016-12-14 20:14:32
1 208112785 2016-12-14 20:27:08
1 205525785 2016-12-14 17:01:26
1 208812785 2016-12-14 20:18:23
1 206432785 2016-12-11 20:32:20
1 206698785 2016-12-14 10:50:15
2 209524795 2016-11-26 18:06:21
3 206529925 2016-10-01 10:43:57
3 203729925 2016-10-08 10:43:11
4 204876145 2016-09-24 10:23:49
5 203363157 2016-07-13 23:56:43
6 207784875 2017-01-04 12:21:21
7 206437177 2016-06-25 02:40:33
8 202819645 2016-09-09 11:47:27
8 202819645 2016-09-09 11:47:27
8 202819646 2016-09-08 11:47:27
9 205127187 2016-06-05 22:21:18
9 205127187 2016-06-05 22:21:18
11 207874877 2016-06-17 16:49:44
12 204927595 2016-11-28 23:05:40
This is the code that I am currently using:
SELECT e1.user_id,datediff(e1.order_time,e2.time), e1.order_id FROM
sales e1
JOIN
sales e2
ON
e1.user_id=e2.user_id
AND
e1.order_id = (SELECT distinct order_id FROM sales temp1 WHERE temp1.order_id =e1.order_id ORDER BY order_time DESC LIMIT 1)
AND
e2.order_id = (SELECT distinct order_id FROM sales temp2 WHERE temp2.order_id=e2.order_id ORDER BY order_time DESC LIMIT 1 OFFSET 1)
My output does not produce the desired output and it also ignores the cases where order_ids are the same.
Edit: I would also like the query to be extended to larger datasets where the 2nd most recent order_time may not be the min(order_time)
Based on your fiddle:
select user_id,
datediff(max(order_time),
( -- Scalar Subquery to get the 2nd largest order_time
select max(order_time)
from orders as o2
where o2.user_id = o.user_id -- same user
and o2.order_time < max(o.order_time) -- but not the max time
)
) as diff
from orders as o
group by user_id
having diff is not null -- if there's no 2nd largest time diff will be NULL
Following would work:
Schema (MySQL v5.7)
CREATE TABLE orders
(`user_id` int, `order_id` int, `order_time` datetime)
;
INSERT INTO orders
(`user_id`, `order_id`, `order_time`)
VALUES
(1,208965785,'2016-12-15 17:14:13'),
(1,201765785,'2016-12-14 17:19:05'),
(1,203932785,'2016-12-13 20:41:30'),
(1,209612785,'2016-12-14 20:14:32'),
(1,208112785,'2016-12-14 20:27:08'),
(1,205525785,'2016-12-14 17:01:26'),
(1,208812785,'2016-12-14 20:18:23'),
(1,206432785,'2016-12-11 20:32:20'),
(1,206698785,'2016-12-14 10:50:15'),
(2,209524795,'2016-11-26 18:06:21'),
(3,206529925,'2016-10-01 10:43:57'),
(3,203729925,'2016-10-08 10:43:11'),
(4,204876145,'2016-09-24 10:23:49'),
(5,203363157,'2016-07-13 23:56:43'),
(6,207784875,'2017-01-04 12:21:21'),
(7,206437177,'2016-06-25 02:40:33'),
(8,202819645,'2016-09-09 11:47:27'),
(8,202819645,'2016-09-09 11:47:27'),
(8,202819646,'2016-09-08 11:47:27'),
(9,205127187,'2016-06-05 22:21:18'),
(9,205127187,'2016-06-05 22:21:18'),
(11,207874877,'2016-06-17 16:49:44'),
(12,204927595,'2016-11-28 23:05:40');
Query #1
SELECT dt2.user_id,
MIN(datediff(dt2.latest_order_time,
dt2.second_latest_order_time)) AS order_diff
FROM (
SELECT o.user_id,
o.order_time AS latest_order_time,
(SELECT o2.order_time
FROM orders AS o2
WHERE o2.user_id = o.user_id AND
o2.order_id <> o.order_id
ORDER BY o2.order_time DESC LIMIT 1) AS second_latest_order_time
FROM orders AS o
JOIN (SELECT user_id, MAX(order_time) AS latest_order_time
FROM orders
GROUP BY user_id) AS dt
ON dt.user_id = o.user_id AND
dt.latest_order_time = o.order_time
) AS dt2
WHERE dt2.second_latest_order_time IS NOT NULL
GROUP BY dt2.user_id;
| user_id | order_diff |
| ------- | ---------- |
| 1 | 1 |
| 3 | 7 |
| 8 | 1 |
View on DB Fiddle
Details:
We determine maximum order_time for a user_id in a sub-select query (Derived Table). We can alias it as latest_order_time.
We Join this result-set to the orders table. This will help us in considering only the row(s) with maximum value of order_time for a user_id.
Now, we use a Correlated Subquery to determine the maximum order_time value for the same user, out of the rest of order_id value(s). We can alias it as second_latest_order_time.
Finally, use this as a Derived Table again, and remove all the cases where second_latest_order_time is null, and calculate datediff() for the rest.
A final Group By is needed, as your data has multiple entries for a
Here is the solution:
SELECT user_id,
DATEDIFF(MAX(order_time), MIN(order_time)) as order_diff
FROM orders
GROUP BY user_id
HAVING order_diff > 0;
Here is a link to test it.

Combining 2 SUMS in MySQL and optimising query

I have the below code which works:
SELECT admin_teams.name,
SUM(temp_orders.amount_paid) as amount,
SUM(instalments.amount) as amount2
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
(DATE(temp_orders.date_paid) = CURDATE()
OR DATE(instalments.date_paid) = CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
It produces a table that looks like:
+-------------+--------+---------+
| name | amount | amount2 |
+-------------+--------+---------+
| team name 1 | 100 | 150 |
| team name 2 | 200 | 250 |
| team name 3 | 300 | 175 |
+-------------+--------+---------+
I have two issues;
I actually only want one column which is the sum of amount and amount2.
The query is VERY slow - this took 190 sec to run.
I did have it almost working with a Union which was almost instant - I couldn't however get it fully working because the number of columns in my first select statement will not match those in the second - the table 'instalments' does not have a team column but the table temp_orders does.
Can anyone help with either problem?
Thanks.
SELECT admin_teams.name,
(SUM(temp_orders.amount_paid) + SUM(instalments.amount)) as amount,
FROM temp_orders
LEFT JOIN admin_teams
ON admin_teams.id = temp_orders.team
LEFT JOIN instalments
ON instalments.order_id = temp_orders.order_id
WHERE
temp_orders.date_paid >= CURDATE()
OR instalments.date_paid >= CURDATE())
AND (temp_orders.pay_status = 4
OR instalments.pay_status = 4)
GROUP BY temp_orders.team
ORDER BY temp_orders.team ASC
LIMIT 5
And add these indexes
ALTER TABLE temp_orders ADD KEY (date_paid ,pay_status,team);
ALTER TABLE instalments ADD KEY (date_paid ,pay_status);

Mysql count with case when statement

Consider:
SELECT(count(c.id),
case when(count(c.id) = 0)
then 'loser'
when(count(c.id) BETWEEN 1 AND 4)
then 'almostaloser'
when(count(c.id) >= 5)
then 'notaloser'
end as status,
...
When all is said and done, the query as a whole produces a set of results that look similar to this:
Count | status
--------|-------------
2 | almostaloser //total count is between 2 and 4
--------|-------------
0 | loser // loser because total count = 0
--------|-------------
3 | almostaloser //again, total count between 2 and 4
--------|-------------
What I would like to achieve:
a method to reatain the information from the above table, but add a third column that will give a total count of each status, something like
select count(c.id)
case when(count(c.id) = 0 )
then loser as status AND count how many of the total count does this apply to
results would look similar to:
Count | status |total_of each status |
--------|-------------|---------------------|
2 | almostaloser| 2 |
--------|-------------|---------------------|
0 | loser | 1 |
--------|-------------|---------------------|
3 | almostaloser| 2 |
--------|-------------|----------------------
I've been told this could be achieved using a derived table, but i've not yet been able to get them both, only one or the other.
This can be achieved with this query (you must place your original query as subquery in two places):
SELECT t1.*, t2.total_of_each_status
FROM (
-- put here your query --
) t1
INNER JOIN (
SELECT status, count(*) AS total_of_each_status
FROM (
-- put here your query --
) t2
GROUP BY status
) t2 ON t2.status = t1.status

Count new and returning items by year

Following up on the question "Select new or returning items for a specified year", I would now like to create statistics on how many items per year are new or returning. Here's my example table:
+---------------------+----------+
| date | item |
+---------------------+----------+
| 2008-11-30 11:15:59 | Plums |
| 2012-11-08 19:42:37 | Lemons |
| 2013-01-30 18:58:07 | Apples |
| 2013-02-12 13:44:45 | Pears |
| 2014-06-08 11:46:48 | Apples |
| 2014-09-01 20:28:03 | Oranges |
+---------------------+----------+
I'm looking for a query which will return the amount of items that have not appeared in previous years, and the amount of items that have already appeared in previous years.
The result should look something along the lines of this:
Year New Items Returning Items
2008 1 0
2012 1 0
2013 2 0
2014 1 1
How can this be achieved?
Well, using the queries you are linking to, I came up with the following (SQL Fiddle):
SELECT m1.UniqYear,
CASE WHEN m2.NewItems IS NULL THEN 0 ELSE m2.NewItems END AS NewItems,
CASE WHEN m3.ReturningItems IS NULL THEN 0 ELSE m3.ReturningItems END AS ReturningItems
FROM
(
SELECT DISTINCT YEAR(s.date) AS UniqYear
FROM MyTable s
) m1 LEFT JOIN
(
SELECT YEAR(s1.date) AS Year, COUNT(*) AS NewItems
FROM MyTable s1
LEFT JOIN MyTable s2 ON s1.item = s2.item AND YEAR(s2.date) < YEAR(s1.date)
WHERE s2.date IS NULL
GROUP BY YEAR(s1.date)
) m2 ON m1.UniqYear = m2.Year
LEFT JOIN
(
SELECT YEAR(s3.date) AS Year, COUNT(*) AS ReturningItems
FROM MyTable s3
INNER JOIN MyTable s4 ON s3.item = s4.item
WHERE YEAR(s4.date) < YEAR(s3.date)
GROUP BY YEAR(s3.date)
) m3 ON m1.UniqYear = m3.Year
ORDER BY m1.UniqYear;
I am using the CASE statements to return 0 instead off null. The first sub query (m1) is used to get the distinct years. The second sub query (m2) is used to get the New Items count. The third sub query (m3) is used to get the Returning Items count. Notice I had to add a group by statement to get the count per year. I had to LEFT JOINed m2 and m3 to m1 because you are not always going to have a NewItems count or a Returning Items for every year.