Using two lookup functions for one DIM KEY - function

I have created three lookup functions for three scenarios that can occur within the data. Each of these functions should return a account_dim_key, the first scenario takes the sr account ID and call data-time from a call table and returns a dim key if the call date-time is between effective start and end dates, the second scenario uses call date(no time) and the third scnario take just uses account id (no date). If the first lookup function returns a -1 then I want to use the second lookup function, and if that returns a -1 then I was to use the third lookup function. I have been trying to use them in a case statement but in all the scenarios I have tried it always returns the dim key from the second lookup function (in the THEN part of my case statement). Is it not possible to use three functions in a case statement this way or am I going about it the complete wrong way.
lookup 1
CREATE OR REPLACE FUNCTION LOOKUP_D_ACCT(p_acct_id IN VARCHAR2, p_date IN DATE)
RETURN NUMBER
PARALLEL_ENABLE
IS
v_dim_id NUMBER;
BEGIN
SELECT ACCOUNT_DIM_KEY
INTO v_dim_id
FROM
schema.D_ACCOUNT ac
WHERE p_acct_id = ac.ID
AND (p_date BETWEEN ac.EFFECTIVE_START_DT AND ac.EFFECTIVE_END_DT)
;
RETURN v_dim_id;
EXCEPTION
WHEN OTHERS
THEN
RETURN -1;
END LOOKUP_D_ACCT;
/
Lookup 2
CREATE OR REPLACE FUNCTION LOOKUP_D_ACCT_TRUNC_DT (p_acct_id IN VARCHAR2, p_date IN DATE)
RETURN NUMBER
PARALLEL_ENABLE
IS
v_dim_id NUMBER;
BEGIN
SELECT MAX(ACCOUNT_DIM_KEY)
INTO v_dim_id
FROM
schema.D_ACCOUNT ac
WHERE p_acct_id = ac.ID
AND p_date >= TRUNC(ac.EFFECTIVE_START_DT) AND p_date <= TRUNC(ac.EFFECTIVE_END_DT)
;
RETURN v_dim_id;
EXCEPTION
WHEN OTHERS
THEN
RETURN -1;
END;
/
Lookup 3
CREATE OR REPLACE FUNCTION LOOKUP_D_ACCT_NO_DT (p_acct_id IN VARCHAR2)
--LOOKUP FUNCTION TO PASS THROUGH FIELD_ALIGN_DIM_ID WITH DATE CRITERIA
--new one for practice load
--dated 8/31
RETURN NUMBER
PARALLEL_ENABLE
IS
v_dim_id NUMBER;
BEGIN
SELECT ACCOUNT_DIM_KEY
INTO v_dim_id
FROM
schema.D_ACCOUNT ac
WHERE p_acct_id = ac.ID
;
RETURN v_dim_id;
EXCEPTION
WHEN OTHERS
THEN
RETURN -1;
END;
/
The select of my procedure looks like this:
SELECT cl.id,
cl.account_vod__c,
cl.call_date_vod__c,
cl.call_datetime_vod__c,
(CASE
WHEN NVL (
LOOKUP_D_ACCT(cl.ACCOUNT_VOD__C,
CL.CALL_DATETIME_VOD__C),
-1) = -1
--lookup 1
THEN
NVL (
LOOKUP_ACCT_TRUNC_DT (cl.ACCOUNT_VOD__C,
CL.CALL_DATE_VOD__C),
-1)
--lookup 2
WHEN NVL (
LOOKUP_ACCT_TRUNC_DT (cl.ACCOUNT_VOD__C,
CL.CALL_DATE_VOD__C),
-1) = -1
THEN
NVL (LOOKUP_D_ACCT_NO_DT (cl.ACCOUNT_VOD__C), -1)
--lookup 3
ELSE -3
END)
AS ACCOUNT_DIM_KEY,
LOOKUP_D_ACCT_VEEVA (cl.ACCOUNT_VOD__C, CL.CALL_DATETIME_VOD__C)
AS DTIME_DIM_KEY,
--lookup 1
LOOKUP_ACCOUNT_DIM_TEST (cl.ACCOUNT_VOD__C, CL.CALL_DATE_VOD__C)
AS DT_DIM_KEY,
--lookup 2
LOOKUP_D_ACCT_VEEVA_NO_DT (cl.ACCOUNT_VOD__C) AS NODT_DIM_KEY
--lookup 3
FROM schema.CALLTABLE CL
WHERE CL.ID IN ('a043600000Bija3AAB')
I would expect the 'ACCOUNT_DIM_KEY' field to always be populated with the functions from lookup 1, 2, or 3
enter image description here
Any assistance would be greatly appreciated!

