how to differentiate columns based on if else in sql - mysql

i have a order table with columns scheduled_arrival_date and actual_arrival_date , i need to find out which order delivered early and late and i need to differentiate orders based on that(two columns should be created ) , as i don't have permission to create new table so i need get this from query.
i tried this, but am getting in single column
SELECT order.id,order.item,
CASE WHEN scheduled_arrival_date >= actual_arrival_date
THEN 'late'
WHEN scheduled_arrival_date <= actual_arrival_date
THEN 'Early'
END as delivered
FROM order

SELECT order.id, order.item, 'late' AS late FROM order WHERE scheduled_arrival_date > actual_arrival_date
UNION ALL
SELECT order.id, order.item, 'early' AS early FROM order WHERE scheduled_arrival_date <= actual_arrival_date
This query is definely not optimized, but it will work. The first query gather all late shippings and put 'late' into the late column. The second one do the exact same trick but with early, and we union all of the result together.
However, i don't really understand why you want two columns, it looks weird this way, your initial request was much better IMO.
PS: Note that you should not use <= and >= in both of your condition, there is a conflit for the equal case

As you want to create a pie diagram it seems to me that you are only interested in the numbers (counts) of the two cases. You can do that like this:
SELECT COUNT(CASE WHEN actual_arrival_date-scheduled_arrival_date>0 THEN 1 END) late,
COUNT(CASE WHEN actual_arrival_date-scheduled_arrival_date<=0 THEN 1 END) early
FROM order

Related

To find the date difference using derived column in mysql

Goal: I want to create a report showing each member experience since joining date with case condition showing 3 categories.
I managed to get the experience(in days) from joining date of the member. trying to derive new column categories say low, med and high. I have tried the below query but it is not working, The below query works for non-date values in reg_date column.
select m.member_id, m.first_name, m.last_name, m.store_id,
(case when m.reg_date<='2018-04-01' then "low"
case when m.reg_date>'2018-04-01' and m.reg_date<='2018-07-31' then "med"
case when m.reg_date>'2018-07-31' then "high"
end, '%y%m%d') as category from member m;
multiple bugs:
syntax should be (case when ... when ... when ...) instead of case for each 'when'.
second argument you pass '%y%m%d' is not necessary.
not a bug, but for the sake of clarity, consider using "else" for the last case.
(case
when m.reg_date<='2018-04-01' then "low"
when m.reg_date>'2018-04-01' and m.reg_date<='2018-07-31' then "med"
else "high"
end)

MySQL - get users who placed 25th order during period

