MySQL keep type in case statement - mysql

I have a field that changes based on some input:
SELECT
CASE input
WHEN 1 THEN id
WHEN 2 THEN created_at
WHEN 3 THEN some_varchar_field
END as test
FROM ...
ORDER BY 1 ASC;
If I were to order by this field any numeric field isn't sorted properly, I noticed that the order by doesn't keep the type of the field that was selected rather, as soon as there is a VARCHAR/DATETIME field it will return that type rather then keep the type of the column itself. I was just wondering if there was a way to keep the type of the record that was selected.
Edit: Casting field as an int CAST(id as INT) did not work
Putting the case statement in the order by did not work as well.

I think on this solution, isn't pretty but should work.
SQL Fiddle Demo
Create a new column for each of your fields with the order on it.
SELECT CASE 1
WHEN 1 THEN id
WHEN 2 THEN created_at
WHEN 3 THEN scomment
END as field_name
FROM (
SELECT I.id, I.rid,
C.created_at, C.rcreated_at,
S.scomment, S.rscomment
FROM (
SELECT t.id,
#rowid := #rowid + 1 AS rid
FROM test t,
(SELECT #rowid := 0) r
ORDER BY t.id
) I -- add sort column for id
JOIN (
SELECT t.id, created_at,
#rowcreated_at := #rowcreated_at + 1 AS rcreated_at
FROM test t,
(SELECT #rowcreated_at := 0) r
ORDER BY t.created_at
) C -- add sort column for created_at
ON I.id = C.id
JOIN (
SELECT t.id, scomment,
#rowscomment := #rowscomment + 1 AS rscomment
FROM test t,
(SELECT #rowscomment := 0) r
ORDER BY t.scomment
) S -- add sort column for scomment
ON I.id = S.id
) T
ORDER BY CASE 1
WHEN 1 THEN rid
WHEN 2 THEN rcreated_at
WHEN 3 THEN rscomment
END

So after some more research and testing, I was able to find a way to do it, a little bit 'cleaner'.
SELECT id, created_at, scomment
FROM test
ORDER BY
CASE 1 WHEN 1 THEN id END ASC,
CASE 1 WHEN 2 THEN created_at END ASC,
CASE 1 WHEN 3 THEN scomment END ASC;
This will allow me to sort on a specific field correctly.
You could even add different directions by doing the following:
SELECT id, created_at, scomment
FROM test
ORDER BY
CASE p_order_by WHEN 1 THEN AND p_dir = 'asc' id END ASC,
CASE p_order_by WHEN 1 THEN AND p_dir = 'desc' id END DESC,
CASE p_order_by WHEN 2 THEN AND p_dir = 'asc' created_at END ASC,
CASE p_order_by WHEN 2 THEN AND p_dir = 'desc' created_at END DESC,
CASE p_order_by WHEN 3 THEN AND p_dir = 'asc' scomment END ASC,
CASE p_order_by WHEN 3 THEN AND p_dir = 'desc' scomment END DESC;
Where p_order_by is the number field you want to sort, and p_dir is the direction.

Related

Getting the latest row from status

Supposed I have a table with the following rows
test_no
test_count_no
test_status
test_run_no
1
0
STOP
1
2
66
FINISH
1
3
67
FINISH
1
4
0
STOP
2
5
0
STOP
2
I need to get the row which has a test_status of FINISH and has the latest test_no,
for example test_run_no=1 since there is two test_status of FINISH, I only need to get the latest test_no which is 3
In case of test_run_no=2 since there is no test_status=FINISH i only need to return the first row.
The result will be:
test_no
test_count_no
test_status
test_run_no
3
67
FINISH
1
4
0
STOP
2
My current query is
SELECT MAX(test_table.test_run_no)
FROM test_table
WHERE test_table.run_status = 'FINISH'
GROUP BY test_table.test_no
Any idea on what query should I add in order to achieve the result ?
A correlated subquery fits this:
select t.*
from test_table t
where t.test_no = (select t2.test_no
from test_table t2
where t2.test_run_no = t.test_run_no
order by (test_status = 'FINISH') desc,
(case when test_status = 'FINISH' then test_no end) desc,
test_no asc
);
The order by has three keys for your conditions:
Put 'FINISH' first.
For 'FINISH' get the largest test_no.
For others, get the smallest.
This logic can also be expressed using row_number():
select t.*
from (select t.*,
row_number() over (partition by test_run_no
order by (test_status = 'FINISH') desc,
(case when test_status = 'FINISH' then test_no end) desc,
test_no asc
) as seqnum
from test_table t
) t
where seqnum = 1;
What about the following statement:
select * from test t where t.test_status = 'FINISH'
and t.test_no = (select max(t1.test_no) from test t1
where t1.test_run_no = t.test_run_no)
union
select * from test t where t.test_status = 'STOP'
and not exists (select 1 from test t2 where t2.test_run_no = t.test_run_no
and t2.test_status = 'FINISH')
and t.test_no = (select min(test_no) from test t1
where t1.test_run_no = t.test_run_no);
It consists of two SELECT parts. In the first part, for the tests that have a FINISH status, the one with the highest test number is selected.
The second part selects the smallest test number for the tests for which there is no run with the status FINISH.
Is this what you are looking for
SELECT MAX(test_table.test_run_no) FROM test_table
where test_table.run_status = 'FINISH' and test_run_no=1
GROUP BY test_table.test_no
IF ##ROWCOUNT = 0
BEGIN
select MAX(test_table.test_run_no) from test_table GROUP BY test_table.test_no
END
try it :
select * from Table_3 where Table_3.test_count_no = (
select max(test_count_no) from Table_3
) and Table_3.test_status = 'FINISH' and Table_3.test_run_no = 1
union all
select x.test_no, x.test_count_no, x.test_status, x.test_run_no from
(select ROW_NUMBER() over (order by Table_3.test_run_no) as rownum, Table_3.* from Table_3 where Table_3.test_run_no = 2) as x where x.rownum = 1
Use order by desc and use limit 1 to get the exact row.
Like this
SELECT *
FROM test_table
WHERE test_table.test_status = 'FINISH'
ORDER BY test_no desc
LIMIT 1
if you need all the rows
SELECT *
FROM test_table
WHERE test_table.test_status = 'FINISH'
ORDER BY test_no desc
if you need test_no only
SELECT test_table.test_no
FROM test_table
WHERE test_table.test_status = 'FINISH'
ORDER BY test_no desc
LIMIT 1

Mysql session variable emulating row_num does not increase in value

Since MySql does not provide a row_num facility, I was trying to emulate its functionality using session variables (#variable_name) in my query
the problem statement is as follows
Given a table 'product_view' having columns 'productTitle' and 'categoryName',
select the first 2 products from each category whose title matches a provided string
Going by the following query
SELECT
#row_num := CASE
WHEN #cat_name = categoryName THEN #row_num + 1
ELSE 1
END
AS num,
#cat_name := categoryName AS categoryName,
productTitle
FROM product_search
WHERE
cityId = 1 AND
mainStatus= 'Active' AND
stock_status = 'In Stock' AND
prodQuantity != 0 AND
productTitle LIKE '%apple%'
ORDER BY categoryName, LOCATE('apple', productTitle), productTitle
This is expected to give row_num values 1,2 for category1 and 1,2 for category 2 and so on (wherever ther is a match for 'Apple').
But the rownum value is always stuck at 1
Something similar on the fiddle below
FIDDLE
Can someone please help me out on this
In more recent versions of MySQL, you need to do the order by in a subquery.
In addition, you cannot set #cat_name in one expression and use it in another. MySQL does not guarantee the order of evaluation of expressions in a SELECT. So, you don't know which gets evaluated first, the assignment or the comparison.
I usually write this as:
SELECT (#rn := IF(#cat_name = categoryName, #rn + 1,
IF(#cat_name := categoryName, 1, 1)
)
) as num,
categoryName, productTitle
FROM (SELECT ps.*
FROM product_search ps
WHERE cityId = 1 AND
mainStatus= 'Active' AND
stock_status = 'In Stock' AND
prodQuantity <> 0 AND
productTitle LIKE '%apple%'
ORDER BY categoryName, LOCATE('apple', productTitle), productTitle
) ps CROSS JOIN
(SELECT #cat_name := '', #rn := 0) params

Sql query with three conditions

I have a database with a table having content as below :
message_number message_type message_chat
0 IN Hi
1 OB Hello
2 IN Help
3 IN Want to find this thing
4 OB Sure
5 OB Please let me know
I have written 5 rows since i want to incorporate all possible cases that i want in my query in the example table that i showed.
Now in my query output, i want something like :
message_in message_out
Hi Hello
Help NULL
Want to find this string Sure
NULL Please let me know
So the cases that i want to consider are :
suppose if message_number=0 and message_number=1 both have message_type value as IN then put message_chat_in as message_chat(at message_number=0) and message_chat out as NULL and the iterate over message_number=1
if message_number =0 have message_type=IN and message_number =1 have message_type=OB, then show message_chat(at message_number=0) as message_chat_in and message_chat(at message_number=1) as message_out and dont iterate over message_number=1;
hope i have clarified the condition though i have included all three condition in the expected output.How should my sqlquery look like?
Edit : I am using mysql version 5.5.8
Try the following query
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i1:=IFNULL(#i1,0)+1 num
FROM Chat
ORDER BY message_number
) q1
LEFT JOIN
(
SELECT *,#i2:=IFNULL(#i2,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q1.message_type='IN'
UNION ALL
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i3:=IFNULL(#i3,0)+1 num
FROM Chat
ORDER BY message_number
) q1
RIGHT JOIN
(
SELECT *,#i4:=IFNULL(#i4,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q2.message_type='OB'
AND q1.message_type IS NULL
ORDER BY IFNULL(in_num,out_num)
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/1
The second variant
SET #i1 = 0;
SET #i2 = 0;
SET #i3 = 0;
SET #i4 = 0;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/2
Or
SELECT 0,0,0,0 INTO #i1,#i2,#i3,#i4;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/5
why not using a analytic function here? I would do it with Lead() like this:
with inc as (
--Do the incorporation in this block. could be subquery too
--but its easier to read this way.
select
case when message_type = 'IN'
then message_chat
end as message_in
,case when LEAD(message_type) OVER (Order by message_number) = 'OB' --get the next message by number if it is type OB
then LEAD(message_chat) OVER (order by message_number)
end as message_out
from input
)
select *
from inc
where coalesce(message_in, message_out) is not null --filter out rows where with in & out is null
Ok, since there is no analytical functions in MySQL less than 8 the code may not be easy to follow:
with data_rn as
(
-- this isolate consecutive rows with the same message_type
select d1.*, count(d2.message_number) rn
from data d1
left join data d2 on d1.message_number > d2.message_number and d1.message_type != d2.message_type
group by d1.message_number
),
data_rn2 as
(
-- this marks the rows where new rows has to be added (i.e. when rn2 != 0)
select d1.*, count(d2.message_number) rn2
from data_rn d1
left join data_rn d2 on d1.rn = d2.rn and d1.message_type = d2.message_type and d1.message_number > d2.message_number
group by d1.message_number
),
data_added as
(
-- this add new rows
select message_number, message_type, message_chat
from data_rn2
union all
select message_number - 0.5, 'OB', NULL from data_rn2 where message_type = 'IN' and rn2 != 0
union all
select message_number - 0.5, 'IN', NULL from data_rn2 where message_type = 'OB' and rn2 != 0
order by message_number
), data_added_rn as
(
-- this compute new row numbering
select d1.*, ceil((count(d2.message_number)+1)/2) rn
from data_added d1
left join data_added d2 on d1.message_number > d2.message_number
group by d1.message_number
)
-- this will do the final formating
select max(case when message_type = 'IN' then message_chat end) message_in,
max(case when message_type = 'OB' then message_chat end) message_out
from data_added_rn
group by rn
demo
I have tried to comment each section appropriately.

How to combine overlapping date ranges in MySQL?

I have a table „lessons_holidays“. It can contain several holiday date ranges i.e.
"holiday_begin" "holiday_end"
2016-06-15 2016-06-15
2016-06-12 2016-06-16
2016-06-15 2016-06-19
2016-06-29 2016-06-29
I would like to combine each entry if the date ranges overlap. And I need an output like this:
"holiday_begin" "holiday_end"
2016-06-12 2016-06-19
2016-06-29 2016-06-29
SQL: The following sql-statement loads all rows. Now I am stuck.
SELECT lh1.holiday_begin, lh1.holiday_end
FROM local_lessons_holidays lh1
WHERE lh1.holiday_impact = '1' AND
(DATE_FORMAT(lh1.holiday_begin, '%Y-%m') <= '2016-06' AND DATE_FORMAT(lh1.holiday_end, '%Y-%m') >= '2016-06') AND
lh1.uid = '1'
This is a hard problem, made harder because you are using MySQL. Here is an idea. Find the beginning date of all holidays in each group. Then a cumulative sum of the flag for the "beginning" handles the groups. The rest is just aggregation.
The following should do what you want, assuming you have no duplicate records:
select min(holiday_begin) as holiday_begin, max(holiday_end) as holiday_end
from (select lh.*, (#grp := #grp + group_flag) as grp
from (select lh.*,
(case when not exists (select 1
from lessons_holidays lh2
where lh2.holiday_begin <= lh.holiday_end and
lh2.holiday_end > lh.holiday_begin and
(lh2.holiday_begin <> lh.holiday_begin or
lh2.holiday_end < lh.holiday_end)
)
then 1 else 0
end) as group_flag
from lessons_holidays lh
) lh cross join
(select #grp := 0) params
order by holiday_begin, holiday_end
) lh
group by grp;
If you have duplicates, just use select distinct on the innermost references to the table.
Here is a SQL Fiddle.
EDIT:
This version appears to work better:
select min(holiday_begin) as holiday_begin, max(holiday_end) as holiday_end
from (select lh.*, (#grp := #grp + group_flag) as grp
from (select lh.*,
(case when not exists (select 1
from lessons_holidays lh2
where lh2.holiday_begin <= lh.holiday_begin and
lh2.holiday_end > lh.holiday_begin and
lh2.holiday_begin <> lh.holiday_begin and
lh2.holiday_end <> lh.holiday_end
)
then 1 else 0
end) as group_flag
from lessons_holidays lh
) lh cross join
(select #grp := 0) params
order by holiday_begin, holiday_end
) lh
group by grp;
As illustrated here.

MySql - How get value in previous row and value in next row? [duplicate]

This question already has answers here:
How to get next/previous record in MySQL?
(23 answers)
Closed 4 years ago.
I have the following table, named Example:
id(int 11) //not autoincriment
value (varchar 100)
It has the following rows of data:
0 100
2 150
3 200
6 250
7 300
Note that id values are not contiguous.
I've written this SQL so far:
SELECT * FROM Example WHERE id = 3
However, I don't know how to get the value of previous id and value of the next id...
Please help me get previous value and next value if id = 3 ?
P.S.: in my example it will be: previous - 150, next - 250.
Select the next row below:
SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1
Select the next row above:
SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1
Select both in one query, e.g. use UNION:
(SELECT * FROM Example WHERE id < 3 ORDER BY id DESC LIMIT 1)
UNION
(SELECT * FROM Example WHERE id > 3 ORDER BY id LIMIT 1)
That what you mean?
A solution would be to use temporary variables:
select
#prev as previous,
e.id,
#prev := e.value as current
from
(
select
#prev := null
) as i,
example as e
order by
e.id
To get the "next" value, repeat the procedure. Here is an example:
select
id, previous, current, next
from
(
select
#next as next,
#next := current as current,
previous,
id
from
(
select #next := null
) as init,
(
select
#prev as previous,
#prev := e.value as current,
e.id
from
(
select #prev := null
) as init,
example as e
order by e.id
) as a
order by
a.id desc
) as b
order by
id
Check the example on SQL Fiddle
May be overkill, but it may help you
please try this sqlFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.value < e1.value
ORDER BY value DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.value > e1.value
ORDER BY value ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
Edit: OP mentioned to grab value of previous id and value of next id in one of the comments so the code is here SQLFiddle
SELECT value,
(SELECT value FROM example e2
WHERE e2.id < e1.id
ORDER BY id DESC LIMIT 1) as previous_value,
(SELECT value FROM example e3
WHERE e3.id > e1.id
ORDER BY id ASC LIMIT 1) as next_value
FROM example e1
WHERE id = 3
SELECT *,
(SELECT value FROM example e1 WHERE e1.id < e.id ORDER BY id DESC LIMIT 1 OFFSET 0) as prev_value,
(SELECT value FROM example e2 WHERE e2.id > e.id ORDER BY id ASC LIMIT 1 OFFSET 0) as next_value
FROM example e
WHERE id=3;
And you can place your own offset after OFFSET keyword if you want to select records with higher offsets for next and previous values from the selected record.
Here's my solution may suit you:
SELECT * FROM Example
WHERE id IN (
(SELECT MIN(id) FROM Example WHERE id > 3),(SELECT MAX(id) FROM Example WHERE id < 3)
)
Demo: http://sqlfiddle.com/#!9/36c1d/2
A possible solution if you need it all in one row
SELECT t.id, t.value, prev_id, p.value prev_value, next_id, n.value next_value
FROM
(
SELECT t.id, t.value,
(
SELECT id
FROM table1
WHERE id < t.id
ORDER BY id DESC
LIMIT 1
) prev_id,
(
SELECT id
FROM table1
WHERE id > t.id
ORDER BY id
LIMIT 1
) next_id
FROM table1 t
WHERE t.id = 3
) t LEFT JOIN table1 p
ON t.prev_id = p.id LEFT JOIN table1 n
ON t.next_id = n.id
Sample output:
| ID | VALUE | PREV_ID | PREV_VALUE | NEXT_ID | NEXT_VALUE |
|----|-------|---------|------------|---------|------------|
| 3 | 200 | 2 | 150 | 4 | 250 |
Here is SQLFiddle demo
This query uses a user defined variable to calculate the distance from the target id, and a series of wrapper queries to get the results you want. Only one pass is made over the table, so it should perform well.
select * from (
select id, value from (
select *, (#x := ifnull(#x, 0) + if(id > 3, -1, 1)) row from (
select * from mytable order by id
) x
) y
order by row desc
limit 3
) z
order by id
See an SQLFiddle
If you don't care about the final row order you can omit the outer-most wrapper query.
If you do not have an ID this has worked for me.
Next:
SELECT * FROM table_name
WHERE column_name > current_column_data
ORDER BY column_name ASC
LIMIT 1
Previous:
SELECT * FROM table_name
WHERE column_name < current_column_data
ORDER BY column_name DESC
LIMIT 1
I use this for a membership list where the search is on the last name of the member. As long as you have the data from the current record it works fine.