Select values from different rows of same column INTO multiple variables in Oracle SQL - sql

Here's the example:
ID | value
1 51
2 25
3 11
4 27
5 21
I need to get first three parameters and place them into variables e.g. out_x, out_y, out_z.
Is it possible to do it without multiple selects?

You can do something like this:
select max(case when id = 1 then value end),
max(case when id = 2 then value end),
max(case when id = 3 then value end)
into out_x, out_y, out_z
from t
where id in (1, 2, 3);
However, I think three queries of the form:
select value into out_x
from t
where id = 1;
is a cleaner approach.

You can use a PIVOT:
SELECT x, y, z
INTO out_x, out_y, out_z
FROM your_table
PIVOT ( MAX( value ) FOR id IN ( 1 AS x, 2 AS y, 3 AS z ) )
Or, if you do not know which IDs you need (but just want the first 3) then:
SELECT x, y, z
INTO out_x, out_y, out_z
FROM (
SELECT value, ROWNUM AS rn
FROM ( SELECT value FROM your_table ORDER BY id )
WHERE ROWNUM <= 3
)
PIVOT ( MAX( value ) FOR rn IN ( 1 AS x, 2 AS y, 3 AS z ) )

Related

Filtering on two conditions in SQL

I am using Oracle SQL and have the following table, which I would like to filter to exclude the records in which ID = 2 and GRP = X, and ID = 3 and GRP = X, as these were entered in error.
ID GRP
1 X
2 B
2 X
3 C
3 X
What is the correct syntax to do so? My desired end result table is:
ID GRP
1 X
2 B
3 C
Using row value constructor:
SELECT *
FROM tab
WHERE (ID, GRP) NOT IN ((2,'X'),(3,'X'))
SELECT *
FROM tab
WHERE ID NOT IN (2,3) AND GRP <> 'X'
or
SELECT *
FROM tab
WHERE (ID <> 2 OR ID <> 3) AND GRP <> 'X'

How to calculate leave-out-median using PL/SQL analytic function

I'm trying to write an analytic function in PL/SQL that, when applied to a column within a table, returns for each row in the table, the median of the column excluding the given row.
An example to clarify: Suppose I have a table TABLE consisting of one column X that takes on the following values:
1
2
3
4
5
I want to define an analytic function LOOM() such that:
SELECT LOOM(X)
FROM TABLE
delivers the following:
3.5
3.5
3
2.5
2.5
i.e., for each row, the median of X, excluding the given row. I've been struggling to build the desired LOOM() function.
I'm not sure if there is a "clever" way to do this. You can do the calculation with a correlated subquery.
Assuming the x values are unique -- as in your example --
with t as (
select 1 as x from dual union all
select 2 as x from dual union all
select 3 as x from dual union all
select 4 as x from dual union all
select 5 as x from dual
)
select t.*,
(select median(x)
from t t2
where t2.x <> t.x
) as loom
from t;
EDIT:
A more efficient method uses analytic functions but requires more direct calculation of the median. For instance:
with t as (
select 1 as x from dual union all
select 2 as x from dual union all
select 3 as x from dual union all
select 4 as x from dual union all
select 5 as x from dual
)
select t.*,
(case when mod(cnt, 2) = 0
then (case when x <= candidate_1 then candidate_2 else candidate_1 end)
else (case when x <= candidate_1 then (candidate_2 + candidate_3)/2
when x = candidate_2 then (candidate_1 + candidate_3)/2
else (candidate_1 + candidate_2) / 2
end)
end) as loom
from (select t.*,
max(case when seqnum = floor(cnt / 2) then x end) over () as candidate_1,
max(case when seqnum = floor(cnt / 2) + 1 then x end) over () as candidate_2,
max(case when seqnum = floor(cnt / 2) + 2 then x end) over () as candidate_3
from (select t.*,
row_number() over (order by x) as seqnum,
count(*) over () as cnt
from t
) t
) t

SQL Rows to Separate Columns

