Can I use dbms_random.string to generate a random value between two string options in Oracle? - sql

I have a people table with two fields (id, gender). Most of my gender values are either 'M' or 'F', but there are some 'N' and some nulls. Fixing these bad values is not an option, but I still need to account for them in a query. I am trying to randomly assign the 'N' & null values to either 'M' or 'F'. The query I have below seems like it should work, but it gives me a ORA-01722: invalid number error. I also tried wrapping both sides of the WHEN line in to_char, but get the same error. Does the dbms_random function have to use numbers?
I can do a subquery that assigns them to either 1 or 2, and then an outer query to convert them to either M or F, but would rather not if I can just do it in one statement.
select id,
gender,
case
when nvl(gender, 'N') = 'N' then dbms_random.string('M', 'F')
else gender
end as gender_new
from people;

select decode(round(dbms_random.value), 1, 'F', 'M') rnd
from dual
connect by level <= 10;
R
-
F
F
F
F
M
M
F
F
M
F
10 rows selected.

Oracle function DBMS_RANDOM.STRING() does not work like you expect. As arguments, it takes a format specifier and a number that represents the expected length of the output string (you are getting an error because the second argument that you are giving, F, is not a number). You cannot specify a list of strings to pick from.
To generate a random distribution between two values, you could use DBMS_RANDOM.VALUE() to generate a random decimal value between 0 and 1 and interpret the result in a CASE expression, like:
CASE WHEN DBMS_RANDOM.VALUE() < 0.5 THEN 'M' ELSE 'F' END
Example:
WITH tries AS (SELECT ROWNUM rn FROM DUAL CONNECT BY ROWNUM <= 5)
SELECT CASE WHEN DBMS_RANDOM.VALUE() < 0.5 THEN 'M' ELSE 'F' END AS gender FROM tries
| GENDER |
| :----- |
| M |
| M |
| F |
| M |
| M |

Related

postgreSQL getting column values as new column and adding

I have the following table
ident
name
count
A1
X
1
A1
Y
2
A1
X
6
A2
X
2
A2
Z
3
What i need is a new table which should look like:
ident
X
Y
Z
A1
7
2
0
A2
2
0
3
so it should give me for every distinct id a sum of all the existing names.
But the columns with are build out of the names should be build automaticly.
i have try
SELECT
ident ,
MAX(CASE WHEN (name = 'X') THEN 1 ELSE NULL END) AS X
FROM
mytable
GROUP BY ident
ORDER BY ident
but with this code i have to make the columns and just can set the sum to 0 or 1.
Thx
Use conditional aggregation, which in Postgres looks like this:
select ident,
sum(count) filter (where name = 'X') as x_sum,
sum(count) filter (where name = 'Y') as y_sum,
sum(count) filter (where name = 'Z') as z_sum
from mytable
group by ident;
The FILTER clause is Standard SQL, but Postgres is one of the few databases that actually supports it. You can, of course, do the same thing with CASE expressions, but FILTER gives the optimizer more hints that can help performance (and many people find it cleaner).
you are looking for sum of count:
SELECT
ident ,
SUM(CASE WHEN (name = 'X') THEN count ELSE NULL END) AS X,
SUM(CASE WHEN (name = 'Y') THEN count ELSE NULL END) AS Y,
SUM(CASE WHEN (name = 'Z') THEN count ELSE NULL END) AS Z
FROM
mytable
GROUP BY ident
ORDER BY ident

Query Selecting Row when using distinct

I would like to do the following.
Lets say the data looks somthing like this
Number letter
1 b
1 c
1 a
2 d
2 b
2 c
3 a
3 b
3 c
I want to filter the data in the folowing way column number to get all the distinct numbers and then also filter out the letter d
The output should be as follows
Number letter
1
2
2 d
3
Would this be possible?
I can do this in two table but i would like to know if it was possible to combine it into one
thank you
Chris
This should do it:
select distinct number, case when letter = 'd' then 'd' end letter from mytable
In Oracle you can use decode to shorten the query:
select distinct number, decode(letter, 'd', 'd') letter from mytable
How about union all?
select distinct number, null as letter
from t
union all
select number, 'd'
from t
where letter = 'd';

Show 2 count in 1 result

I need to display a single result so that I can extract it.
This is my table named 'TRY'
id | type |
01 | A |
01 | D |
01 | A |
My query is like this:
select
lpad(id,2,'0')||
lpad(count(case when type = 'A' then 1 end),3,'0')||
lpad(count(case when type = 'D' then 1 end),3,'0')||
lpad(count(case when type in ('A','D') then 1 end),3,'0') then 1 end)
as results
from
TRY
group by
id
,type
I want the result to show like this 01002001003 but instead I got 2 result which are like this
01002000002 and 01000001001. I just want to combine the count result as one.
I think that the main thing that you need to consider is how the GROUP BY clause actually works, as you have actually grouped by id and type meaning that you'll never have a positive count for type A and type D on the same row as they are not grouped together.
In your example I have noticed the following and worked to these parameters:
You want to GROUP BY id (not GROUP BY id, type)
Your returned result is a concatenated string which consists of
the id (padded to 2 characters)
the number of Type A's (padded to 3 characters)
the number of type D (padded to 3 characters)
the total number of A's and D's combined. (padded to 3 characters)
You actually also want to be using the SUM function instead of COUNT so something along the lines of the following should work:
Oracle/PLSQL
SELECT
LPAD(id,2,'0')
|| LPAD(SUM(CASE WHEN type = 'A' THEN 1 ELSE 0 END),3,'0')
|| LPAD(SUM(CASE WHEN type = 'A' THEN 1 ELSE 0 END),3,'0')
|| LPAD(SUM(CASE WHEN type = 'A' OR type = 'D' THEN 1 ELSE 0 END),3,'0')
AS results
FROM
TRY
GROUP BY
id
On the unlikely off-chance you're actually using SQL Server or another Database engine the LPAD function will not actually work so using something along the lines of the following may work instead. (Also added in case some non ORACLE users have a similar quandary and it's what I used to resolve your issue)
SELECT
[results] =
RIGHT('00' + CONVERT(VARCHAR,[id]),2)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'A' THEN 1 ELSE 0 END)),3)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'D' THEN 1 ELSE 0 END)),3)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'A' OR type = 'D' THEN 1 ELSE 0 END)),3)
FROM
[TRY]
GROUP BY
[id]

