pivoting table in postgresql - sql

what i have
customerid status
Ax 1
Bx 3
Cx 5
Dx 4
Ex 2
i am looking to pivot above table.
What i need
customerid status_1 status_2 status_3 status_4 status_5
Ax 1 0 0 0 0
Bx 0 0 1 0 0
Cx 0 0 0 0 1
Dx 0 0 0 1 0
Ex 0 1 0 0 0

select customerid,
case when status = 1 then 1 else 0 end as status_1,
case when status = 2 then 1 else 0 end as status_2,
case when status = 3 then 1 else 0 end as status_3,
case when status = 4 then 1 else 0 end as status_4,
case when status = 5 then 1 else 0 end as status_5
from your_table
order by customerid;

Using tablefunc module in Postgres
postgres=# create extension tablefunc ;
CREATE EXTENSION
postgres=# create table your_table (customerid char(2),status int);
insert into your_table values('Ax',1),('Bx',3),('Cx',5),('Dx',4),('Ex',2);
CREATE TABLE
INSERT 0 5
postgres=# SELECT * FROM crosstab(
$$select customerid, status,
count(status) AS "# of status"
from your_table group by customerid, status
order by customerid,status$$ ,
$$select distinct status from your_table
order by status$$)
AS ("customerid" text,
"status_1" text, "status_2" text, "status_3" text,
"status_4" text, "status_5" text);
customerid | status_1 | status_2 | status_3 | status_4 | status_5
------------+----------+----------+----------+----------+----------
Ax | 1 | | | |
Bx | | | 1 | |
Cx | | | | | 1
Dx | | | | 1 |
Ex | | 1 | | |
(5 rows)
postgres=#

Related

Select specific columns by their alias, and later order by it

My table are the following
+----+----------+--------+
| id | priority | User |
+----+----------+--------+
| 1 | 2 | [null] |
| 2 | 1 | [null] |
| 3 | 3 | Tony |
| 4 | 2 | John |
| 5 | 2 | Andy |
| 6 | 1 | Mike |
+----+----------+--------+
My goal is to extract them, and order by the following combined conditions:
priority = 1
User is null
+----+----------+--------+-----------+
| id | priority | User | peak_rows |
+----+----------+--------+-----------+
| 1 | 2 | [null] | 1 |
| 2 | 1 | [null] | 1 |
| 6 | 1 | Mike | 0 |
| 3 | 3 | Tony | 1 |
| 4 | 2 | John | 0 |
| 5 | 2 | Andy | 0 |
+----+----------+--------+-----------+
This is what I guess I can do
select
id,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User is NULL THEN 1 ELSE 0 END as c2,
c1 + c2 AS peak_rows
FROM mytable
ORDER BY peak_rows DESC
but it cause an error:
ERROR: column "c1" does not exist
LINE 5: c1+ c2as pp
^
SQL state: 42703
Character: 129
I don't know why I make 2 columns(c1 and c2), but I can not use it later.
Any good idea to do that?
You are not making two columns and using them later, you are making them and want to use them at the same time. You could use a subquery.
SELECT a.id, a.priority, a.User, a.c1 + a.c2 AS peak_rows
FROM
(SELECT id,
priority,
User,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User IS NULL THEN 1 ELSE 0 END as c2,
FROM mytable) a
ORDER BY peak_rows DESC;
select
id,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User is NULL THEN 1 ELSE 0 END as c2,
(CASE WHEN priority = 1 THEN 1 ELSE 0) + ( CASE WHEN User is NULL THEN 1 ELSE 0 END) AS peak_rows
FROM mytable
ORDER BY peak_rows DESC
I suppose your aim is to order by those c1 and c2, so you can directly use in the order by clause. You just need to interchange 0 and 1 in the case..when statements. And depending on your priority=1 criteria id=2 must stay at the top.
with mytable( id, priority, "User" ) as
(
select 1 , 2, null union all
select 2, 1, null union all
select 3, 3, 'Tony' union all
select 4, 2, 'John' union all
select 5, 2, 'Andy' union all
select 6, 1, 'Mike'
)
select *
from mytable
order by ( case when priority = 1 then 0 else 1 end ) +
( case when "User" is null then 0 else 1 end );
id priority User
-- -------- -------
2 1 [null]
1 2 [null]
6 1 Mike
3 3 Tony
4 2 John
5 2 Andy
Demo

