Transpose one row of a table access - sql

I have a table where each row has a Scale Name and then 6 points representing the 6 Reponses to a survey
Scale
Point1
Point2
Point3
Point4
Point5
Point6
Ability
Extremely Unable
Moderately Unable
Somewhat Unable
Somewhat Able
Moderately Able
Extremely Able
I need to figure out how to write a query that will return:
ID
Response
1
Extremely Unable
2
Moderately Unable
3
Somewhat Unable
4
Somewhat Able
5
Moderately Able
6
Extremely Able
I've tried to do this with the crosstab query wizard and SQL but I'm not making any headway.
UPDATE - This is what I ended up with:
SELECT 1 AS ID, [Ref-SCALELIST].Point1 AS Response
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
UNION
SELECT 2 , [Ref-SCALELIST].Point2
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
UNION
SELECT 3, [Ref-SCALELIST].Point3
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
UNION
SELECT 4, [Ref-SCALELIST].Point4
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
UNION
SELECT 5, [Ref-SCALELIST].Point5
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
UNION
SELECT 6, [Ref-SCALELIST].Point6
FROM [Ref-SCALELIST]
WHERE ((([Ref-SCALELIST].SCALE)="Ability"))
;
"Ability" is going to be replaced with a variable so I can dynamically update a combo box based on which scale is selected. Convoluted, but at least I avoid creating a table or query for each scale in my list (>30)

You could try something like this:
WITH col as(
select '1' AS c
UNION ALL
select '2' AS c
UNION ALL
select '3' AS c
UNION ALL
select '4' AS c
UNION ALL
select '5' AS c
UNION ALL
select '6' AS c
)
select
c 'ID',
CASE c
when '1' then "Extremely Unable"
when '2' then "Moderately Unable"
when '3' then "Somewhat Unable"
when '4' then "Somewhat Able"
when '5' then "Moderately Able"
when '6' then "Extremely Able"
else null
END as response
FROM table1 corss join col
db fiddle link

Related

Shouldn't this statement end with an error?

Consider the SELECT statement below:
SELECT 1, 'A'
UNION ALL
SELECT 2, 'B'
UNION ALL
SELECT 3, 'C';
The result is obvious:
1 'A'
2 'B'
3 'C'
I tried to store it as a separate table:
CREATE TABLE tmp AS
SELECT 1, 'A'
UNION ALL
SELECT 2, 'B'
UNION ALL
SELECT 3, 'C';
The contents of the tmp table are surprising:
1 'A'
2 'A'
3 'A'
Ok, that can be fixed providing explicit field names:
CREATE TABLE tmp AS
SELECT 1 AS field1, 'A' AS field2
UNION ALL
SELECT 2, 'B'
UNION ALL
SELECT 3, 'C';
Now I have a question whether the observed behavior is defined and valid. I feel that the statement without explicit field names should end with an error instead of producing a surprising result.
I'm using SQLite.

How do I select rows from table that have one or more than one specific value in a column?

I have a table containing data such as:
BP_NUMBER,CONTRACT_TYPE
0000123, 1
0000123, 2
0000123, 3
0000123, 4
0000124, 4
0000124, 4
0000124, 4
0000125, 4
0000126, 1
0000126, 5
I want to select rows containing one or more occurrences of CONTRACT_TYPE = 4. In other words, I want to know who are the clients with one or more contracts of the same type and type 4.
I tried this query:
SELECT * FROM (
SELECT BP_NUMBER, CONTRACT_TYPE, COUNT(*) OVER (PARTITION BY BP_NUMBER) CT FROM CONTRACTS
WHERE (1=1)
AND DATE = '18/10/2022'
AND CONTRACT_TYPE = 4)
WHERE CT= 1;
But it returns rows with only one occurrence of CONTRACT_TYPE = 4.
Also tried something like:
SELECT BP_NUMBER FROM CONTRACTS
WHERE (1=1)
AND CONTRACT_TYPE = 4
AND CONTRACT_TYPE NOT IN (SELECT CONTRACT_TYPE FROM CONTRACTS WHERE CONTRACT_TYPE != 4 GROUP BY CONTRACT_TYPE);
Trying to avoid any other contract types than 4. I really don't understand why it doesn't work.
The expected result would be:
0000124 --(4 occurrences of type 4)
0000125 --(1 occurrence of type 4)
Any help? Thanks
You can try something like this:
SELECT
BP_NUMBER
FROM CONTRACTS c1
WHERE CONTRACT_TYPE = 4
AND NOT EXISTS
(SELECT 1 FROM CONTRACTS c2 WHERE c2.BP_NUMBER = c1.BP_NUMBER
AND c2.CONTRACT_TYPE <> c1.CONTRACT_TYPE)
Depending on how you actually want to see it (and what other values you might want to include), you could either do a DISTINCT on the BP_NUMBER, or group on that column (and potentially others)
A similar result could also be achieved using an outer join between two instances of the CONTRACTS table. Essentially, you need the second instance of the same table so that you can exclude output rows when there are records with the "unwanted" contract types
You can just do the aggregation like here:
WITH
tbl AS
(
Select '0000123' "BP_NUMBER", '1' "CONTRACT_TYPE" From Dual Union All
Select '0000123', '2' From Dual Union All
Select '0000123', '3' From Dual Union All
Select '0000123', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000124', '4' From Dual Union All
Select '0000125', '4' From Dual Union All
Select '0000126', '1' From Dual Union All
Select '0000126', '5' From Dual
)
Select
BP_NUMBER "BP_NUMBER",
Count(*) "OCCURENCIES"
From
tbl
WHERE CONTRACT_TYPE = '4'
GROUP BY BP_NUMBER
ORDER BY BP_NUMBER
--
-- R e s u l t :
--
-- BP_NUMBER OCCURENCIES
-- --------- -----------
-- 0000123 1
-- 0000124 3
-- 0000125 1

