How to transpose this table in postgresql - database

I have this table:
lnumber | lname | bez_gem
---------+----------------+------------------------------
1 | name1 | Berg b.Neumarkt i.d.OPf.
1 | name1 | Altdorf b.Nürnberg
2 | name2 | Berg b.Neumarkt i.d.OPf.
2 | name2 | Altdorf b.Nürnberg
3 | name3 | Mainleus
3 | name3 | Weismain
4 | name4 | Weismain
4 | name4 | Mainleus
The code for the query is:
WITH double AS (
SELECT
partnumber,
bez_gem
FROM accumulation a, municipality b
WHERE ST_Intersects(a.geom, b.geom)
AND EXISTS (
SELECT
lnumber
FROM mun_more_than_once c
WHERE a.partnumber=c.lnumber)
ORDER BY partnumber)
SELECT
landslide.lnumber,
lname,
bez_gem
FROM double, landslide
WHERE double.partnumber=landslide.lnumber
ORDER BY lnumber
I want to transpose in this format
lnumber | lname | bez_gem1 | bez_gem2
---------+----------------+------------------------------------------------
1 | name1 | Berg b.Neumarkt i.d.OPf. | Altdorf b.Nürnberg
2 | name2 | Berg b.Neumarkt i.d.OPf. | Altdorf b.Nürnberg

It depends. If you always have two bez_gem per lnumber, you can simply use:
SELECT lnumber, lname
, min(bez_gem) AS bez_gem1
, max(bez_gem) AS bez_gem2
FROM test
GROUP BY 1,2
ORDER BY 1;
SQL Fiddle.
Note that the order of peers is undefined in your question. Collation rules (alphabetical order) decide in my example.
For an actual cross tabulation you would use the crosstab() function from the additional module tablefunc. But your table is missing a category name (no indication which row holds bez_gem1 and which bez_gem2). Explanation, details and links:
PostgreSQL Crosstab Query

SQLFiddle
Data
-- drop table if exists test;
create table test (lnumber int, lname varchar, bez_gem varchar);
insert into test values
(1 , 'name1' , 'Berg b.Neumarkt i.d.OPf.'),
(1 , 'name1' , 'Altdorf b.Nürnberg'),
(2 , 'name2' , 'Berg b.Neumarkt i.d.OPf.'),
(2 , 'name2' , 'Altdorf b.Nürnberg'),
(3 , 'name3' , 'Mainleus'),
(3 , 'name3' , 'Weismain'),
(4 , 'name4' , 'Weismain'),
(4 , 'name4' , 'Mainleus'),
(4 , 'name4' , 'XXMainleus')
;
Query
select
lnumber,
lname,
max(case when rn = 1 then bez_gem end) as bez_gem1,
max(case when rn = 2 then bez_gem end) as bez_gem2,
max(case when rn = 3 then bez_gem end) as bez_gem3
from
(
select
*,
row_number() over(partition by lname) rn
from
test
) a
group by
lnumber,
lname
Result
1;name1;Berg b.Neumarkt i.d.OPf.;Altdorf b.Nürnberg;
2;name2;Berg b.Neumarkt i.d.OPf.;Altdorf b.Nürnberg;
3;name3;Mainleus;Weismain;
4;name4;Weismain;Mainleus;XXMainleus
Old Answer
If you have only two possible rows for every lnumber (you should add this important info to your question), you can simply use min and max:
WITH double AS (
SELECT
partnumber,
bez_gem
FROM accumulation a, municipality b
WHERE ST_Intersects(a.geom, b.geom)
AND EXISTS (
SELECT
lnumber
FROM mun_more_than_once c
WHERE a.partnumber=c.lnumber)
ORDER BY partnumber)
SELECT
landslide.lnumber,
lname,
min(bez_gem) as bez_gem1,
max(bez_gem) as bez_gem2
FROM double, landslide
WHERE double.partnumber=landslide.lnumber
group by
landslide.lnumber,
lname
ORDER BY lnumber
If you have possibly more than two rows for every lnumber and you really need crosstab, there is a lot of questions regarding crosstab in PostgreSQL on SO (example). As an alternative you can try the following approach.
Because this is one-time analysis, you can easily get maximum number of unique bez_gem values:
select
landslide.lnumber,
count(distinct bez_gem) cnt
from
<<some_data>>
group by
landslide.lnumber
order by
cnt desc limit 1
Then you can use:
select
landslide.lnumber,
lname,
max(case when rn=1 then bez_gem end) as bez_gem1,
max(case when rn=2 then bez_gem end) as bez_gem2,
max(case when rn=3 then bez_gem end) as bez_gem3,
max(case when rn=4 then bez_gem end) as bez_gem4,
max(case when rn=5 then bez_gem end) as bez_gem5,
... up to cnt ...
from(
select
landslide.lnumber,
lname,
bez_gem,
row_number() over(partition by landslide.lnumber) rn
from
<<some_data>>
) a
group by
landslide.lnumber,
lname
For your data and 5 possible values it would look like:
WITH double AS (
SELECT
partnumber, bez_gem
FROM
accumulation a, municipality b
WHERE
ST_Intersects(a.geom, b.geom)
AND EXISTS (
SELECT lnumber
FROM mun_more_than_once c
WHERE a.partnumber=c.lnumber)
ORDER BY
partnumber
)
select
landslide.lnumber,
lname,
max(case when rn=1 then bez_gem end) as bez_gem1,
max(case when rn=2 then bez_gem end) as bez_gem2,
max(case when rn=3 then bez_gem end) as bez_gem3,
max(case when rn=4 then bez_gem end) as bez_gem4,
max(case when rn=5 then bez_gem end) as bez_gem5
from (
select
landslide.lnumber,
lname,
bez_gem,
row_number() over(partition by landslide.lnumber) rn
from
double, landslide
where
double.partnumber=landslide.lnumber
) a
group by
landslide.lnumber,
lname

