Informix CASE and summing results - subquery

I have finally joined after many month of always getting my answer from here without having to ask but rather simply searching on here :).
I am fairly new to Informix and am struggling with my first CASE statement:
The rough idea of it is that I am awarding points based on revenue for a rewards program and two months in there are double point months.
My Select looks something like this:
Select YearMonth,
Account_Number,
Customer_Name,
Gross_Revenue,
Gross_Revenue * BLC_multiplier BLC_Points
from (
Select salesstat.year_num||salesstat.int_num YearMonth,
Customer.cust_code Account_Number,
Customer.name_text Customer_Name,
sum(salesstat.net_amt-salesstat.return_net_amt) Gross_Revenue,
CASE
WHEN
salesstat.int_num in (4,8)
THEN
.02
ELSE
.01
END BLC_multiplier
from Customer,
salesstat,
warereptdetl,
Product,
maingrp
Where product.cmpy_code = 'AB'
and Product.part_code = salesstat.part_code
and salesstat.cmpy_code = 'AB'
and customer.cmpy_code = 'AB'
and customer.cust_code = salesstat.cust_code
and salesstat.rept_code = '0'
and warereptdetl.ware_code = salesstat.ware_code
and warereptdetl.cmpy_code = 'AB'
and salesstat.year_num||salesstat.int_num in ('201412','20151','20152','20153','20154','20155','20156','20157','20158','20159')
and salesstat.ord_ind in ('7','8')
and salesstat.stat_type_code = 'MLY'
and salesstat.int_num > 0
and warereptdetl.warereptgrp_code in('NSW','MNW')
and maingrp.maingrp_code = salesstat.maingrp_code
and maingrp.cmpy_code = 'AB'
and customer.ref6_code = 'BLC'
and customer.ref7_code in ('Y','U')
and customer.cust_code in ('408759','112348')
group by YearMonth,
Customer.cust_code,
Customer.name_text,
BLC_multiplier
Order by YearMonth asc,
Customer.cust_code asc
)
I wanted to do it all in one go something like:
Select salesstat.int_num MonthValue,
sum(salesstat.net_amt-salesstat.return_net_amt) Gross_Revenue,
sum(salesstat.net_amt-salesstat.return_net_amt) *
CASE
WHEN
salesstat.int_num in (4,8)
THEN
.02
ELSE
.01
END BLC_Points
but when I do it tells me that BLC_Points needs to be in the group by (which it can't because it is an aggregate field)
I also tried:
Select salesstat.int_num MonthValue,
sum(salesstat.net_amt-salesstat.return_net_amt) Gross_Revenue,
CASE
WHEN
salesstat.int_num in (4,8)
THEN
sum(salesstat.net_amt-salesstat.return_net_amt) * .02
ELSE
sum(salesstat.net_amt-salesstat.return_net_amt) * .01
END BLC_Points
Whilst my fix DOES WORK, I want to make sure I am doing it properly. Incorrect code is hard to maintain.

I note JL's comments in your question, and agree that is all very good advice.
Long story short, you cannot use labels/aliases for columns or expressions in the GROUP BY clause. The simple solution is to use the ordinal position alternative: GROUP BY 1, 2, 3, 4
For what it's worth, an alternative to the CASE statement that I prefer for legibility would be:
DECODE(salesstat.int_num,
4, 0.2
8, 0.2
0.1) AS BLC_Points
(Do with that last bit as you please.)

Related

Using Case statement in Where clause in SQL developer

I am trying to write a "case" statement inside a "where clause" which has an "in" statement in the "then" part. Basically, the following code is what I want, but it's not the Oracle correct syntax. Does anybody have any idea what the correct syntax should be like?
create or replace PROCEDURE "Test"
(
Type in number
)
as
begin
select Id, Name, AccCode from myTable
where case Type
when 0 then AccCode in (130,131)
when 1 then AccCode in (230,231);
end;
I don't think you want a case statement. You probably just want
where (accCode in (130,131) and type = 1)
or (accCode in (230,231) and type = 0)
If you really want to use a case statement, you could say
where (case when accCode in (130,131) then 1
when accCode in (230,231) then 0
else null
end) = type
But that will not be as readable nor will it be as easy for the optimizer to find a good execution plan.

Oracle PLSQL : How to remove duplicate data in string

Step 01 : I have a column A in table tab_T contains that strings :
SELECT A FROM tab_T;
((<123>+<123>+<123>)(*<213>+<213>+<213>+<354>+<354>+<354>+1)(*<985>))(+<654>+<654>+1)
(<599>*<592>*<591>)
(<10945>)
(<736>+<736>+1)
(<216>*<518>)
(<598>*<593>)(*<594>+<594>+<594>+<597>+<595>+<595>+<595>)
...
...
I want to get :
((<123>)(*<213>+<354>+1)(*<985>))(+<654>+1)
(<599>*<591>)
(<10945>)
(<736>)
(<216>*<518>)
(<598>*<593>)(*<594>+<597>+<595>)
...
...
Step 02 : Then i will replace '+' by 'AND' and '*' by 'OR' and delete the number '1' from my string
this is my query (it works good and i share it with you if you need a help)
SELECT RTRIM(RTRIM(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(A,'+','AND'),'*','OR'),'(OR','OR('),'(AND','AND('),'(1)','')
,'OR1',''),'AND1',''),'1OR',''),'1AND',''),'ANDAND','AND'),'OROR','OR'),'AND'),'OR') AS logic
FROM tab_T
Result :
((<123>AND<123>AND<123>)OR(<213>AND<213>AND<213>AND<354>AND<354>AND<354>)OR(<985>))OR(<654>AND<654>)
(<599>OR<592>OR<591>)
(<10945>)
(<736>AND<736>)
(<216>OR<518>)
(<598>OR<593>)OR(<594>AND<594>AND<594>AND<597>AND<595>AND<595>AND<595>)
...
...
so when i apply step 01 and step 2 i will have this result
((<123>)OR(<213>AND<354>)OR(<985>))AND(<654>)
(<599>OR<591>)
(<10945>)
(<736>)
(<216>OR<518>)
(<598>OR<593>)OR(<594>AND<597>AND<595>)
...
...
I need a help or an idea for the step 01 please?
Thx
This will preserve the plus signs in-between the bracketed numbers:
select A original, regexp_replace(A, '(<\d+>)(\+?\1){1,}', '\1') fixed
from tab_T;
The regex can be read as: Remember a group of one or more digits inside of brackets when followed by a group of one or more of the SAME group of remembered numbers preceded by an optional plus sign. When this group is encountered, replace it with the first remembered group.
EDIT: For the sake of completeness, here's the whole thing done with successive CTE's breaking the replaces into logical groupings. This way it's a complete answer and I believe reduced the number of REPLACE() calls. You could do it as a bunch of nested REPLACE's, but I think this is arguably cleaner and easier to understand and maintain down the road.
with tab_T(A) as (
select '((<123>+<123>+<123>)(*<213>+<213>+<213>+<354>+<354>+<354>+1)(*<985>))(+<654>+<654>+1)' from dual union all
select '(<599>*<592>*<591>)' from dual union all
select '(<10945>)' from dual union all
select '(<736>+<736>+1)' from dual union all
select '(<216>*<518>)' from dual union all
select '(<598>*<593>)(*<594>+<594>+<594>+<597>+<595>+<595>+<595>)' from dual
),
-- Remove dups and '+1'
pass_1(original, fixed) as (
select A original, replace(regexp_replace(A, '(<\d+>)(\+?\1){1,}', '\1'), '+1') fixed
from tab_T
),
replace_ors(original, fixed) as (
select original, replace(replace(fixed, '(*', 'OR('), '*', 'OR')
from pass_1
),
replace_ands(original, fixed) as (
select original, replace(replace(fixed, '(+', 'AND('), '+', 'AND')
from replace_ors
)
select original, fixed
from replace_ands
;
I know this is not full answer for your question. But maybe it can help you:
with t as (select '((<123>+<123>+<123>)(*<213>+<213>+<213>+<354>+<354>+<354>+1)(*<985>))(+<654>+<654>+1)' as exp from dual)
, t1 as ( select distinct regexp_substr(exp, '[^+]+', 1, level) names
from t
connect by level <= length(regexp_replace(exp, '[^*+]'))+1
)
SELECT
RTrim(listagg(t1.names,'+') WITHIN GROUP (order by names desc)) string
from t1
I found it :)
select REGEXP_REPLACE
(A,
'(<[^>]+>)(\+|\*?\1)*',
'\1') as logic
FROM tab_T
Thank you anyway ;)

