Select one column value for other columns based on a condition - sql

I have a table like given below structure, I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.My table structure is
I am Expecting the result like the below structure.
Here in June actual value is 100 and comment 'Closed' after thet user will not enter anything(Actual or comments) since actual meets target. so i need to display the commment 'Closed' in all remaining months(July-Dec)

Your expected output is not clear, Please add clarity.
How about this? -
SELECT *
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
Do you want to aggregate comments by Months_ID?
SELECT MONTHS_ID,
LISTAGG(COMMENTS, ',') WITHIN GROUP(ORDER BY COMMENTS) AS COMMENTS
FROM YOUR_TABLE
WHERE MONTHS_ID IN (SELECT MONTHS_ID FROM YOUR_TABLE WHERE ACTUAL_VALUE >= TARGET)
GROUP BY MONTHS_ID

you can use a where based on you filter condition
select a.comment
from your_table_with_commen a
where a.comment is not null
and a.target is not null
and a.target <= a.actual

Your expected output isn't very clear. But if I got it correctly, then you can achieve the desired result using below query(may not be best performing):
with commentValue as (
select month_id, comments from your_table where actual_value = ( select max(target)
from your_table)
)
select yt.target,yt.month_id,
case when yt.month_id >= cv.month_id then cv.comments else yt.comments end as
comments,
yt.actual_value
from your_table yt
join commentValue cv on 1 = 1

From the explanation, seems you need nothing more than this :
Select month, nvl(comment,'Closed') as comment, target, actual
From tableDemands;

I need to write a query to fetch result like achived month comnet retaine for all remaining months from the table based on a condition if the actual value is greater than or equal to target then select that particular month comments value for remaining months too.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( month, "COMMENT", target, actual ) AS
SELECT 1, 'initiated', NULL, 5 FROM DUAL UNION ALL
SELECT 2, 'feb', NULL, 6 FROM DUAL UNION ALL
SELECT 3, 'Mar- On going', NULL, 10 FROM DUAL UNION ALL
SELECT 4, 'Apr- work On going', NULL, 20 FROM DUAL UNION ALL
SELECT 5, 'May- Ongoing', NULL, 50 FROM DUAL UNION ALL
SELECT 6, 'closed', NULL, 100 FROM DUAL UNION ALL
SELECT 7, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 8, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 9, NULL, 100, NULL FROM DUAL UNION ALL
SELECT 10, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 11, NULL, NULL, NULL FROM DUAL UNION ALL
SELECT 12, NULL, NULL, NULL FROM DUAL;
Query 1:
SELECT month,
"COMMENT",
max_target As target,
actual
FROM (
SELECT t.*,
MAX( target ) OVER () AS max_target
FROM table_name t
)
WHERE actual >= max_target
Results:
| MONTH | COMMENT | TARGET | ACTUAL |
|-------|---------|--------|--------|
| 6 | closed | 100 | 100 |

It seems you want to replace a null comment with the last non-null comment. Use LAST_VALUE for this:
select
month,
last_value(comment ignore nulls) over (order by month) as comment,
target,
actual
from mytable
order by month;

Related

Oracle subselect case