I realise this maybe similar to other questions, but I am stuck!
I am having trouble organising some data into an appropriate format to export to another tool. Basically I have an ID column and then 2 response columns. I would like to separate the ID and then list the responses under each. See the example below for clarification.
I have played around with Pivot and UnPivot but can't get it quite right.
Here is how the data looks now.
ID X1 X2
1 2 Y
1 5 Y
1 3 N
1 7 N
1 6 Y
2 5 N
2 4 Y
2 8 Y
2 3 N
3 5 Y
3 1 N
3 9 N
Here is how I would like the data to look
ID1_X1 ID1_X2 ID2_X1 ID2_X2 ID3_X1 ID3_X2
2 Y 5 N 5 Y
5 Y 4 Y 1 N
3 N 8 Y 9 N
7 N 3 N null null
6 Y null null null null
Here is the code to create/populate the table.
create table #test (ID int, X1 int, X2 varchar(1))
insert into #test values
('1','2','Y'),('1','5','Y'),('1','3','N'),('1','7','N'),
('1','6','Y'),('2','5','N'),('2','4','Y'),('2','8','Y'),
('2','3','N'),('3','5','Y'),('3','1','N'),('3','9','N')
You can do this using aggregation and row_number() . . . assuming you know the ids in advance:
select max(case when id = 1 then x1 end) as x1_1,
max(case when id = 1 then x2 end) as x2_1,
max(case when id = 2 then x1 end) as x1_2,
max(case when id = 2 then x2 end) as x2_2,
max(case when id = 3 then x1 end) as x1_3,
max(case when id = 3 then x2 end) as x2_3
from (select t.*,
row_number() over (partition by id order by (select null)) a seqnum
from #test t
) t
group by seqnum;
I should note that SQL tables represent unordered sets. Your original data doesn't have an indication of the ordering, so this is not guaranteed to put the values in the same order as the original data (actually, there is no such order that that statement is a tautology). If you have another column with the ordering, then you can use that.
Here is a alternative approach to Gordan's good answer using OUTER JOIN's
Considering that there is a Identity column in your table to define the order of X1 in each ID and fixed number of ID's
;WITH FST
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID1_X1,X2 AS ID1_X2
FROM #TEST A
WHERE ID = 1),
SCD
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID2_X1,X2 AS ID2_X2
FROM #TEST A
WHERE ID = 2),
TRD
AS (SELECT ROW_NUMBER()OVER(ORDER BY IDENTITY_COL) RN,X1 AS ID3_X1,X2 AS ID3_X2
FROM #TEST A
WHERE ID = 3)
SELECT ID1_X1,ID1_X2,ID2_X1,ID2_X2,ID3_X1,ID3_X2
FROM FST A
FULL OUTER JOIN SCD B
ON A.RN = B.RN
FULL OUTER JOIN TRD C
ON C.RN = COALESCE(B.RN, A.RN)

SELECT records until new value SQL