Getting a string value that is between two characters

I need to get the value that is between !03 and !03.
Example:
JDC!0320151104!03OUT
I should get following string in return: 20151104
NOTE: The string isn't always 22 characters long, but I am only concerned with the value that is between !03 and !03.
This is what I have so far. I couldn't make any progress further than this:
SELECT
SUBSTRING(
RegStatsID,
CHARINDEX('!', RegStatsID) + 3,
CHARINDEX('!', REVERSE(RegStatsID))
)
From TableX
Great that you found a solution!
This might be better:
By replacing the "!03" with XML-tags you can easily pick the second "node". Your string will be transformed into <x>JDC</x><x>20151104</x><x>OUT</x>:
DECLARE #test VARCHAR(100)='JDC!0320151104!03OUT';
SELECT CAST('<x>' + REPLACE(#test,'!03','</x><x>') + '</x>' AS XML).value('/x[2]','datetime')
One advantage was to get the value between the two "!03" typed. In this case you get a "real" datetime back without any further casts. If the value there is not a datetime (or date) in all cases, you just use nvarchar(max) as type.
Another advantage was: If you - why ever - need the other values later, you just have them with .value('/x[1 or 3]'...)
I was able to get it right by doing following:
SELECT
SUBSTRING(
RegStatsID,
CHARINDEX('!', RegStatsID) + 3,
len(RegStatsID) - CHARINDEX('!', RegStatsID ) - 2 - CHARINDEX('!', Reverse(RegStatsID))
)

SELECT part of string between symbol and space

I need to create a subquery (or view) column with values pulled from part of a long string. Values will appear like this:
"Recruiter: Recruiter Name Date:..."
I need to select the recruiter name after : and end with the space after the recruiter name. I understand that normalizing would be better, but we only have query access not database setup access in this case.
Ideas appreciated!
You can use a regex for this. A regex will let you express that you want to search for the text Recruiter followed by a colon, a space, and a series of characters followed by a space, and that you want it to extract those characters.
The expression might look a bit like this (untested)
Recruiter: (.+) Date:
This would look for 'Recruiter: ' literally, followed by a string of any characters (.) of length 1 or larger (+), which is to be extracted (the brackets), followed by the literal string ' Date:'.
How you use this with SQL depends on your vendor.
I would create a function that pulls out the value for a given key. You would use it like:
select [dbo].[GetValue]('recruiter',
'aKey: the a value Recruiter: James Bond cKey: the c value')
This returns 'James Bond'
Here is the function:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create function [dbo].[GetValue](#Key varchar(50), #Line varchar(max))
returns varchar(max)
as
begin
declare #posStart int, #posEnd int
select #posStart=charindex(#Key, #Line) -- the start of the key
if(#posStart = 0)
return '' -- key not found
set #posStart = #posStart + len(#Key) + 1 -- the start of the value
select #line = substring(#line, #posStart, 1000) -- start #Line at the value
select #posEnd=charindex(':', #line) -- find the next key
if(#posEnd > 0)
begin
-- shorten #line to next ":"
select #line = substring(#line, 0, #posEnd)
-- take off everything after the value
select #posEnd = charindex(' ', reverse(#line));
if(#posEnd > 0)
select #line = substring(#line, 0, len(#line) - #posEnd + 1)
end
return rtrim(ltrim(#line))
end
go

Using REGEXP_SUBSTR to get key-value pair data

I have a column with below values,
User_Id=446^User_Input=L307-60#/25" AP^^
I am trying to get each individual value based on a specified key.
All value after User_Id= until it encounters ^
All value after User_Input= until it encounters ^
I tried for and so far I have this,
SELECT LTRIM(REGEXP_SUBSTR('User_Id=446^User_Input=L307-60#/25" AP^'
,'[0-9]+',1,1),'^') User_Id
from dual
How do I get the value for the User_Input??
P.S: User input can have anything, like ',", *,% including a ^ in the middle of the string (that is, not as a delimiter).
Any help would be greatly appreciated..
This can be easily solved using boring old INSTR to calculate the offsets of the start and end points for the KEY and VALUE strings.
The trick is to use the optional occurrence parameter to identify each the correct instance of =. Because the input can contain carets which aren't intended as delimiters we need to use a negative position to identify the last ^.
with cte as (
select kv
, instr(kv, '=', 1, 1)+1 as k_st -- first occurrence
, instr(kv, '^', 1) as k_end
, instr(kv, '=', 1, 2)+1 as v_st -- second occurrence
, instr(kv, '^', -1) as v_end -- counting from back
from t23
)
select substr(kv, k_st, k_end - k_st) as user_id
, substr(kv, v_st, v_end - v_st) as user_input
from cte
/
Here is the requisite SQL Fiddle to prove it works. I think it's much easier to understand than any regex equivalent.
If there is no particular need to use Regex, something like this returns the value.
WITH rslt AS (
SELECT 'User_Id=446^User_Input=L307-60#/25" AP^' val
FROM dual
)
SELECT LTRIM(SUBSTR(val
,INSTR(val, '=', 1, 2) + 1
,INSTR(val, '^', 1, 2) - (INSTR(val, '=', 1, 2) + 1)))
FROM rslt;
Of course, if you can't guarantee that there will not be any carets that are valid text characters, this will possibly return partial results.
Assuming that you will always have 'User_Id=' and 'User_Input=' in your string, I would use a character group approach to parsing
Use the starting anchor,^, and ending anchor, $. Look for 'User_Id=' and 'User_Input='
Associate the value you are searching for with a character group.
SCOTT#dev>
1 SELECT REGEXP_SUBSTR('User_Id=446^User_Input=L307-60#/25" AP^','^User_Id=(.*\^)User_Input=(.*\^)$',1, 1, NULL, 1) User_Id
2* FROM dual
SCOTT#dev> /
USER
====
446^
SCOTT#dev>
1 SELECT REGEXP_SUBSTR('User_Id=446^User_Input=L307-60#/25" AP^','^User_Id=(.*\^)User_Input=(.*\^)$',1, 1, NULL, 2) User_Input
2* FROM dual
SCOTT#dev> /
USER_INPUT
================
L307-60#/25" AP^
SCOTT#dev>
Got this answer from a friend of mine.. Looks simple and works great...
SELECT
regexp_replace('User_Id=446^User_Input=L307-60#/25" AP^^', '.*User_Id=([^\^]+).*', '\1') User_Id,
regexp_replace('User_Id=446^User_Input=L307-60#/25" AP^^', '.*User_Input=(.*)[\^]$', '\1') User_Input
FROM dual
Posting here just in case any of you find it interesting..

Resources