I have users and orders tables with this structure (simplified for question):
USERS
userid
registered(date)
ORDERS
id
date (order placed date)
user_id
I need to get array of users (array of userid) who placed their 25th order during specified period (for example in May 2019), date of 25th order for each user, number of days to place 25th order (difference between registration date for user and date of 25th order placed).
For example if user registered in April 2018, then placed 20 orders in 2018, and then placed 21-30th orders in Jan-May 2019 - this user should be in this array, if he placed 25th (overall for his account) order in May 2019.
How I can do this with MySQL request?
Sample data and structure: http://www.sqlfiddle.com/#!9/998358 (for testing you can get 3rd order as ex., not 25th, to not add a lot of sample data records).
One request is not required - if this can't be done in one request, few is possible and allowed.
You can use a correlated subquery to get the count of orders placed before the current one by a user. If that's 24 the current order is the 25th. Then check if the date is in the desired range.
SELECT o1.user_id,
o1.date,
datediff(o1.date, u1.registered)
FROM orders o1
INNER JOIN users u1
ON u1.userid = o1.user_id
WHERE (SELECT count(*)
FROM orders o2
WHERE o2.user_id = o1.user_id
AND o2.date < o1.date
OR o2.date = o1.date
AND o2.id < o1.id) = 24
AND o1.date >= '2019-01-01'
AND o1.date < '2019-06-01';
The basic inefficient way of doing this would be to get the user_id for every row in ORDERS where the date is in your target range AND the count of rows in ORDERS with the same user_id and a lower date is exactly 24.
This can get very ugly, very quickly, though.
If you're calling this from code you control, can't you do it from the code?
If not, there should be a way to assign to each row an index describing its rank among orders for its specific user_id, and select from this all user_id from rows with an index of 25 and a correct date. This will give you a select from select from select, but it should be much faster. The difficulty here is to control the order of the rows, so here are the selects I envision:
Select all rows, order by user_id asc, date asc, union-ed to nothing from a table made of two vars you'll initialize at 0.
from this, select all while updating a var to know if a row's user_id is the same as the last, and adding a field that will report so (so for each user_id the first line in order will have a specific value like 0 while the other rows for the same user_id will have a 1)
from this, select all plus a field that equals itself plus one in case the first added field is 1, else 0
from this, select the user_id from the rows where the second added field is 25 and the date is in range.
The union thingy is only necessary if you need to do it all in one request (you have to initialize them in a lower select than the one they're used in).
Edit: Well if you need the date too you can just select it along with the user_id, but calculating the number of days in sql will be a pain. Just join the result table to the users table and get both the date of 25th order and their date of registration, you'll surely be able to do the difference in code.
I'll try building an actual request, however if you want to truly understand what you need to make this you gotta read up on mysql variables, unions, and conditional statements.
"Looks too complicated. I am sure that this can be done with current DB structure and 1-2 requests." Well, yeah. Use the COUNT request, it will be easy, and slow as hell.
For the complex answer, see http://www.sqlfiddle.com/#!9/998358/21
Since you can use multiple requests, you can just initialize the vars first.
It isn't actually THAT complicated, you just have to understand how to concretely express what you mean by "an user's 25th command" to a SQL engine.
See http://www.sqlfiddle.com/#!9/998358/24 for the difference in days, turns out there's a method for that.
Edit 5: seems you're going with the COUNT method. I'll pray your DB is small.
Edit 6: For posterity:
The count method will take years on very large databases. Since OP didn't come back, I'm assuming his is small enough to overlook query speed. If that's not your case and let's say it's 10 years from now and the sqlfiddle links are dead; here's the two-queries solution:
SET #PREV_USR:=0;
SELECT user_id, date_ FROM (
SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
#RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE #RANK_USR+1 END) AS RANK FROM (
SELECT orders.*, CASE WHEN #PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
#PREV_USR:=user_id AS IGNORE_USR FROM
orders
ORDER BY user_id ASC, date_ ASC, id ASC
) AS DERIVED_1
) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;
Just change RANK = ? and the conditions to fit your needs. If you want to fully understand it, start by the innermost SELECT then work your way high; this version fuses the points 1 & 2 of my explanation.
Now sometimes you will have to use an API or something and it wont let you keep variable values in memory unless you commit it or some other restriction, and you'll need to do it in one query. To do that, you put the initialization one step lower and make it so it does not affect the higher statements. IMO the best way to do this is in a UNION with a fake table where the only row is excluded. You'll avoid the hassle of a JOIN and it's just better overall.
SELECT user_id, date_ FROM (
SELECT user_id, date_, SAME_USR AS IGNORE_SMUSR,
#RANK_USR:=(CASE SAME_USR WHEN 0 THEN 1 ELSE #RANK_USR+1 END) AS RANK FROM (
SELECT DERIVED_4.*, CASE WHEN #PREV_USR = user_id THEN 1 ELSE 0 END AS SAME_USR,
#PREV_USR:=user_id AS IGNORE_USR FROM
(SELECT * FROM orders
UNION
SELECT * FROM (
SELECT (#PREV_USR:=0) AS INIT_PREV_USR, 0 AS COL_2, 0 AS COL_3
) AS DERIVED_3
WHERE INIT_PREV_USR <> 0
) AS DERIVED_4
ORDER BY user_id ASC, date_ ASC, id ASC
) AS DERIVED_1
) AS DERIVED_2
WHERE RANK = 25 AND YEAR(date_) = 2019 AND MONTH(date_) = 4 ;
With that method, the thing to watch for is the amount and the type of columns in your basic table. Here orders' first field is an int, so I put INIT_PREV_USR in first then there are two more fields so I just add two zeroes with names and call it a day. Most types work, since the union doesn't actually do anything, but I wouldn't try this when your first field is a blob (worst comes to worst you can use a JOIN).
You'll note this is derived from a method of pagination in mysql. If you want to apply this to other engines, just check out their best pagination calls and you should be able to work thinks out.

Calculate difference in Dates for each cell with next cell