SQL Counting the number of occurence based on a subject

I find it hard to word what I am trying to achieve. I have a table that looks like this:
user char
---------
a | x
a | y
a | z
b | x
b | x
b | y
c | y
c | y
c | z
How do I write a query that would return me the following result?
user x y z
-------
a |1|1|1|
b |2|1|0|
c |0|2|1|
the numbers represent the no of occurences of chars in the original table
EDIT:
The chars values are unknown hence the solution cannot be restricted to these values. Sorry for not mentioning it sooner. I am using Oracle DB but planning to use JPQL to construct the query.
select user,
sum(case when char='x' then 1 else 0 end) as x,
sum(case when char='y' then 1 else 0 end) as y,
sum(case when char='z' then 1 else 0 end) as z
from thetable
group by user
Or, if you don't mind stacking vertically, this solution will give you a solution that works even with unknown sets of characters:
select user, char, count(*) as count
from thetable
group by user, char
This will give you:
user char count
a x 1
a y 1
a z 1
b x 2
If you want to string an unknown set of values out horizontally (as in your demo output), you're going to need to get into dynamic queries... the SQL standard is not designed to generate output with an unknown number of columns... Hope this is helpful!
Another option, using T-SQL PIVOT (SQL SERVER 2005+)
select *
from userchar as t
pivot
(
count([char]) for [char] in ([x],[y],[z])
) as p
Result:
user x y z
----------- ----------- ----------- -----------
a 1 1 1
b 2 1 0
c 0 2 1
(3 row(s) affected)
Edit ORACLE:
You can build a similar PIVOT table using ORACLE.
The tricky part is that you need the right column names in the IN ([x],[y],[z],...) statement. It shouldn't be too hard to construct the SQL query in code, getting a (SELECT DISTINCT [char] from table) and appending it to your base query.
Pivoting rows into columns dynamically in Oracle
If you don't know the exact values on which to PIVOT, you'll either need to do something procedural or mess with dynamic sql (inside an anonymous block), or use XML (in 11g).
If you want the XML approach, it would be something like:
with x as (
select 'a' as usr, 'x' as val from dual
union all
select 'a' as usr, 'y' as val from dual
union all
select 'b' as usr, 'x' as val from dual
union all
select 'b' as usr, 'x' as val from dual
union all
select 'c' as usr, 'z' as val from dual
)
select * from x
pivot XML (count(val) as val_cnt for val in (ANY))
;
Output:
USR VAL_XML
a <PivotSet><item><column name = "VAL">x</column><column name = "VAL_CNT">1</column></item><item><column name = "VAL">y</column><column name = "VAL_CNT">1</column></item></PivotSet>
b <PivotSet><item><column name = "VAL">x</column><column name = "VAL_CNT">2</column></item></PivotSet>
c <PivotSet><item><column name = "VAL">z</column><column name = "VAL_CNT">1</column></item></PivotSet>
Hope that helps

Group by multiple criteria

Given the table like
| userid | active | anonymous |
| 1 | t | f |
| 2 | f | f |
| 3 | f | t |
I need to get:
number of users
number of users with 'active' = true
number of users with 'active' = false
number of users with 'anonymous' = true
number of users with 'anonymous' = false
with single query.
As for now, I only came out with the solution using union:
SELECT count(*) FROM mytable
UNION ALL
SELECT count(*) FROM mytable where active
UNION ALL
SELECT count(*) FROM mytable where anonymous
So I can take first number and find non-active and non-anonymous users with simple deduction .
Is there any way to get rid of union and calculate number of records matching these simple conditions with some magic and efficient query in PostgreSQL 9?
You can use an aggregate function with a CASE to get the result in separate columns:
select
count(*) TotalUsers,
sum(case when active = 't' then 1 else 0 end) TotalActiveTrue,
sum(case when active = 'f' then 1 else 0 end) TotalActiveFalse,
sum(case when anonymous = 't' then 1 else 0 end) TotalAnonTrue,
sum(case when anonymous = 'f' then 1 else 0 end) TotalAnonFalse
from mytable;
See SQL Fiddle with Demo
Assuming your columns are boolean NOT NULL, this should be a bit faster:
SELECT total_ct
,active_ct
,(total_ct - active_ct) AS not_active_ct
,anon_ct
,(total_ct - anon_ct) AS not_anon_ct
FROM (
SELECT count(*) AS total_ct
,count(active OR NULL) AS active_ct
,count(anonymous OR NULL) AS anon_ct
FROM tbl
) sub;
Find a detailed explanation for the techniques used in this closely related answer:
Compute percents from SUM() in the same SELECT sql query
Indexes are hardly going to be of any use, since the whole table has to be read anyway. A covering index might be of help if your rows are bigger than in the example. Depends on the specifics of your actual table.
-> SQLfiddle comparing to #bluefeet's version with CASE statements for each value.
SQL server folks are not used to the proper boolean type of Postgres and tend to go the long way round.