Related

Pivoting table in SQL

i have a problem with approach how to pivot this table:
How can i convert this:
Would you be kind enough give me hints (not whole code) how to do this? i really dont know where should i start (i dont know whether it is typical pivoting unpivoting)
+------+------+--------+----------+-----------+-----+
| ColI | Col2 | Month | Turnover | Provision | Fee |
+------+------+--------+----------+-----------+-----+
| 123 | Asdf | 201810 | 10000 | 100 | 0,1 |
| 123 | Asdf | 201811 | 20000 | 200 | 0,2 |
| 123 | Asdf | 201812 | 30000 | 300 | 0,3 |
+------+------+--------+----------+-----------+-----+
into this:
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
| ColI | Col2 | Turnover20810 | Provision201810 | Fee201810 | Turnover20811 | Provision201811 | Fee201811 | Turnover20812 | Provision201812 | Fee201812 |
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
| 123 | Asdf | 10000 | 100 | 0,1 | 20000 | 200 | 0,2 | 30000 | 300 | 0,3 |
+------+------+---------------+-----------------+------------+---------------+-----------------+------------+---------------+-----------------+-----------+
Thank you!
Assuming that each ColI, Col2 group would only have a maximum of three records, then we can try pivoting using the help of ROW_NUMBER:
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ColI, Col2 ORDER BY Month) rn
FROM yourTable
)
SELECT
ColI,
Col2,
MAX(CASE WHEN rn = 1 THEN Turnover END) AS Turnover1,
MAX(CASE WHEN rn = 1 THEN Provision END) AS Provision1,
MAX(CASE WHEN rn = 1 THEN Fee END) AS Fee1,
MAX(CASE WHEN rn = 2 THEN Turnover END) AS Turnover2,
MAX(CASE WHEN rn = 2 THEN Provision END) AS Provision2,
MAX(CASE WHEN rn = 2 THEN Fee END) AS Fee2,
MAX(CASE WHEN rn = 3 THEN Turnover END) AS Turnover3,
MAX(CASE WHEN rn = 3 THEN Provision END) AS Provision3,
MAX(CASE WHEN rn = 3 THEN Fee END) AS Fee3
FROM cte
GROUP BY
ColI,
Col2;
Note that I did not hardwire more specific column names, to keep the query as general as possible. For example, perhaps there might be another ColI, Col2 group which would have a different three months.
By using Dynamic Sql
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
DROP TABLE #TEMP
;WITH CTE (ColI , Col2 , Month , Turnover , Provision , Fee )
AS
(
SELECT 123 , 'Asdf' , 201810 , 10000 ,100 ,'0,1' UNION ALL
SELECT 123 , 'Asdf' , 201811 , 20000 ,200 , '0,2' UNION ALL
SELECT 123 , 'Asdf' , 201812 , 30000 ,300 , '0,3'
)
SELECT ColI , Col2,Turnover , Provision , Fee,MixedCol,Reqcol , ROW_NUMBER()OVER(ORDER BY (SELECT NULL)) AS Seq
INTO #Temp
FROM CTE
CROSS APPLY (VALUES (CONCAT('Turnover','_',[Month]),CAST(Turnover AS VARCHAR(20))),
(CONCAT('Provision','_',[Month]),CAST(Provision AS VARCHAR(20))),
(CONCAT('Fee','_',[Month]),CAST(Fee AS VARCHAR(20)))
)DT (MixedCol,Reqcol)
DECLARE #Sql nvarchar(max),
#DynamicColumn nvarchar(max),
#MaxDynamicColumn nvarchar(max)
SELECT #DynamicColumn = STUFF((SELECT ', '+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SELECT #MaxDynamicColumn = STUFF((SELECT ', '+'MAX('+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))+') AS '+QUOTENAME(CAST(MixedCol AS VARCHAR(100)))
FROM #TEMP ORDER BY Seq FOR XML PATH ('')),1,1,'')
SET #Sql=' SELECT ColI , Col2,'+ #MaxDynamicColumn+'
FROM
(
SELECT * FROM #TEMP
)AS src
PIVOT
(
MAX(Reqcol) FOR [MixedCol] IN ('+#DynamicColumn+')
) AS Pvt
GROUP BY ColI , Col2 '
EXEC (#Sql)
PRINT #Sql
Q: "...give me hints (not whole code) how to do this"
A: This example with dynamic transpose of table. You can try this. Unpivot your original table and transpose.
CREATE table #tbl (
color varchar(10), Paul int, John int, Tim int, Eric int);
insert #tbl select
'Red' ,1 ,5 ,1 ,3 union all select
'Green' ,8 ,4 ,3 ,5 union all select
'Blue' ,2 ,2 ,9 ,1;
select * FROM #tbl
--1) Transpose. Example without dynamic code. You create list of fields in query
select *
from #tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Blue],[Green],[Red])) p
--2) Transpose. Example with dynamic code, without names of fields.
DECLARE #cols NVARCHAR(MAX), #query NVARCHAR(MAX), #rows NVARCHAR(MAX)='';
-- XML with all values
SET #cols = STUFF(
(
SELECT DISTINCT
','+QUOTENAME(T.Color) -- Name of first column
FROM #tbl T FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 1, '');
SELECT #rows=#rows+','+QUOTENAME(Name)
FROM
(SELECT Name,ROW_NUMBER() OVER(ORDER BY column_id) AS 'RowNum'
FROM tempdb.sys.Columns
WHERE Object_ID = OBJECT_ID('tempdb..#tbl')
) AS A WHERE A.RowNum>1
SET #rows=STUFF(#rows,1,1,'')
SET #query='SELECT *
from #tbl
unpivot (value for name in ('+#rows+')) up
pivot (max(value) for color in ('+#Cols+')) p'
EXEC (#query)

How to pivot table from multiple column to multiple rows

CodeDt CodeHeader Item Qty Type Remark Attachment
LK4-033502 RK-K-LK4-032438 IA01001023 2.00 TPR002 2 1.jpeg
LK4-033502RK RK-K-LK4-032438 IA01001023RK 2.00 NULL IA01001023 NULL
Above is my data from Sql server table (using 2008 R2). I want to make it one row only. Here is my expected result:
CodeDt CodeHeader Item NewItem Qty
LK4-033502 RK-K-LK4-032438 IA01001023 IA01001023RK 2.00
How can I achieve that? Here is the relation:
Row 1 Item = Row 2 Remark,
Row 1 Code DT = Row 2 CodeDt+'RK'
There are several solutions
1) Using JOIN. It assumes that Type field is null for rows with CodeDt+'RK'
select
a.CodeDt, a.CodeHeader, a.Item, b.Item, a.Qty
from
myTable a
join myTable b on a.Item = b.Remark
where
a.Type is not null
2) Conditional aggregation
select
max(case when rn = 1 then CodeDt end)
, CodeHeader
, max(case when rn = 1 then Item end)
, max(case when rn = 2 then Item end)
, max(case when rn = 1 then Qty end)
from (
select
*, rn = row_number() over (partition by CodeHeader order by CodeDt)
from
myTable
) t
group by CodeHeader

