How to pivot Oracle SQL query result? - sql

I have an ORACLE SQL query that is as follows :
SELECT B1 "Categories",
B2 "Category1",
B3 "Category2",
B4 "Category3",
B5 "Category4",
B6 "Total"
FROM (
SELECT C_NAME B1,
SUM(BUCKET_1) B2,
SUM(BUCKET_2) B3,
SUM(BUCKET_3) B4,
SUM(BUCKET_4) + SUM(BUCKET_5) + SUM(BUCKET_6) B5,
SUM(BUCKET) B6
FROM BUCKETTABLE
WHERE ID = bucketID
GROUP BY C_NAME
ORDER BY C_NAME
);
The result I get from the query :
The result I want to obtain :
How do I get this result?

Related

How to UPIVOT all columns in a table and aggregate into Data Quality/ Validation Metrics? SQL SNOWFLAKE

I have a table with 60+ columns in it that I would like to UNPIVOT so that each column becomes a row and then find the fill rate, min value and max value of each entry.
For Example
ID
START_DATE
END_DATE
EVENT_ID
PROVIDER_CODE
01
01/23/21
03/14/21
0023401
0012323
02
06/04/21
09/20/21
0025906
0023454
03
07/20/21
12/02/21
0027093
0034983
And I want the output to look like
Column_Name
Fill_Rate
Min
Max
ID
0.7934
01
03
Start_Date
0.6990
01/23/21
07/20/21
End_Date
0.9089
03/14/21
12/02/21
Event_ID
1.0000
0023401
0027093
Struggling to get the desired output, especially because of different data types in the different columns
i tried doing the following, but it doesn't allow taking the agg functions within the unpivot
select *
from "DSVC_MERCKPAN_PROD"."COHORTS_LATEST"."MEDICAL_HEADERS"
UNPIVOT (
max(code) as max_value,
min(code) as min_value,
avg(code) as fill_rate,
code as column_name
)
For fill rate, I was trying to use this logic as ID is always populated so it has the total number of rows, however the other columns can be null
(COUNT_IF(start_date is not null))/(COUNT_IF(ID is not null))) as FILL_RATE,
I have 2 ideas to implement the report.
The first way is casting all values to VARCHAR and then using UNPIVOT:
-- Generate dummy data
create or replace table t1 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 int, c7 int, c8 int, c9 int, c10 int) as
select
iff(random()%2=0, random(), null), iff(random()%2=0, random(), null),
iff(random()%2=0, random(), null), iff(random()%2=0, random(), null),
iff(random()%2=0, random(), null), iff(random()%2=0, random(), null),
iff(random()%2=0, random(), null), iff(random()%2=0, random(), null),
iff(random()%2=0, random(), null), iff(random()%2=0, random(), null)
from table(generator(rowcount => 1000000000))
;
-- Query
with
cols as (
select column_name, ordinal_position
from information_schema.columns
where table_catalog = current_database()
and table_schema = current_schema()
and table_name = 'T1'
),
stringified as (
select
c1::varchar c1, c2::varchar c2, c3::varchar c3, c4::varchar c4, c5::varchar c5,
c6::varchar c6, c7::varchar c7, c8::varchar c8, c9::varchar c9, c10::varchar c10
from t1
),
data as (
select column_name, column_value
from stringified
unpivot(column_value for column_name in (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10))
)
select
c.column_name,
count(d.column_value)/(select count(*) from t1) fill_rate,
min(d.column_value) min,
max(d.column_value) max
from cols c
left join data d using (column_name)
group by c.column_name, c.ordinal_position
order by c.ordinal_position
;
/*
COLUMN_NAME FILL_RATE MIN MAX
C1 0.500000 -1000000069270747870 999999972962694409
C2 0.499980 -1000000027928146782 999999946877079818
C3 0.499996 -1000000012155323098 999999942281548701
C4 0.500017 -1000000056353213091 999999946421698482
C5 0.500015 -1000000015608859996 999999993977648967
C6 0.500003 -1000000007081089270 999999998851014730
C7 0.499987 -100000008605944993 999999968272328033
C8 0.499992 -1000000042470913027 999999977402822725
C9 0.500011 -1000000058928465662 999999969060696774
C10 0.500029 -1000000011306371004 99999996061390938
*/
It's a straightforward way, but it still needs to list up all column names twice and it's a bit tough in the case the number of columns is very massive (but I believe it's much better than a huge UNION ALL query).
Another solution is a bit tricky, but you can unpivot a table by using OBJECT_CONSTRUCT(*) aggregation if the row length doesn't exceed a VARIANT value limit (16 MiB):
with
cols as (
select column_name, ordinal_position
from information_schema.columns
where table_catalog = current_database()
and table_schema = current_schema()
and table_name = 'T1'
),
data as (
select f.key column_name, f.value::varchar column_value
from (select object_construct(*) rec from t1) up,
lateral flatten(up.rec) f
)
select
c.column_name,
count(d.column_value)/(select count(*) from t1) fill_rate,
min(d.column_value) min,
max(d.column_value) max
from cols c
left join data d using (column_name)
group by c.column_name, c.ordinal_position
order by c.ordinal_position
;
/*
COLUMN_NAME FILL_RATE MIN MAX
C1 0.500000 -1000000069270747870 999999972962694409
C2 0.499980 -1000000027928146782 999999946877079818
C3 0.499996 -1000000012155323098 999999942281548701
C4 0.500017 -1000000056353213091 999999946421698482
C5 0.500015 -1000000015608859996 999999993977648967
C6 0.500003 -1000000007081089270 999999998851014730
C7 0.499987 -100000008605944993 999999968272328033
C8 0.499992 -1000000042470913027 999999977402822725
C9 0.500011 -1000000058928465662 999999969060696774
C10 0.500029 -1000000011306371004 99999996061390938
*/
OBJECT_CONSTRUCT(*) aggregation is a special usage of the OBJECT_CONSTRUCT function that extracts column names as a key of each JSON object. As far as I know, this is the only way to extract column names from a table along with values in a programmatic way.
Since OBJECT_CONSTRUCT is relatively a heavy operation, it usually takes a longer time than the first solution, but you don't need to write all column names with this trick.

How to sort alphanumeric parameter/field in SSRS 2016 correctly?

I have a report for inventory that needs to be fixed. We have parameters of beginning bins and ending bins. Right now, the parameters are sorting the bins like A1, A10, A11, A12, A2, A20, A21, A3 etc.
I realize the mistake was not putting in a "0" before A2, A3 etc. I am trying to figure out how to sort this correctly so it displays A1, A2, A3 before A10, A11, A12...
I am not sure if the best way to go about this is with a tablix properties sort expression or casting it in my dataset query? Any help is appreciated.
WHERE
(p21_view_location.location_id = #Location_ID)
AND (p21_view_inv_loc.qty_on_hand >= #Minimum_QOH)
--AND bins.[QOH (Bin)] >= #Minimum_QOH
AND (bins.bin BETWEEN #Beginning_Bin_No AND #Ending_Bin_No OR bins.Bin is null)
AND p21_view_inv_mast.item_id NOT LIKE '%FEE%'
AND p21_view_item_uom.delete_flag <> 'Y'
Maybe something like this will help...
with t (afield) as
(SELECT 'a10' UNION
SELECT 'a1' UNION
SELECT 'a2' )
select afield, substring(afield,1,1) aletter, CAST(substring(afield,2,len(afield)) as int) anumber from t
order by aletter, anumber
You can try:
select cast(replace('A10', 'A', '') as int)
From the above query,
replace first character as empty string ' ' of your bin value in table and bin parameter value. Then compare both in where clause.
WHERE
(p21_view_location.location_id = #Location_ID)
AND (p21_view_inv_loc.qty_on_hand >= #Minimum_QOH)
--AND bins.[QOH (Bin)] >= #Minimum_QOH
AND ((cast(stuff(bins.bin,1,1,'') as int) BETWEEN cast(stuff(#Beginning_Bin_No,1,1,'') as int) AND cast(stuff(#Ending_Bin_No,1,1,'') as int)) OR bins.Bin is null)
AND p21_view_inv_mast.item_id NOT LIKE '%FEE%'
AND p21_view_item_uom.delete_flag <> 'Y'

In SQL, How can I generate every possible unique combination of 5!56?

I have a TABLE "elements" with one COLUMN "number", type SMALLINT that contains numbers 1 thru 56. How can I generate unique sets of 5 numbers of every possible combination from 1 to 56, using an SQL statement?
In APL (programming language) a simple dyadic function 5!56 does the trick!
EDIT: In good ole MS-DOS QBASIC, I accomplished it like this:
10 OPEN "C:\5NUMBERS.OUT" FOR OUTPUT ACCESS READ WRITE AS #1
12 LET SER = 0
15 LET E = 56
30 FOR B5 = 5 TO E
40 FOR B4 = 4 TO E
50 FOR B3 = 3 TO E
60 FOR B2 = 2 TO E
70 FOR B1 = 1 TO E
80
88 IF B5 = B1 THEN 190
89 IF B5 = B2 THEN 190
90 IF B5 = B3 THEN 190
91 IF B5 = B4 THEN 190
92 IF B4 = B1 THEN 180
93 IF B4 = B2 THEN 180
94 IF B4 = B3 THEN 180
95 IF B3 = B1 THEN 170
96 IF B3 = B2 THEN 170
97 IF B2 = B1 THEN 160
98 LET SER = SER + 1
100 PRINT #1, SER; "|";
130 PRINT #1, B1; "|";
131 PRINT #1, B2; "|";
132 PRINT #1, B3; "|";
133 PRINT #1, B4; "|";
134 PRINT #1, B5; "|";
140 PRINT #1, B1 + B2 + B3 + B4 + B5; "|"
150 NEXT B1
160 NEXT B2
170 NEXT B3
180 NEXT B4
190 NEXT B5
205 CLOSE
210 END
220 SYSTEM
This, by the way, created my load file into an INFORMIX-SQL table
TABLE combos
(
seq_id SERIAL,
ball_1 SMALLINT,
ball_2 SMALLINT,
ball_3 SMALLINT,
ball_4 SMALLINT,
ball_5 SMALLINT,
sum SMALLINT
);
I used combos.sum to generate a bell curve graph, showing the count of combinations having the same sum of each element.
If by "unique sets" you mean what I think you do (sorry, I don't know APL!), you can write:
SELECT e1.number, e2.number, e3.number, e4.number, e.number
FROM elements e1, elements e2, elements e3, elements e4, elements e5
WHERE e1.number < e2.number
AND e2.number < e3.number
AND e3.number < e4.number
AND e4.number < e5.number
;
"could this be accomplished without actually having to store the
elements in a table?.. i.e. let the server do it without resorting to
table I/O? "
Yes, there is an Oracle trick to generate data on the fly, using the hierarchical query and the CTE syntax:
WITH elements AS
( select rownum as number
from dual
connect by level <= 56 )
SELECT e1.number, e2.number, e3.number, e4.number, e.number
FROM elements e1, elements e2, elements e3, elements e4, elements e5
WHERE e1.number < e2.number
AND e2.number < e3.number
AND e3.number < e4.number
AND e4.number < e5.number
;
If you want to include pairs of identical numbers, e.g. (5,5):
SELECT e1.number AS number1
,e2.number AS number2
FROM elements e1
,elements e2
WHERE e1.number <= e2.number;
If you want to only have different numbers in each pair:
SELECT e1.number AS number1
,e2.number AS number2
FROM elements e1
,elements e2
WHERE e1.number < e2.number;
Not that I would actually use a database for this type of task but, if you were forced to do this under threat of torture or dismemberment, I would look into something like (number shortened to num for formatting purposes):
select a.num, b.num, c.num, d.num, e.num
from elements a, elements b, elements c, elements d, elements e
where a.num <> b.num and a.num <> c.num and a.num <> d.num and a.num <> e.num
and b.num <> c.num and b.num <> d.num and b.num <> e.num
and c.num <> d.num and c.num <> e.num
and d.num <> e.num
It basically cross joins the table to itself to generate five columns and then filters out those where any of the numbers are identical.
Note that this gives you permutations: (1,2,3,4,5) is distinct from (1,2,3,5,4). If you want combinations (where the order doesn't matter), you would use slightly different clauses:
select a.num, b.num, c.num, d.num, e.num
from elements a, elements b, elements c, elements d, elements e
where a.num > b.num and b.num > c.num and c.num > d.num and d.num > e.num
My FIRST thought would be to do a Cartesian and just make sure that every record is higher than the last so you don't ever get numbers duplicated anywhere. Now this would create something like
1,2,3,4,5
1,2,3,4,6
1,2,3,4,7, etc...
but will NEVER have the reverse or mixed such as
6,4,3,2,1
6,2,4,3,1
4,6,1,2,3
as those would already be a "same" set of numbers (more along the lines of lottery style where no same number appears twice)
HOWEVER, if you also wanted duplicates, such as
1,1,1,1,1
1,2,1,2,1
1,2,3,1,1
Where a number COULD get repeated numbers just change the equality to <= instead of just <.
select
YT1.Number as Num1,
YT2.Number as Num2,
YT3.Number as Num3,
YT4.Number as Num4,
YT5.Number as Num5
from
YourTable YT1
JOIN YourTable YT2
ON YT1.Number < YT2.Number
JOIN YourTable YT3
ON YT2.Number < YT3.Number
JOIN YourTable YT4
ON YT3.Number < YT4.Number
JOIN YourTable YT5
ON YT4.Number < YT5.Number

Prolog - Formatting crossword

I've written a predicate called solve_crossword that looks like this:
solve_crossword(X,C):-
C= [A1,A2,A3,A4,A5,
B1,' ',B3, ' ', B5,
C1, C2,C3,C4,C5,
D1,' ',D3,' ', D5,
E1,E2,E3,E4,E5],
member([A1, A2, A3, A4, A5], X),
member([C1, C2, C3, C4, C5], X),
member([E1, E2, E3, E4, E5], X),
member([A1, B1, C1, D1, E1], X),
member([A3, B3, C3, D3, E3], X),
member([A5, B5, C5, D5, E5], X).
Now, I want to write a predicate called write_crossword that formats the crossword. If I have a list of words I want it to look like this:
| ?- words(X), solve_crossword(X, C), write_crossword(C).
DITCH
O U O
DITTO
G O E
EARLY
C = [[68,73,84,67,72],[79,32,85,32,79],...
X = [[68,73,83,84,82],[68,73,84,67,72],...
With
words([
"DISTR",
"DITCH",
"DITTO",
"DITTY",
"DODGE",
"EARED",
"EARLY",
"EARTH",
"EASEL",
"HONOR",
"HOOEY",
"HORDE",
"TUQUE",
"TURPS",
"TUTOR",
"TWAIN"
]).
Rows 1, 3, 5 and columns 1, 3, 5 are supposed to be words.
You can try something like this (note I corrected your code for solve_crossword):
solve_crossword(X,C):-
C= [[A1,A2,A3,A4,A5],
[B1,Space,B3, Space,B5],
[C1,C2,C3,C4,C5],
[D1,Space,D3,Space,D5],
[E1,E2,E3,E4,E5]],
atom_codes(' ', [Space]),
member([A1, A2, A3, A4, A5], X),
member([C1, C2, C3, C4, C5], X),
member([E1, E2, E3, E4, E5], X),
member([A1, B1, C1, D1, E1], X),
member([A3, B3, C3, D3, E3], X),
member([A5, B5, C5, D5, E5], X).
write_crossword([]).
write_crossword([Line|Lines]):-
atom_codes(SLine, Line),
write(SLine),
nl,
write_crossword(Lines).
atom_codes/2 converts between an atom and a list of character codes.

MS-SQL Average Columns with NULL

So I've got 3 different columns (basket 1, 2, and 3). Sometimes these columns have all the information and sometimes one or two of them are null. I have another column that I'm going to average these values into and save.
Is there a sleek/easy way to get the average of these three columns even if one of them is null? Or do I have to have a special check for each one being null?
Example data( ~~ is null)
- B1 - B2 - B3 - Avg
------------------------------
- 10 - 20 - 30 - 20
- 10 - ~~ - 30 - 20
- ~~ - 20 - ~~ - 20
How would I write the T-SQL to update my temp table?
UPDATE #MyTable
SET Avg = ???
Answer:
Thanks to Aaronaught for the method I used. I'm going to put my code here just in case someone else has the same thing.
WITH AverageView AS
(
SELECT Results_Key AS xxx_Results_Key,
AVG(AverageValue) AS xxx_Results_Average
FROM #MyResults
UNPIVOT (AverageValue FOR B IN (Results_Basket_1_Price, Results_Basket_2_Price, Results_Basket_3_Price)) AS UnpivotTable
GROUP BY Results_Key
)
UPDATE #MyResults
SET Results_Baskets_Average_Price = xxx_Results_Average
FROM AverageView
WHERE Results_Key = xxx_Results_Key;
Assuming you have some sort of ID column, the most effective way is probably to use UNPIVOT so you can use the normal row-based AVG operator (which ignores NULL values):
DECLARE #Tbl TABLE
(
ID int,
B1 int,
B2 int,
B3 int
)
INSERT #Tbl (ID, B1, B2, B3) VALUES (1, 10, 20, 30)
INSERT #Tbl (ID, B1, B2, B3) VALUES (2, 10, NULL, 30)
INSERT #Tbl (ID, B1, B2, B3) VALUES (3, 10, NULL, NULL)
SELECT ID, AVG(Value) AS Average
FROM #Tbl
UNPIVOT (Value FOR B IN (B1, B2, B3)) AS u
GROUP BY ID
If you don't have the ID column, you can generate a surrogate ID using ROW_NUMBER:
;WITH CTE AS
(
SELECT
B1, B2, B3,
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ID
FROM #Tbl
)
SELECT ID, AVG(Value)
FROM CTE
UNPIVOT (Value FOR B IN (B1, B2, B3)) AS u
GROUP BY ID
SELECT (
SELECT AVG(b)
FROM (
SELECT b1 AS b
UNION ALL
SELECT b2
UNION ALL
SELECT b3
) q
)
FROM mytable
SELECT (ISNULL(B1,0) + ISNULL(B2,0) + ISNULL(B3,0))
/(CASE WHEN B1 IS NULL THEN 0 ELSE 1 END
+CASE WHEN B2 IS NULL THEN 0 ELSE 1 END
+CASE WHEN B3 IS NULL THEN 0 ELSE 1 END)
and put logic in there to exclude cases where all three are null if you need to.