Convert columns data into rows in PostgreSQL - sql

I have data in the following format.
order_no rate jan feb mar ....
1 1200 2 4
2 1000 1 5
3 2400 14 3
Now I want to transpose this table to get the following output.
order_no rate month unit
1 1200 feb 2
1 1200 mar 4
2 1000 jan 1
2 2400 mar 5 and so on..
How can I do this?

You can create a "temporary" normalized view on the data using a cross join:
select o.order_no, o.rate, v.*
from orders o
cross join lateral (
values
('jan', jan),
('feb', feb),
('mar', mar),
...
('dec', dec)
) as v(month, unit)
If you want to exclude the months with no values, you can add
where v.unit is not null
to the query
Online example: http://rextester.com/PBP46544

One simple approach uses UNION:
SELECT order_no, rate, 'jan' AS month, jan AS unit UNION ALL
SELECT order_no, rate, 'feb', feb UNION ALL
...
SELECT order_no, rate, 'dec', dec
ORDER BY order_no;
Postgres also has CROSSTAB capabilities. But to use that, you have to be really good at SQL, which I'm not.

Try this
Select order_no, rate, 'jan' as month, jan as unit
from tbl
where jan is not null
union all
Select order_no, rate, 'feb' as month, feb as unit
from tbl
where feb is not null
union all
Select order_no, rate, 'mar' as month, mar as unit
from tbl
where mar is not null
order by order_no

Related

Generate a range of records depending on from-to dates

I have a table of records like this:
Item
From
To
A
2018-01-03
2018-03-16
B
2021-05-25
2021-11-10
The output of select should look like:
Item
Month
Year
A
01
2018
A
02
2018
A
03
2018
B
05
2021
B
06
2021
B
07
2021
B
08
2021
Also the range should not exceed the current month. In example above we are asuming current day is 2021-08-01.
I am trying to do something similar to THIS with CONNECT BY LEVEL but as soon as I also select my table next to dual and try to order the records the selection never completes. I also have to join few other tables to the selection but I don't think that would make a difference.
I would very much appreciate your help.
Row generator it is, but not as you did it; most probably you're missing lines #11 - 16 in my query (or their alternative).
SQL> with test (item, date_from, date_to) as
2 -- sample data
3 (select 'A', date '2018-01-03', date '2018-03-16' from dual union all
4 select 'B', date '2021-05-25', date '2021-11-10' from dual
5 )
6 -- query that returns desired result
7 select item,
8 extract(month from (add_months(date_from, column_value - 1))) month,
9 extract(year from (add_months(date_from, column_value - 1))) year
10 from test cross join
11 table(cast(multiset
12 (select level
13 from dual
14 connect by level <=
15 months_between(trunc(least(sysdate, date_to), 'mm'), trunc(date_from, 'mm')) + 1
16 ) as sys.odcinumberlist))
17 order by item, year, month;
ITEM MONTH YEAR
----- ---------- ----------
A 1 2018
A 2 2018
A 3 2018
B 5 2021
B 6 2021
B 7 2021
B 8 2021
7 rows selected.
SQL>
Recursive CTEs are the standard SQL approach to this type of problem. In Oracle, this looks like:
with cte(item, fromd, tod) as (
select item, fromd, tod
from t
union all
select item, add_months(fromd, 1), tod
from cte
where add_months(fromd, 1) < last_day(tod)
)
select item, extract(year from fromd) as year, extract(month from fromd) as month
from cte
order by item, fromd;
Here is a db<>fiddle.

Ways to find Percentage increase in sales Month over Month

I want to find the percentage increase in Month over month sales amount using SQL Server. I want to find % MoM increase in sales by using self join and also using partition with rows unbounded preceding. I do not want to use lag(). Can anyone let me know about the the ways to generate this solution.
Here is my table.
create table growth_new(slno bigint,mon varchar(30),sales_amount bigint)
insert into growth_new values(1, 'Jan', 5000)
insert into growth_new values(2, 'Feb', 12000)
insert into growth_new values(3, 'Mar', 32000)
insert into growth_new values(4, 'Apr', 20000)
Slno Mon sales_amount
1 Jan 5000
2 Feb 12000
3 Mar 32000
4 Apr 20000
You can use lag(). If slno orders the rows, then:
select gn.*,
(gn.sales_amount * 1.0 / lag(gn.sales_amount) over (order by slno)) - 1 as increase
from growth_new gn;
A self-join doesn't really make sense for this problem. But if you really needed to with this data structure:
with gn as (
select gn.*, convert(date, month + ' 2000') as mm
from growth_new
)
select gn.*,
(gn.sales_amount * 1.0 / gnprev.sales_amount) - 1
from gn left join
gn gnprev
on gnprev.mm = dateadd(month, -1, gn.mm);
You should, however, really fix the data so the month is in a reasonable format.
If you don't want to use the LEAD or LAG, you can use the following:
I assumed that you can compare using the ids, otherwise, you can have a table to store the months Ids
selecT g.*, growth = 100*cast(iif(p.sales_amount is null,0,(g.sales_amount-p.sales_amount)*1.0/p.sales_amount) as money)
from growth_new g
left join growth_new p on p.slno=g.slno-1
the output is:
slno mon sales_amount growth
1 Jan 5000 0.00
2 Feb 12000 140.00
3 Mar 32000 166.67
4 Apr 20000 -37.50
Hope this helps you
You could use the lag function really unless you want to try other alternatives. Also as mentioned above your month format is not ideal and not scalable at all.
WITH growth_new(slno ,mon ,sales_amount)
AS (SELECT 1, 'Jan', 5000 UNION
SELECT 2, 'Feb', 12000 UNION
SELECT 3, 'Mar', 32000 UNION
SELECT 4, 'Apr', 20000
)
SELECT cur.*, prev.mon as prev_month,
ISNULL(prev.sales_amount,0) AS prev_month_sales_amount,
[%MoM Change] = ((cur.sales_amount -
ISNULL(prev.sales_amount,0))/CAST(prev.sales_amount as float))*100
FROM growth_new cur
LEFT JOIN growth_new prev ON prev.slno = cur.slno - 1
slno mon sales_amount prev_month prev_month_sales_amount %MoM Change
1 Jan 5000 NULL 0 NULL
2 Feb 12000 Jan 5000 140
3 Mar 32000 Feb 12000 166.666666666667
4 Apr 20000 Mar 32000 -37.5

