oracle transposing with analytical function [duplicate] - sql

This question already has answers here:
Oracle10g SQL pivot
(2 answers)
Closed 5 years ago.
I am transposing key value pairs from a table and facing an issue.
I am using Oracle 12C database.
Test data looks like this. table is tab1
+---------------------------+
| Name | VAL | ID | grp_id|
+---------------------------+
| a | 3 | 1 | 1 |
| b | 5 | 2 | 1 |
| c | 8 | 3 | 1 |
| c | 9 | 4 | 2 |
+---------------------------+
My expected result is
+-------------------------+
| grp_id| a | b | c |
+-------------------------+
| 1 | 3 | 5 | 8 |
| 2 | null | null | 9 |
+-------------------------+
What I did so far is
with t as(
select row_number() over (partition by grp_id order by grp_id) rn,
name,
grp_id,
lead(val,0) over (partition by grp_id order by grp_id) as a,
lead(val,1) over (partition by grp_id order by grp_id) as b,
lead(val,2) over (partition by grp_id order by grp_id) as c
from tab1 where grp_id in (1,2) and name in ('a', 'b','c')
)
select grp_id,a,b,c from t where rn=1;
When data is consistent and for all grp_id-s key value pairs are the same then this query works fine, But in case when some keys are missing for one grp_id then I get a result like the following which is wrong and not what I expect
+----------------------------+
| grp_id| a | b | c |
+----------------------------+
| 1 | 3 | 5 | 8 |
| 2 | 9 | null | null |
+----------------------------+
How can I improve the query to work correctly? And I want to avoid using pivot

I would do this using conditional aggregation:
select grp_id,
max(case when name = 'a' then val end) as a,
max(case when name = 'b' then val end) as b,
max(case when name = 'c' then val end) as c
from tab1
group by grp_id;
grp_id is already defined so I see no need for analytic functions.

Related

How to get duplicate columns in multiple columns and single row in SQL

Below is the original table
+----------+---------+
| Trade | Key |
+----------+---------+
| A | 1 |
| A | 2 |
| A | 3 |
| B | 1 |
| B | 2 |
| B | 3 |
+----------+---------+
Below is the results i need
+----------+---------+---------+---------+
| Trade | Key1 | Key2 | Key3 |
+----------+---------+---------+---------+
| A | 1 | 2 | 3 |
| B | 1 | 2 | 3 |
+----------+---------+---------+---------+
Any pointers to the SQL code is appreciated.
Thanks in advance
Sarge
Look for PIVOT.
It is sql function that allows you to do exactly what you need.
The downside with PIVOT is that not all DBs support it. Make sure your does.
Look at the following answer for further explanation: https://stackoverflow.com/a/15931734/16462128
I think you want conditional aggregation like this:
select trade,
max(case when seqnum = 1 then key end) as key_1,
max(case when seqnum = 2 then key end) as key_2,
max(case when seqnum = 3 then key end) as key_3
from (select t.*,
row_number() over (partition by trade order by key) as seqnum
from t
) t
group by trade;
You will need to explicit list the number of columns to be sure you get all keys for all trades (your example data has three).