SQL Subquery with a single table result with NULL

I have tried to select with sub queries in only a single table. but for some reason i got a NULL result.
The table I have look like this.
| id | ItemCode | ItemAmount | Counter |
-----------------------------------------
| 1 | 001 | 1 | Counter-1 |
| 2 | 001 | 1 | Counter-2 |
| 3 | 002 | 2 | Counter-1 |
| 4 | 002 | 2 | Counter-2 |
| 5 | 002 | 1 | Counter-2 |
I have tried this SQL :
select
id,
itemCode,
(select ItemAmount where Counter = 'Counter-1') as 'Count 1 Result',
(select Counter where Counter = 'COUNTER-1') as 'Count Is 1',
(select ItemAmount where Counter = 'Counter-2') as 'Count 2 Result',
(select Counter where Counter = 'COUNTER-2') as 'Count Is 2',
from
My_Table
and the result I got is :
| id | ItemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
----------------------------------------------------------------------
| 1 | 001 | 1 | Counter-1 | NULL | NULL |
| 2 | 001 | NULL | NULL | 1 | Counter-2 |
| 3 | 002 | 2 | Counter-1 | NULL | NULL |
| 4 | 002 | NULL | NULL | 2 | Counter-2 |
| 5 | 002 | NULL | NULL | 1 | Counter-2 |
As you can see, i got NULL result with the NULL Value. How can i do it with the result like this :
| id | ItemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
-------------------------------------------------------------------------
| 1 | 001 | 1 | Counter-1 | 2 | Counter-2 |
--------------------------------------------------------------------------
| 2 | 002 | 2 | Counter-1 | 3 | Counter-2 |
--------------------------------------------------------------------------
I want to make it no NULL value anymore and there is no double Item Code with the SUM Item Amount if the counter and item code is have the same value.
Is that even possible to do it with one table ? if it is how do i do that. thanks in advance
Try conditional aggregation, something like:
SELECT min(id) id,
itemcode,
sum(CASE
WHEN counter = 'Counter-1' THEN
itemamount
ELSE
0
END) count1result,
'Counter-1' countis1,
sum(CASE
WHEN counter = 'Counter-2' THEN
itemamount
ELSE
0
END) count2result,
'Counter-2' countis2
FROM my_table
GROUP BY itemcode;
You can try to use Aggregate function condition to make it.
Here is SQL-server sample:
CREATE TABLE My_Table(
id INT,
ItemCode VARCHAR(50),
ItemAmount INT,
Counter VARCHAR(50)
);
INSERT INTO My_Table VALUES (1, '001', 1 ,'Counter-1');
INSERT INTO My_Table VALUES (2, '001', 1 ,'Counter-2');
INSERT INTO My_Table VALUES (3, '002', 2 ,'Counter-1');
INSERT INTO My_Table VALUES (4, '002', 2 ,'Counter-2');
INSERT INTO My_Table VALUES (5, '002', 1 ,'Counter-2');
Query 1:
select
ROW_NUMBER() OVER(ORDER BY itemCode) id,
itemCode,
SUM(CASE WHEN Counter = 'Counter-1' THEN ItemAmount ELSE 0 END) as 'Count 1 Result',
MAX(CASE WHEN Counter = 'COUNTER-1' THEN Counter END) as 'Count Is 1',
SUM(CASE WHEN Counter = 'Counter-2' THEN ItemAmount ELSE 0 END) as 'Count 2 Result',
MAX(CASE WHEN Counter = 'COUNTER-2' THEN Counter END) as 'Count Is 2'
from
My_Table
GROUP BY
itemCode
Results:
| id | itemCode | Count 1 Result | Count Is 1 | Count 2 Result | Count Is 2 |
|----|----------|----------------|------------|----------------|------------|
| 1 | 001 | 1 | Counter-1 | 1 | Counter-2 |
| 2 | 002 | 2 | Counter-1 | 3 | Counter-2 |
Even though I'm not sure what you are expecting with out disturbing your code I have given the query. Implement in your requirement
;WITH CTE AS (select
itemCode,
(select SUM(ItemAmount) where Counter = 'Counter-1') as 'Count 1 Result',
(select MAX(Counter) where Counter = 'COUNTER-1') as 'Count Is 1',
(select SUM(ItemAmount) where Counter = 'Counter-2') as 'Count 2 Result',
(select MAX(Counter) where Counter = 'COUNTER-2') as 'Count Is 2'
from
My_table
GROUP BY
itemCode,Counter )
Select RANK()OVER ( ORDER BY itemcode)Id,
itemCode,
MAX([Count 1 Result])[Count 1 Result],
MAX([Count Is 1])[Count Is 1],
MAX([Count 2 Result])[Count 2 Result],
MAX([Count Is 2])[Count Is 2]
from CTE
GROUP BY itemCode
For the data retrieval part the following would give your data,
by grouping on ItemCode and Counter:
select
ItemCode,
Counter,
min(Id) as Id,
sum(ItemAmount) as ItemAmount,
from
My_Table
group by ItemCode, Counter
ItemCode Counter Id ItemAmount
======== ========= === ==========
001 Counter-1 1 1
001 Counter-2 1 2
002 Counter-1 3 1
002 Counter-2 3 3
To display that as so called pivot table, rows-to-columns, there are several solutions, one generic SQL solution:
In Modern SQL:
select ItemCode,
ItemAmount filter (where Counter = 'Counter-1') as A1,
ItemAmount filter (where Counter = 'Counter-2') as A2
from (select
ItemCode,
Counter,
min(Id) as Id,
sum(ItemAmount) as ItemAmount,
from
My_Table
group by ItemCode, Counter)
group by ItemCode
If filter is still not supported in the used SQL:
select ItemCode,
sum(case when Counter = 'Counter-1' then ItemAmount end) as A1,
sum(case when Counter = 'Counter-2' then ItemAmount end) as A2,

