SQL - create pivot table/cross tab from raw data - sql

What is the best way to transform the layout of a flat table into a crosstab/pivot table with SQL.
there is no need to calculations.
The first column of the raw table will be the first column in PV table, second colum of raw will be spitted in columns (12 distinct values) in PVtable. Values will be then the third column in raw table.
I am having difficulties to transform this layout and I guess I am putting to much cases for this making it hard to read/maintain.
does anyone has an idea how to do it?
Many thanks!
Example, below
RAW:
indx rank score
1 1 59
1 2 15
1 3 17
1 4 7
1 5 56
1 6 13
1 7 7
1 8 3
1 9 7
1 10 10
1 11 2
1 12 181
2 2 16
2 3 19
2 4 7
2 5 79
2 6 20
2 7 13
2 8 5
2 9 10
2 10 18
2 11 5
2 12 268
3 3 12
3 4 6
3 5 56
3 6 10
3 7 9
3 8 5
3 9 8
3 10 17
3 11 3
3 12 219
4 4 1
4 5 19
4 6 4
4 7 3
4 8 2
4 9 6
4 10 5
4 11 1
4 12 102
PVtable:
Rank
indx 1 2 3 4 5 6 7 8 9 10 11 12
1 59 15 17 7 56 13 7 3 7 10 2 181
2 - 16 19 7 79 20 13 5 10 18 5 267
3 - - 12 6 56 10 9 5 8 17 3 219
4 - - - 1 19 4 3 2 6 5 1 101
5 - - - - 0 0 0 0 0 0 0 0
6 - - - - - 0 0 0 0 0 0 0
7 - - - - - - 0 0 0 0 0 0
8 - - - - - - - 0 0 0 0 0
9 - - - - - - - - 0 0 0 0
10 - - - - - - - - - 0 0 0
11 - - - - - - - - - - 0 0
12 - - - - - - - - - - - 0

A canonical way that works in just about any database is the conditional aggregation approach:
select indx
max(case when rank = 1 then score end) as rank1,
max(case when rank = 2 then score end) as rank2,
. . .
max(case when rank = 12 then score end) as rank12
from table t
group by indx
order by indx;
In your example, though, it is unclear what indexes of 5 and more all have values of 0.
EDIT:
If you want the values of 5+ to be 0 and to get values from 1-2, then use a left outer join and change the logic in the max():
select n.n as indx,
max(case when rank = 1 then score end) as rank1,
max(case when rank = 2 then score end) as rank2,
. . .
max(case when rank = 12 then score when n.n >= 12 then 0 end) as rank12
from (select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all select 12
) n left outer join
table t
on n.n = t.indx
group by n.n
order by n.n;

Related

Select only data which columns does not have specific corresponding values respectively

image
Select only data which columns does not have specific corresponding values.
Table Values:
1 D675F009-6908-47A4-816A-AD25A68D8514 0
2 7C96A948-B889-4630-BF67-2187ECFA37DC 1
3 FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E 1
4 178B055F-45FF-4951-A9E2-3470B1DE25E9 1
5 FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E 0
6 D675F009-6908-47A4-816A-AD25A68D8514 0
7 59737584-F44F-4B42-AF9C-1550DFEC1EA5 1
8 FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E 1
9 D675F009-6908-47A4-816A-AD25A68D8514 1
10 7C96A948-B889-4630-BF67-2187ECFA37DC 0
11 178B055F-45FF-4951-A9E2-3470B1DE25E9 1
12 016FAF52-8FBF-4C9C-802D-CA9E13071719 0
Don't select values which have:
(D675F009-6908-47A4-816A-AD25A68D8514) have 1 respectively and
(FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E) have 1 respectively
Allow select values:
(D675F009-6908-47A4-816A-AD25A68D8514) have 0
respectively and (FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E) have 0
respectively
Expected Result::
1 D675F009-6908-47A4-816A-AD25A68D8514 0
2 7C96A948-B889-4630-BF67-2187ECFA37DC 1
4 178B055F-45FF-4951-A9E2-3470B1DE25E9 1
5 FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E 0
6 D675F009-6908-47A4-816A-AD25A68D8514 0
7 59737584-F44F-4B42-AF9C-1550DFEC1EA5 1
10 7C96A948-B889-4630-BF67-2187ECFA37DC 0
11 178B055F-45FF-4951-A9E2-3470B1DE25E9 1
12 016FAF52-8FBF-4C9C-802D-CA9E13071719 0
Is this what you want?
Select * from table where
(is_active=1 and
Participant_id NOT IN
('D675F009-6908-47A4-816A-AD25A68D8514', 'FD6DD4B4-6E5D-4282-B421-A849DB4B1D3E' )
) or
is_active=0;

