Convert tuple value to column names - sql

Got something like:
+-------+------+-------+
| count | id | grade |
+-------+------+-------+
| 1 | 0 | A |
| 2 | 0 | B |
| 1 | 1 | F |
| 3 | 1 | D |
| 5 | 2 | B |
| 1 | 2 | C |
I need:
+-----+---+----+---+---+---+
| id | A | B | C | D | F |
+-----+---+----+---+---+---+
| 0 | 1 | 2 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 5 | 1 | 0 | 0 |
I don't know if I can even do this. I can group by id but how would you read the count value for each grade column?

CREATE TABLE #MyTable(_count INT,id INT , grade VARCHAR(10))
INSERT INTO #MyTable( _count ,id , grade )
SELECT 1,0,'A' UNION ALL
SELECT 2,0,'B' UNION ALL
SELECT 1,1,'F' UNION ALL
SELECT 3,1,'D' UNION ALL
SELECT 5,2,'B' UNION ALL
SELECT 1,2,'C'
SELECT *
FROM
(
SELECT _count ,id ,grade
FROM #MyTable
)A
PIVOT
(
MAX(_count) FOR grade IN ([A],[B],[C],[D],[F])
)P

You need a "pivot" table or "cross-tabulation". You can use a combination of aggregation and CASE statements, or, more elegantly the crosstab() function provided by the additional module tablefunc. All basics here:
PostgreSQL Crosstab Query
Since not all keys in grade have values, you need the 2-parameter form. Like this:
SELECT * FROM crosstab(
'SELECT id, grade, count FROM table ORDER BY 1,2'
, $$SELECT unnest('{A,B,C,D,F}'::text[])$$
) ct(id text, "A" int, "B" int, "C" int, "D" int, "F" int);

Related

Generate matrix of access in source-target table

