psql how to turn group by functions into columns - sql

I have the following table:
ID Day Count
1 '1' 1
1 '2' 2
1 '2' 7
2 '2' 4
2 '2' 3
and I'm doing the following query:
select ID, Day, sum(Count) from test_table group by ID, Day
and I get the following table:
ID Day Count
1 '1' 1
1 '2' 9
2 '2' 7
what I want is
ID Day1 Day2
1 1 9
2 0 7
How do I do that?

I would just use conditional aggregation:
select ID,
sum(case when day = 1 then count else 0 end) as day_1,
sum(case when day = 2 then count else 0 end) as day_2
from test_table
group by ID;

Use crosstab() from the tablefunc module.
Something like this:
SELECT *
FROM crosstab(
$$select ID, Day, sum(Count)
from foo2
where Day = '1' or Day = '2'
group by ID, Day
order by 1,2$$)
AS ct(ID int, day_1 BIGINT, day_2 BIGINT);

Related

SQL to calculate Net Capacity

How to write a SQL to get the Net change in capacity by using the capacity (when status is 1 or 2) and minus the total capacity (when status is 3) for each month? Thanks. Here is the table:
STATUS MONTH CAPACITY
1 01/16 5
3 01/16 2
1 02/16 11
3 02/16 20
1 03/16 8
3 03/16 12
1 04/16 4
2 04/16 10
3 04/16 18
2 05/16 14
3 05/16 37
2 06/16 4
3 06/16 8
For example, the net change in capacity for Jan. 16 is 5 minus 2 equals 3.
You need a conditional sum:
SUM(CASE WHEN STATUS IN (1,2) THEN CAPACITY ELSE 0 END) -
SUM(CASE WHEN STATUS IN (3) THEN CAPACITY ELSE 0 END)
dnoeth answer can be simplified to
SUM(CASE WHEN STATUS IN (1,2) THEN CAPACITY WHEN STATUS IN (3) THEN -CAPACITY ELSE 0 END)
Builds on 1,2 < 3
select MONTH, [Net change]=SUM(CASE STATUS/3 WHEN 0 THEN CAPACITY ELSE -CAPACITY END)
from t
group by MONTH;
no CASE statement:
select month, sum(capacity)-2*sum((status/3)*capacity) from table group by month;
Here is an example
You can join the table to itself and perform the calculation like so:
SELECT
a.status,
a.month,
a.capacity,
b.capacity AS total_capacity,
a.capacity - b.capacity AS net_capacity
FROM
table a
JOIN
table b
ON (a.month = b.month)
AND (b.status = 3)
WHERE
a.status IN (1,2);
-- If you don't want to have the status and instead aggregate in the event there are two within the same month:
SELECT
a.month,
SUM(a.capacity) AS capacity,
SUM(b.capacity) AS total_capacity,
SUM(a.capacity) - MAX(b.capacity) AS net_capacity
FROM
table a
JOIN
table b
ON (a.month = b.month)
AND (b.status = 3)
WHERE
a.status IN (1,2)
GROUP BY
a.month;
SELECT
"Status",
"Month",
SUM(Capacity) AS Capacity
FROM ( SELECT
"Status",
"Month",
CASE WHEN Status = 3 THEN -1 * Capacity ELSE Capacity END AS Capacity FROM tbl
) t
GROUP BY
"Status",
"Month"

Count all rows by status and day

I want to use this table to store tickets in PostgreSQL.
CREATE TABLE TICKET(
ID INTEGER NOT NULL,
TITLE TEXT,
STATUS INTEGER,
LAST_UPDATED DATE,
CREATED DATE
)
;
I use column 'status' to set different status(1, 2, 3, 4). How I can count all tickets by status and day with one SQL query?
I would like to get the final result for example like simple array of numbers:
32 31 23 42 9.11.215
31 21 13 22 10.11.215
3 3 2 43 11.11.215
You can doing it with conditional aggregation, which will unite separate rows into 1 aggregated row :
SELECT t.created,
COUNT(CASE WHEN t.status = '1' THEN 1 END) as cnt_status1,
COUNT(CASE WHEN t.status = '2' THEN 1 END) as cnt_status2,
COUNT(CASE WHEN t.status = '3' THEN 1 END) as cnt_status3,
COUNT(CASE WHEN t.status = '4' THEN 1 END) as cnt_status4
FROM ticket t
GROUP BY t.created

Count Values from Table for each type

