SQL to replace NULL based on other fetched row - sql

I have a table T_REF which contains the following data.
select * from T_REF
order by invent_status nulls first;
DIV REF INVENT_STATUS
---- --------- -------------
1 REF001XT NULL
1 REF001XT A
How to get INVENT_STATUS as A for the following.
If there is no 2nd row.
INVENT_STATUS is anything other than A for 2nd row.
The SQL must not change the first NULL if the second row contains an A.
So basically, I need an SQL that replaces an A for the NULL if there is no A in the result of the SQL.

If I understood correctly you need this:
select div, ref, invent_status,
case when invent_status is null and
count(case when invent_status = 'A' then 1 end) over () = 0
then 'A'
else invent_status
end as new_status
from t_ref
order by invent_status nulls first;
demo
Conditional, analytical function count(case when invent_status = 'A' then 1 end) over () checks if there are any A in your table. If no and if current status is null then it is replaced by A.

sample table:
create table NS_11(
div int ,ref varchar(10),INVENT_STATUS varchar(1));
insert into NS_11(div,ref) values(1,'REF001XT');
insert into NS_11 values(1,'REF002XT','A');
insert into NS_11 values(1,'REF003XT','B');
insert into NS_11 values(1,'REF004XT','C');
insert into NS_11(div,ref) values(1,'REF005XT');
insert into NS_11(div,ref) values(1,'REF006XT');
select * from NS_11;
select div,ref,nvl(INVENT_STATUS,'A') from(select div,ref,INVENT_STATUS from NS_11
minus select div,ref,INVENT_STATUS from NS_11 where rownum<=1 )
union all
select div,ref,INVENT_STATUS from NS_11 where rownum<=1;
sample output:
1 REF002XT A
1 REF003XT B
1 REF004XT C
1 REF005XT A
1 REF006XT A
1 REF001XT (null)
this query will work for your table:
select div,ref,INVENT_STATUS from T_REF where rownum<=1
union
select div,ref,nvl(INVENT_STATUS,'A') from(select div,ref,INVENT_STATUS from T_REF
minus select div,ref,INVENT_STATUS from T_REF where rownum<=1 );

select DIV, REF,
case
when (INVENT_STATUS is null) then 'A'
when INVENT_STATUS = 'A' then null
when INVENT_STATUS != 'A' then INVENT_STATUS
else INVENT_STATUS
end as INVENT_STATUS
from
t_ref
order by INVENT_STATUS nulls first;

Related

How to flatten a SQL statement

