PostgreSQL, SUM and GROUP from numeric column and hstore - postgresql

I would kindly ask if someone could make me a query which may SUM up values from numeric column and from hstore column. This is obviously too much for my SQL abbilities.
A table:
DROP TABLE IF EXISTS mytry;
CREATE TABLE IF NOT EXISTS mytry
(mybill int, price numeric, paym text, combined_paym hstore);
INSERT INTO mytry (mybill, price, paym, combined_paym)
VALUES (10, 10.14, '0', ''),
(11, 23.56, '0', ''),
(12, 12.16, '3', ''),
(13, 12.00, '6', '"0"=>"4","3"=>"4","2"=>"4"'),
(14, 14.15, '6', '"0"=>"2","1"=>"4","3"=>"4","4"=>"4.15"'),
(15, 13.00, '1', ''),
(16, 9.00, '4', ''),
(17, 4.00, '4', ''),
(18, 4.00, '1', '');
Here is a list of bills, price and payment method for each bill.
Some bills (here 13 and 14) could have combined payment. Payment methods are enumerated from 0 to 5 which describes specific payment method.
For this I make this query:
SELECT paym, SUM(price) FROM mytry WHERE paym::int<6 GROUP BY paym ORDER BY paym;
This sums prices for payment methods 0-5. 6 is not payment method but a flag which means that we should here consider payment methods and prices from hstore 'combined_paym'. This is what I don't know how to solve. To sum payment methods and prices from 'combined paym' with ones from 'paym' and 'price'.
This query gives result:
"0";33.70
"1";17.00
"3";12.16
"4";13.00
But result is incorrect because here are not summed data from bill's 13 and 14.
Real result should be:
"0";39.70
"1";21.00
"2";4.00
"3";20.16
"4";17.15
Please if someone can make me proper query which would give this last result from given data.

Unnest the hstore column:
select key, value::dec
from mytry, each(combined_paym)
where paym::int = 6
key | value
-----+-------
0 | 4
2 | 4
3 | 4
0 | 2
1 | 4
3 | 4
4 | 4.15
(7 rows)
and use it in union:
select paym, sum(price)
from (
select paym, price
from mytry
where paym::int < 6
union all
select key, value::dec
from mytry, each(combined_paym)
where paym::int = 6
) s
group by 1
order by 1;
paym | sum
------+-------
0 | 39.70
1 | 21.00
2 | 4
3 | 20.16
4 | 17.15
(5 rows)

Related

PostgreSQL Crosstab Query With Changing Rows