Reshaping a table SQL

I am using SQL and currently have a large table that contains data for 1000's of accounts sorted by date:
ID July 2018 August 2018 September 2018 …
1 10 20 30
2 50 40 10
3 20 10 80
I need to reshape the table so the table is displayed like this:
ID Month Value
1 July 2018 10
1 August 2018 20
1 September 2018 30
: : :
I don't know how to do this or if this is even possible. I have tried to use the pivot function in SQL but I have not been successful. Is there a way to do this?
You can use APPLY :
SELECT t.id, tt.*
FROM table t CROSS APPLY
( VALUES ('July 2018', [July 2018]),
('August 2018', [August 2018]),
('September 2018', [September 2018])
) tt (Month, Val);
You can use unpivot - it will work in MSSQL
select t.id, up.months, up.value
from tablename t
unpivot
(
value
for months in ([July 2018], [August 2018], [September 2018])
) up;
You can try with the help of UNION operator:
select id, 'July 2018' as Month, July2018 as value from <table>
UNION
select id, 'August 2018' as Month, August2018 as value from <table>
UNION
select id, 'September 2018' as Month, September2018 as value from <table>

Summing together values from the same table in different databases

I have a table on each database for a region of a company with the number of sales per month like so:
Region1.dbo.SalesPerMonth Region2.dbo.SalesPerMonth
ID Month Sales ID Month Sales
1 Jan 23 1 Jan 21
2 Feb 19 2 Feb 15
3 Jan 31 3 Jan 25
... ... ... ... ... ...
I am looking to write a query to join these tables into one table that shows the sales for the entire company per month, so it has the total sales from all regions added together:
AllRegions
ID Month Sales
1 Jan 44
2 Feb 34
3 Jan 56
... ... ...
I am however new to SQL and am not sure how to go about doing so. Any help or advice on how to write the query would be greatly appreciated.
Union together the two tables, and then aggregate by ID and Month to generate the sum of sales.
SELECT
ID, Month, SUM(Sales) AS Sales
FROM
(
SELECT ID, Month, Sales
FROM Region1.dbo.SalesPerMonth
UNION ALL
SELECT ID, Month, Sales
FROM Region2.dbo.SalesPerMonth
) t
GROUP BY
ID, Month
ORDER BY
ID;
Demo here:
Rextester
Try this:
WITH DataSource AS
(
SELECT *
FROM Region1.dbo.SalesPerMonth
UNION ALL
SELECT *
FROM Region2.dbo.SalesPerMonth
)
SELECT [id]
,[Month]
,SUM(Sales) AS Sales
FROM DataSource
GROUP BY [id]
,[Month]

How do I write a query that imputes values for records that are not present in a table?

