understand the resultset of the below query - sql

with sample_data as (
select 1 id, 1 num, 'Hello' val from dual union all
select 1 id, 2 num, 'Goodbye' val from dual union all
select 2 id, 2 num, 'Hey' val from dual union all
select 2 id, 4 num, 'What''s up?' val from dual union all
select 3 id, 5 num, 'See you' val from dual)
select id,
NUM,
CASE
WHEN ID=1 THEN
SUM(NUM) OVER (PARTITION BY CASE WHEN NUM=3 THEN ID END )
END AS SUM_1,
from sample_data
Can any one help me to understand the how this query will work..case inside the partition make me difficult to understand the resultset for this query. I am l

(Ignoring the comma before the from that generates a syntax error.)
There are two case statements. The outer one is saying "I'm only going to assign a number when the id is 1. Everything else will get a NULL value.
The inner one is calculating the value. It is splitting the data into two groups, those with num = 3 and everything else. The calculation is the sum of num.
So, the query is doing the following. It is assigning the sum of num where either num is equal to 3 or not equal to 3 to each row where id = 1.

Related

Oracle SQL - Count based on a condition to include distinct rows with zero matches

Is there a "better" way to refactor the query below that returns the number occurrences of a particular value (e.g. 'A') for each distinct id? The challenge seems to be keeping id = 2 in the result set even though the count is zero (id = 2 is never related to 'A'). It has a common table expression, NVL function, in-line view, distinct, and left join. Is all of that really needed to get this job done? (Oracle 19c)
create table T (id, val) as
select 1, 'A' from dual
union all select 1, 'B' from dual
union all select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 2, 'B' from dual
union all select 3, 'A' from dual
;
with C as (select id, val, count(*) cnt from T where val = 'A' group by id, val)
select D.id, nvl(C.cnt, 0) cnt_with_zero from (select distinct id from T) D left join C on D.id = C.id
order by id
;
ID CNT_WITH_ZERO
---------- -------------
1 2
2 0
3 1
A simple way is conditional aggregation:
select id,
sum(case when val = 'A' then 1 else 0 end) as num_As
from t
group by id;
If you have another table with one row per id, you I would recommend:
select i.id,
(select count(*) from t where t.id = i.id and t.val = 'A') as num_As
from ids i;

How can I add two columns sequentially (and not concatenate)?

I am trying to pull two tables from an Oracle SQL database, and want to join them sequentially, so they appear as if they are one list.
List one has items [1,2,3,4]
List two has items [a,b,c,d]
I want to output [1,2,3,4,a,b,c,d]
Any thoughts?
One option uses a UNION with a computed column:
SELECT val
FROM
(
SELECT val, 1 AS position FROM table1
UNION ALL
SELECT val, 2 AS position FROM table2
) t
ORDER BY
position, val;
Demo
Note that I assume that all data here is text. If not, e.g. the first table be numeric, then we would have to do a cast along the way. But, this is not the main focus of your question anyway.
SELECT id_1, value_column1 from table_1
UNION
SELECT id_2, value_column2 from table_2;
if the types of columns are different - make sure you cast/convert them to char() - the resulting type should be same.
https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries004.htm
use union, i think 1,2,3 as numeric value that why converted it on varchar as for union you have to same data type
with t1 as (
select 1 as id from dual union all
select 2 from dual union all
select 3 from dual union all
select 4 from dual
), t2 as (
select 'a' as item from dual union all
select 'b' from dual union all
select 'c' from dual union all
select 'd' from dual
)
select cast(id as varchar(20)) as id from t1
union
select * from t2
demo
output
1
2
3
4
a
b
c
d

Condition in subquery- select one value if subquery return 2 records else the actual value