How can we pivot the query in the following way in the image

I try to make pivot the table to the following image but i failed , the final result i need is as follow
and the query i used is
SELECT COUNT(T.TTOutID) AS Currenct, TTOutTargetTrxnCount AS Targets,
B.BranchCode ,
ROW_NUMBER() OVER (Order BY COUNT(T.TTOutID) DESC) AS RANK,
CAST((CAST (COUNT(T.TTOutID) AS DECIMAL)/CAST (TTOutTargetTrxnCount AS decimal))*100 AS decimal (4,2)) AS Percentages ,
TTOutTargetTrxnCount-COUNT(T.TTOutID) as Difference
FROM ALX_SalesTargets S
LEFT JOIN ALX_TTOut T ON S.BranchID=T.BranchID
LEFT JOIN ALX_Branches B ON S.BranchID=B.BranchID
Group BY S.TTOutTargetTrxnCount,B.BranchCode
and i make a tmp table for help
CREATE TABLE #Tempsample
(
currenct int ,
targets bigint,
branchcode nvarchar(128),
rank int,
percentage decimal,
difference int
);
INSERT INTO #Tempsample
(currenct, targets,branchcode,rank,percentage,difference)
VALUES
('131', '2650','EXB', '1','4.94', '2519'),
('25', '3500','MHQ', '2','0.71', '3475'),
('3', '850','MNM', '3','0.35', '847')
using cross apply(values ...) to unpivot your data, and conditional aggregation to re-pivot your data:
select
u.Attribute
, EXB = max(case when branchcode = 'EXB' then value end)
, MHQ = max(case when branchcode = 'MHQ' then value end)
, MNM = max(case when branchcode = 'MNM' then value end)
, Totals = sum(case when u.Attribute = 'rank' then null else value end)
from #tempsample t
cross apply (values
('currenct',currenct)
, ('targets',targets)
, ('difference',difference)
, ('rank',rank)
) u (attribute,value)
group by u.Attribute
order by (case when u.Attribute='rank' then 1 else 0 end)
rextester demo: http://rextester.com/WLES86332
returns:
+------------+------+------+-----+--------+
| Attribute | EXB | MHQ | MNM | Totals |
+------------+------+------+-----+--------+
| currenct | 131 | 25 | 3 | 159 |
| difference | 2519 | 3475 | 847 | 6841 |
| targets | 2650 | 3500 | 850 | 7000 |
| rank | 1 | 2 | 3 | NULL |
+------------+------+------+-----+--------+
You can try pivot like this:
select 'Targets' as [Header], * from (
select targets, branchcode from #Tempsample ) a
pivot (sum(targets) for branchcode in ([EXB], [MHQ], [MNM])) p
union all
select 'Current' as [Header],* from (
select currenct, branchcode from #Tempsample ) a
pivot (sum(currenct) for branchcode in ([EXB], [MHQ], [MNM])) p
union all
select 'Difference' as [Header], * from (
select difference, branchcode from #Tempsample ) a
pivot (sum(difference) for branchcode in ([EXB], [MHQ], [MNM])) p
union all
select 'Rank' as [Header], * from (
select Rank, branchcode from #Tempsample ) a
pivot (MAX(rank) for branchcode in ([EXB], [MHQ], [MNM])) p

Select First, Max, and Last non-null value per group

Trying to select, per group, the first and last values (chronologically) as well as the max value. I had written a query that works fine except it does not handle the NULL values. I need it to ignore NULL values.
Here's an example:
DECLARE #T table (
LabName VARCHAR(20)
, CreatedOn date
, LabValue int
)
INSERT INTO #T
( LabName,CreatedOn,LabValue )
VALUES
('Creatinine', '2016-01-01', NULL)
, ('Creatinine', '2016-02-01', 15)
, ('Creatinine', '2016-03-01', 20)
, ('Creatinine', '2016-04-01', 19)
, ('SGOT (ST)', '2016-01-01', 25)
, ('SGOT (ST)', '2016-02-01', 31)
, ('SGOT (ST)', '2016-03-01', 25)
, ('SGOT (ST)', '2016-04-01', NULL)
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM #T
) AS T
It was working fine until I realized some labs aren't run on some dates. Once I put some NULLs into the test data, the results for First and Last will include them.
Here is the result I get:
+------------+------------+----------+-----------+
| LabName | FirstValue | MaxValue | LastValue |
+------------+------------+----------+-----------+
| Creatinine | NULL | 20 | 19 |
| SGOT (ST) | 25 | 31 | NULL |
+------------+------------+----------+-----------+
Here is the result I want:
+------------+------------+----------+-----------+
| LabName | FirstValue | MaxValue | LastValue |
+------------+------------+----------+-----------+
| Creatinine | 15 | 20 | 19 |
| SGOT (ST) | 25 | 31 | 25 |
+------------+------------+----------+-----------+
Use conditional aggregation with ROW_NUMBER():
SELECT LabName,
MAX(CASE WHEN seqnum_asc = 1 THEN LabValue END) as FirstValue,
MAX(LabValue) as MaxValue,
MAX(CASE WHEN seqnum_desc = 1 THEN LabValue END) as LastValue
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY LabName
ORDER BY (CASE WHEN LabValue IS NOT NULL THEN 1 ELSE 2 END),
CreatedOn
) as seqnum_asc,
ROW_NUMBER() OVER (PARTITION BY LabName
ORDER BY (CASE WHEN LabValue IS NOT NULL THEN 1 ELSE 2 END),
CreatedOn DESC
) as seqnum_desc
FROM #T t
) T
GROUP BY LabName;
As you said there are 13 such columns where you need to check not null values.
I think you should first filter all not null values using CTE,then using CTE you can write your actual query.CTE will reduce your result set and applying window function on reduce resultset will give better performance.
BTW,13 such columns appear t be bad DB design.you may have to 100 query in future.
IMHO, DISTINCT often indicate bad DB design than query.
;With CTE as
(-- try to reduce resultset if possible
SELECT * FROM #T
WHERE LabValue IS NOT NULL
)
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM CTE
) AS T
Your database is handling NULL values properly.
First value for Creatinine is actually null and last value for SGOT (ST) is null as well.
If you wish to discard rows with null values just add it in the WHERE clause:
SELECT DISTINCT
*
FROM (
SELECT
LabName
, FIRST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC) AS FirstValue
, MAX(LabValue) OVER(PARTITION BY LabName) AS MaxValue
, LAST_VALUE(LabValue) OVER(PARTITION BY LabName ORDER BY CreatedOn ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) LastValue
FROM #T
WHERE LabValue IS NOT NULL
) AS T;