SQL Pivot using count

I have a table which has the following entries
ID | column | value
------------------------
1 | status | DONE
2 | status | FAILED
1 | progress | Green
2 | progress | Red
i want the output as
ID | DONE | FAILED | GREEN | RED
1 | 1 | 0 | 1 | 0
2 | 0 | 1 | 0 | 1
Please let me know the query. I have tried pivot but not getting the results.
Here is a standard pivot query solution which does not use SQL Server's built in PIVOT capability:
SELECT ID,
SUM(CASE WHEN value = 'DONE' THEN 1 ELSE 0 END) AS DONE,
SUM(CASE WHEN value = 'FAILED' THEN 1 ELSE 0 END) AS FAILED,
SUM(CASE WHEN value = 'Green' THEN 1 ELSE 0 END) AS GREEN,
SUM(CASE WHEN value = 'Red' THEN 1 ELSE 0 END) AS RED
FROM yourTable
GROUP BY ID
SELECT *
FROM atable
PIVOT (
COUNT(column)
FOR value in ([DONE], [FAILED], [GREEN], [RED])
) p

Count each condition within group

For every unique GroupId I would like to get a count of each IsGreen, IsRound, IsLoud condition and a total number of rows.
Sample data:
-----------------------------------------------------
id | ItemId | GroupId | IsGreen | IsRound | IsLoud
----+--------+---------+---------+---------+---------
1 | 1001 | 1 | 0 | 1 | 1
2 | 1002 | 1 | 1 | 1 | 0
3 | 1003 | 2 | 0 | 0 | 0
4 | 1004 | 2 | 1 | 0 | 1
5 | 1005 | 2 | 0 | 0 | 0
6 | 1006 | 3 | 0 | 0 | 0
7 | 1007 | 3 | 0 | 0 | 0
Desired result:
----------------------------------------------------------
GroupId | TotalRows | TotalGreen | TotalRound | TotalLoud
--------+-----------+------------+------------+-----------
1 | 2 | 1 | 2 | 1
2 | 3 | 1 | 0 | 1
3 | 2 | 0 | 0 | 0
I'm using the following code to create the table, the problem I'm having is that if any of the groups have no rows that match one of the conditions that group does not appear in the final table. What is the best way to accomplish what I want to do?
SELECT total.GroupId
, total.[Count] AS TotalRows
, IsGreen.[Count] AS TotalGreen
, IsRound.[Count] AS TotalRound
, IsLoud.[Count] AS TotalLoud
FROM (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
GROUP BY GroupId
) TotalRows
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsGreen = 1
GROUP BY GroupId
) IsGreen ON IsGreen.GroupId = TotalRows.GroupId
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsRound = 1
GROUP BY GroupId
) IsRound ON IsRound.GroupId = TotalRows.GroupId
INNER JOIN (
SELECT GroupId
, count(*) AS [Count]
FROM TestData
WHERE IsLoud = 1
GROUP BY GroupId
) IsLoud ON IsLoud.GroupId = TotalRows.GroupId
You can use count to count rows per each [GroupId] and sum to count each property .
select [GroupId]
, count([GroupId]) as [TotalRows]
, sum([IsGreen]) as [TotalGreen]
, sum([IsRound]) as [TotalRound]
, sum([IsLoud]) as [TotalLoud]
from [TestData]
group by [GroupId]
Use conditional Aggregate. Try this.
SELECT GroupId,
Count(GroupId) TotalRows,
Count(CASE WHEN IsGreen = 1 THEN 1 END) TotalGreen,
Count(CASE WHEN IsRound = 1 THEN 1 END) TotalRound,
Count(CASE WHEN IsLoud = 1 THEN 1 END) TotalLoud
FROM tablename
GROUP BY GroupId