Oracle generate Data

I tried to generate data using the "by level" expression.
I want to generate GROUPNR, starting with 1,
meanwhile each group contains 4 items with nr 1 to 4
This is, how the result should look like
groupnr itemnr
1 1
1 2
1 3
1 4
2 1
2 2
2 3
2 4
3 1
I used this statement, but I have no idea how to handle the nvl2 or other functions to get the right values
select level, floor(level+3/4) GROUPNR, nvl2(0, mod(level,4),4) ITEMNR
from dual
connect by level <= 25;
The result of the select is:
groupnr itemnr
1 1
1 2
1 3
1 0
2 1
2 2
2 3
2 0
4 1
Please give me a hint how to modify the level to get the right values.
Bye Jochen
This could be a way:
select floor((level -1) / 4) +1 as groupNR,
row_number() over (partition by floor((level -1) / 4) +1 order by level) as itemNR
from dual
connect by level <= 25;
or even, without analytic functions:
level - 4*floor((level -1) / 4) as itemNR
TEST:
SQL> select floor((level -1) / 4) +1 as groupNR,
2 row_number() over (partition by floor((level -1) / 4) +1 order by level) as itemNR,
3 level - 4*floor((level -1) / 4) as itemNR_2
4 from dual
5 connect by level <= 25
6 order by level;
GROUPNR ITEMNR ITEMNR_2
---------- ---------- ----------
1 1 1
1 2 2
1 3 3
1 4 4
2 1 1
2 2 2
2 3 3
2 4 4
3 1 1
3 2 2
3 3 3
3 4 4
4 1 1
4 2 2
4 3 3
4 4 4
5 1 1
5 2 2
5 3 3
5 4 4
6 1 1
6 2 2
6 3 3
6 4 4
7 1 1
25 rows selected.
as varaition to Aleksejs solution (Thanks):
select floor((level +3) / 4) as groupNR,
row_number() over (partition by floor((level +3) / 4) +1 order by 1) as itemNR
from dual
connect by level <= 25;
Your query was very close. I suggest you change it to:
select level,
TRUNC((LEVEL-1) / 4) + 1 AS GROUPNR,
mod(LEVEL-1, 4) + 1 AS ITEMNR
from dual
connect by level <= 25
This produces:
LEVEL GROUPNR ITEMNR
1 1 1
2 1 2
3 1 3
4 1 4
5 2 1
6 2 2
7 2 3
8 2 4
9 3 1
10 3 2
11 3 3
12 3 4
13 4 1
14 4 2
15 4 3
16 4 4
17 5 1
18 5 2
19 5 3
20 5 4
21 6 1
22 6 2
23 6 3
24 6 4
25 7 1
LEVEL won't be NULL so there's no need to fiddle around with NVL or anything like that.
Best of luck.

Repeat values with in the GROUP in SQL

I am trying to repeat a row value in the subsequent rows with in GROUP. A Group can have one or more TAG. The requirement is to populate NEW_TAG in the row where the TAG is populated and in the subsequent rows until another TAG populated with in the same group or we reach end of that GROUP.
Current Table Required Table
GROUPID SEQ TAG GROUPID SEQ TAG NEW_TAG
------- --- ---- ------- --- --- --------
1 1 1 1
1 2 1 2
1 3 1 3
1 4 4 1 4 4 4
1 5 1 5 4
1 6 1 6 4
1 7 1 7 4
1 8 1 8 4
2 1 2 1
2 2 2 2
2 3 2 3
2 4 2 4
2 5 5 2 5 5 5
2 6 2 6 5
2 7 2 7 5
2 8 2 8 5
2 9 9 2 9 9 9
2 10 2 10 9
2 11 2 11 9
select
groupid,
seq,
tag,
last_value(tag) ignore nulls over (
partition by groupid
order by seq
) as new_tag
from t
order by groupid, seq;
GRO SEQ TAG NEW_TAG
1 1 - -
1 2 - -
1 3 - -
1 4 4 4
1 5 - 4
1 6 - 4
1 7 - 4
1 8 - 4
2 1 - -
2 2 - -
2 3 - -
2 4 - -
2 5 5 5
2 6 - 5
2 7 - 5
2 8 - 5
2 9 9 9
2 10 - 9
2 11 - 9
19 rows selected.