Generate random value from a list of values with even distribution in Oracle

I am trying to generate a random value from a list of valid values [1,2,3,4,5,9]. If I run my function 10 times, there are 2 values that never appear. I need every value in the list to be present in my sample. How can I ensure this ? An equal distribution would be good, but at least few rows of every value.
select random_code
from (
with temp_code_table as (
select '1' as random_code from dual union
select '2' as random_code from dual union
select '3' as random_code from dual union
select '4' as random_code from dual union
select '5' as random_code from dual union
select '9' as random_code from dual)
select random_code from temp_code_table order by dbms_random.value)
where rownum = 1;
I am running the above SQL inside a function, which checks that the random code generated is not the same as the original value.
Edit: Not sure the answer in the below post will help me achieve what I want.
Generating Random Value using CASE
Any advice on how to achieve this ?
"A random number" and "at least one of each" cannot be combined. The definition of random is that you down't know what you'll get... Roll a dice 10 times. Are you sure you'll have thrown all sides at least once ? No.
So to solve your problem, you can select one of each first - that way you have those already and then fill up the rest with randomly selected numbers from the set.
with temp_code_table (nr) as
(
SELECT '1' FROM dual UNION
SELECT '2' FROM dual UNION
SELECT '3' FROM dual UNION
SELECT '4' FROM dual UNION
SELECT '5' FROM dual UNION
SELECT '9' FROM dual
)
-- now select one of each. order doesn't matter, we're doing that at the end
, one_of_each AS
(
SELECT nr FROM temp_code_table
)
-- ok we got at least one of each. Now fill up the rest with randoms
-- generate a list of hundred random values 1,2,3,4,5,9 (100 is chosen randomly)
, hundred_random AS
(
SELECT CASE round(dbms_random.value(1,6))
WHEN 1 THEN '1'
WHEN 2 THEN '2'
WHEN 3 THEN '3'
WHEN 4 THEN '4'
WHEN 5 THEN '5'
WHEN 6 THEN '9'
END AS nr
FROM DUAL connect by LEVEL < 101
)
-- select 4 out of those.
, four_more (nr) AS
(
SELECT nr FROM hundred_random WHERE rownum < 5
)
-- put it all together
, ten_rows AS
(
SELECT nr FROM one_of_each
UNION ALL
SELECT nr FROM four_more
)
-- and shuffle...
SELECT nr FROM ten_rows order by dbms_random.value
;
The "temp_code_table" cte generates exactly 6 rows. That way you have at least one of each number in the set.
The "hundred_random" cte uses the case statement on the random number which will generate a single random value of the set. Then that is run 100 times using CONNECT BY LEVEL. Out of those 100, 4 are picked. Those 4 could all be 1 - this is random, there is no guarantee that you have distinct numbers.
At the end we union the 6 rows of the temp_code_table cte and the 4 rows of the four_more and order them randomly.

Sort a value list that contains letters and also numbers in a specific order