I have a subquery inside a big query which returns multiple values sometime and some time only one value. Below is my query and the returned values
select tran.customer_type from transaction_record tran where tran.TRANSACTION_ID=txn.id
customer_type can be 2 records - "LP" and "NA"
or
customer_type can be 2 records - "SOEMTHING ELSE" and "NA"
or
customer_type can be 1 records - "NA"
Here my probem is if i have 2 records i have to print value without NA and if i have one record i have to print what ever be the value is
Not exectly efficient (2 queries), but it should work!
Inner query counts status, id combinatios per group and outer query
removes all NA statuses that have another record on same ID.
Innermost query is just for table simulation (I like it more than create table, insert scripts).
SELECT * FROM
(
SELECT status, id, count(*)
OVER (PARTITION BY id ORDER BY 3 ) AS rn
from (
SELECT 'NA' status, 1 id FROM dual
UNION ALL
SELECT 'LP' status, 1 id FROM dual
UNION ALL
SELECT 'NA' status, 2 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 2 id FROM dual
UNION ALL
SELECT 'NA' status, 3 id FROM dual
UNION ALL
SELECT 'NA' status, 5 id FROM dual
UNION ALL
SELECT 'LP' status, 5 id FROM dual
UNION ALL
SELECT 'NA' status, 6 id FROM dual
UNION ALL
SELECT 'SOEMTHING ELSE' status, 6 id FROM dual
UNION ALL
SELECT 'NA' status, 22 id FROM dual
))
WHERE NOT (status = 'NA' AND rn=2)

How to use decode or case function to build a condition base on the result of two records?