SQL or PL/SQL queries to Print sequence of given N numbers

I need help in building the query as the below structured.i have tried using for loop but its just printing as 1 2 3 4 5 6 7 8 9
For example if the N Value is 9 means the output should be like this.
EXAMPLE Output :
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0
To do this just in SQL - albeit with a bind variable if you want to be able to specify n - you need to combine start with the connect by and build from there. This is one way, though I'm pretty sure it can be done without the union:
with t as (
select level as rn, level - 1 as val
from dual
connect by level <= :n + 1
)
select t1.rn as rn,
listagg(t2.val, ' ') within group (order by t2.val) as answer
from t t1
join t t2 on t2.val <= t1.val
group by t1.rn, t1.val
union all
select (2 * (:n + 1)) - t1.rn,
listagg(t2.val, ' ') within group (order by t2.val) as answer
from t t1
join t t2 on t2.val <= t1.val
where t1.rn <= :n
group by t1.rn, t1.val
order by rn;
The CTE generates the numbers 0 to n. The two halves of the union create the mirror halves of the output; the second has the rn <= :n filter to prevent the 'middle' line being duplicated.
With:
var n number;
exec :n := 9;
This gives:
RN ANSWER
------ ----------------------------------------
1 0
2 0 1
3 0 1 2
4 0 1 2 3
5 0 1 2 3 4
6 0 1 2 3 4 5
7 0 1 2 3 4 5 6
8 0 1 2 3 4 5 6 7
9 0 1 2 3 4 5 6 7 8
10 0 1 2 3 4 5 6 7 8 9
11 0 1 2 3 4 5 6 7 8
12 0 1 2 3 4 5 6 7
13 0 1 2 3 4 5 6
14 0 1 2 3 4 5
15 0 1 2 3 4
16 0 1 2 3
17 0 1 2
18 0 1
19 0
Or for 6:
exec :n := 6;
RN ANSWER
------ ----------------------------------------
1 0
2 0 1
3 0 1 2
4 0 1 2 3
5 0 1 2 3 4
6 0 1 2 3 4 5
7 0 1 2 3 4 5 6
8 0 1 2 3 4 5
9 0 1 2 3 4
10 0 1 2 3
11 0 1 2
12 0 1
13 0
You don't really want to see the rn but you can remove that by putting this in a subquery.
This select query gives exact result:
WITH CTE1 AS
(SELECT 9 AS COL FROM DUAL)
,CTE2
AS (
SELECT LEVEL - 1 AS A,
SYS_CONNECT_BY_PATH(LEVEL - 1, ' ') AS B,
COL
FROM DUAL, CTE1
CONNECT BY LEVEL <= COL + 1)
SELECT B
FROM (SELECT A, B FROM CTE2
UNION ALL
SELECT 2 * COL - A, B
FROM CTE2
WHERE A != COL)
ORDER BY A;
OUTPUT:
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0
Using a while loop:
DECLARE
my_limit SIMPLE_INTEGER := 9;
my_step SIMPLE_INTEGER := +1;
i SIMPLE_INTEGER := 0;
s VARCHAR2(32000);
BEGIN
WHILE (i > -1) LOOP
s := '';
FOR j IN 0 .. i LOOP
IF j>0 THEN s := s || ' '; END IF;
s := s || to_char(j);
END LOOP;
dbms_output.put_line(s);
IF (i >= my_limit) THEN my_step := -1; END IF;
i := i + my_step;
END LOOP;
END;
/
I think, that sys_connect_by_path is what you are looking for.
Oracle Doc
SYS_CONNECT_BY_PATH is valid only in hierarchical queries. It returns the path of a column value from root to node, with column values separated by char for each row returned by CONNECT BY condition.
with desired_number as (select 5 as nm from dual)
,tree
as ( select level - 1 as a, sys_connect_by_path(level - 1, ' ') as b, nm
from dual, desired_number
connect by level <= nm + 1)
select *
from (select a, b from tree
union all
select 2 * nm - a, b
from tree
where a != nm)
order by a
Btw, how do you format it like sql?
You may find connect by to be a useful basis for working out the answer.
select level n
from dual
connect by level <= 10
Using three nested for loops:
DECLARE
my_limit SIMPLE_INTEGER := 9;
PROCEDURE one_line(n simple_integer) IS
s VARCHAR2(4000);
BEGIN
FOR j IN 0 .. n LOOP
IF j>0 THEN s := s || ' '; END IF;
s := s || to_char(j);
END LOOP;
dbms_output.put_line(s);
END one_line;
BEGIN
FOR i IN 0 .. my_limit LOOP
one_line(i);
END LOOP;
FOR i IN REVERSE 0 .. my_limit-1 LOOP
one_line(i);
END LOOP;
END;
/
Got it at last:
WITH CTE AS
(
SELECT LEVEL-1 COL
FROM DUAL
CONNECT BY LEVEL <= 10
)
SELECT SYS_CONNECT_BY_PATH(COL, ' ') COL
FROM ( SELECT COL,
ROW_NUMBER() OVER (ORDER BY COL) RN
FROM CTE)
START WITH RN = 1
CONNECT BY PRIOR RN = RN - 1
UNION ALL
SELECT COL FROM(
SELECT RPAD(SYS_CONNECT_BY_PATH(COL, ' ') , '20' )COL
FROM ( SELECT COL,
ROW_NUMBER() OVER (ORDER BY COL) RN
FROM CTE)
START WITH RN = 1
CONNECT BY PRIOR RN = (RN - 1 )
ORDER BY COL DESC
);
Produces This output:
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6
0 1 2 3 4 5
0 1 2 3 4
0 1 2 3
0 1 2
0 1
0