I have two tables in PostgreSQL, class and inheritance.
Each row in inheritance has 2 class IDs source_id and target_id:
CREATE TABLE public.class (
id bigint NOT NULL DEFAULT nextval('class_id_seq'::regclass),
name character varying(500) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT class_pkey PRIMARY KEY (id)
)
CREATE TABLE public.inheritance (
id bigint NOT NULL DEFAULT nextval('inherited_id_seq'::regclass),
source_id bigint NOT NULL,
target_id bigint NOT NULL,
CONSTRAINT inherited_pkey PRIMARY KEY (id),
CONSTRAINT inherited_source_id_fkey FOREIGN KEY (source_id)
REFERENCES public.class (id),
CONSTRAINT inherited_target_id_fkey FOREIGN KEY (target_id)
REFERENCES public.class (id)
)
I want to create Access Matrix between all classes based in inheritance relationship in inheritance table.
I try this code:
select * , case when id in (select target_id from inheritance where source_id=1) then 1 else 0 end as "1"
, case when id in (select target_id from inheritance where source_id=2) then 1 else 0 end as "2"
, case when id in (select target_id from inheritance where source_id=3) then 1 else 0 end as "3"
, case when id in (select target_id from inheritance where source_id=4) then 1 else 0 end as "4"
, case when id in (select target_id from inheritance where source_id=5) then 1 else 0 end as "5"
, case when id in (select target_id from inheritance where source_id=6) then 1 else 0 end as "6"
, case when id in (select target_id from inheritance where source_id=7) then 1 else 0 end as "7"
, case when id in (select target_id from inheritance where source_id=8) then 1 else 0 end as "8"
, case when id in (select target_id from inheritance where source_id=9) then 1 else 0 end as "9"
from class
and get the right answer, but it's just for 9 static rows in class.
How can I get all number of rows in class using a dynamic SQL command?
If we can't do it with SQL, how can we do it with PL/pgSQL?
Static solution
SQL demands to know name and type of each result column (and consequently their number) at call time. You cannot derive result columns from data dynamically with plain SQL. You can use an array or a document type instead of separate columns:
SELECT *
FROM class c
LEFT JOIN (
SELECT target_id AS id, array_agg(source_id) AS sources
FROM (SELECT source_id, target_id FROM inheritance i ORDER BY 1,2) sub
GROUP BY 1
) i USING (id);
id
name
sources
1
c1
{2,3,4}
2
c2
{5}
3
c3
{5,6,7}
4
c4
{7}
5
c5
{8}
6
c6
{9}
7
c7
{9}
8
c8
null
9
c9
null
Dynamic solution
If that's not good enough you need dynamic SQL with two round-trips to the DB server: 1. Generate SQL. 2. Execute SQL. Using the crosstab() function from the additional module tablefunc. If you are unfamiliar, read this first:
PostgreSQL Crosstab Query
Generate SQL:
SELECT format(
$q$SELECT *
FROM class c
LEFT JOIN crosstab(
'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
, 'VALUES (%s)'
) AS ct (id int, %s int)
USING (id)
ORDER BY id;
$q$
, string_agg(c.id::text, '), (')
, string_agg('"' || c.id || '"', ' int, ')
)
FROM (SELECT id FROM class ORDER BY 1) c;
Returns a query of this form, which we ...
2. Execute:
SELECT *
FROM class c
LEFT JOIN crosstab(
'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
, 'VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)'
) AS ct (id int, "1" int, "2" int, "3" int, "4" int, "5" int, "6" int, "7" int, "8" int, "9" int)
USING (id)
ORDER BY id;
... to get:
id | name | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
----+------+---+---+---+---+---+---+---+---+---
1 | c1 | | 1 | 1 | 1 | | | | |
2 | c2 | | | | | 1 | | | |
3 | c3 | | | | | 1 | 1 | 1 | |
4 | c4 | | | | | | | 1 | |
5 | c5 | | | | | | | | 1 |
6 | c6 | | | | | | | | | 1
7 | c7 | | | | | | | | | 1
8 | c8 | | | | | | | | |
9 | c9 | | | | | | | | |
db<>fiddle here
See:
Dynamic alternative to pivot with CASE and GROUP BY
Dynamically convert hstore keys into columns for an unknown set of keys
Dynamic execution with psql
Still two round-trips to the server, but only a single command.
Both of the following solutions use psql meta-commands and only work from within psql!
With \gexec
Using the standard interactive terminal, you can feed the generated SQL back to the Postgres server for execution directly with \gexec:
test=> SELECT format(
$q$SELECT *
FROM class c
LEFT JOIN crosstab(
'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
, 'VALUES (%s)'
) AS ct (id int, %s int)
USING (id)
ORDER BY id;
$q$
, string_agg(c.id::text, '), (')
, string_agg('c' || c.id, ' int, ')
)
FROM (SELECT id FROM class ORDER BY 1) c\gexec
Same result.
With \crosstabview
test=> SELECT *
test-> FROM class c
test-> LEFT JOIN (
test(> SELECT target_id AS id, source_id, 1 AS val
test(> FROM inheritance
test(> ) i USING (id)
test-> \crosstabview id source_id val
id | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
----+---+---+---+---+---+---+---+---+--
1 | 1 | 1 | 1 | | | | | |
2 | | | | 1 | | | | |
3 | | | | 1 | 1 | 1 | | |
4 | | | | | | 1 | | |
5 | | | | | | | 1 | |
6 | | | | | | | | 1 |
7 | | | | | | | | 1 |
8 | | | | | | | | |
9 | | | | | | | | |
(9 rows)
See (with related answers for both):
How do I generate a pivoted CROSS JOIN where the resulting table definition is unknown?
There are lots of subtleties in these solutions ...
Aside
Assuming there are some mechanisms in place to disallow duplicates and directly contradicting relationships. Like:
CREATE UNIQUE INDEX inheritance_uni_idx
ON inheritance (GREATEST(source_id, target_id), LEAST(source_id, target_id));
See:
How to create a Postgres table with unique combined primary key?

Count the number of appearances of char given a ID

I have to perform a query where I can count the number of distinct codes per Id.
|Id | Code
------------
| 1 | C
| 1 | I
| 2 | I
| 2 | C
| 2 | D
| 2 | D
| 3 | C
| 3 | I
| 3 | D
| 4 | I
| 4 | C
| 4 | C
The output should be something like:
|Id | Count | #Code C | #Code I | #Code D
-------------------------------------------
| 1 | 2 | 1 | 1 | 0
| 2 | 3 | 1 | 0 | 2
| 3 | 3 | 1 | 1 | 1
| 4 | 2 | 2 | 1 | 0
Can you give me some advise on this?
This answers the original version of the question.
You are looking for count(distinct):
select id, count(distinct code)
from t
group by id;
If the codes are only to the provided ones, the following query can provide the desired result.
select
pvt.Id,
codes.total As [Count],
COALESCE(C, 0) AS [#Code C],
COALESCE(I, 0) AS [#Code I],
COALESCE(D, 0) AS [#Code D]
from
( select Id, Code, Count(code) cnt
from t
Group by Id, Code) s
PIVOT(MAX(cnt) FOR Code IN ([C], [I], [D])) pvt
join (select Id, count(distinct Code) total from t group by Id) codes on pvt.Id = codes.Id ;
Note: as I can see from sample input data, code 'I' is found in all of Ids. Its count is zero for Id = 3 in the expected output (in the question).
Here is the correct output:
DB Fiddle

SQL Combining multiple rows into one

I want to merge multiple rows into one, and only keep the values where the value is not NULL
Here is what i want to achieve:
I want from this
+----+-----------------+-----------------+-----------------+--------------------+
| ID | 1stNofification | 2ndNotification | 3rdNotification | NotificationNumber |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | 01.01.2019 | NULL | NULL | 1 |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | NULL | 02.02.2019 | NULL | 2 |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | NULL | NULL | 03.03.2019 | 3 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | 06.01.2019 | NULL | NULL | 1 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | NULL | 09.02.2019 | NULL | 2 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | NULL | NULL | 11.03.2019 | 3 |
+----+-----------------+-----------------+-----------------+--------------------+
to this:
+----+-----------------+-----------------+-----------------+
| ID | 1stNofification | 2ndNotification | 3rdNotification |
+----+-----------------+-----------------+-----------------+
| 1 | 01.01.2019 | 02.02.2019 | 03.03.2019 |
+----+-----------------+-----------------+-----------------+
| 2 | 06.01.2019 | 09.02.2019 | 11.03.2019 |
+----+-----------------+-----------------+-----------------+
I tried something like:
SELECT
ID,
MAX(CASE WHEN a.NotificationNumber = 1 THEN 1stNotification END)1stNotification,
MAX(CASE WHEN a.NotificationNumber = 2 THEN 2ndNotification END)2ndNotification,
MAX(CASE WHEN a.NotificationNumber = 3 THEN 3rdNotification END)3rdNotification
FROM Notifications
GROUP BY ID
But that did not give me my expected results unfortunately.
Would really appreciate if someone could help me out :)
You just need to use max without any case
SELECT
ID,
MAX(1stNotification) AS 1stNotification,
MAX(2ndNotification) AS 2ndNotification,
MAX(3rdNotification) AS 3rdNotification
FROM Notifications
GROUP BY ID
I think you need something like this...
; with cte as (
select 1 as id, 'dd' as not1, null as not2, null as not3 , 1 as notifications
union all
select 1, null, 'df', null , 2
union all
select 1, null, null, 'vc', 3
union all
select 2, 'ws', null, null, 1
union all
select 2, null, 'xs', null, 2
union all
select 2, null, null, 'nm', 3
)
, ct as (
select id, coalesce(not1, not2, not3) as ol, notifications ,
'notification' + cast(notifications as varchar(5)) as Col
from cte
)
select * from (
select id, ol, col from ct )
as d
pivot (
max(ol) for col in ( [notification1], [notification2], [notification3] )
) as P
Here as per my understanding your notification columns in result are actually notification number mention in rows.

SQL select distinct when one column in and another column greater than

Consider the following dataset:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 1 | a | 0.2 |
| 1 | b | 8 |
| 1 | c | 3.5 |
| 1 | d | 2.2 |
| 2 | b | 4 |
| 2 | c | 0.5 |
| 2 | d | 6 |
| 3 | a | 2 |
| 3 | b | 4 |
| 3 | c | 3.6 |
| 3 | d | 0.2 |
+---------------------+
I'm tying to develop a sql select statement that returns the top or distinct ID where NAME 'a' and 'b' both exist and both of the corresponding VALUE's are >= '1'. Thus, the desired output would be:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 3 | a | 2 |
+----+-------+--------+
Appreciate any assistance anyone can provide.
You can try to use MIN window function and some condition to make it.
SELECT * FROM (
SELECT *,
MIN(CASE WHEN NAME = 'a' THEN [value] end) OVER(PARTITION BY ID) aVal,
MIN(CASE WHEN NAME = 'b' THEN [value] end) OVER(PARTITION BY ID) bVal
FROM T
) t1
WHERE aVal >1 and bVal >1 and aVal = [Value]
sqlfiddle
This seems like a group by and having query:
select id
from t
where name in ('a', 'b')
having count(*) = 2 and
min(value) >= 1;
No subqueries or joins are necessary.
The where clause filters the data to only look at the "a" and "b" records. The count(*) = 2 checks that both exist. If you can have duplicates, then use count(distinct name) = 2.
Then, you want the minimum value to be 1, so that is the final condition.
I am not sure why your desired results have the "a" row, but if you really want it, you can change the select to:
select id, 'a' as name,
max(case when name = 'a' then value end) as value
you can use in and sub-query
select top 1 * from t
where t.id in
(
select id from t
where name in ('a','b')
group by id
having sum(case when value>1 then 1 else 0)>=2
)
order by id

Oracle SQL, how to select * having distinct columns

I want to have a query something like this (this doesn't work!)
select * from foo where rownum < 10 having distinct bar
Meaning I want to select all columns from ten random rows with distinct values in column bar. How to do this in Oracle?
Here is an example. I have the following data
| item | rate |
-------------------
| a | 50 |
| a | 12 |
| a | 26 |
| b | 12 |
| b | 15 |
| b | 45 |
| b | 10 |
| c | 5 |
| c | 15 |
And result would be for example
| item no | rate |
------------------
| a | 12 | --from (26 , 12 , 50)
| b | 45 | --from (12 ,15 , 45 , 10)
| c | 5 | --from (5 , 15)
Aways having distinct item no
SQL Fiddle
Oracle 11g R2 Schema Setup:
Generate a table with 12 items A - L each with rates 0 - 4:
CREATE TABLE items ( item, rate ) AS
SELECT CHR( 64 + CEIL( LEVEL / 5 ) ),
MOD( LEVEL - 1, 5 )
FROM DUAL
CONNECT BY LEVEL <= 60;
Query 1:
SELECT item,
rate
FROM (
SELECT i.*,
-- Give the rates for each item a unique index assigned in a random order
ROW_NUMBER() OVER ( PARTITION BY item ORDER BY DBMS_RANDOM.VALUE ) AS rn
FROM items i
ORDER BY DBMS_RANDOM.VALUE -- Order all the rows randomly
)
WHERE rn = 1 -- Only get the first row for each item
AND ROWNUM <= 10 -- Only get the first 10 items.
Results:
| ITEM | RATE |
|------|------|
| A | 0 |
| K | 2 |
| G | 4 |
| C | 1 |
| E | 0 |
| H | 0 |
| F | 2 |
| D | 3 |
| L | 4 |
| I | 1 |
I mention table create and query for distinct and top 10 rows;
(Ref SqlFiddle)
create table foo(item varchar(20), rate int);
insert into foo values('a',50);
insert into foo values('a',12);
insert into foo values('a',26);
insert into foo values('b',12);
insert into foo values('b',15);
insert into foo values('b',45);
insert into foo values('b',10);
insert into foo values('c',5);
insert into foo values('c',15);
--Here first get the distinct item and then filter row number wise rows:
select item, rate from (
select item, rate, ROW_NUMBER() over(PARTITION BY item ORDER BY rate desc)
row_num from foo
) where row_num=1;