SQL SERVER How to select the latest record in each group? [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Closed 2 years ago.
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:00:20 | 0 |
| 1 | 0:00:40 | 1 |
| 1 | 0:01:00 | 1 |
| 2 | 0:01:20 | 1 |
| 2 | 0:01:40 | 0 |
| 2 | 0:02:00 | 1 |
| 3 | 0:02:20 | 1 |
| 3 | 0:02:40 | 1 |
| 3 | 0:03:00 | 0 |
I have this and I would like to turn it into
| ID | TimeStamp | Item |
|----|-----------|------|
| 1 | 0:01:00 | 1 |
| 2 | 0:02:00 | 1 |
| 3 | 0:03:00 | 0 |
Please advise, thank you!
A correlated subquery is often the fastest method:
select t.*
from t
where t.timestamp = (select max(t2.timestamp)
from t t2
where t2.id = t.id
);
For this, you want an index on (id, timestamp).
You can also use row_number():
select t.*
from (select t.*,
row_number() over (partition by id order by timestamp desc) as seqnum
from t
) t
where seqnum = 1;
This is typically a wee bit slower because it needs to assign the row number to every row, even those not being returned.
You need to group by id, and filter out through timestamp values descending in order to have all the records returning as first(with value 1) in the subquery with contribution of an analytic function :
SELECT *
FROM
(
SELECT *,
DENSE_RANK() OVER (PARTITION BY ID ORDER BY TimeStamp DESC) AS dr
FROM t
) t
WHERE t.dr = 1
where DENSE_RANK() analytic function is used in order to include records with ties also.

SQL select distinct when one column in and another column greater than

Consider the following dataset:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 1 | a | 0.2 |
| 1 | b | 8 |
| 1 | c | 3.5 |
| 1 | d | 2.2 |
| 2 | b | 4 |
| 2 | c | 0.5 |
| 2 | d | 6 |
| 3 | a | 2 |
| 3 | b | 4 |
| 3 | c | 3.6 |
| 3 | d | 0.2 |
+---------------------+
I'm tying to develop a sql select statement that returns the top or distinct ID where NAME 'a' and 'b' both exist and both of the corresponding VALUE's are >= '1'. Thus, the desired output would be:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 3 | a | 2 |
+----+-------+--------+
Appreciate any assistance anyone can provide.
You can try to use MIN window function and some condition to make it.
SELECT * FROM (
SELECT *,
MIN(CASE WHEN NAME = 'a' THEN [value] end) OVER(PARTITION BY ID) aVal,
MIN(CASE WHEN NAME = 'b' THEN [value] end) OVER(PARTITION BY ID) bVal
FROM T
) t1
WHERE aVal >1 and bVal >1 and aVal = [Value]
sqlfiddle
This seems like a group by and having query:
select id
from t
where name in ('a', 'b')
having count(*) = 2 and
min(value) >= 1;
No subqueries or joins are necessary.
The where clause filters the data to only look at the "a" and "b" records. The count(*) = 2 checks that both exist. If you can have duplicates, then use count(distinct name) = 2.
Then, you want the minimum value to be 1, so that is the final condition.
I am not sure why your desired results have the "a" row, but if you really want it, you can change the select to:
select id, 'a' as name,
max(case when name = 'a' then value end) as value
you can use in and sub-query
select top 1 * from t
where t.id in
(
select id from t
where name in ('a','b')
group by id
having sum(case when value>1 then 1 else 0)>=2
)
order by id

Efficient ROW_NUMBER increment when column matches value

I'm trying to find an efficient way to derive the column Expected below from only Id and State. What I want is for the number Expected to increase each time State is 0 (ordered by Id).
+----+-------+----------+
| Id | State | Expected |
+----+-------+----------+
| 1 | 0 | 1 |
| 2 | 1 | 1 |
| 3 | 0 | 2 |
| 4 | 1 | 2 |
| 5 | 4 | 2 |
| 6 | 2 | 2 |
| 7 | 3 | 2 |
| 8 | 0 | 3 |
| 9 | 5 | 3 |
| 10 | 3 | 3 |
| 11 | 1 | 3 |
+----+-------+----------+
I have managed to accomplish this with the following SQL, but the execution time is very poor when the data set is large:
WITH Groups AS
(
SELECT Id, ROW_NUMBER() OVER (ORDER BY Id) AS GroupId FROM tblState WHERE State=0
)
SELECT S.Id, S.[State], S.Expected, G.GroupId FROM tblState S
OUTER APPLY (SELECT TOP 1 GroupId FROM Groups WHERE Groups.Id <= S.Id ORDER BY Id DESC) G
Is there a simpler and more efficient way to produce this result? (In SQL Server 2012 or later)
Just use a cumulative sum:
select s.*,
sum(case when state = 0 then 1 else 0 end) over (order by id) as expected
from tblState s;
Other method uses subquery :
select *,
(select count(*)
from table t1
where t1.id < t.id and state = 0
) as expected
from table t;

Updating Rows Into Different Columns

Both of these tables already exist, so not looking for a dynamic situation. The goal is to consolidate the data rows horizontally, but have them to the leftmost "data" field available. There will never be a 4th entry.
I am using Microsoft SQL Server
Table1:
ID|Data
--------
A | 1
A | 2
B | 3
C | 4
C | 5
C | 6
Table2:
ID | Data 1 | Data 2 | Data 3
------------------------------
A | | |
B | | |
C | | |
Desired Result of Table2:
ID | Data 1 | Data 2 | Data 3
------------------------------
A | 1 | 2 |
B | 3 | |
C | 6 | 7 | 8
You can use row_number:
select id,
max(case when rn = 1 then data end) as data_1,
max(case when rn = 2 then data end) as data_2,
max(case when rn = 3 then data end) as data_3
from (
select t.*,
row_number() over (
partition by id order by data
) as rn
from your_table t
) t
group by id;