if you want to pass all the chain of your conditions, then you shouldn't use case statement, but in series check each condition:
if (NVL (LOOKUP_D_ACCT(cl.ACCOUNT_VOD__C, CL.CALL_DATETIME_VOD__C), -1) = -1) then
if (NVL ( LOOKUP_ACCT_TRUNC_DT (cl.ACCOUNT_VOD__C, CL.CALL_DATE_VOD__C), -1) = -1 ) then
if (NVL (LOOKUP_D_ACCT_NO_DT (cl.ACCOUNT_VOD__C), -1) = -1) then
-- if third func returned -1
else
-- action if third case
end if;
else
--action if second case
end if;
else
--action if first case
end if;
UPD
Sure, if it's a SQL not a PL/SQL, than you should use nested case, as has been said:
SELECT
(CASE
WHEN NVL (
LOOKUP_D_ACCT(cl.ACCOUNT_VOD__C,
CL.CALL_DATETIME_VOD__C),
-1) = -1
--lookup 1
THEN
CASE
WHEN
NVL (
LOOKUP_ACCT_TRUNC_DT (cl.ACCOUNT_VOD__C,
CL.CALL_DATE_VOD__C),
-1)= -1
THEN
NVL (LOOKUP_D_ACCT_NO_DT (cl.ACCOUNT_VOD__C), -1)
ELSE -3
END
WHEN NVL (
LOOKUP_ACCT_TRUNC_DT (cl.ACCOUNT_VOD__C,
CL.CALL_DATE_VOD__C),
-1) = -1
THEN
NVL (LOOKUP_D_ACCT_NO_DT (cl.ACCOUNT_VOD__C), -1)
--lookup 3
ELSE -3
END)
AS ACCOUNT_DIM_KEY,
LOOKUP_D_ACCT_VEEVA (cl.ACCOUNT_VOD__C, CL.CALL_DATETIME_VOD__C)
AS DTIME_DIM_KEY,
--lookup 1
LOOKUP_ACCOUNT_DIM_TEST (cl.ACCOUNT_VOD__C, CL.CALL_DATE_VOD__C)
AS DT_DIM_KEY,
--lookup 2
LOOKUP_D_ACCT_VEEVA_NO_DT (cl.ACCOUNT_VOD__C) AS NODT_DIM_KEY
--lookup 3
FROM schema.CALLTABLE CL
WHERE CL.ID IN ('a043600000Bija3AAB')

Related

SQL delete unnecessary characters from a row and parse a table into a new table

I have created the below code:
with t as (select *,
case
when `2kids`= '1' then '2kids' else'' end as new_2kids,
case
when `3kids`= '1' then '3kids' else'' end as new_3kids,
case
when kids= '1' then 'kids' else'' end as kids
from test.family)
select concat_ws('/',new_2kids, new_3kids, new_kids) as 'nc_kids'
from t;
If I run this query my output will be:
nc_kids
2kids/new_3kids/
2kids//
/new_3kids/new_kids
2kids/new_3kids/new_kids
How can I remove all the unnecessary '/' which not followed by character.
For example:
nc_kids
2kids/new_3kids
2kids
new_3kids/new_kids
2kids/new_3kids/new_kids
concat_ws() ignore nulls, so you can just turn the empty strings to null values at concatenation time:
select concat_ws('/',
nullif(new_2kids, ''),
nullif(new_3kids, ''),
nullif(new_kids, '')
) as nc_kids
from t;
Better yet, fix the case expressions so they produce null values instead of empty stings in the first place:
with t as (
select f.*,
case when `2kids`= 1 then '2kids' end as new_2kids,
case when `3kids`= 1 then '3kids' end as new_3kids,
case when kids = 1 then 'kids' end as kids
from test.family f
)
select concat_ws('/',new_2kids, new_3kids, new_kids) as nc_kids
from t;

Concatenating fields in select clause JOOQ

