Related
Database is Oracle 18c
Data info:
We have the temporal notion of Seasons, and we have a set of Business Groups in each Season. My table, AGROUP, has columns(ID*[auto-generated], NAME, SEASON_ID). Group Names must be unique by Season. Said another way, I can have two groups with the same name ONLY if they are in different seasons.
Task At Hand:
I need to create a stored procedure that, given a source season and a target season, can copy all the groups from the source season to the target season. This part is easy enough. The naive solution is:
PROCEDURE COPY_GROUPS(IN_SOURCE_SEASON_ID IN SEASON.ID%TYPE,
IN_TARGET_SEASON_ID IN SEASON.ID%TYPE)
AS
WITH SOURCE AS
(SELECT AG.ID AS AG_ID,
AG.NAME,
AG.SEASON_ID
FROM AGROUP AG
WHERE AG.SEASON_ID = IN_SOURCE_SEASON_ID)
INSERT INTO AGROUP (NAME, SEASON_ID)
SELECT SRC.NAME,
IN_TARGET_SEASON_ID AS SEASON_ID,
FROM SOURCE SRC;
COMMIT;
END;
The tricky bit is that it is possible that there exists a group in the target season that already has the same name as a group in the source season. Without intervention, this would generate a unique constraint error. In this case, we want to copy the group but re-name it to something unique. Any method to rename is fine, but my first thought is simply to rename with a suffixed number. To do this, I suppose I would change things around to open a REFCURSOR and loop through, inserting one at a time and catching the unique constraint error and responding with a re-attempt to insert with a number suffixed. I'm really in-experienced with routing error handling like this in Oracle however. I could really use some pointing in the right direction.
Thanks for the help!
I played around with this a bit. I'd say to try to do the conditional logic in a case statement as per the following example. You'll need a better way to generate sequences though.
/* INSERT INTO AGROUP (id, NAME, SEASON_ID) */
WITH agroup as
(
select 1 id, 'A' name, 1 season_id from dual union
select 2 id, 'A' name, 2 season_id from dual union
select 3 id, 'B' name, 1 season_id from dual union
select 4 id, 'C' name, 2 season_ud from dual
)
, SOURCE AS
(SELECT rownum row_num, AG.ID AS AG_ID,
AG.NAME,
AG.SEASON_ID
FROM AGROUP AG
WHERE AG.SEASON_ID = 1 /* source season */
)
SELECT /* sequence generation */ (select max(id) from agroup) + row_num as id
, case when SRC.NAME in (select name
from agroup
where agroup.season_id = 2 /* target season */
and agroup.name = src.name
)
then src.name || ' ' || to_char(sysdate, 'DD-MON-YYYY hh24:MI:SS')
else src.name
end as name
, 2 /* target season */ AS SEASON_ID
FROM SOURCE SRC
;
using v('APP_USER') in where condition is returning to null records
select FULL_NAME, EMAIL_ADDRESS from headcount where EMAIL_ADDRESS = v('APP_USER')
There are 3 ways to get the value of APP_USER in an apex session. The preferred way is the bind variable (:APP_USER) but you can you also use the sys_context variable SYS_CONTEXT('APEX$SESSION','APP_USER') or the V function (V('APP_USER')). To prove that works I created a report with the following query:
WITH app_user_tab AS (SELECT 'bind var' as type, :APP_USER as username FROM DUAL
UNION
SELECT 'context',SYS_CONTEXT('APEX$SESSION','APP_USER') FROM DUAL
UNION
SELECT 'v', v('APP_USER') FROM DUAL)
SELECT * FROM app_user_tab
And i got 3 identical rows as expected.
Which leads me to believe your problem is not with the app user, but with the value in headcount.email_address. It could be:
case not matched.
leading/trailing control characters in the headcount.email_address column
you have to use bind variables in your sql.
V / NV functions are used in compiled code, such as views, packages, and procedures.
more info are here
I am trying to compare the current row value to the previous row value but only within the same group.
I have Group1 (Plant), ChildGroup1 (DeviceTag), Details Group,
I have tried this code within the Fill property but it does not work, it just seems to change the color of the first row every time per group regardless of the value of the previous row.
=IIf(Fields!DeviceTag.Value <> Previous(Fields!DeviceTag.Value), "Yellow", "White")
So my data set looks like this:
Plant DeviceTag Description Location
A Tag1 ABCD West
Tag1 WXYZ West DeviceTag Group 1
_____________________________________________
A Tag2 EFGH East
Tag2 IJKL East DeviceTag Group 2
Tag2 IJKL West
In both these DeviceTag Groups, the description changed so I would like to change the color of ABCD to yellow and EFGH to yellow but not WXYZ because it is not in the same group as EFGH. Also the Second row that says East should be Yellow since it is different than the previous West location.
In Crystal Reports you would do:
if {#ChangeCounter}=1 then nocolor else
if currentfieldvalue <> previous({DataSet.Field}) then cryellow else nocolor
Where the formula #ChangeCounter is just 1
Clear as mud??
I always find this kind of thing easier to do in the dataset query (assuming you can control the query in the dataset).
Manipulate your final dataset (the one SSRS will receive) to have another column which tells SSRS to color the cell or not. And keep your business logic in the DB query.
You could number each row (within each group) using row_number() and then join the table back into itself on the new row number column.
Something like this perhaps? I am not sure I 100% follow your cell shading logic and I am sure your dataset is larger than what you provided, but you may be able to adapt this to meet your needs.
;with Data as
(
select 'A' as Plant, 'Tag1' as DeviceTag, 'ABCD' as Description, 'West' as Location union all
select 'A' as Plant, 'Tag1' as DeviceTag, 'WXYZ' as Description, 'West' as Location union all
select 'A' as Plant, 'Tag2' as DeviceTag, 'EFGH' as Description, 'East' as Location union all
select 'A' as Plant, 'Tag2' as DeviceTag, 'IJKL' as Description, 'East' as Location union all
select 'A' as Plant, 'Tag2' as DeviceTag, 'IJKL' as Description, 'West' as Location
),
DataWithRowNumbers as
(
select
*,
row_number() over (partition by DeviceTag order by Description) as DeviceTagGroupRowNumber
from
Data
)
select
a.*,
case when a.Description != b.Description then 'Yellow' else 'Transparent' end as CellColor
from
DataWithRowNumbers a
left join DataWithRowNumbers b on a.DeviceTag = b.DeviceTag and a.DeviceTagGroupRowNumber = b.DeviceTagGroupRowNumber - 1
Then you can set the background in the cell (in SSRS) to an expression which will just be Fields!CellColor.Value.
You need to use the Scope parameter of the Previous function, providing the name of your ChildGroup1 (DeviceTag). e.g.
=IIf(Fields!DeviceTag.Value <> Previous(Fields!DeviceTag.Value , "ChildGroup1" ), "Yellow", "White")
Here's the doco:
https://learn.microsoft.com/en-us/sql/reporting-services/report-design/report-builder-functions-previous-function
I am currently using the following code to pivot a table and it works perfectly. Now I want to replace any null values with 'No Data' after it is summed but I am getting errors, so I think I am placing the case statement in the wrong place.
This works:
SELECT *
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI', 'MORT_30_HF', 'MORT_30_PN'))
order by PROV_NO, DATA_YEAR, DATA_MONTH;
but this does not
SELECT *
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI', 'MORT_30_HF', 'MORT_30_PN'))
case when MORT_30_HF is null then 'No Data' else MORT_30_HF end
order by PROV_NO, DATA_YEAR, DATA_MONTH;
I get "ORA-00933: SQL command not properly ended" as the error. I'm trying to place ";" around but the error is still the same. I am currently in Oracle 11g and using Golden as my scripting/retrieval software.
You can move the CASE statement to the SELECT statement and handle the NULL values there. Better yet, use COALESCE. But unfortunately you have to do this for each item in the SELECT list:
SELECT
--Must manually reference each column.
COALESCE(TO_CHAR(MORT_30_AMI), 'No Data') MORT_30_AMI,
COALESCE(TO_CHAR(MORT_30_HF), 'No Data') MORT_30_HF,
COALESCE(TO_CHAR(MORT_30_PN), 'No Data') MORT_30_PN,
PROV_NO, DATA_YEAR, DATA_MONTH
FROM (SELECT PROV_NO, DATA_YEAR, DATA_MONTH, MEASURE_ID, CASES
FROM pivot_test_2)
PIVOT
(
SUM(CASES)
FOR (MEASURE_ID) IN
--Use aliases to make the columns easier to use.
('MORT_30_AMI' MORT_30_AMI, 'MORT_30_HF' MORT_30_HF, 'MORT_30_PN' MORT_30_PN))
ORDER BY PROV_NO, DATA_YEAR, DATA_MONTH;
A Simpler Version That Doesn't Work
Ideally you would be able to replace this part of the code:
SUM(CASES)
With this:
COALESCE(TO_CHAR(SUM(CASES)), 'No data')
Then you wouldn't need to handle each column separately. But there doesn't appear to be a way to automatically apply a non-aggregate function to the results of a PIVOT. Using the above code generates this error message:
ORA-56902: expect aggregate function inside pivot operation
Sample Schema
create table pivot_test_2
(
PROV_NO CHAR(6),
DATA_YEAR NUMBER(4),
DATA_MONTH Number(2),
MEASURE_ID VARCHAR2(250),
CASES NUMBER
);
insert into pivot_test_2
select 'A', 2000, 1, 'MORT_30_AMI', 1 from dual union all
select 'A', 2000, 1, 'MORT_30_AMI', 1 from dual union all
select 'A', 2000, 1, 'MORT_30_HF', 2 from dual union all
select 'A', 2000, 1, 'MORT_30_HF', 2 from dual;
Thanks everyone, with help from you all, I was able to cob this together and it works.
SELECT PROV_NO, DATA_YEAR, DATA_MONTH,
case when MORT_30_AMI is null then 'No Data' else to_char(MORT_30_AMI) end as MORT_30_AMI,
case when MORT_30_HF is null then 'No Data' else to_char(MORT_30_HF) end as MORT_30_HF,
case when MORT_30_PN is null then 'No Data' else to_char(MORT_30_PN) end as MORT_30_PN
FROM pivot_test_2
PIVOT (SUM(CASES) FOR (MEASURE_ID) IN ('MORT_30_AMI' as MORT_30_AMI,'MORT_30_HF' as MORT_30_HF, 'MORT_30_PN' as MORT_30_PN))
order by PROV_NO, DATA_YEAR, DATA_MONTH;
Use DECODE keyword for the query defined fields (from the list of pivot defined fields) and then replace NULL with whatever value you need. i.e., instead of NULL replace it with 'No Data'. I believe that should solve this issue.
I have a query that uses a connect by statement to order the recursive data. The problem I have is that there is occasionally a one to many or a many to one relationship and I dont know how to deal with it.
SELECT *
FROM (SELECT * FROM bdTable WHERE parentek = t_parKey)
START WITH source is null
CONNECT BY PRIOR target = source
So to explain. I have a source and target columns. About 99% of the time these are a single unique ID. Unfortunately the other 1% of the time there are a grouping of IDs in one of the columns. This table is a flat representation of a flowchart type tool, so there are splits and decisions which can have many outputs and merges which can have many inputs.
To deal with this in loading the data for the table, the unique IDs are concatenated together using the listagg function. So I end up with a Target value of something like '1254143,2356334,6346436,3454363,3462354,442356'.
So when my connect by statement is executed, it works perfectly until it comes to one of these scenarios, at which point it just stops (which is expected of course).
I thought I might be able to use IN or INSTR in some way to get it working, but haven't had any luck yet and I can't find anything on it online.
Any help would be appreciated.....
If you want to join target and source with use of some logic, based on intersection of set of values, listed in each column, then most reliable way to do so is to split strings to collections and operate on collection from prior row and collection from current row to build a tree.
There are a number of techniques to build collection from separated string in Oracle, one of them illustrated in this answer to another question.
Create required collection type:
create or replace type TIdList as table of varchar2(100);
Inner select in your case would look like this:
SELECT
t.*,
(
cast(multiset( -- Convert set of values into collection
select -- Build list of values from separated string
substr(
source,
decode( level, 1, 1, instr(source,',',1,level-1)+1 ),
decode( instr(source,',',1,level), 0, length(source)+1, instr(source,',',1,level) )
-
decode( level, 1, 1, instr(source,',',1,level-1)+1 )
) code
from dual
start with source is not null
connect by instr(source,',',1,level-1) > 0
) as TIdList )
) source_id_list,
(
cast(multiset( -- Convert set of values into collection
select -- Build list of values from separated string
substr(
target,
decode( level, 1, 1, instr(target,',',1,level-1)+1 ),
decode( instr(target,',',1,level), 0, length(target)+1, instr(target,',',1,level) )
-
decode( level, 1, 1, instr(target,',',1,level-1)+1 )
) code
from dual
start with target is not null
connect by instr(target,',',1,level-1) > 0
) as TIdList )
) target_id_list
FROM bdTable t
WHERE t.parentek = t_parKey
Because I don't know which column (source or target) contains separated list, I include column for each.
After building collection(s) it's possible to use multiset operators and available test functions to match target with source. E.g.
with inner_query as (
SELECT
t.*,
(
cast(multiset( -- Convert set of values into collection
select -- Build list of values from separated string
substr(
source,
decode( level, 1, 1, instr(source,',',1,level-1)+1 ),
decode( instr(source,',',1,level), 0, length(source)+1, instr(source,',',1,level) )
-
decode( level, 1, 1, instr(source,',',1,level-1)+1 )
) code
from dual
start with source is not null
connect by instr(source,',',1,level-1) > 0
) as TIdList )
) source_id_list,
(
cast(multiset( -- Convert set of values into collection
select -- Build list of values from separated string
substr(
target,
decode( level, 1, 1, instr(target,',',1,level-1)+1 ),
decode( instr(target,',',1,level), 0, length(target)+1, instr(target,',',1,level) )
-
decode( level, 1, 1, instr(target,',',1,level-1)+1 )
) code
from dual
start with target is not null
connect by instr(target,',',1,level-1) > 0
) as TIdList )
) target_id_list
FROM bdTable t
WHERE t.parentek = t_parKey
)
select
level lvl,
tree_list.*
from
inner_query tree_list
start with
source is null
connect by
nvl(cardinality(prior target_id_list MULTISET INTERSECT source_id_list),0) > 0
If only one column can contain list of values, then MEMBER OF construct are useful.