Can someone please help me put this query together?
I have this table:
store name status orders
billys store new 15
billys store ordered 20
billys store canceled 2
johnny store new 5
johnny store out_of_stock 20
rosie store new 6
rosie store ordered 4
rosie store out_of_stock 10
So as you can see, some stores have some statuses that others don't.
My desired result is the following:
store name new ordered canceled out of stock
billys store 15 20 2 0
johnny store 5 0 0 20
rosie store 6 4 0 10
I have tried the following:
SELECT * FROM crosstab(
'SELECT store_name::text as store_name,
status::text as status,
count(*)::int as orders
FROM organizations
INNER JOIN orders ON organization_id = organizations.id
GROUP BY store_name, status
ORDER BY store_name, status'
) x (store_name text, "new" int, "ordered" int)
But this doesn't work since it will break when the new row is not an expected value. For example with 'johnny store', after 'new' is not 'ordered', it's 'out_of_stock' so that won't work.
I've looked through a bunch of StackOverflow posts but I'm just overall pretty confused. Thank you
We can do this using CASE to avoid using sub-queries.
CREATE TABLE organisation (
store_name VARCHAR(25),
status VARCHAR(25),
orders INT);
INSERT INTO organisation VALUES
('billys store', 'new' , 15),
('billys store', 'ordered' , 20),
('billys store', 'canceled' , 2),
('johnny store', 'new' , 5),
('johnny store', 'out_of_stock', 20),
('rosie store' , 'new' , 6),
('rosie store' , 'ordered' , 4),
('rosie store' , 'out_of_stock', 10);
8 rows affected
SELECT store_name,
SUM(CASE WHEN status='new' THEN orders ELSE 0 END) new_,
SUM(CASE WHEN status='canceled' THEN orders ELSE 0 END) canceled,
SUM(CASE WHEN status='ordered' THEN orders ELSE 0 END) ordered,
SUM(CASE WHEN status='new' THEN orders ELSE 0 END) o_o_s
FROM organisation o
GROUP BY store_name;
GO
store_name | new | canceled | ordered | o_o_s
:----------- | --: | -------: | ------: | ----:
billys store | 15 | 2 | 20 | 15
johnny store | 5 | 0 | 0 | 5
rosie store | 6 | 0 | 4 | 6
db<>fiddle here
Maybe you couldn't understand it from the link I provided but tablefunc extension makes this much easier IMHO. Here is a sample based on your code, you would replace the first query with yours that gets the data from your tables:
create temporary table myTable (storename text, status text, orders int);
insert into myTable (storename, status, orders)
values
('billys store','new', 15),
('billys store','ordered', 20),
('billys store','canceled', 2),
('johnny store','new', 5),
('johnny store','out_of_stock', 20),
('rosie store','new', 6),
('rosie store','ordered', 4),
('rosie store','out_of_stock', 10);
SELECT * FROM crosstab(
'SELECT storename,
status,
orders
FROM myTable',
'select * from unnest(string_to_array(''new,ordered,canceled,out_of_stock'', '',''))'
) x (storename text, "new" int, "ordered" int, "canceled" int, "out_of_stock" int);
drop table myTable;
Here is DBFiddle demo

How to write a select query for displaying data on a table in another way using Postgresql?

I want to write a select query to pick data from a table which is shown in this image below,PICTURE_1
1.Table Containing Data
and display it like this image in this link below, PICTURE_2
2.Result of the query
About the data: The first picture shows data logged into a table for 2 seconds from 3 IDs(1,2&3) having 2 sub IDs (aa&bb). Values and timestamp are also displayed in the picture. The table conatins only 3 column as shown in PICTURE_1. Could you guys help me write a query to display data in the table to get displayed as shown in the second image using Postgresql?. You can extract ID name using substring function. The language that Im using is plpgsql. Any ideas/logic also will be good.Thank you for your time.
Please try this. Here row value has been shown in column wise and also use CTE.
-- PostgreSQL(v11)
WITH cte_t AS (
SELECT LEFT(name, 1) id
, RIGHT(name, POSITION('.' IN REVERSE(name)) - 1) t_name
, value
, time_stamp
FROM test
)
SELECT id
, time_stamp :: DATE "date"
, time_stamp :: TIME "time"
, MAX(CASE WHEN t_name = 'aa' THEN value END) "aa"
, MAX(CASE WHEN t_name = 'bb' THEN value END) "bb"
FROM cte_t
GROUP BY id, time_stamp
ORDER BY date, time, id;
Please check from url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=6d35047560b3f83e6c906584b23034e9
Check this query dbfiddle
with cte (name, value, timeStamp) as (values
('1.aa', 1, '2021-08-20 10:10:01'),
('2.aa', 2, '2021-08-20 10:10:01'),
('3.aa', 3, '2021-08-20 10:10:01'),
('1.bb', 4, '2021-08-20 10:10:01'),
('2.bb', 5, '2021-08-20 10:10:01'),
('3.bb', 6, '2021-08-20 10:10:01'),
('1.aa', 7, '2021-08-20 10:10:02'),
('2.aa', 8, '2021-08-20 10:10:02'),
('3.aa', 9, '2021-08-20 10:10:02'),
('1.bb', 0, '2021-08-20 10:10:02'),
('2.bb', 1, '2021-08-20 10:10:02'),
('3.bb', 2, '2021-08-20 10:10:02')
), sub_cte as (
select split_name[1] as id, split_name[2] as name, value, tt::date as date, tt::time as time from (
select
regexp_split_to_array(name, '\.') split_name,
value,
to_timestamp(timestamp, 'YYYY-MM-DD HH:MI:SS') as tt
from cte
) foo
)
select id, date, time, a.value as aa, b.value as bb from sub_cte a
left join (
select * from sub_cte where name = 'bb'
) as b using (id, date, time)
where a.name = 'aa'
Result
id | date | time | aa | bb
----+------------+----------+----+----
1 | 2021-08-20 | 10:10:01 | 1 | 4
2 | 2021-08-20 | 10:10:01 | 2 | 5
3 | 2021-08-20 | 10:10:01 | 3 | 6
1 | 2021-08-20 | 10:10:02 | 7 | 0
2 | 2021-08-20 | 10:10:02 | 8 | 1
3 | 2021-08-20 | 10:10:02 | 9 | 2
(6 rows)