SQL Select records excluding some statuses

I'm totally stuck on how to create this select. I need to select from the status table only those order_id's which to not have status 2.
Here is the table:
+----+---------+---------+--
| id | order_id| status |
+----+---------+---------+--
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 1 |
| 1 | 3 | 3 |
| 2 | 4 | 2 |
| 3 | 4 | 1 |
| 4 | 4 | 2 |
| 5 | 5 | 3 |
+----+---------+----------+--
So he select result will be only order_id = 5
Please help!
If you want to include orders with status 1 and exclude those with status 3, then you can use a similar idea:
having sum(case when status_id = 1 then 1 else 0 end) > 0 and
sum(case when status_id = 3 then 1 else 0 end) = 0
EDIT: I like to EXCLUDE those order_id's:
- Which has only status 1 (not status 2)
- and
- which has status 3
Lets have table like this:
id--order-id-Prod---Status
------------------------------
1 1 a 1
6 1 b 2
7 1 a 2
8 1 b 1
9 2 a 1
10 3 a 1
11 3 b 1
12 3 a 2
13 3 b 2
14 4 a 1
15 4 b 1
16 5 a 1
17 5 b 1
18 5 a 2
19 5 b 2
20 5 a 3
21 5 b 3
Select should show only order_id "5"
This is an example of a set-within-sets query:
select order_id
from t
group by order_id
having sum(case when status = 2 then 1 else 0 end) = 0
The having clause counts the number of rows with a status of 2. The = 0 finds the orders with no matches.
EDIT:
If you want to include orders with status 1 and exclude those with status 3, then you can use a similar idea:
having sum(case when status_id = 1 then 1 else 0 end) > 0 and
sum(case when status_id = 3 then 1 else 0 end) = 0
Here's one way.
Select * from TableName
where Order_ID not in (Select order_ID from tableName where status=2)
Another way would be to use the not exists clause.
Another way is to use EXCEPT:
SELECT order_id
FROM StatusTable
EXCEPT
SELECT order_id
FROM StatusTable
WHERE status = 2;
It works in SQL-Server and Postgres (and in Oracle if you replace the EXCEPT with MINUS.)
I think this works, one query to select all ids, one to get those with a status of 2 and left joining on order_id and picking those with null order_id in the list of orders with a status of 2.
select
all_ids.order_id
from
(
select distinct
order_id
from status
) all_ids
left join
(
select
order_id
from status
where status = 2
) two_ids
on all_ids.order_id = two_ids.order_id
where two_ids.order_id is null