I have a case statement
Select customer, group, case when group = one then 'A' else 'B' end as Indicator FROM TABLE1
How do I "flatten" the indicator so for each customer I have 2 column for each indicator type (Goal Table)
Current Table:
Customer
Group
Indicator
Joh
One
A
Joh
Two
B
Jane
One
A
Jane
Two
B
Goal Table:
Customer
Indicator1
Indicator2
Joh
A
B
Jane
A
B
Since values are being hard-coded ('A','B') for indicator column, we can use max, as it will yield one value only -
with data_cte(Customer,Group_1,Indicator) as(
select * from values
('Joh','One','A'),
('Joh','Two','B'),
('Jane','One','A'),
('Jane','Two','B')
)select d.customer
,max(case when d.group_1 = 'One' then 'A' end) as indicator1
,max(case when d.group_1 = 'Two' then 'B' end) as indicator2
from data_cte d
group by d.customer;
The form of Pankaj's answer is good if you have fixed group's, but his code has the indicator values hard coded, this it should look like:
with data_cte(Customer, Group_1, Indicator) as (
select *
from values
('Joh','One','A'),
('Joh','Two','B'),
('Jane','One','A'),
('Jane','Two','B')
)
select
d.customer
,max(case when d.group_1 = 'One' then d.indicator end) as indicator1
,max(case when d.group_1 = 'Two' then d.indicator end) as indicator2
from data_cte as d
group by 1;
The CASE in the MAX can be swapped for a IFF in the form
MAX(IFF(d.group_1 = 'One` then d.indicator, null)) as indicator1
This works as MAX takes the larest value, so if you only have one matching group_1 per customer, the other will be null and those are not larger so the wanted value is taken.
If you have many, you will want to somehow rank then, and then FIRST_VALUE with a partition on customer, and ordered by something like a date..
anyways, if you have unkown/dynamic columns this can be solve using Snowflake Scripting to double query the data.
create or replace table table1 as
select column1 customer, column2 as _group, column3 as indicator
from values
('Joh',1,'A'),
('Joh',2,'B'),
('Jane',1,'C'),
('Jane',3,'E'),
('Jane',2,'D');
declare
sql string;
res resultset;
c1 cursor for select distinct _group as key from table1 order by key;
begin
sql := 'select customer ';
for record in c1 do
sql := sql || ',max(iff(_group = '|| record.key ||', indicator, null)) as col_' || record.key::text;
end for;
sql := sql || ' from table1 group by 1 order by 1';
res := (execute immediate :sql);
return table (res);
end;
gives:
CUSTOMER
COL_1
COL_2
COL_3
Jane
C
D
E
Joh
A
B
null

how to avoid duplicates in hive query

I have two tables:
table1
the_date | my_id |
02/03/2021,123
02/03/2021, 1234
02/03/2021, 12345
table2
the_date | my_id |seq | txt
02/03/2021, 1234, 1 , 'OK'
02/03/2021, 12345, 1, 'OK'
02/03/2021, 12345, 2, 'HELLO HI THERE'
02/03/2021, 123456, 1, 'Ok'
Here is my code:
WITH AB AS (
SELECT A1.my_id
FROM DB1.table1 A1 , DB1.MSG_REC A2 WHERE
A1.my_id=A2.my_id
),
BC AS (
SELECT AB.the_date
COUNT ( DISTINCT (CASE WHEN (TXT like '%OK%') THEN AB.my_id ELSE NULL END )) AS
CASE1 ,
COUNT ( DISTINCT (CASE WHEN (TXT like '%HELLO HI THERE%') THEN AB.my_id ELSE NULL END )) AS
CASE2
FROM AB left JOIN DB1.my_id BC ON AB.my_id =BC.my_id
The issue that stems from above is I am looping over the value '12345' twice because it satisfies both of the case statements.
That causes data duplicates when capturing metrics of the counts. Is there a way to execute the first case and then perform the second case but exclude looping any of the "my_id' records from the first case.
So for example, when it is time to run the above script and the first case executes, it will pick up the below records and the count would be 3
02/03/2021, 1234, 1 , 'OK'
02/03/2021, 12345, 1, 'OK'
02/03/2021, 123456, 1, 'Ok
The second case should only be looping through the below records and the count would be only 1
02/03/2021, 12345, 2, 'HELLO HI THERE'
CASE1 would be 4 and CASE2 would by 2 if I don't create a condition to circumvent this issue. Any tips or suggestions?
Assign case to each your ID before DISTINCT aggregation . After that do distinct aggregation, in such way you will eliminate same IDs counted in different cases. See comments in the code:
select --do final distinct aggregation
count(distinct (case when assigned_case='CASE1' then my_id else null end ) ) as CASE1,
count(distinct (case when assigned_case='CASE2' then my_id else null end ) ) as CASE2
from
(
select my_id
--assign single CASE to all rows with the same id based on some logic:
case when case1_flag = 1 then 'CASE1'
when case1_flag = 1 then 'CASE2'
else NULL
end as assigned_case
from
(--calculate all CASE flags for each ID
select AB.my_id,
max(CASE WHEN (TXT like '%OK%') THEN 1 ELSE NULL END) over (partition by AB.my_id) as case1_flag
max(CASE WHEN (TXT like '%HELLO HI THERE%') THEN 1 ELSE NULL END) over (partition by AB.my_id) as case2_flag
from ...
) s
) s

How to write query to return value regardless of existance?

Given this:
with data_row as (select 1 as col_1 from dual)
select 'Y' as row_exists from dual where exists
(select null
from data_row
where col_1 in (2,1))
How can I get this?
Col_1 Row_exists
--------------------
1 Y
2 N
In order to get a row of output, you need a row of input. You want to get the second row with a "2", but there is no table with that value.
The approach is to generate a table that has the values that you want, and then use left outer join to find which match:
with data_row as (
select 1 as col_1
from dual
),
what_i_care_about as (
select 1 as col from dual union all
select 2 from dual
)
select wica.col,
(case when dr.col_1 is NULL then 'N' else 'Y' end) as row_exists
from what_i_care_about wica left outer join
data_row dr
on wica.col = dr.col_1;
You cannot do directly what you want -- which is to create a row for each missing value in the in list. If you have a lot of values and they are consecutive numeric, then you can use connect by or a recursive CTE to generate the values.

sql query group

SQL query question
I have a query like
select proposal_id, service_id,account_type
from table1
The result is like this:
proposal_id service_id account_type
1 1001 INTERVAL
1 1002 INTERVAL
2 1003 NON INTERVAL
2 1004 NON INTERVAL
3 1005 NON INTERVAL
3 1006 INTERVAL
I want to write a query: for each proposal_id, if all the service have INTERVAL then get 'INTERVAL', if all NON-INTERVAL get 'NON-INTERVAL', if both, get 'Both'
For the example above, it should return
proposal_id account_type
1 INTERVAL
2 NON-INTERVAL
3 BOTH
Data:
declare #table table (id int, sid int, acc nvarchar(20))
insert #table VALUES (1,1001,'INTERVAL'),(1,1002,'INTERVAL'),(2,1003,'NON INTERVAL'),(2,1004,'NON INTERVAL'),
(3,1005,'NON INTERVAL'),(3,1006,'INTERVAL')
Query:
select x.Id
, CASE counter
WHEN 1 THEN x.Account_Type
ELSE 'BOTH'
END AS Account_Type
from (
select Id, Count(DISTINCT(acc)) AS counter, MAX(acc) As Account_Type
from #table
GROUP BY Id
) x
Results
Id Account_Type
----------- --------------------
1 INTERVAL
2 NON INTERVAL
3 BOTH
SELECT
b.proposal_id
,CASE
WHEN s1.proposal_id IS NOT NULL AND s2.proposal_id IS NOT NULL THEN 'BOTH'
WHEN s1.proposal_id IS NOT NULL THEN 'INTERVAL'
WHEN s2.proposal_id IS NOT NULL THEN 'NON-INTERVAL'
ELSE 'UNKNOWN'
END [account_type]
FROM table1 b
LEFT JOIN(
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'INTERVAL'
) s1
ON b.proposal_id = s1.proposal_id
LEFT JOIN (
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'NON-INTERVAL'
)s2
ON b.proposal_id = s2.proposal_id
You could use count distinct to determinate if it is both then use CASE to determinate what to display
SELECT DISTINCT proposal.proposal_id,
CASE cou
WHEN 1 THEN type ELSE 'Both' END as TYPE
FROM proposal
INNER JOIN (SELECT proposal_id, count(distinct type) cou
FROM proposal GROUP BY proposal_id) inn
ON proposal.id = inn.id
select proposal_id,
case when count(distinct account_type) > 1 then 'BOTH'
else max(account_type)
end
from table1
group by proposal_id
You have the fiddler here.

one sql (oracle) query for getting unique information that has two different (null and not null) values per column

Table foobar is, for clarity, structured and has data as follows:
id, action_dt, status_id
1, '02-JUL-10', 'x'
1, '02-JUL-10', '2'
1, '02-JUL-10', NULL
2, '02-JUL-10', 'a'
2, '02-JUL-10', 'b'
3, '02-JUL-10', 'k'
3, '02-JUL-10', NULL
3, '03-JUL-10', 'k'
3, '03-JUL-10', NULL
I need a query that gets IDs such that for each ID a NULL value and a NOT NULL value exists per day. So, in the example dataset above, the query needs to return:
'02-JUL-10', 1
'02-JUL-10', 3
'03-JUL-10', 3
Yes, it can be done using something like:
SELECT
nulls.action_dt
, nulls.id
FROM (SELECT
action_dt
, id
FROM foobar
WHERE status_id IS NULL
GROUP BY action_dt) nulls
INNER JOIN (SELECT
action_dt
, id
FROM foobar
WHERE status_id IS NOT NULL
GROUP BY action_dt) non_nulls ON nulls.action_dt = non_nulls.action_dt
AND nulls.id = non_nulls.id
but as you can see, among other things, two subqueries and another iteration for the join...
The query I've been working on and have hopes for is of the form:
SELECT
action_dt
, id
FROM
foobar
GROUP BY
action_dt
, id
, CASE WHEN status_id IS NOT NULL THEN 1 ELSE 0 END
HAVING
COUNT(prim_card_nb) > 1
but it doesn't quite return what I need (as you know, the HAVING clause applies to the underlying data that is being queried). Any ideas?
After all this, it seems a solution would be to have the above query in a subquery and filter it down that way, such as:
SELECT
action_dt
, id
FROM (SELECT
action_dt
, id
FROM
foobar
GROUP BY
action_dt
, id
, CASE WHEN status_id IS NOT NULL THEN 1 ELSE 0 END
) repeat_ids_per_day
GROUP BY
action_dt
, id
HAVING
COUNT(id) > 1
but I feel it can be better...
Your idea is sound: in such a case you don't need a subquery, an aggregate is sufficient and should be more efficient. This should work:
SQL> SELECT action_dt, id
2 FROM foobar
3 GROUP BY action_dt, ID
4 HAVING COUNT(DISTINCT CASE WHEN status_id IS NULL THEN 1 ELSE 0 END) > 1;
ACTION_DT ID
--------- ----------
02-JUL-10 1
02-JUL-10 3
03-JUL-10 3
I think you have to do some minor changes in your first posted query
as below -
SELECT
nulls.action_dt, nulls.id
FROM
(SELECT
action_dt
, id
FROM foobar
WHERE status_id IS NULL
GROUP BY action_dt,id
uniou all
SELECT
action_dt
, id
FROM foobar
WHERE status_id IS NOT NULL
GROUP BY action_dt,id)
group by action_dt, id
having count(*) >1
what you have posted there is not a correct, as in oracle database..
you can't include not grouped column name while selecting..
so please check that .. it could be your mistake .. and may be it was couse of problem..