I have a Data as follows:
Order_id Created_on Comment
1 20-07-2015 18:35 Order Placed by User
1 20-07-2015 18:45 Order Reviewed by the Agent
1 20-07-2015 18:50 Order Dispatched
2 20-07-2015 18:36 Order Placed by User
And I am trying to find the difference between the
first and second Date
Second and third Date for each Order. How Do i Obtain this through a SQL query?
SQL is about horizontal relations - vertical relations do not exist. To a relational database they're just 2 rows, stored somewhere on a disk, and until you apply ordering to a result set the 'first and second' are just 2 randomly picked rows.
In specific cases it's possible to calculate the time difference within SQL, but rarely a good idea for performance reason, as it requires costly self-joins or subqueries. Just selecting the right data in the right order and then calculating the differences during postprocessing in C#/PHP/whatever is far more practical and faster.
I think you can use a query like this:
SELECT t1.Order_id, t1.Created_on, TIMEDIFF(mi, t1.Created_on, COALESCE(MIN(t2.Created_on), t1.Created_on)) AS toNextTime
FROM yourTable t1
LEFT JOIN yourTable t2 ON t1.Order_id = t2.Order_id AND t1.Created_on < t2.Created_on
GROUP BY t1.Order_id, t1.Created_on
Posting this even though another answer has been accepted already - and I don't disagree with the accepted answer - but there is in fact a fairly neat way to do this with mySQL variables.
This query will give you the time between stages in minutes - it can't be expressed as a datetime as it's an interval between two dates:
SELECT
Order_id,
Created_on,
Comment,
if (#last_id = Order_id, TIMESTAMPDIFF(MINUTE, #last_date, Created_on), 0) as StageMins,
#last_id := Order_id,
#last_date := Created_on
FROM tblData
ORDER BY Order_id, Created_on;
SQL Fiddle here: http://sqlfiddle.com/#!9/6ffdd/10
Info on mySQL TIMESTAMPDIFF function here: https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_timestampdiff

Combine multiple room availability queries into one

I'm currently trying to optimize an database by combining queries. But I keep hitting dead ends while optimizing an room availability query.
I have a room availability table where each records states the available number of rooms per date. It's formatted like so:
room_availability_id (PK)
room_availability_rid (fk_room_id)
room_availability_date (2011-02-11)
room_availability_number (number of rooms available)
The trouble is getting a list of rooms that are available for EACH of the provided days. When I use IN() like so:
WHERE room_availability_date IN('2011-02-13','2011-02-14','2011-02-15')
AND room_availability_number > 0
If the 14th has availability 0 it still gives me the other 2 dates. But I only want that room_id when it is available on ALL three dates.
Please tell me there is a way to do this in MySQL other than querying each date/room/availability combination separately (that is what is done now :-( )
I tried all sorts of combinations, tried to use room_availability_date = ALL (...), tried some dirty repeating subqueries but to no avail.
Thank you in advance for any thoughts!
You would need to construct a query to group on the room ID and then check that there is availability on each date, which can be done using the having clause. Leaving the where clause predicate in for room_availability_date will help to keep the query efficient (as indexes etc. can't be used with a having clause easily).
SELECT
room_availability_rid
WHERE room_availability_date IN ('2011-02-13','2011-02-14','2011-02-15')
AND room_availability_number > 0
GROUP BY room_availability_rid
HAVING count(case room_availability_date when '2011-02-13' THEN 1 END) > 0
AND count(case room_availability_date when '2011-02-14' THEN 1 END) > 0
AND count(case room_availability_date when '2011-02-15' THEN 1 END) > 0
I think I can improve on a'r's answer:
SELECT
room_availability_rid, count(*) n
WHERE room_availability_date IN ('2011-02-13','2011-02-14','2011-02-15')
AND room_availability_number > 0
GROUP BY room_availability_rid
HAVING n=3
Edit: This of course assumes that there is only one table entry per room per day. Is this a valid assumption?
You can group by room ID, generate a list of dates available, and then see if all the dates you need are included.
This will give you a list of dates each room is available:
select `room_availability_rid`,group_concat(`room_ availability_date`) as `datelist`
from `table` where room_availability_number>0
group by `room_availability_rid`
Then we can add a having clause to get the rooms that are available on all of the dates we need:
select `room_availability_rid`,group_concat(`room_ availability_date`) as `datelist`
from `table` where room_availability_number>0
group by `room_availability_rid`
having find_in_set('2011-02-13',`datelist`) and
find_in_set('2011-02-14',`datelist`) and
find_in_set('2011-02-15',`datelist`)
This should work. Test it for me will ya? :)

Count changes of a value over time?

again I am stuck with counting something in MySQL. The database structure is far from SOers´d call optimal, but nevertheless I do not have an influence here and have to live with it. Probably that´s one of the reasons why I need help again to get some information out of it :)
Assume I have:
some_id (not the PK of the table, not unique),
year, month (no date fields just two integer fields),
some_flag (character that is either A or B) .
Now I´d like to know how often some_flag has changed (in a given time span). The time span is not utterly important in the first approach, I just need to know how many changes happened. Note that changes can only happen monthly. My query:
SELECT some_id,year,some_flag FROM mytable
WHERE some_flag = "A" OR someflag = "B"
AND year > 2005
GROUP BY some_id,some_flag
HAVING COUNT(DISTINCT some_flag) > 1
returns an empty result set. What´s wrong with it? I am sure there are years in which the flag changes over months...
Isn't something like
select .... , sum(case when month=month-1 and some_flag != some_flag then 1 else 0 end) as changecount
possible ?
Try this:
SELECT some_flag, COUNT(some_id) FROM mytable
WHERE some_flag = "A" OR someflag = "B"
AND year > 2005
GROUP BY some_flag
HAVING COUNT(some_id) > 1
-Edit-
If you want to see a month over month count, try this:
(Note: it will only show months where it has changed)
SELECT some_flag, year, month, COUNT(some_id) FROM mytable
WHERE some_flag = "A" OR someflag = "B"
AND year > 2005
GROUP BY some_flag, month, year
HAVING COUNT(some_id) > 1
It looks to me like you need to do this in two parts.
First, execute this SQL query to get all of the values for some_id, some_flag:
SELECT some_id, some_flag, year, month
FROM ...
WHERE year > 2005
ORDER BY some_id, some_flag, year, month
Then, run the output through a match / merge process to detect when some_flag changes for a given some_id. Save the year and month that some_flag changes for reporting in the match / merge process.
When grouping by some_flag you're making COUNT(DISTINCT some_flag) to be always 1.
Try gouping only by some_id. I hope this helps.