Display Rows only if group of rows' sum is greater then 0

I have a table like the one below. I would like to get this data to SSRS (Grouped by LineID and Product and Column as Hour) to show only those rows where HourCount > 0 for every LineID and Product.
LineID Product Hour HourCount
3 A 0 0
3 A 1 0
3 A 2 0
3 A 3 0
3 A 4 0
3 A 5 0
3 B 0 65
3 B 1 56
3 B 2 45
3 B 3 34
3 B 4 43
3 B 5 45
4 A 0 54
4 A 1 34
4 A 2 45
4 A 3 44
4 A 4 55
4 A 5 44
4 B 0 0
4 B 1 0
4 B 2 0
4 B 3 0
4 B 4 0
4 B 5 0
5 A 0 45
5 A 1 77
5 A 2 66
5 A 3 55
5 A 4 0
5 A 5 0
5 B 0 0
5 B 1 0
5 B 2 45
5 B 3 0
5 B 4 0
5 B 5 0
Basically I would like this table to look like this before it's in SSRS:
LineID Product Hour HourCount
3 B 0 65
3 B 1 56
3 B 2 45
3 B 3 34
3 B 4 43
3 B 5 45
4 A 0 54
4 A 1 34
4 A 2 45
4 A 3 44
4 A 4 55
4 A 5 44
5 A 0 45
5 A 1 77
5 A 2 66
5 A 3 55
5 A 4 0
5 A 5 0
5 B 0 0
5 B 1 0
5 B 2 45
5 B 3 0
5 B 4 0
5 B 5 0
So display Product for the line only if any of the Hourd have HourCount higher then 0.
Is there any query that could give me these results or I should play with display settings in SSRS?
Something like this should work:
with NonZero as
(
select *
, GroupZeroCount = sum(HourCount) over (partition by LineID, Product)
from HourTable
)
select LineID
, Product
, [Hour]
, HourCount
from NonZero
where GroupZeroCount > 0
SQL Fiddle with demo.
You could certainly so something similar in SSRS, but it's certainly much easier and intuitive to apply at the T-SQL level.
I think you are looking for
SELECT LineID,Product,Hour,Count(Hour) AS HourCount
FROM abc
GROUP BY LineID,Productm,Hour HAVING Count(Hour) > 0