Let's say if I search by a key, it returns 2 records with 2 different values for each record.
It will return value 'A' and value 'B' for the 1st and 2nd records respectively.
ID VALUE
1 A
1 B
If the returned records contains 'A' and 'B' then I want to change all their value to 'C'.
If the returned record only contains 'A' or 'B' then i don't want to change to 'C'
How do i use the decode or case function to do that?
I tried (Case when value in('A','B') then 'C' else value end)
but it also changes the records that only returns either 'A' or 'B' to 'C'
So basically if my result are like this :
ID VALUE
1 A
1 B
I want it to be like this
ID VALUE
1 C
1 C
If the result is
ID VALUE or ID VALUE
1 A 1 B
1 A 1 B
Then don't implement the above conversion rule.
Edit for clarity
select id, value from t1
where id =123
gives me below
ID VALUE
1 A
1 B
I want a condition that uses the value of the two records--change the value to 'C' only when clm1.value=A and clm2.value=B
something like below but it does not work.
select id,
case when value ='A' and value ='B' then 'C' else value end
from t1
where id=123
Sorry for the confusion.
Thanks
What about something like this:
create table csm (id int, value varchar(5))
insert into csm (id,value)
SELECT 1,'A' UNION
SELECT 1,'B' UNION
SELECT 2,'A' UNION
SELECT 3,'B' UNION
SELECT 4,'A' UNION
SELECT 4,'B' UNION
SELECT 4,'D'
SELECT t.id
, case when tsub.TotalTimes=2 AND tsub.NumTimes=2 THEN 'C' ELSE t.value END as Value
FROM csm t
JOIN (
SELECT id, COUNT(DISTINCT CASE WHEN value IN ('A','B') THEN value END) AS NumTimes
, COUNT(DISTINCT value) TotalTimes
FROM csm
GROUP BY id
) AS tsub ON t.id=tsub.id
I get the following output:
1 C
1 C
2 A
3 B
4 A
4 B
4 D
The subquery finds out the number of times A and B occur for that id, and then your case statement checks if that value is 2, and if so changes it to C.
Seems like a perfect match for an analytic function:
with v_data(id, value) as (
select 1, 'A' from dual union all
select 1, 'B' from dual union all
select 2, 'A' from dual union all
select 3, 'B' from dual union all
select 3, 'B' from dual
)
select
v1.*,
(case
when v1.cnt_distinct > 1 then 'C'
else v1.value end)
as new_value
from (
select
id,
value,
count(*) over (partition by id) as cnt_overal,
count(distinct value) over (partition by id) as cnt_distinct
from v_data)
v1
This computes the number of distinct values for each ID (using count(distinct...) and then replaces the values with C if the number of distinct values is larger than 1.

Oracle SQL -- Analytic functions OVER a group?

My table:
ID NUM VAL
1 1 Hello
1 2 Goodbye
2 2 Hey
2 4 What's up?
3 5 See you
If I want to return the max number for each ID, it's really nice and clean:
SELECT MAX(NUM) FROM table GROUP BY (ID)
But what if I want to grab the value associated with the max of each number for each ID?
Why can't I do:
SELECT MAX(NUM) OVER (ORDER BY NUM) FROM table GROUP BY (ID)
Why is that an error? I'd like to have this select grouped by ID, rather than partitioning separately for each window...
EDIT: The error is "not a GROUP BY expression".
You could probably use the MAX() KEEP(DENSE_RANK LAST...) function:
with sample_data as (
select 1 id, 1 num, 'Hello' val from dual union all
select 1 id, 2 num, 'Goodbye' val from dual union all
select 2 id, 2 num, 'Hey' val from dual union all
select 2 id, 4 num, 'What''s up?' val from dual union all
select 3 id, 5 num, 'See you' val from dual)
select id, max(num), max(val) keep (dense_rank last order by num)
from sample_data
group by id;
When you use windowing function, you don't need to use GROUP BY anymore, this would suffice:
select id,
max(num) over(partition by id)
from x
Actually you can get the result without using windowing function:
select *
from x
where (id,num) in
(
select id, max(num)
from x
group by id
)
Output:
ID NUM VAL
1 2 Goodbye
2 4 What's up
3 5 SEE YOU
http://www.sqlfiddle.com/#!4/a9a07/7
If you want to use windowing function, you might do this:
select id, val,
case when num = max(num) over(partition by id) then
1
else
0
end as to_select
from x
where to_select = 1
Or this:
select id, val
from x
where num = max(num) over(partition by id)
But since it's not allowed to do those, you have to do this:
with list as
(
select id, val,
case when num = max(num) over(partition by id) then
1
else
0
end as to_select
from x
)
select *
from list
where to_select = 1
http://www.sqlfiddle.com/#!4/a9a07/19
If you're looking to get the rows which contain the values from MAX(num) GROUP BY id, this tends to be a common pattern...
WITH
sequenced_data
AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY id ORDER BY num DESC) AS sequence_id,
*
FROM
yourTable
)
SELECT
*
FROM
sequenced_data
WHERE
sequence_id = 1
EDIT
I don't know if TeraData will allow this, but the logic seems to make sense...
SELECT
*
FROM
yourTable
WHERE
num = MAX(num) OVER (PARTITION BY id)
Or maybe...
SELECT
*
FROM
(
SELECT
*,
MAX(num) OVER (PARTITION BY id) AS max_num_by_id
FROM
yourTable
)
AS sub_query
WHERE
num = max_num_by_id
This is slightly different from my previous answer; if multiple records are tied with the same MAX(num), this will return all of them, the other answer will only ever return one.
EDIT
In your proposed SQL the error relates to the fact that the OVER() clause contains a field not in your GROUP BY. It's like trying to do this...
SELECT id, num FROM yourTable GROUP BY id
num is invalid, because there can be multiple values in that field for each row returned (with the rows returned being defined by GROUP BY id).
In the same way, you can't put num inside the OVER() clause.
SELECT
id,
MAX(num), <-- Valid as it is an aggregate
MAX(num) <-- still valid
OVER(PARTITION BY id), <-- Also valid, as id is in the GROUP BY
MAX(num) <-- still valid
OVER(PARTITION BY num) <-- Not valid, as num is not in the GROUP BY
FROM
yourTable
GROUP BY
id
See this question for when you can't specify something in the OVER() clause, and an answer showing when (I think) you can: over-partition-by-question