I have a problem in SQL Oracle, I'm trying to create a view that contains values with letters and numbers and I want to sort them in a specific order.
Here is my query:
create or replace view table1_val (val, msg_text) as
select
val, msg_text
from
table_val
where
val in ('L1','L2','L3','L4','L5','L6','L7','L8','L9','L10','L11','L12','L13','L14','G1','G2','G3','G4')
order by lpad(val, 3);
The values are displayed like this:
G1,G2,G3,G4,L1,L2,L3,L4,L5,L6,L7,L8,L9,L10,L11,L12,L13
The thing is that I want to display the L values first and then the G values like in the where condition. The 'val' column is VARCHAR2(3 CHAR). The msg_text column is irrelevant. Can someone help me with that? I use Oracle 12C.
You must interpret the second part of the val column as a number
order by
case when val like 'L%' then 0 else 1 end,
to_number(substr(val,2))
This work fine for your current data, but may fail in future if a new record is added with non-numeric structure.
More conservative (and more hard to write), but safe would be to used a decode for all the current keys, ordering unknown keys on the last position (id = 18 in the example):
order by
decode(
'L1',1,
'L2',2,
'L3',3,
'L4',4,
'L5',5,
'L6',6,
'L7',7,
'L8',8,
'L9',9,
'L10',10,
'L11',11,
'L12',12,
'L13',13,
'G1',14,
'G2',15,
'G3',16,
'G4',17,18)
You can't do anything based on the order of the WHERE condition
But you can use a CASE on the ORDER BY
ORDER BY CASE
WHEN SUBSTR(val, 1, 1) = 'L' THEN 1
WHEN SUBSTR(val, 1, 1) = 'G' THEN 2
ELSE 3
END,
TO_NUMBER (SUBSTR(val, 2, 10));
Another option to consider might be using regular expressions, such as
SQL> with table1_val (val) as
2 (select 'L1' from dual union all
3 select 'L26' from dual union all
4 select 'L3' from dual union all
5 select 'L21' from dual union all
6 select 'L11' from dual union all
7 select 'L4' from dual union all
8 select 'G88' from dual union all
9 select 'G10' from dual union all
10 select 'G2' from dual
11 )
12 select val
13 from table1_val
14 order by regexp_substr(val, '^[[:alpha:]]+') desc,
15 to_number(regexp_substr(val, '\d+$'));
VAL
---
L1
L3
L4
L11
L21
L26
G2
G10
G88
9 rows selected.
SQL>

Keep order from 'IN' clause

Is it possible to keep order from a 'IN' conditional clause?
I found this question on SO but in his example the OP have already a sorted 'IN' clause.
My case is different, 'IN' clause is in random order
Something like this :
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
I would like to retrieve results in (45,2,445,12,789) order. I'm using an Oracle database. Maybe there is an attribute in SQL I can use with the conditional clause to specify to keep order of the clause.
There will be no reliable ordering unless you use an ORDER BY clause ..
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
order by case TestResult.SomeField
when 45 then 1
when 2 then 2
when 445 then 3
...
end
You could split the query into 5 queries union all'd together though ...
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 4
union all
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 2
union all
...
I'd trust the former method more, and it would probably perform much better.
Decode function comes handy in this case instead of case expressions:
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
ORDER BY DECODE(SomeField, 45,1, 2,2, 445,3, 12,4, 789,5)
Note that value,position pairs (e.g. 445,3) are kept together for readability reasons.
Try this:
SELECT T.SomeField,T.OtherField
FROM TestResult T
JOIN
(
SELECT 1 as Id, 45 as Val FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 445 FROM dual UNION ALL
SELECT 4, 12 FROM dual UNION ALL
SELECT 5, 789 FROM dual
) I
ON T.SomeField = I.Val
ORDER BY I.Id
There is an alternative that uses string functions:
with const as (select ',45,2,445,12,789,' as vals)
select tr.*
from TestResult tr cross join const
where instr(const.vals, ','||cast(tr.somefield as varchar(255))||',') > 0
order by instr(const.vals, ','||cast(tr.somefield as varchar(255))||',')
I offer this because you might find it easier to maintain a string of values rather than an intermediate table.
I was able to do this in my application using (using SQL Server 2016)
select ItemID, iName
from Items
where ItemID in (13,11,12,1)
order by CHARINDEX(' ' + Convert("varchar",ItemID) + ' ',' 13 , 11 , 12 , 1 ')
I used a code-side regex to replace \b (word boundary) with a space. Something like...
var mylist = "13,11,12,1";
var spacedlist = replace(mylist,/\b/," ");
Importantly, because I can in my scenario, I cache the result until the next time the related items are updated, so that the query is only run at item creation/modification, rather than with each item viewing, helping to minimize any performance hit.
Pass the values in via a collection (SYS.ODCINUMBERLIST is an example of a built-in collection) and then order the rows by the collection's order:
SELECT t.SomeField,
t.OtherField
FROM TestResult t
INNER JOIN (
SELECT ROWNUM AS rn,
COLUMN_VALUE AS value
FROM TABLE(SYS.ODCINUMBERLIST(45,2,445,12,789))
) i
ON t.somefield = i.value
ORDER BY rn
Then, for the sample data:
CREATE TABLE TestResult ( somefield, otherfield ) AS
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 12, 'C' FROM DUAL UNION ALL
SELECT 37, 'D' FROM DUAL UNION ALL
SELECT 45, 'E' FROM DUAL UNION ALL
SELECT 100, 'F' FROM DUAL UNION ALL
SELECT 445, 'G' FROM DUAL UNION ALL
SELECT 789, 'H' FROM DUAL UNION ALL
SELECT 999, 'I' FROM DUAL;
The output is:
SOMEFIELD
OTHERFIELD
45
E
2
A
445
G
12
C
789
H
fiddle