SQL Counting the number of occurence based on a subject - sql

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

Related

How can I select if row contains a specific type, not contains select existing row

I have a table below. There are 3 types(A,B,C) available. I want to create general a Sql query. If row has type B or type C, the row with type B or C should be listed. If row has just type A, the row with type A should be listed.
Table;
Number
Type
1
A
1
B
2
A
3
A
3
C
4
A
5
A
6
A
6
B
6
C
Expected result when the query run;
Number
Type
1
B
2
A
3
C
4
A
5
A
6
B
6
C
How can I create the query? Thank you in advance for your help.
I would assign each type a precedence, and only return the types of the highest precedence.
Where two types can be the same precedence (because you want to return both), RANK() (rather than ROW_NUMBER()) will ensure both are assigned the same value.
WITH
precedence AS
(
SELECT
*,
RANK()
OVER (
PARTITION BY Number
ORDER BY CASE Type WHEN 'C' THEN 2
WHEN 'B' THEN 2
WHEN 'A' THEN 1
ELSE 0
END
DESC
)
AS row_precedence
FROM
your_table
)
SELECT
*
FROM
precedence
WHERE
row_precedence = 1
One option to make it a little neater could be to use APPLY (or a join on a lookup table) to derive the integers outside of the window function's code...
WITH
precedence AS
(
SELECT
*,
RANK()
OVER (
PARTITION BY Number
ORDER BY type_precedence.value DESC
)
AS row_precedence
FROM
your_table
CROSS APPLY
(
SELECT
CASE Type WHEN 'C' THEN 2
WHEN 'B' THEN 2
WHEN 'A' THEN 1
ELSE 0
END
AS value
)
AS type_precedence
)
SELECT
*
FROM
precedence
WHERE
row_precedence = 1
Demo: https://dbfiddle.uk/2Abwpa8p

UNION - inconsistent datatype got CLOB

I have a table that look like this
name
value
a
1
b
2
c
3
Of course not with these datas, but I will use it as an example
I need to use it as a inner join, where I can have each name as a column.
It is a defined amount of rows, so that should not be a problem
I have tried to do it as
SELECT
value AS a,
NULL as b
FROM ex
WHERE name = 'a'
UNION
SELECT
NULL as a,
value AS b
FROM ex
WHERE name = 'b'
And so on, but I get the error ORA-00932: inconsistent datatypes: expected - got CLOB
I have also tried with
SELECT
CASE WHEN name = 'a' THEN value ELSE NULL END AS a,
CASE WHEN name = 'b' THEN value ELSE NULL END AS b
FROM ex
WHERE name IN ('a', 'b')
But the result from this is of course
a
b
1
NULL
NULL
2
But I need to eliminate the NULL values, so I only have one row like
a
b
1
2
Does anybody have a good idea of how to solve this problem?
I can of course make 4 joins, but I was thinking, if it could be done in one join, as that will possibly be faster than look in the same table 4 times
I have the impression that you just want to get the value per name. Is this what you aim for:
WITH dat AS
(
SELECT 'a' AS NAME, 1 AS VALUE FROM dual UNION ALL
SELECT 'b', 2 FROM dual UNION ALL
SELECT 'c', 3 FROM dual
)
SELECT *
FROM (SELECT *
FROM dat
PIVOT (MAX(VALUE) FOR NAME IN ('a','b'))); -- list of the ones you need

query to select columns from a row in which another column has certain value only SQL

Consider the following table
id attribute
1 a
1 a
1 b
2 a
2 a
3 c
4 a
I want to select the ids that have attribute of 'a' only, ie 2 and 4.
Cant select 1 because 1 has 'a' and 'b', cant select 3 because it has 'c' only. We select 2 and 4 because it has 'a' value only.
You can use
SELECT id
FROM YourTable
GROUP BY id
HAVING MAX(attribute) = 'a' AND MIN(attribute) = 'a'
AND COUNT(*) = COUNT(attribute)
the
COUNT(*) = COUNT(attribute)
is to discard any id that have NULL attribute as well as a. Remove this if that is not the semantics you want or the column is not nullable anyway.
Please test this:
SELECT id
FROM attribute
GROUP BY id
HAVING
COUNT(DISTINCT attribute) = 1 AND MIN(attribute)= 'a';

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

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

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 |