I have a table that looks like this:
MONTH | WIDGET | VALUE
------+--------+------
Dec | A | 3
Jan | B | 5
Feb | B | 6
Mar | B | 7
and I want to write a query that produces, for each MONTH and WIDGET the difference in VALUE between the current month and the previous month. So I want an output table like this:
MONTH | WIDGET | VALUE
------+--------+------
Dec | A | 3
Jan | A | -3
Feb | A | 0
Mar | A | 0
Dec | B | 0
Jan | B | 5
Feb | B | 1
Mar | B | 1
If there is no recorded value for the previous month for a given widget, I want to assume the previous month's value is zero. Conversely, if there is no recorded value for the current month, I want to assume the current month's value is zero.
I believe a cross join over all combinations of month and widget might work, by giving me a "spine" to which I can left join my data and then use coalesce - but is there a better way?
Edit: We can assume the MONTH column actually has a numeric representation to make it easier to identify the previous.
I would use the lag function. IBM Reference I just defaulted to 0 for values whose prior value doesn't exist but you can handle that a number of different ways.
create temp table test (
mth date
,widget char(1)
,value integer
)
distribute on random;
insert into test values('2013-12-01','A',3);
insert into test values('2014-01-01','A',-3);
insert into test values('2014-02-01','A',0);
insert into test values('2014-03-01','A',0);
insert into test values('2013-12-01','B',0);
insert into test values('2014-01-01','B',5);
insert into test values('2014-02-01','B',1);
insert into test values('2014-03-01','B',1);
select *
,lag(value,1) over(partition by widget order by mth) as prior_row
,value - nvl(lag(value,1) over(partition by widget order by mth),0) as diff
from test
OK. I have solved this in MS SQL but it should be transferable to PostgresQL. I have SQLFiddled the answer:
CREATE TABLE WidgetMonths (Month tinyint, Widget varchar(1), Value int)
CREATE TABLE Months (Month tinyint, MonthOrder tinyint)
insert into WidgetMonths Values
(12, 'A', 3),
(1,'B', 5),
(2,'B', 6),
(3,'B', 7);
insert into Months Values
(12, 1), (1, 2), (2, 3), (3, 4)
Select
AllWidgetMonths.Widget,
AllWidgetMonths.Month,
IsNull(wm.Value,0) - IsNull(wmn.Value,0) as Value
from (
select Distinct Widget, Months.Month, Months.MonthOrder
from WidgetMonths
Cross Join months
) AllWidgetMonths
left join WidgetMonths wm on wm.Widget = AllWidgetMonths.Widget
AND wm.Month = AllWidgetMonths.Month
left join WidgetMonths wmn on wmn.Widget = AllWidgetMonths.Widget
AND Case When wmn.Month = 12 Then 1 Else wmn.Month + 1 End = AllWidgetMonths.Month
Order by AllWidgetMonths.Widget, AllWidgetMonths.MonthOrder
I have started off with a Table of WidgetMonths from your example the only difference being I have converted the months into a representative integer.
I have then Created the Months Table of All months we are interested in from your example. If you want months for the whole year you can simply add to this table or find another way of generating a 1-12 row result set. The MonthOrder is optional and just helped me achieve your answer ordering.
As you mentioned AllwidgetMonths has the Cross join which gives us all combinations of Widgets and Months. This maybe better achieved by a Cross join between 'Widgets' Table and the Months Table. But I wasn't sure if this existed so left this out.
We left join WidgetMonths onto our master table of All widget months to show us which months we have a value for.
The trick up the sleeve is then left joining the same table again but this time adding 1 to the month number inside the join. This shifts the rows down one. Notice I have a Case statement (not sure about this in PostgresSql) to deal with the roll over of Month 12 to Month 1. This effectively gives me the values for each month and its previous on each row of AllwidgetMonths.
The final bit is to take one value from the other.
Hey presto. I can try to update this to PostgresSQL but you may have more knowledge and can solve it quicker than I.
Here is another alternative to get the required data. Two CTE's are used, including one to contain the month numbers.
The SQL Fiddle can be accessed here.
WITH month_order as
(
SELECT 'Jan' as month, 1 as month_no, 12 as prev_month_no
UNION ALL
SELECT 'Feb' as month, 2 as month_no, 1 as prev_month_no
UNION ALL
SELECT 'Mar' as month, 3 as month_no, 2 as prev_month_no
UNION ALL
SELECT 'Apr' as month, 4 as month_no, 3 as prev_month_no
UNION ALL
SELECT 'May' as month, 5 as month_no, 4 as prev_month_no
UNION ALL
SELECT 'Jun' as month, 6 as month_no, 5 as prev_month_no
UNION ALL
SELECT 'Jul' as month, 7 as month_no, 6 as prev_month_no
UNION ALL
SELECT 'Aug' as month, 8 as month_no, 7 as prev_month_no
UNION ALL
SELECT 'Sep' as month, 9 as month_no, 8 as prev_month_no
UNION ALL
SELECT 'Oct' as month, 10 as month_no, 9 as prev_month_no
UNION ALL
SELECT 'Nov' as month, 11 as month_no, 10 as prev_month_no
UNION ALL
SELECT 'Dec' as month, 12 as month_no, 11 as prev_month_no
)
, values_all_months as
(
SELECT
month_order.prev_month_no as prev_month_no
, month_order.month_no as month_no
, w4.month as month
, w4.widget as widget
, COALESCE(w3.value, 0) as value
FROM widgets w3
RIGHT OUTER JOIN
(
SELECT
w1.widget as widget
,w2.month as month
FROM
(SELECT
DISTINCT
widget
FROM widgets) w1,
(SELECT
DISTINCT
month
FROM widgets) w2
) w4
ON w3.month = w4.month and w3.widget = w4.widget
INNER JOIN month_order
ON w4.month = month_order.month
)
SELECT mo.month, vam1.widget, vam1.value - COALESCE(vam2.value, 0) VALUE
FROM values_all_months vam1
LEFT OUTER JOIN values_all_months vam2
ON vam1.widget = vam2.widget AND vam1.prev_month_no = vam2.month_no
INNER JOIN month_order mo
ON vam1.month_no = mo.month_no
ORDER BY vam1.widget, (SELECT CASE vam1.month_no WHEN 12 THEN 0 ELSE vam1.month_no END);