So, there are 2 tables
Table1 (Contains all articles,center,date 00000)
Tabla2 (Contains articles handwritten (that are also in Table1),center, date)
We have a procedure that every day compares Table1 and Table2 articles and center, and if they match, an update changes th Table1 date for that article and center.
Now, we also want to add something, we want that in case center is ' ' (empty) on Tabla2, it updates every center that has that article in Table1.
Here is the OracleSQL:
update Table1 r
set date1= (SELECT max(date2) FROM Tabla2 t
where t.articulo = r.articulo
and t.center = to_char(center) //It gets the center from a select behind
and t.date2 >= to_char(sysdate,'yyyymmdd')
group by t.center);
We want both cases to work
If center has a real center like 20, it only updates center 20.
If center has a empty '' then it updates every center with that article.
You can use:
UPDATE Table1 r
SET date1 = ( SELECT MAX(date2)
KEEP (DENSE_RANK FIRST ORDER BY t.center NULLS LAST)
FROM Tabla2 t
WHERE t.articulo = r.articulo
AND (t.center = r.center OR t.center IS NULL)
AND t.date2 >= TRUNC(sysdate)
);
Note: KEEP (DENSE_RANK LAST... is used to prefer dates from a row with a non-NULL center over rows with a NULL center.
Which, if you have the sample data:
CREATE TABLE table1 (articulo, center, date1) AS
SELECT 1, 1, CAST(NULL AS DATE) FROM DUAL UNION ALL
SELECT 2, 2, NULL FROM DUAL UNION ALL
SELECT 3, 3, NULL FROM DUAL UNION ALL
SELECT 4, 4, NULL FROM DUAL;
CREATE TABLE tabla2 (articulo, center, date2) AS
SELECT 1, 1, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 2, 2, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 2, 2, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 3, 3, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 3, NULL, DATE '2023-05-19' FROM DUAL UNION ALL
SELECT 4, NULL, DATE '2023-01-01' FROM DUAL UNION ALL
SELECT 4, NULL, DATE '2023-05-19' FROM DUAL;
Then, after the update Table1 contains:
ARTICULO
CENTER
DATE1
1
1
2023-05-19 00:00:00
2
2
2023-05-19 00:00:00
3
3
2023-01-01 00:00:00
4
4
2023-05-19 00:00:00
db<>fiddle here
I would use something like this:
and t.center = nvl(to_char(center),t.center)
If the center is populated, it will use that value. If center is null, the nvl will instead result in the value of t.center. Basically resulting in t.center=t.center (which again means always true).

How to query data which is not unique up to a certain point?

Basically the current conditions of the query are
WHERE data_payload_uri BETWEEN
'/organization/team/folder/2021'
AND
'/organization/team/folder/2022'
And this gets all data for the year of 2021.
A sample of the data_payload_uri data looks like this:
/organization/team/folder/20210101/orig
/organization/team/folder/20210102/orig
/organization/team/folder/20210102/orig_v1
/organization/team/folder/20210103/orig
/organization/team/folder/20210104/orig
/organization/team/folder/20210105/orig
/organization/team/folder/20210105/orig_v1
/organization/team/folder/20210105/orig_v2
What I would like to do is only query the rows where up until the last forward-slash, the row is NOT unique.
What this means, is I want to NOT query the rows which ONLY have one orig
/organization/team/folder/20210101/orig
/organization/team/folder/20210103/orig
/organization/team/folder/20210104/orig
but I DO want to query all the other rows
/organization/team/folder/20210105/orig
/organization/team/folder/20210105/orig_v1
/organization/team/folder/20210105/orig_v2
/organization/team/folder/20210102/orig
/organization/team/folder/20210102/orig_v1
What is the best way to do this? Pls let me know if anything is unclear and thank you for any help
You can use the analytic COUNT function:
SELECT *
FROM (
SELECT t.*,
COUNT(DISTINCT data_payload_uri) OVER (
PARTITION BY SUBSTR(data_payload_uri, 1, INSTR(data_payload_uri, '/', -1))
) AS cnt
FROM table_name t
WHERE data_payload_uri >= '/organization/team/folder/2021'
AND data_payload_uri < '/organization/team/folder/2022'
)
WHERE cnt > 1
Which, for the sample data:
CREATE TABLE table_name (id, data_payload_uri) AS
SELECT 1, '/organization/team/folder/20210101/orig' FROM DUAL UNION ALL
SELECT 2, '/organization/team/folder/20210102/orig' FROM DUAL UNION ALL
SELECT 3, '/organization/team/folder/20210102/orig_v1' FROM DUAL UNION ALL
SELECT 4, '/organization/team/folder/20210103/orig' FROM DUAL UNION ALL
SELECT 5, '/organization/team/folder/20210104/orig' FROM DUAL UNION ALL
SELECT 6, '/organization/team/folder/20210105/orig' FROM DUAL UNION ALL
SELECT 7, '/organization/team/folder/20210105/orig_v1' FROM DUAL UNION ALL
SELECT 8, '/organization/team/folder/20210105/orig_v2' FROM DUAL;
Outputs:
ID
DATA_PAYLOAD_URI
CNT
2
/organization/team/folder/20210102/orig
2
3
/organization/team/folder/20210102/orig_v1
2
6
/organization/team/folder/20210105/orig
3
7
/organization/team/folder/20210105/orig_v1
3
8
/organization/team/folder/20210105/orig_v2
3
db<>fiddle here

Pivoting Multiple Attributres and grouping them as a 'single' attribute (many to one)

So I Have a table called Value that's associated with different 'Fields'. Note that some of these fields have similar 'names' but they are named differently. Ultimately I want these 'similar names' to be pivoted/grouped as the same field name in the result set
VALUE_ID VALUE_TX FIELD_NAME Version_ID
1 Yes Adult 1
2 18 Age 1
3 Black Eye Color 1
4 Yes Is_Adult 2
5 25 Years_old 2
6 Brown Color_of_Eyes 2
I have a table called Submitted that looks like the following:
Version_ID Version_Name
1 TEST_RUN
2 REAL_RUN
I need a result set that Looks like this:
Submitted_Name Adult? Age Eye_Color
TEST_RUN Yes 18 Black
REAL_RUN Yes 25 Brown
I've tried the following:
SELECT * FROM (
select value_Tx, field_name, version_id
from VALUE
)
PIVOT (max (value_tx) for field_name in (('Adult', 'Is_Adult') as 'Adult?', ('Age', 'Years_old') as 'Age', ('Eye Color', 'Color_of_Eyes') as 'Eye_Color')
);
What am I doing wrong? Please let me know if I need to add any additional details / data.
Thanks in advance!
The error message that I am getting is the following:
ORA-00907: missing right parenthesis
I would change the field names in the subquery:
SELECT *
FROM (select value_Tx,
(case when field_name in ('Adult', 'Is_Adult') then 'Adult?'
field_name in ('Age', 'Years_old') then 'Age'
field_name in ('Eye Color', 'Color_of_Eyes') then 'Eye_Color'
else field_name
end) as field_name, version_id
from VALUE
)
PIVOT (max(value_tx) for field_name in ('Adult?', 'Age', 'Eye_Color'));
You can use double quotes for column aliasing within the pivot clause's part, and I think decode function suits well for this question. You can consider using the following query :
with value( value_id, value_tx, field_name, version_id ) as
(
select 1 ,'Yes' ,'Adult' ,1 from dual union all
select 2 ,'18' ,'Age' ,1 from dual union all
select 3 ,'Black','Eye_Color' ,1 from dual union all
select 4 ,'Yes' ,'Is_Adult' ,2 from dual union all
select 5 ,'25' ,'Years_old' ,2 from dual union all
select 6 ,'Brown','Color_of_Eyes',2 from dual
), Submitted( version_id, version_name ) as
(
select 1 ,'TEST_RUN' from dual union all
select 2 ,'REAL_RUN' from dual
)
select * from
(
select s.version_name as "Submitted_Name", v.value_Tx,
decode(v.field_name,'Adult','Is_Adult','Age','Years_old','Eye_Color',
'Color_of_Eyes',v.field_name) field_name
from value v
join Submitted s
on s.version_id = v.version_id
group by decode(v.field_name,'Adult','Is_Adult','Age','Years_old','Eye_Color',
'Color_of_Eyes',v.field_name),
v.value_Tx, s.Version_Name
)
pivot(
max(value_tx) for field_name in ( 'Is_Adult' as "Adult?", 'Years_old' as "Age",
'Color_of_Eyes' as "Eye_Color" )
);
Submitted_Name Adult? Age Eye_Color
REAL_RUN Yes 25 Brown
TEST_RUN Yes 18 Black
I think, better to solve as much as shorter way, as an example, using modular arithmetic would even be better as below :
select *
from
(
select s.version_name as "Submitted_Name", v.value_Tx, mod(v.value_id,3) as value_id
from value v
join Submitted s
on s.version_id = v.version_id
group by v.value_Tx, s.version_name, mod(v.value_id,3)
)
pivot(
max(value_tx) for value_id in ( 1 as "Adult?", 2 as "Age", 0 as "Eye_Color" )
)
Demo

Excluding a row that contains a specific value

I want to exclude people who have joined a specific group. For example, if some students signed up for an Orchestra club, and I want to retrieve a list of students who did NOT sign up for orchestra, how do I do so?
I am unable to simply do a Group By clause because some students may have joined multiple clubs, and would bypass the Where condition and still show up in the query,
as shown here.
I am thinking about using a CASE statement in the SELECT clause to flag the person as '1' if they have joined Orchestra, and '0' if they have not, but I'm struggling to write an aggregate CASE function, which would cause issues from the GROUP BY clause.
Any thoughts on how to flag people with a certain row value?
Apparently my table didn't get saved onto SQLFiddle so you can paste the code below on your own screen:
CREATE TABLE activity ( PersonId, Club) as
select 1, 'Soccer' from dual union
select 1, 'Orchestra' from dual union
select 2, 'Soccer' from dual union
select 2, 'Chess' from dual union
select 2, 'Bball' from dual union
select 3, 'Orchestra' from dual union
select 3, 'Chess' from dual union
select 3, 'Bball' from dual union
select 4, 'Soccer' from dual union
select 4, 'Bball' from dual union
select 4, 'Chess' from dual;
Use the HAVING clause instead of using WHERE, with case expression :
HAVING max(case when column = ‘string’ then 1 else 0 end) = 0
Add this after your group by .
How about selecting a list of user ids from the activity table and excluding it:
SELECT * FROM users WHERE id NOT IN
(SELECT PersonId FROM activity WHERE Club = 'Orchestra');
You could use a subquery to return a list of people to exclude.
-- Returns person 2 and 4.
SELECT
PersonId
FROM
activity
WHERE
PersonId NOT IN
(
-- People to exclude.
SELECT
PersonId
FROM
activity
WHERE
Club = 'Orchestra'
)
GROUP BY
PersonId
;
EDIT Removed superfluous distinct in subquery - thanks #mathguy.
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =1; --> get some students signed up for an Orchestra club
select * from
(
select a.*, case when Club ='Orchestra' then 1 else 0 end flag
from activity a
) where flag =0; --> get students not signed up for an Orchestra club

Oracle SQL Min in Select Clause

Can some one please help me in writing a sql query that should do a oracle min function based on the following conditions.
For eg for column values
0,0,0,0 then output should be 0
0,null,0,null then output should be o
0,2,4,5,6 then output should be 2 (Note that we are excluding Zero here)
0,2,null,4,5 then output should be 2 (same here we are excluding zero)
null,null,null, null then output should be null.
I wrote query already that satisfies all the above cases but failing for last case when all the column values are null. Instead of returning null it is returning 0. Can some one modify the below query to fit for the last case as well?
select NVL(MIN(NULLIF(columnname,0)),0) from tablename;
Please also keep in mind that the query should be runnable in oracle as well as hsqldb as we are using hsql db for running junits.
If all 4 cases satisfied by your query then just a case will solve your problem.
SELECT CASE WHEN MIN(COLUMNNAME) IS NULL THEN NULL ELSE NVL(MIN(NULLIF(COLUMNNAME,0)),0) END FROM TABLENAME;
Note:- assuming all the cases satisfied by your query except 5th.
I will show below an input table with two columns, ID and VAL, to illustrate the various possibilities. You want a single result per ID (or even for the entire table), so this must be a job for GROUP BY and some aggregate function. You want to distinguish between three types of values: Greater than zero, zero, and null (in this order); you want to pick the "highest priority group" that exists for each ID (in this order of priority), and for that priority group only, you want to pick the min value. This is exactly what the aggregate FIRST/LAST function does. To order by the three "classes" of values, we use a CASE expression in the ORDER BY clause of the aggregate LAST function.
The WITH clause below is not part of the solution - I only include it to create test data (in your real life situation, use your actual table and column names and remove the entire WITH clause).
with
inputs ( id, val ) as (
select 1, 0 from dual union all
select 1, 0 from dual union all
select 1, 0 from dual union all
select 2, 0 from dual union all
select 2, null from dual union all
select 2, 0 from dual union all
select 3, 0 from dual union all
select 3, 2 from dual union all
select 3, 5 from dual union all
select 4, 0 from dual union all
select 4, 3 from dual union all
select 4, null from dual union all
select 5, null from dual union all
select 5, null from dual
)
select id,
min(val) keep (dense_rank last order by case when val > 0 then 2
when val = 0 then 1
else 0
end
) as min_val
from inputs
group by id
order by id
;
ID MIN_VAL
---------- ----------
1 0
2 0
3 2
4 3
5