I'm working in a query using JOOQ and I'm trying to output a column as a
concatenation (space separated) of other fields extracted in the same query.
Getting into detail, with the next code I try to create a select statement with a column called fullAdress by grouping all the address lines contained in the address table. So, for each field, if it's not null or empty it will be concatenated to the result (actually no space is being added).
#Override
protected List<Field<?>> selectCustomFields() {
List<Field<?>> customSelect = new ArrayList<Field<?>>();
// Fields to use in the concatenation
Field<?> field1 = field("addr.AddressLine1"), field2 = field("addr.AddressLine2"),field3 = field("addr.AddressLine3"),
field4 = field("addr.AddressLine4"), field5 = field("addr.PostalCode"), field6 = field("addr.City"),
field7 = field("addr.State"), field8 = field("addr.County"), field9 = field("addr.Country");
// Create non null/empty conditions
Condition condLine1 = field1.isNotNull().and(field1.length().ne(0));
Condition condLine2 = field2.isNotNull().and(field2.length().ne(0));
Condition condLine3 = field3.isNotNull().and(field3.length().ne(0));
Condition condLine4 = field4.isNotNull().and(field4.length().ne(0));
Condition condLine5 = field5.isNotNull().and(field5.length().ne(0));
Condition condLine6 = field6.isNotNull().and(field6.length().ne(0));
Condition condLine7 = field7.isNotNull().and(field7.length().ne(0));
Condition condLine8 = field8.isNotNull().and(field8.length().ne(0));
Condition condLine9 = field9.isNotNull().and(field9.length().ne(0));
// Concat address lines when meets condition
customSelect.add(concat(DSL.when(condLine1, field1),
DSL.when(condLine2, field2),
DSL.when(condLine3, field3),
DSL.when(condLine4, field4),
DSL.when(condLine5, field5),
DSL.when(condLine6, field6),
DSL.when(condLine7, field7),
DSL.when(condLine8, field8),
DSL.when(condLine9, field9))
.as("fullAddress"));
return customSelect;
}
JOOQ will generate the next from the previous select statement, which is giving a null value and not concatenating the fields correctly.
select
concat(
cast(case when (
addr.AddressLine1 is not null
and char_length(cast(addr.AddressLine1 as char)) <> 0
) then addr.AddressLine1 end as char),
cast(case when (
addr.AddressLine2 is not null
and char_length(cast(addr.AddressLine2 as char)) <> 0
) then addr.AddressLine2 end as char),
cast(case when (
addr.AddressLine3 is not null
and char_length(cast(addr.AddressLine3 as char)) <> 0
) then addr.AddressLine3 end as char),
cast(case when (
addr.AddressLine4 is not null
and char_length(cast(addr.AddressLine4 as char)) <> 0
) then addr.AddressLine4 end as char),
cast(case when (
addr.PostalCode is not null
and char_length(cast(addr.PostalCode as char)) <> 0
) then addr.PostalCode end as char),
cast(case when (
addr.City is not null
and char_length(cast(addr.City as char)) <> 0
) then addr.City end as char),
cast(case when (
addr.State is not null
and char_length(cast(addr.State as char)) <> 0
) then addr.State end as char),
cast(case when (
addr.County is not null
and char_length(cast(addr.County as char)) <> 0
) then addr.County end as char),
cast(case when (
addr.Country is not null
and char_length(cast(addr.Country as char)) <> 0
) then addr.Country end as char)) as `fullAddress`
from Address as `addr`
....
My questions are,
how should I create my select statement correctly?
how can I best add the space separator?
is there any better alternative to JOOQ ( when = case ) condition clause?
how should I create my select statement correctly?
You forgot the CASE .. ELSE part, or otherwise() in jOOQ:
// Concat address lines when meets condition
customSelect.add(concat(DSL.when(condLine1, field1).otherwise(""),
DSL.when(condLine2, field2).otherwise(""),
DSL.when(condLine3, field3).otherwise(""),
DSL.when(condLine4, field4).otherwise(""),
DSL.when(condLine5, field5).otherwise(""),
DSL.when(condLine6, field6).otherwise(""),
DSL.when(condLine7, field7).otherwise(""),
DSL.when(condLine8, field8).otherwise(""),
DSL.when(condLine9, field9).otherwise(""))
.as("fullAddress"));
how can I best add the space separator?
If you want an additional space separator between your address parts, you could write:
// Concat address lines when meets condition
customSelect.add(concat(DSL.when(condLine1, field1.concat(" ")).otherwise(""),
DSL.when(condLine2, field2.concat(" ")).otherwise(""),
DSL.when(condLine3, field3.concat(" ")).otherwise(""),
DSL.when(condLine4, field4.concat(" ")).otherwise(""),
DSL.when(condLine5, field5.concat(" ")).otherwise(""),
DSL.when(condLine6, field6.concat(" ")).otherwise(""),
DSL.when(condLine7, field7.concat(" ")).otherwise(""),
DSL.when(condLine8, field8.concat(" ")).otherwise(""),
DSL.when(condLine9, field9.concat(" ")).otherwise("")).trim()
.as("fullAddress"));
is there any better alternative to JOOQ ( when = case ) condition clause?
I think the approach is sound. Of course, you probably shouldn't repeat all that logic all the time, but create a loop of the sort:
List<Field<String>> list = new ArrayList<>();
for (int i = 0; i < fields.size(); i++) {
list.add(DSL.when(conditions.get(i), (Field) fields.get(i)).otherwise(""));
}
customSelect.add(concat(list.toArray(new Field[0])).trim().as("fullAddress"));