How to retrieve top 3 results for each column in postgresql?

I have given a question. The table looks like this..
STATE | year1 | ... | year 10
AP | 100 | ... | 120
assam | 13 | .. | 42
madhya pradesh | 214 | ... | 421
Now, I need to get the top - 3 states for each year.
I tried everything possible. But, I am not able to filter results per column.
You have a design problem. The enumerated column are almost always a sign of bad design.
For now you could unpivot using unnest and then use window function row_number to get the top 3 states per year:
with unpivoted as (
select state,
unnest(array[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) as year,
unnest(array[
year_1, year_2, year_3,
year_4, year_5, year_6,
year_7, year_8, year_9,
year_10
]) as value,
from your_table
)
select *
from (
select t.*,
row_number() over (
partition by year
order by value desc
) as seqnum
from unpivoted t
) t
where seqnum <= 3;
Demo

How to find median by attribute with Postgres window functions?

I use PostgreSQL and have records like this on groups of people:
name | people | indicator
--------+--------+-----------
group 1 | 1000 | 1
group 2 | 100 | 2
group 3 | 2000 | 3
I need to find the indicator for the median person. The result should be
group 3 | 2000 | 3
If I do
select median(name) over (order by indicator) from table1
It will be group 2.
Not sure if I can select this with a window function.
Generating 1000/2000 rows per record seems impractical, because I have millions of people in the records.
Find the first cumulative sum of people greater than the median of total sum:
with the_data(name, people, indicator) as (
values
('group 1', 1000, 1),
('group 2', 100, 2),
('group 3', 2000, 3)
)
select name, people, indicator
from (
select *, sum(people) over (order by name)
from the_data
cross join (select sum(people)/2 median from the_data) s
) s
where sum > median
order by name
limit 1;
name | people | indicator
---------+--------+-----------
group 3 | 2000 | 3
(1 row)

Calculate total spread covered by several ranges

I have a table where each record has an indicator and a range, and I want to know the total spread covered by the ranges for each indicator -- but not double-counting when ranges overlap for a certain indicator.
I can see that the wording is hard to follow, but the concept is pretty simple. Let me provide an illustrative example.
CREATE TABLE records(id int, spread int4range);
INSERT INTO records VALUES
(1, int4range(1, 4)),
(1, int4range(2, 7)),
(1, int4range(11, 15)),
(2, int4range(3, 5)),
(2, int4range(6, 10));
SELECT * FROM records;
Yielding the output:
id | spread
----+---------
1 | [1,4)
1 | [2,7)
1 | [11,15)
2 | [3,5)
2 | [6,10)
(5 rows)
I would now like a query which gives the following output:
id | total
---+--------
1 | 10
2 | 6
Where did the numbers 10 and 6 come from? For ID 1, we have ranges that include 1, 2, 3, 4, 5, 6, 11, 12, 13, and 14; a total of 10 distinct integers. For ID 2, we have ranges that include 3, 4, 6, 7, 8, and 9; a total of six distinct integers.
If it helps you understand the problem, you might imagine it as something like "if these records represent the day and time range for meetings on my calendar, how many total hours in each day are there where I'm booked at least once?"
Postgres version is 9.4.8, in case that matters.
select id, count(*)
from (
select distinct id, generate_series(lower(spread), upper(spread) - 1)
from records
) s
group by id
;
id | count
----+-------
1 | 10
2 | 6