Different order by clause for different columns

I hope my title made sense, here's a sample of my table.
My table has 1 column for a unique product barcode, and 4 date columns which is for storing what date and time a product passed an inspection area(4 of them), i removed the date in my example so just assume they're all the same dates.
Product | Time1 | Time2 | Time3 | Time4
---------------------------------------
A | 10:00 | 10:15 | 10:30 | 10:45
B | 10:05 | 10:25 | 10:35 | 10:50
C | 10:10 | 10:20 | 10:40 | 10:55
Output:
Inspect1 | Inspect2 | Inspect3 | Inspect4
-----------------------------------------
A A A A
B C B B
C B C C
In my output, I want to select the product multiple times with different order by(order by Time1, order by Time2, etc.)
I used case select to select the product multiple times, how do I put an order by so that I get something like my example above.
SELECT
CASE WHEN time1 BETWEEN '10:00' AND '11:00' THEN product END AS inspect1,
CASE WHEN time2 BETWEEN '10:00' AND '11:00' THEN product END AS inspect2,
CASE WHEN time3 BETWEEN '10:00' AND '11:00' THEN product END AS inspect3,
CASE WHEN time4 BETWEEN '10:00' AND '11:00' THEN product END AS inspect4
FROM Table
Here is a solution using ROW_NUMBER:
SQL Fiddle
WITH CteUnion(Product, TimeNum, Value) AS(
SELECT Product, 'Time1', Time1 FROM YourTable UNION ALL
SELECT Product, 'Time2', Time2 FROM YourTable UNION ALL
SELECT Product, 'Time3', Time3 FROM YourTable UNION ALL
SELECT Product, 'Time4', Time4 FROM YourTable
),
CteRN AS(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY TimeNum ORDER BY Value)
FROM CteUnion
),
CteOrd AS(
SELECT *,
Ord = ROW_NUMBER() OVER(PARTITION BY RN ORDER BY Value)
FROM CteRN
)
SELECT
Inspect1 = MAX(CASE WHEN Ord = 1 THEN Product END),
Inspect2 = MAX(CASE WHEN Ord = 2 THEN Product END),
Inspect3 = MAX(CASE WHEN Ord = 3 THEN Product END),
Inspect4 = MAX(CASE WHEN Ord = 4 THEN Product END)
FROM CteOrd
GROUP BY RN
ORDER BY RN

Resources