SQL remove the next letter/character after a character ^

How to remove a character after the character ^ from a selected rows in table?
e.g.
TABLE Things
Boat
Do^2gs
Cat^fs
^KBear
Mi^&ce
D^Rice
RESULTS:
Boat
Dogs
Cats
Bear
Mice
Dice
select case when charindex('^', col) <> 0
then stuff(col, charindex('^', col), 2, '')
else col
end
-- to handle multiple ^ up to max of 4
select t.col,
r4.col
from Things t
cross apply
(
select col = case when charindex('^', col) <> 0
then stuff(col, charindex('^', col), 2, '')
else col
end
) r1
cross apply
(
select col = case when charindex('^', r1.col) <> 0
then stuff(r1.col, charindex('^', r1.col), 2, '')
else r1.col
end
) r2
cross apply
(
select col = case when charindex('^', r2.col) <> 0
then stuff(r2.col, charindex('^', r2.col), 2, '')
else r2.col
end
) r3
cross apply
(
select col = case when charindex('^', r3.col) <> 0
then stuff(r3.col, charindex('^', r3.col), 2, '')
else r3.col
end
) r4
-- UDF to remove the ^
create function remove_chr
(
#str varchar(100)
)
returns varchar(100)
as
begin
while charindex('^', #str) <> 0
begin
select #str = case
when charindex('^', #str) <> 0
then stuff(#str, charindex('^', #str), 2, '')
else #str
end
end
return #str
end
If you are using MySQL you could use:
SELECT col,
IF(INSTR(col,'^') > 0,CONCAT(LEFT(col,INSTR(col, '^')-1),
RIGHT(col,LENGTH(col) - INSTR(col, '^')-1)), col) AS result
FROM Things;
SqlFiddleDemo
And SQL Server equivalent:
SELECT col,
IIF(CHARINDEX('^',col) > 0,CONCAT(LEFT(col,CHARINDEX('^',col)-1),
RIGHT(col,LEN(col) - CHARINDEX('^',col)-1)), col) AS result
FROM Things
LiveDemo
SQL Server 2008:
SELECT col,
CASE WHEN CHARINDEX('^',col) > 0
THEN LEFT(col,CHARINDEX('^',col)-1) + RIGHT(col,LEN(col) - CHARINDEX('^',col)-1)
ELSE col
END AS result
FROM Things;
Keep in mind that it will work only if there is none or one occurence of ^.
Here is the solution which removes any number of occurrence of '^' .
I have created a function SQL server which is not used any loop or cursor.
CREATE FUNCTION [dbo].[FnReplaceChar](#pOriginalText VARCHAR(2000))
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #vText VARCHAR(1000)
,#vXML XML
--Convert text as XML format
SELECT #vXML = '<Root><dtl><f>' + REPLACE(#pOriginalText,'^','</f></dtl><dtl><f>^')+'</f></dtl></Root>'
--Splits words started with '^' and combines after removing character starts with '^'
SET #vText = (
SELECT '' + ACT_TEXT
FROM
(
SELECT CASE WHEN CHARINDEX('^',DOC.COL.value('f[1]','VARCHAR(100)') ,0) > 0
THEN STUFF(DOC.COL.value('f[1]','VARCHAR(100)'),CHARINDEX('^',DOC.COL.value('f[1]','VARCHAR(100)') ,0),2,'')
ELSE DOC.COL.value('f[1]','VARCHAR(100)')
END AS ACT_TEXT
FROM #vXML.nodes('/Root/dtl') DOC(COL)
)T
FOR XML PATH('')
)
RETURN #vText
END
You can use this function in your select query
SELECT dbo.[FnReplaceChar](col_Name)
FROM [Things]

Count flags for a variable (big) number of colums

I have a table which looks like this: http://i.stack.imgur.com/EyKt3.png
And I want a result like this:
Conditon COL
ted1 4
ted2 1
ted3 2
I.e., the count of the number of '1' only in this case.
I want to know the total no. of 1's only (check the table), neglecting the 0's. It's like if the condition is true (1) then count +1.
Also consider: what if there are many columns? I want to avoid typing expressions for every single one, like in this case ted1 to ted80.
Using proc means is the most efficient method:
proc means data=have noprint;
var ted:; *captures anything that starts with Ted;
output out=want sum =;
run;
proc print data=want;
run;
Try this
select
sum(case when ted1=1 then 1 else 0 end) as ted1,
sum(case when ted2=1 then 1 else 0 end) as ted2,
sum(case when ted3=1 then 1 else 0 end) as ted3
from table
In PostgreSQL (tested with version 9.4) you could unpivot with a VALUES expression in a LATERAL subquery. You'll need dynamic SQL.
This works for any table with any number of columns matching any pattern as long as selected columns are all numeric or all boolean. Only the value 1 (true) is counted.
Create this function once:
CREATE OR REPLACE FUNCTION f_tagcount(_tbl regclass, col_pattern text)
RETURNS TABLE (tag text, tag_ct bigint)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE (
SELECT
'SELECT l.tag, count(l.val::int = 1 OR NULL)
FROM ' || _tbl || ', LATERAL (VALUES '
|| string_agg( format('(%1$L, %1$I)', attname), ', ')
|| ') l(tag, val)
GROUP BY 1
ORDER BY 1'
FROM pg_catalog.pg_attribute
WHERE attrelid = _tbl
AND attname LIKE col_pattern
AND attnum > 0
AND NOT attisdropped
);
END
$func$;
Call:
SELECT * FROM f_tagcount('tbl', 'ted%');
Result:
tag | tag_ct
-----+-------
ted1 | 4
ted2 | 1
ted3 | 2
The 1st argument is a valid table name, possibly schema-qualified. Defense against SQL-injection is built into the data type regclass.
The 2nd argument is a LIKE pattern for the column names. Hence the wildcard %.
db<>fiddle here
Old sqlfiddle
Related:
Select columns with particular column names in PostgreSQL
SELECT DISTINCT on multiple columns

mysql ORDER BY and CASE casts INT to CHAR?

For a stored procedure with configurable sort order (_Sort parameter), i use code like this:
SELECT * FROM distances
ORDER BY
CASE _Sort
WHEN 1 THEN uid
WHEN 2 THEN NULL
WHEN 3 THEN name
WHEN 4 THEN NULL
WHEN 5 THEN distance
WHEN 6 THEN NULL
ELSE distance
END ASC,
CASE _Sort
WHEN 2 THEN uid
WHEN 4 THEN name
WHEN 6 THEN distance
ELSE NULL
END DESC
in which uid is INT and distance is DOUBLE.
But if _Sort = 1, uid is ordered like it was a CHAR e.g.
200
207
25
4
Same thing for distance.
Casting to unsigned and decimal did not help.
ORDER BY uid ASC does the right thing, i.e. 4, 25, 200, 207
Any idea?
As documented under Control Flow Functions:
The return type of a CASE expression is the compatible aggregated type of all return values
Whilst the manual does not explicitly document how the "compatible aggregated type" is determined, one can follow the source from Item_func_case::fix_length_and_dec() through agg_result_type() to item_store_type():
static Item_result item_store_type(Item_result a, Item *item,
my_bool unsigned_flag)
{
Item_result b= item->result_type();
if (a == STRING_RESULT || b == STRING_RESULT)
return STRING_RESULT;
else if (a == REAL_RESULT || b == REAL_RESULT)
return REAL_RESULT;
else if (a == DECIMAL_RESULT || b == DECIMAL_RESULT ||
unsigned_flag != item->unsigned_flag)
return DECIMAL_RESULT;
else
return INT_RESULT;
}
One can therefore see that if a single return value is a string, then the return value of the overall CASE expression will also be a string.
In your case, one presumes that name is a string; therefore the data type returned by your CASE expression is a string. Consequently, your numeric values are compared as strings and thus sorted lexicographically (hence the output that you observe).
One way of overcoming this would be to pad all numeric values to equal widths in order that sorting lexicographically will deliver the desired results: using the ZEROFILL attribute of integer-type columns will do this automatically; however, this is still pretty inefficient and you may wish to consider redesigning your logic.
For example, you could instead build a string that contains the desired SQL; then prepare and execute a statement from that string:
SET #sql := CONCAT(
'SELECT * FROM distances ORDER BY ',
CASE _Sort
WHEN 1 THEN 'uid ASC'
WHEN 2 THEN 'uid DESC'
WHEN 3 THEN 'name ASC'
WHEN 4 THEN 'name DESC'
WHEN 5 THEN 'distance ASC'
WHEN 6 THEN 'distance DESC'
ELSE 'distance ASC'
END
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Try this solution (edited) -
SELECT * FROM distances
ORDER BY
IF(_Sort = 1, uid, 0),
IF(_Sort = 2, NULL, 0),
IF(_Sort = 3, name, 0),
IF(_Sort = 4, NULL, 0),
...