I have a table
Val | Number
08 | 1
09 | 1
10 | 1
11 | 3
12 | 0
13 | 1
14 | 1
15 | 1
I need to return the last values where Number = 1 (however many that may be) until Number changes, but do not need the first instances where Number = 1. Essentially I need to select back until Number changes to 0 (15, 14, 13)
Is there a proper way to do this in MSSQL?
Based on following:
I need to return the last values where Number = 1
Essentially I need to select back until Number changes to 0 (15, 14,
13)
Try (Fiddle demo ):
select val, number
from T
where val > (select max(val)
from T
where number<>1)
EDIT: to address all possible combinations (Fiddle demo 2)
;with cte1 as
(
select 1 id, max(val) maxOne
from T
where number=1
),
cte2 as
(
select 1 id, isnull(max(val),0) maxOther
from T
where val < (select maxOne from cte1) and number<>1
)
select val, number
from T cross join
(select maxOne, maxOther
from cte1 join cte2 on cte1.id = cte2.id
) X
where val>maxOther and val<=maxOne
I think you can use window functions, something like this:
with cte as (
-- generate two row_number to enumerate distinct groups
select
Val, Number,
row_number() over(partition by Number order by Val) as rn1,
row_number() over(order by Val) as rn2
from Table1
), cte2 as (
-- get groups with Number = 1 and last group
select
Val, Number,
rn2 - rn1 as rn1, max(rn2 - rn1) over() as rn2
from cte
where Number = 1
)
select Val, Number
from cte2
where rn1 = rn2
sql fiddle demo
DEMO: http://sqlfiddle.com/#!3/e7d54/23
DDL
create table T(val int identity(8,1), number int)
insert into T values
(1),(1),(1),(3),(0),(1),(1),(1),(0),(2)
DML
; WITH last_1 AS (
SELECT Max(val) As val
FROM t
WHERE number = 1
)
, last_non_1 AS (
SELECT Coalesce(Max(val), -937) As val
FROM t
WHERE EXISTS (
SELECT val
FROM last_1
WHERE last_1.val > t.val
)
AND number <> 1
)
SELECT t.val
, t.number
FROM t
CROSS
JOIN last_1
CROSS
JOIN last_non_1
WHERE t.val <= last_1.val
AND t.val > last_non_1.val
I know it's a little verbose but I've deliberately kept it that way to illustrate the methodolgy.
Find the highest val where number=1.
For all values where the val is less than the number found in step 1, find the largest val where the number<>1
Finally, find the rows that fall within the values we uncovered in steps 1 & 2.
select val, count (number) from
yourtable
group by val
having count(number) > 1
The having clause is the key here, giving you all the vals that have more than one value of 1.
This is a common approach for getting rows until some value changes. For your specific case use desc in proper spots.
Create sample table
select * into #tmp from
(select 1 as id, 'Alpha' as value union all
select 2 as id, 'Alpha' as value union all
select 3 as id, 'Alpha' as value union all
select 4 as id, 'Beta' as value union all
select 5 as id, 'Alpha' as value union all
select 6 as id, 'Gamma' as value union all
select 7 as id, 'Alpha' as value) t
Pull top rows until value changes:
with cte as (select * from #tmp t)
select * from
(select cte.*, ROW_NUMBER() over (order by id) rn from cte) OriginTable
inner join
(
select cte.*, ROW_NUMBER() over (order by id) rn from cte
where cte.value = (select top 1 cte.value from cte order by cte.id)
) OnlyFirstValueRecords
on OriginTable.rn = OnlyFirstValueRecords.rn and OriginTable.id = OnlyFirstValueRecords.id
On the left side we put an original table. On the right side we put only rows whose value is equal to the value in first line.
Records in both tables will be same until target value changes. After line #3 row numbers will get different IDs associated because of the offset and will never be joined with original table:
LEFT RIGHT
ID Value RN ID Value RN
1 Alpha 1 | 1 Alpha 1
2 Alpha 2 | 2 Alpha 2
3 Alpha 3 | 3 Alpha 3
----------------------- result set ends here
4 Beta 4 | 5 Alpha 4
5 Alpha 5 | 7 Alpha 5
6 Gamma 6 |
7 Alpha 7 |
The ID must be unique. Ordering by this ID must be same in both ROW_NUMBER() functions.

compare within groups to fetch common value

I have a table with the follwing data
Case 1:
table1
-------------
id type
-------------
1 X
1 Y
2 X
3 Z
3 X
-------------
Now as you see X is common to all the id ,so i need to return X in this case
Case2 :
table1
-------------
id type
-------------
1 X
1 Y
2 X
2 Y
3 X
3 Y
--------------
In this case both X and Y are common,then i need to return both Xand Y comma seperated (X,y)
Case 3
table1
-------------
id type
-------------
1 X
1 Y
2 X
2 Y
3 X
3 Y
4 NULL
------------------
If a null came to any of the record , i need to return NULL
Actually ,the data i have shouwn you , is been populated from 3 tables , so i have already written the query for that ,but now i need to compare the groups for the common data within groups ,that is confusing me ,how to compare the groups ?
Note :Here group is based on ID
Any help would be appriciated
you could count the occurances compared to the count of the IDs?
with data as (select rownum id, 'X' type from dual connect by level <= 3
union all
select rownum id, 'Y' type from dual connect by level <= 3
union all
select 3 id, 'Z' type from dual)
select wm_concat(distinct type)
from (select type, count(*) over (partition by type) cnt, count(distinct id) over () total_ids
from data)
where cnt = total_ids;
in 11g you have LISTAGG instead of WM_CONCAT of course. if for each id, the same type occurs many times, you can change count(*) over (partition by type) to count(distinct id) over (partition by type)
edit:
If you had
3, Z
3, NULL
(rather than 4, NULL) and also want to return a NULL rather than a delimited list in that case then you could add a check (with the 4, NULL above it would return a null even on the prior SQL version as the counts would'nt tie up):
with data as (select rownum id, 'X' type from dual connect by level <= 3
union all
select rownum id, 'Y' type from dual connect by level <= 3
union all
select 3 id, 'Z' type from dual)
select wm_concat(distinct type)
from (select type, count(*) over (partition by type) cnt, count(distinct id) over
() total_ids,
max(case when type is null then 'Y' else 'N' end) over () has_null_in_set
from data)
where cnt = total_ids
and has_null_in_set = 'N';