I have the following table
UserId [nvarchar(128)], Rating [varchar(170)] :values will be mostly 1,2,3 but can have exceptions
Rating contains 3 values [1,2, or 3]
I want to get a result something like
UserId Count(1's),Count(2's) Count(3's)
1. 1001 10 8 2
2. 1002 5 10 3
Is it possible in a single query
Do a GROUP BY UserId to count for each user-id. Use CASE to count 1's, 2's and 3's separately:
select UserId,
count(case when Rating = 1 then 1 end) as [Count(1's)],
count(case when Rating = 2 then 1 end) as [Count(2's)],
count(case when Rating = 3 then 1 end) as [Count(3's)]
from tablename
group by UserId
Use a CASE statement in each COUNT and then GROUP BY UserId.
SELECT UserId, COUNT(CASE WHEN value = '1' THEN 1 END) AS [Count(1's)], COUNT(CASE WHEN value = '2' THEN 1 END) AS [Count(2's)], COUNT(CASE WHEN value = '3' THEN 1 END) AS [Count(3's)]
FROM yourtable
GROUP BY UserId
Use PIVOT:
SELECT
UserId,
COALESCE([1],0) [Count(1's)],
COALESCE([2],0) [Count(2's)],
COALESCE([3],0) [Count(3's)]
FROM
ýour_table
PIVOT
(COUNT([Rating])
FOR Rating
in([1],[2],[3])
)AS p
ORDER BY
UserId

SQL query - sum of values by status for date interval

I get crazy because of one query. I have a table like following and I want to get a data - Summa of Values by Status For every Date in interval.
Table
Id Name Value Date Status
1 pro1 2 01.04.14 0
2 pro1 8 02.04.14 1
3 pro2 6 02.04.14 1
4 pro3 0 03.04.14 0
5 pro4 7 03.04.14 0
6 pro4 2 03.04.14 0
7 pro4 4 03.04.14 1
8 pro4 6 04.04.14 1
9 pro4 1 04.04.14 1
For example,
Input: Name = pro4, minDate = 01.02.14, maxDate = 04.09.14
Output:
Date Values sum for 0 Status Values sum for 1 Status
01.04.14 0 0
02.04.14 0 0
03.04.14 9 (=7+2) 4 (only 4 exist)
04.04.14 0 7 (6+1)
In 01.02.14 and 02.04.14 dates, pro4 has not values by status, but I want to show that rows, because I need all dates in that interval. Can anyone help me to create this query?
Edit:
I can not change structure, I have already that table with data. Every day exist in table many times (minimum 1 time)
Thanks in advance.
Assuming you have a row for each date in the table, use conditional aggregation:
select date,
sum(Case when name = 'pro4' and status = 0 then Value else 0 end) as values_0,
sum(case when name = 'pro4' and status = 1 then Value else 0 end) as values_1
from Table t
where date >= '2014-04-01' and date <= '2014-04-09'
group by date
order by date;
If you don't have this list of dates, you can take this approach instead:
with dates as (
select cast('2014-04-01' as date) as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < '2014-04-09'
)
select dates.thedate,
sum(Case when status = 0 then Value else 0 end) as values_0,
sum(case when status = 1 then Value else 0 end) as values_1
from dates left outer join
table t
on t.date = dates.thedate and t.name = 'pro4'
group by dates.thedate;
just an assumption query :
select Distinct date ,case when status = 0 and MAX(date) then SUM(value) ELSE 0 END Status0 ,
case when status = 1 and MAX(date) then SUM(value) ELSE 0 END Status1 from table
To expand my comment the complete query is
WITH [counter](N) AS
(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1)
, days(N) AS (
SELECT row_number() over (ORDER BY (SELECT NULL)) FROM [counter])
, months (N) AS (
SELECT N - 1 FROM days WHERE N < 13)
, calendar ([date]) AS (
SELECT DISTINCT cast(dateadd(DAY, days.n
, dateadd(MONTH, months.n, '20131231')) AS date)
FROM months
CROSS JOIN days
)
SELECT a.Name
, c.Date
, [Sum of 0] = SUM(CASE Status WHEN 0 THEN Value ELSE 0 END)
, [Sum of 1] = SUM(CASE Status WHEN 1 THEN Value ELSE 0 END)
FROM Calendar c
LEFT JOIN myTable a ON c.Date = a.Date AND a.name = 'pro4'
WHERE c.date BETWEEN '20140201' AND '20140904'
GROUP BY c.Date, a.Name
ORDER BY c.Date
Note that the condition on the name need to be in the JOIN, otherwise you'll get only the date of your table.
If you need multiple years just add another CTE for the count and a dateadd(YEAR,...) in the CTE calendar
This is not really the exact query, but I think you can get that by having a query that looks like:
select date, status, sum(value) from table
where (date between mindate and maxdate) and name = product_name
group by date, status;
this page gives more info.
EDIT
So the above query only gives a part of the answer required by the OP. A LEFT OUTER JOIN of the original table and the result of the above query on thedate and status fields will give the missing info.
e.g.
select x.date, x.status, x.sum_of_values from table as y
left outer join
(select date, status, sum(value) as sum_of_values
from table
where (date between mindate and maxdate) and name = product_name
group by date, status) as x
on y.date= x.date and y.status = x.status
order by x.date;

Exclude value of a record in a group if another is present

In the example table below, I'm trying to figure out a way to sum amount over id for all marks where mark 'C' doesn't exist within an id. When mark 'C' does exist in an id, I want the sum of amounts over that id, excluding the amount against mark 'A'. As illustration, my desired output is at the bottom. I've considered using partitions and the EXISTS command, but I'm having trouble conceptualizing the solution. If any of you could take a look and point me in the right direction, it would be greatly appreciated :)
sample table:
id mark amount
------------------
1 A 1
2 A 3
2 B 2
3 A 2
4 A 1
4 B 3
5 A 1
5 C 3
6 A 2
6 C 2
desired output:
id sum(amount)
-----------------
1 1
2 5
3 2
4 4
5 3
6 2
select
id,
case
when count(case mark when 'C' then 1 else null end) = 0
then
sum(amount)
else
sum(case when mark <> 'A' then amount else 0 end)
end
from sampletable
group by id
Here is my effort:
select id, sum(amount) from table t where not t.id = 'A' group by id
having id in (select id from table t where mark = 'C')
union
select id, sum(amount) from table t where t.id group by id
having id not in (select id from table t where mark = 'C')
SELECT
id,
sum(amount) AS sum_amount
FROM atable t
WHERE mark <> 'A'
OR NOT EXISTS (
SELECT *
FROM atable
WHERE id = t.id
AND mark = 'C'
)
GROUP BY
id
;