I have to do an insert into a single column/row from a source of multiple values. I'm using the stuff case and I'm so close, but I'm missing one of the expected returns, so I'm not sure what I need to do to make sure I get the results I'm looking for.
HERE is my statement:
SELECT STUFF((SELECT CASE WHEN TotalAmount <=0 THEN 'C' ELSE 'D' END FROM Charges WHERE ChargeNumber=123 FOR XML Path('')), 1,1,'')
The results are D D D
But, I actually have four rows, so I should see D D D D
How can I make sure that I don't delete my first return, if that's what it is doing. WHen I try 0,1 it fails.
Any advice is greatly appreciated, thank you! (Running in SQL Server 2012)
For your query you don't need STUFF(). Just do:
SELECT (CASE WHEN TotalAmount <= 0 THEN 'C' ELSE 'D' END)
FROM Charges
WHERE ChargeNumber = 123
FOR XML Path('');
STUFF() is only needed when you have a separator. It removes the separator at the beginning of the result string. Without a separator it removes the first value, which is why you are missing one of the 'D's.
If you change the 2nd 1 to a 0 it works.
For example,
DECLARE #Charges TABLE (ChargeNumber int, TotalAmount int)
INSERT INTO #Charges VALUES (123, 100), (123, 200), (123,100), (123, 300)
SELECT STUFF((SELECT CASE WHEN TotalAmount <=0
THEN 'C' ELSE 'D'
END
FROM #Charges
WHERE ChargeNumber = 123 FOR XML Path('')), 1,0,'')
Gives output:
DDDD
Related
I'm trying to count all values in a column Value that are over 5.
However, some results in that column appear like '>10' (it has the greater than symbol > in the field)
I'd like to still count that as > 5.
To do that, I did the following:
(COUNT(CASE WHEN t.VALUE LIKE '*>*'
and Replace(t.VALUE, '>', ' ') > 5)
Then 1
Else NULL
End
)
But, for whatever reason, it's not replacing.
Well, how about converting to a number?
select sum(case when try_convert(int, replace(t.value, '>', '')) > 5
then 1 else 0
end) as values_over_5
Your data model is suspicious, because you are doing numeric comparisons on a column that contains numbers.
A couple of things.
The asterisk isn't a valid wildcard character in SQL Server, so we'll change that.
Also, if you want the string to become a number, you'll want to replace the greater-than with an empty string, not a space. It doesn't affect the outcome, but it's the right thing to do.
This isn't as elegant as Gordon's one-liner, but it produced the expected results.
DECLARE #t TABLE (VALUE VARCHAR(5));
INSERT #t (VALUE)
VALUES ('1'),('10'),('>10');
SELECT COUNT(*) AS Over5
FROM
(
SELECT
CASE WHEN t.VALUE LIKE '%>%' THEN Replace(t.VALUE, '>', '')
ELSE t.VALUE
END AS NewVal
FROM #t as t
) AS d
WHERE NewVal > 5;
+-------+
| Over5 |
+-------+
| 2 |
+-------+
I have the following column in Microsoft SQL Server called Type and I want to create another column called Type 1 which would be 1 if account starts with letter A, 2 if account starts with D....
I am new with this so could someone please advise?
Type Type 1
AD 1
AV 1
AC 1
DE 2
DR 2
DG 2
KL 3
KL 3
Use CASE instead of IF:
SELECT
Type,
CASE
WHEN Type LIKE 'A%' then 1
WHEN Type Like 'D%' THEN 2
WHEN ...
END AS Type1
...
SELECT Type,
CASE WHEN SUBSTRING(Type, 1, 1)='A' THEN 1
WHEN SUBSTRING(Type, 1, 1)='D' THEN 2
WHEN SUBSTRING(Type, 1, 1)='K' THEN 3
ELSE 0 END as 'Type 1'
FROM TableName
As others have pointed out, a CASE statement will meet this need easily.
If you are going to be doing this translation often, consider adding a computed column to the table like so:
if object_id('ComputedColumnTest') is not null
drop table ComputedColumnTest;
go
create table ComputedColumnTest
(
TYPE varchar(2)
);
alter table ComputedColumnTest --<<<<<<<
add TYPE1 AS --<<<<<<<
( --<<<<<<<
case when [TYPE] like 'A%' then 1--<<<<<<<
when [TYPE] like 'D%' then 2--<<<<<<<
else 0 --<<<<<<<
end --<<<<<<<
) --<<<<<<<
insert into ComputedColumnTest([type]) values ('AD')
insert into ComputedColumnTest([type]) values ('AV')
insert into ComputedColumnTest([type]) values ('AC')
insert into ComputedColumnTest([type]) values ('DE')
insert into ComputedColumnTest([type]) values ('DR')
insert into ComputedColumnTest([type]) values ('DG')
insert into ComputedColumnTest([type]) values ('KL')
insert into ComputedColumnTest([type]) values ('KL')
select *
from ComputedColumnTest
where type1 = 2
If you want to put WHERE on computed column, you have to wrap it in subquery.
SELECT *
FROM (
SELECT Type
, (CASE LEFT(Type, 1)
WHEN 'A' THEN 1
WHEN 'D' THEN 2
END) AS Type1
) a
WHERE Type1 = 'some condition'
I know this question has been answered already but I thought I would add my 2 cents.
If the amount of possible values is going to get very large, causing the case statement to become very large you may want to consider creating a loopkup table for yourself.
You would the be able to just do a join between the 2 tables to get the relevant column value.
If you wanted to number using the first letter of the 'type' column in ascending order, use the DENSE_RANK() in SQL Server.
;WITH cte_1
AS
( SELECT Type
,DENSE_RANK() OVER (ORDER BY LEFT(Type,1) ) [Type 1]
FROM YourTable)
SELECT *
FROM cte_1
WHERE [Type 1]=1 --here you can add your filter criteria
I am trying to get the total sum of all parts in a container. The way I am doing now, sum(weight), will only grab the first weight of the first part in the container. I want to grab all part weights where the container number is the same. There are many different container numbers in the table. I want the statement to work with different container numbers, and only insert the value in the row of the first occurrence of the container number.
http://s33.postimg.org/3t63t83hr/sumweight.png
Each part has a weight in the above. I want to tally those weights for each container number and sum it up on the first row like shown.
,(case when mu.master_unit_no is null
then c.Gross_weight
when mu.master_unit_no is not null
then sum(c.Gross_weight)+mut.tare_weight
end)
as 'Weight in LBS'
Right now I have this query but it returns just the first part weight + the tare weight. I want to grab the sum of all the parts for the container.
/* I-Dashboards Shipping Report */
/* ROTW 11-21-2015 */
select
p.part_no AS 'Part_Number'
,p.name AS 'Description'
,c.serial_no as 'S#'
,c.quantity AS 'Qty'
,cp.customer_part_No as 'F_NUMBER'
--,cast(mut.length AS varchar) + 'X' + Cast(mut.width as varchar) + 'X' + Cast(mut.Height as varchar) as 'dim MU'
,(CASE when mut.length is null
then 0
else cast(mut.length as int) end) as 'M_LEN'
,(CASE when mut.width is null
then 0
else cast(mut.width as int) end) As 'M_WD'
,(CASE when mut.height is null
then 0
else cast(mut.Height as int) end) AS 'M_HT'
,cast(pct.cube_length AS INT) as 'S_LEN'
,cast(pct.cube_width AS INT) AS 'S_WD'
,cast(pct.cube_height AS INT) AS 'S_HT'
,mut.tare_Weight as 'M_Tare_lbs'
,c.Gross_weight as 'Net_Wt_lbs'
,mu.master_unit_no as 'M Number'
,g.Booking_No as 'Booking_HAWB_Num'
,concat(g.cargo_container_no, '-', g.dock_code) as 'Container_ID'
,g.outbound_scac_code AS 'Carrier'
,concat(cast(pct.cube_length as int), 'x', cast(pct.cube_width as int), 'x', cast(pct.cube_height as int)) as 'BOX_DIMS_INCHES'
,(case when row_number() over (partition by mu.master_unit_no order by mu.master_unit_no) = 1
then concat(cast(mut.length as int), 'x', cast(mut.width as int), 'x', cast(mut.Height as int))
when mu.master_unit_no is null
then ''
end)
as 'PALLET_DIMS_INCHES'
,(case when g.booking_container_type_key = 6 THEN
'DIRECT'
when g.booking_container_type_key = 5 THEN
'AIR'
else 'CEVA-Ocean'
end) as 'Shipment Type'
,CASE
--WHEN(ROW_NUMBER() OVER (PARTITION BY mu.master_unit_no ORDER BY mu.master_unit_no)) = 1
--then (select sum((pct.cube_length*0.0254)*(pct.cube_width*0.0254)* (pct.cube_height*0.0254))
--from part_v_container c where c.master_unit_key = mu.master_unit_key)
when mu.master_unit_no is null
then (pct.cube_length*0.0254)*(pct.cube_width*0.0254)* (pct.cube_height*0.0254)
end as 'CBM'
,select c.*, CASE
WHEN(ROW_NUMBER() OVER (PARTITION BY mu.master_unit_no ORDER BY mu.master_unit_no)) = 1
THEN **(**select SUM(c.Gross_weight)+mut.tare_weight
from part_v_container c where c.master_unit_no = mu.master_unit_no**)** END AS 'Total Weight'
from part_v_container c
I'm trying to take the total sum of all the parts gross weight in a m number + the tare weight for that m number and store is as total weight.
Like Siyual said, add tables to help better our understanding. Until then I believe I have most of what you want.
Your table probably looks something like...
part_id container_ id Weight
------- ------------- ------
1 a 5
2 a 5
3 b 99
4 a 3
5 c 99
And you probably want a result like (example using container_id = a)...
Weight
------
13
Try this...
SELECT SUM(Weight) FROM someTable WHERE container_id = someContainer
In the case of the result example I gave I would do...
SELECT SUM(Weight) FROM someTable WHERE container_id = 'a'
I am not fully sure on what you mean by your last part "only insert the value in the row of the first occurrence of the container number". Why would you want this specifically?
EDIT 1
The final result should not have multiple container_id though. I did the following...
My table...
SELECT t1.container_id, SUM(t1.weight) FROM table_1 t1 JOIN table_1 t2 ON t1.part_id = t2.part_id GROUP BY t1.container_id
Result was...
EDIT 2
It took me a while but I think I got it :)
Table:
Query:
SELECT t.*, CASE
WHEN(ROW_NUMBER() OVER (PARTITION BY t.Container ORDER BY t.Container)) = 1
THEN (SELECT SUM(t2.Weight) FROM table1 t2 WHERE t2.Container = t.Container)
ELSE 0 END AS 'Total Weight'
FROM table1 t GROUP BY t.Container, t.Part, t.Weight
Results:
EDIT 3
This was your original...
select c.*, CASE
WHEN(ROW_NUMBER() OVER (PARTITION BY mu.master_unit_no ORDER BY mu.master_unit_no)) = 1
THEN select SUM(c.Gross_weight)+mut.tare_weight
from part_v_container c where c.master_unit_no = mu.master_unit_no END AS 'Total Weight'
This is what I would change (surrounded by two asterix on both sides EX: ** A **)...
select c.*, CASE
WHEN(ROW_NUMBER() OVER (PARTITION BY mu.master_unit_no ORDER BY mu.master_unit_no)) = 1
THEN **(**select SUM(c.Gross_weight)+mut.tare_weight
from part_v_container c where c.master_unit_no = mu.master_unit_no**)** END AS 'Total Weight'
You need the parenthesis because the code doesn't know where the end belongs to otherwise. The parenthesis allows SQL to know that the end belongs to the case statement. I also am not sure where the mu. and mut. come from. It seems like they belong to a different table that you never reference here?
I am not sure if you added it but after 'Total Weight' you are missing
from someTable group by (all things that are in your select aka things that will be output need to be here...see my previous example for a better understanding)
If you want, on your original question you can post screen shots of exactly what your tables look like (or manually create it) so I can use the names you use accurately and make it more easily understandable by you :)
EDIT 4
/* I-Dashboards Shipping Report */
/* ROTW 11-21-2015 */
select
p.part_no AS 'Part_Number'
,p.name AS 'Description'
,c.serial_no as 'S#'
,c.quantity AS 'Qty'
,cp.customer_part_No as 'F_NUMBER'
--,cast(mut.length AS varchar) + 'X' + Cast(mut.width as varchar) + 'X' + Cast(mut.Height as varchar) as 'dim MU'
,(CASE when mut.length is null
then 0
else cast(mut.length as int) end) as 'M_LEN'
,(CASE when mut.width is null
then 0
else cast(mut.width as int) end) As 'M_WD'
,(CASE when mut.height is null
then 0
else cast(mut.Height as int) end) AS 'M_HT'
,cast(pct.cube_length AS INT) as 'S_LEN'
,cast(pct.cube_width AS INT) AS 'S_WD'
,cast(pct.cube_height AS INT) AS 'S_HT'
,mut.tare_Weight as 'M_Tare_lbs'
,c.Gross_weight as 'Net_Wt_lbs'
,mu.master_unit_no as 'M Number'
,g.Booking_No as 'Booking_HAWB_Num'
,concat(g.cargo_container_no, '-', g.dock_code) as 'Container_ID'
,g.outbound_scac_code AS 'Carrier'
,concat(cast(pct.cube_length as int), 'x', cast(pct.cube_width as int), 'x', cast(pct.cube_height as int)) as 'BOX_DIMS_INCHES'
,(case when row_number() over (partition by mu.master_unit_no order by mu.master_unit_no) = 1
then concat(cast(mut.length as int), 'x', cast(mut.width as int), 'x', cast(mut.Height as int))
when mu.master_unit_no is null
then ''
end)
as 'PALLET_DIMS_INCHES'
,(case when g.booking_container_type_key = 6 THEN
'DIRECT'
when g.booking_container_type_key = 5 THEN
'AIR'
else 'CEVA-Ocean'
end) as 'Shipment Type'
,(case when row_number() over (partition by mu.master_unit_no order by mu.master_unit_no) = 1
then (pct.cube_length*0.0254)*(pct.cube_width*0.0254)*(pct.cube_height*0.0254)
when mu.master_unit_no is null
then (pct.cube_length*0.0254)*(pct.cube_width*0.0254)* (pct.cube_height*0.0254)
end)
as 'CBM'
,CASE
WHEN(ROW_NUMBER() OVER (PARTITION BY mu.master_unit_no ORDER BY mu.master_unit_no)) = 1
THEN (SELECT SUM(c.Gross_weight) + mut.tare_weight
from part_v_container c where c.master_unit_no = mu.master_unit_no) END AS 'Total Weight'
from part_v_container c
So this should have fixed my part. I do have an extra comment though. You have all these different prefixs (p., c., mut., mu., g., pct.). Where do you reference all of these? I can see where you reference c (it is right after the final from). Even in my part you use mut. but I don't know how you reference it. For example, c is useable because of from part_v_container c. c represents part_v_container. You can look into joins to help you get the other tables in there. If you want you can edit your original question and add all your tables to it (whether they are actual or examples). I just need to know the different table names and column names. I don't care about the actual data. I wish I personally knew you because this would be much easier in real time xD
EDIT 5
Using this table...
I used this query...
;WITH mult AS (SELECT (m.length*0.0254)*(m.width*0.0254)*(m.height*0.0254) AS multiply, m.container FROM measurement m)
, sumMult AS (SELECT SUM((m.length*0.0254)*(m.width*0.0254)*(m.height*0.0254)) AS sumMultiply, m.container FROM measurement m GROUP BY m.container)
, combine AS (SELECT s.sumMultiply AS sumMultiply, m.multiply AS multiply, m.container FROM mult m JOIN sumMult s ON m.container = s.container)
SELECT c.container, CASE WHEN (ROW_NUMBER() OVER (PARTITION BY c.container ORDER BY c.container)) = 1
THEN (SELECT c.sumMultiply)
ELSE (SELECT c.multiply)
END AS 'Cubic Meters'
FROM combine c GROUP BY c.container, c.sumMultiply, c.multiply
It SUMS all of the volumes for all parts in a container and displays it only in the first row (first part). The rest of the parts have their volume.
I can't completely convert it for you. I trust, since you have done it successfully in my previous queries, that you can convert it properly. I tried to keep the names for the table and columns as bland and recognizable as much as I could. It appears to be working how you want it. Incase you don't know what the ;WITH mult.... is...you can think of it like a function. Put the entire with statement (that is, mult, sumMult, combine) before your gigantic query. You can see in my query that my ;WITH is comes first (above) my SELECT query that produces the actual results.
How can I change column text Not Exists when it is empty or null ?
My query :
Select TOP 1 ISNULL(NULLIF(DR.Name,''),'Not Exists') as Name,
DR.Name as Name ,Coalesce(NullIf(rtrim(DR.Name),''),'Not Exist') as Name,
Name = case when DR.Name is null then 'Not Exists'
when DR.Name='' then 'Not Exists' else DR.Name end
from Transfer TR
join Driver DR on DR.OID=TR.DriverID
WHERE TR.TruckID=51 AND TR.Statues<>7 and TR.DateScheduled<GETDATE()
AND TR.DateScheduled>=DATEADD(DAY,-7,GETDATE()) ORDER BY TR.OID DESC
Result :
If you just need a single column, then you can use a sub-select, this way when no rows are returned by the query you will still get not exists:
SELECT Name = ISNULL(( SELECT TOP 1 NULLIF(DR.Name,'')
FROM Transfer AS TR
INNER JOIN Driver AS DR
ON DR.OID = TR.DriverID
WHERE TR.TruckID = 51
AND TR.Statues <> 7
AND TR.DateScheduled < GETDATE()
AND TR.DateScheduled >= DATEADD(DAY, -7, GETDATE())
ORDER BY TR.OID DESC), 'Not Exists');
If you need multiple columns then you could union your Not Exists record to the bottom of the query, place all this inside a subquery then select the top 1 again, ensuring that your actual value takes precedence (by adding the column SortOrder):
SELECT TOP 1 Name, SomeOtherColumn
FROM ( SELECT TOP 1
Name = NULLIF(DR.Name,''),
SomeOtherColumn,
SortOrder = 0
FROM Transfer AS TR
INNER JOIN Driver AS DR
ON DR.OID = TR.DriverID
WHERE TR.TruckID = 51
AND TR.Statues <> 7
AND TR.DateScheduled < GETDATE()
AND TR.DateScheduled >= DATEADD(DAY, -7, GETDATE())
ORDER BY TR.OID DESC
UNION ALL
SELECT 'Not Exists', NULL, 1
) AS t
ORDER BY SortOrder;
I'm not entirely sure I understand your question, but if you are trying to catch nulls and empty strings "in one go", try this:
select TOP 1
case when length(trim(coalesce(DR.Name, ''))) = 0 then
'Not Exists'
else
DR.Name
as Name
....
The coalesce catches the NULLs and sets a replacement value. The trim gets rid of any padding and the length checks if what is left is an empty string --> so this covers nulls, padded- and non-padded trivial strings.
Assuming the value has regular spaces, the following would keep your approach:
Select TOP 1 ISNULL(NULLIF(ltrim(rtrim((DR.Name))), ''), 'Not Exists') as Name,
I would probably go with the more explicit:
select top 1 (case when ltrim(rtrim((DR.Name)) = '' or DR.Name is null then 'Not Exists'
else DR.Name end) as Name
Unless you also wanted the spaces removed from Name in the output.
If you have other characters, then you can use ASCII() to find them. Something like:
select ASCII(LEFT(DR.Name, 1))
. . .
where LEFT(DR.Name, 1) NOT LIKE '[a-zA-Z0-9 ]' -- you can expand this list of allowed characters
It seems to me you are not actually looking for a way to replace an empty string with 'Not Exists', but an empty result set.
In other words: It looks like you are looking for a way to show 'Not Exists' in case your query returns no rows. If it is this what you are looking for, then first "add" a 'Not Exists' record to your result set and then show the best row, i.e. the desired row in case such a row exists, else your 'Not Exists' row.
select top 1 name
from
(
select name, tr.oid
from transfer tr
join driver dr on dr.oid=tr.driverid
where tr.truckid=51 and tr.statues<>7 and tr.datescheduled<getdate()
and tr.datescheduled>=dateadd(day,-7,getdate())
union all
select 'Not Exists', -1
)
order by oid desc;
I chose -1 for the dummy OID. It must be a value smaller than any real OID. So if you have negative values, make that value even smaller.
Ok I have a table with a indexed key and a non indexed field.
I need to find all records with a certain value and return the row.
I would like to know if I can order by multiple values.
Example:
id x_field
-- -----
123 a
124 a
125 a
126 b
127 f
128 b
129 a
130 x
131 x
132 b
133 p
134 p
135 i
pseudo: would like the results to be ordered like this, where ORDER BY x_field = 'f', 'p', 'i', 'a'
SELECT *
FROM table
WHERE id NOT IN (126)
ORDER BY x_field 'f', 'p', 'i', 'a'
So the results would be:
id x_field
-- -----
127 f
133 p
134 p
135 i
123 a
124 a
125 a
129 a
The syntax is valid but when I execute the query it never returns any results, even if I limit it to 1 record. Is there another way to go about this?
Think of the x_field as test results and I need to validate all the records that fall in the condition. I wanted to order the test results by failed values, passed values. So I could validate the failed values first and then the passed values using the ORDER BY.
What I can't do:
GROUP BY, as I need to return the specific record values
WHERE x_field IN('f', 'p', 'i', 'a'), I need all the values as I'm trying to use one query for several validation tests. And x_field values are not in DESC/ASC order
After writing this question I'm starting to think that I need to rethink this, LOL!
...
WHERE
x_field IN ('f', 'p', 'i', 'a') ...
ORDER BY
CASE x_field
WHEN 'f' THEN 1
WHEN 'p' THEN 2
WHEN 'i' THEN 3
WHEN 'a' THEN 4
ELSE 5 --needed only is no IN clause above. eg when = 'b'
END, id
Try:
ORDER BY x_field='f', x_field='p', x_field='i', x_field='a'
You were on the right track, but by putting x_field only on the 'f' value, the other three were treated as constants and not compared against anything in the dataset.
You can use a LEFT JOIN with a "VALUES ('f',1),('p',2),('a',3),('i',4)" and use the second column in your order-by expression. Postgres will use a Hash Join which will be much faster than a huge CASE if you have a lot of values. And it is easier to autogenerate.
If this ordering information is fixed, then it should have its own table.
I found a much cleaner solution for this:
ORDER BY array_position(ARRAY['f', 'p', 'i', 'a']::varchar[], x_field)
Note: array_position needs Postgres v9.5 or higher.
Use a case switch to translate the codes into numbers that can be sorted:
ORDER BY
case x_field
when 'f' then 1
when 'p' then 2
when 'i' then 3
when 'a' then 4
else 5
end
The CASE and ORDER BY suggestions should all work, but I'm going to suggest a horse of a different color. Assuming that there are only a reasonable number of values for x_field and you already know what they are, create an enumerated type with F, P, A, and I as the values (plus whatever other possible values apply). Enums will sort in the order implied by their CREATE statement. Also, you can use meaninful value names—your real application probably does and you have just masked them for confidentiality—without wasted space, since only the ordinal position is stored.
For someone who is new to ORDER BY with CASE this may be useful
ORDER BY
CASE WHEN GRADE = 'A' THEN 0
WHEN GRADE = 'B' THEN 1
ELSE 2 END
#bobflux's answer is great. I would like to extend it by adding a complete query that uses proposed approach.
select tt.id, tt.x_field
from target_table as tt
-- Here we join our target_table with order_table to specify custom ordering.
left join
(values ('f', 1), ('p', 2), ('i', 3), ('a', 4)) as order_table (x_field, order_num)
on order_table.x_field = tt.x_field
order by
order_table.order_num, -- Here we order values by our custom order.
tt.x_field; -- Other values can be ordered alphabetically, for example.
Here is complete demo.
Since i don't have enough reputation to write as a comment, added this as a new answer.
You can add asc or desc to order by clause.
ORDER BY x_field='A' ASC, x_field='I' DESC, x_field='P' DESC, x_field='F' ASC
which makes I first, P second and A as last one and F before the last.
You can order by a selected column or other expressions.
Here an example, how to order by the result of a case-statement:
SELECT col1
, col2
FROM tbl_Bill
WHERE col1 = 0
ORDER BY -- order by case-statement
CASE WHEN tbl_Bill.IsGen = 0 THEN 0
WHEN tbl_Bill.IsGen = 1 THEN 1
ELSE 2 END
The result will be a List starting with "IsGen = 0" rows, followed by "IsGen = 1" rows and all other rows a the end.
You could add more order-parameters at the end:
SELECT col1
, col2
FROM tbl_Bill
WHERE col1 = 0
ORDER BY -- order by case-statement
CASE WHEN tbl_Bill.IsGen = 0 THEN 0
WHEN tbl_Bill.IsGen = 1 THEN 1
ELSE 2 END,
col1,
col2
if you are using MySQL 4.0 afterwards, consider using FIELD() . It returns the index position of the first argument through the next arguments and it is case-sensitive.
ORDER BY FIELD(x_field, 'f', 'p', 'i', 'a')
you can use position(text in text) in order by for ordering the sequence