sql One-many and one-one relation between columns - sql-server

I have no idea if the title matches what I am trying to achieve in the query given below. The example might not be great but it serves what I am trying to achieve. There are three results that I am trying to get as shown below. Can this be done with a single query showing all the results in a better way?
TABLE:
name surname gender
arjun khadka female
arjun khadka male
arjun basnet male
kumar khadka female
kumar basnet female
arjun khadka female
arjun basnet female
kumar khadka female
query:
WITH cte
AS (SELECT
*,
DENSE_RANK() OVER (ORDER BY name, surname) AS groupid1,
DENSE_RANK() OVER (ORDER BY name, surname, gender) AS groupid2
FROM (SELECT
name,
surname,
gender
FROM name) x)
same person with duplicate gender as well as with non duplicate
gender
SELECT
name,
surname,
gender
FROM CTE
WHERE groupid1 IN (SELECT
groupid1
FROM cte
GROUP BY groupid1,
groupid2
HAVING COUNT(*) > 1)
AND groupid1 IN (SELECT
groupid1
FROM cte
GROUP BY groupid1,
groupid2
HAVING COUNT(*) = 1)
same person with no duplicate gender at all
SELECT
*
FROM cte
WHERE groupid1 NOT IN (SELECT
groupid1
FROM cte
GROUP BY groupid1,
groupid2
HAVING COUNT(*) > 1)
same person having duplicate gender record only
SELECT
*
FROM cte
WHERE groupid1 IN (SELECT
groupid1
FROM cte
GROUP BY groupid1,
groupid2
HAVING COUNT(*) > 1)
AND groupid1 NOT IN (SELECT
groupid1
FROM cte
GROUP BY groupid1,
groupid2
HAVING COUNT(*) = 1)
I modified what #uzi has provided, as solution as it has some flaws:
select
distinct name, surname,personCount as count
, personHasDupNUniqueGender = iif(min(allRowsCount) over (partition by personGroupId) = 1 and max (allRowsCount) over (partition by personGroupId) >1,1,0)
, personHasUniqueGender = iif(max(allRowsCount) over (partition by personGroupId) = 1,1,0)
, personHasAllDupGender = iif(personCount = allrowscount and personcount>1,1,0)
from (
select
*
, personGroupId= DENSE_RANK() over (order by name, surname)
, personCount = count(*) over (partition by name, surname)
, allRowsCount = count(*) over (partition by name, surname, gender)
from
name
) t
order by name, surname

Here's one way. Query returns all rows with extra columns condition_1, condition_2, condition_3. Values of those columns either 1 or 0. 1 - matches condition
declare #t table (
name varchar(100)
, surname varchar(100)
, gender varchar(100)
)
insert into #t values
('arjun', 'khadka', 'female')
,('arjun', 'khadka', 'male')
,('arjun', 'basnet', 'male')
,('kumar', 'khadka', 'female')
,('kumar', 'basnet', 'female')
,('arjun', 'khadka', 'female')
,('arjun', 'basnet', 'female')
,('kumar', 'khadka', 'female')
select
name, surname, gender
, condition_1 = max(iif(cnt_1 = 1 and cnt_2 > 2, 1, 0)) over (partition by name, surname)
, condition_2 = iif(cnt_2 - cnt_1 <= 1 and cnt_1 = 1, 1, 0)
, condition_3 = iif(cnt_2 = cnt_1 and cnt_1 > 1, 1, 0)
from (
select
*, cnt_1 = count(*) over (partition by name, surname, gender)
, cnt_2 = count(*) over (partition by name, surname)
from
#t
) t
order by name, surname
Output
name surname gender condition_1 condition_2 condition_3
-------------------------------------------------------------------
arjun basnet female 0 1 0
arjun basnet male 0 1 0
arjun khadka female 1 0 0
arjun khadka female 1 0 0
arjun khadka male 1 0 0
kumar basnet female 0 1 0
kumar khadka female 0 0 1
kumar khadka female 0 0 1
Edit:
select
name, surname, gender, cnt_1, cnt_2
, condition_1 = max(iif(cnt_1 = 1 and cnt_2 > 2, 1, 0)) over (partition by name, surname)
, condition_2 = iif(max(cnt_1) over (partition by name, surname) = 1, 1, 0)
, condition_3 = iif(cnt_2 = cnt_1 and cnt_1 > 1, 1, 0)
from (
select
*, cnt_1 = count(*) over (partition by name, surname, gender)
, cnt_2 = count(*) over (partition by name, surname)
from
#t
) t
order by name, surname

Related

Get value of two columns when pivoting

I have a table that has entries like
Acct Nurse EntryDateTime DBCode Answer FormSeq
123 Sally 9/8/2020 09:22 Code1 Ans1 0001
123 Jim 9/8/2020 10:25 Code1 Ans2 0001
123 Sally 9/8/2020 09:15 Code2 C2Ans1 0001
I have a query that is pivoting this to get the answer from the last entry based on DBCode and EntryDateTime that works great. What I need to do is get the NURSE as well as the answer.
So my row would be
Acct Code1 Code1Nurse Code2 Code2Nurse
123 Ans2 Jim C2Ans1 Sally
Is there a way to do this? I would need the nurse for each unique DBCode
Here is my pivot code:
SELECT * FROM (
SELECT
[AcctNumber],
[Answer],
[DBCode],
[EntryDate],
[FormCode],[FormSeq]
FROM V_FAC_MULTIAPP_FORM_WITH_HOURLY
) MultiApp
PIVOT (
MAX( [Answer])
FOR [DBCode]
IN (
[AST],
[SDRM],
[SDRF],[SDAAS],[SDDCT],[SDDAS],[SDABY],[SDDAT],[SDTRCC],[SDADMTW],[Prptic],[Pdcrt]
)
) AS PivotTable WHERE EntryDate >='8/15/2020' and FormCode='LL003' ORDER BY EntryDate
You could use conditional aggregation.
Data
drop table if exists #tTEST;
go
select * INTO #tTEST from (values
(123, 'Sally', '9/8/2020 09:22', 'Code1', 'Ans1', '0001'),
(123, 'Jim', '9/8/2020 10:25', 'Code1', 'Ans2', '0001'),
(123, 'Sally', '9/8/2020 09:15', 'Code2', 'C2Ans1', '0001')) V(Acct, Nurse, EntryDateTime, DBCode, Answer, FormSeq);
Query
;with rn_cte as (
select *, row_number() over (partition by DBCode order by EntryDateTime desc) rn
from #tTEST)
select Acct,
max(case when DBCode='Code1' and rn=1 then Answer else null end) Code1,
max(case when DBCode='Code1' and rn=1 then Nurse else null end) Code1Nurse,
max(case when DBCode='Code2' and rn=1 then Answer else null end) Code2,
max(case when DBCode='Code2' and rn=1 then Nurse else null end) Code2Nurse
from rn_cte
group by Acct;
Output
Acct Code1 Code1Nurse Code2 Code2Nurse
123 Ans2 Jim C2Ans1 Sally
Here is something to play with. It would take some tinkering, but you could make the query dynamic to build out the columns to select based upon the DBCodes you have in your table...
IF OBJECT_ID('tempdb..#V_FAC_MULTIAPP_FORM_WITH_HOURLY') IS NOT NULL
DROP TABLE #V_FAC_MULTIAPP_FORM_WITH_HOURLY;
CREATE TABLE #V_FAC_MULTIAPP_FORM_WITH_HOURLY
(
Acct INT,
Nurse VARCHAR(20),
EntryDateTime DATETIME,
DBCode VARCHAR(10),
Answer VARCHAR(10),
FormSeq VARCHAR(10)
)
INSERT #V_FAC_MULTIAPP_FORM_WITH_HOURLY
VALUES
(123,'Sally','9/8/2020 09:22','Code1','Ans1','0001'),
(123,'Jim','9/8/2020 10:25','Code1','Ans2','0001'),
(123,'Sally','9/8/2020 09:15','Code2','C2Ans1','0001');
WITH Top_Row_Per_DBCode_By_EntryDate AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY DBCode ORDER BY EntryDateTime DESC) AS top_row,
Acct,
DBCode,
Nurse+':'+Answer AS Answer
FROM #V_FAC_MULTIAPP_FORM_WITH_HOURLY
), Filtered_CTE AS
(
SELECT *
FROM Top_Row_Per_DBCode_By_EntryDate
WHERE top_row = 1
)
SELECT Acct,
Substring([Code1],CHARINDEX(':',[Code1])+1,LEN([Code1])-CHARINDEX(':',[Code1])) AS Code1,
Substring([Code1],0,CHARINDEX(':',[Code1])) AS Code1Nurse,
Substring([Code2],CHARINDEX(':',[Code2])+1,LEN([Code2])-CHARINDEX(':',[Code2])) AS Code2,
Substring([Code2],0,CHARINDEX(':',[Code2])) AS Code2Nurse
FROM Filtered_CTE
PIVOT
(
MAX( [Answer]) FOR [DBCode]
IN ([Code1],[Code2])
) AS PivotTable

Another way to show ID in pivot

I have the following code. Is there another way to show ID in pivot besides what I have? What I have does not look very efficient:
create table #salary(
id int
, fNAme varchar(10)
, salary int
);
insert into #salary(id, fName, salary)
values(1,'jim',1000)
,(2,'mike',2000)
,(3,'tim',500)
,(1,'jim',300)
,(2,'mike',400)
,(3,'tim',250)
select 'salary' as salary, id, [jim], [mike], [tim]
from (
select id, fNAme, salary
from #salary
) x
PIVOT (
sum(salary) for fNAme in ([jim], [mike], [tim])
) as pvt
Output:
salary id jim mike tim
salary 1 1300 NULL NULL
salary 2 NULL 2400 NULL
salary 3 NULL NULL 750
If you are looking for an alternative approach you can consider conditional aggregation:
select
'salary' as salary
, id
, sum(case when fName = 'jim' then salary else null end) as jim
, sum(case when fName = 'mike' then salary else null end) as mike
, sum(case when fName = 'tim' then salary else null end) as tim
from
#salary
group by id
Result:

How to update rows between two different sets of criteria in SQL Server without using a loop

Issue: How to update rows between two different sets of criteria in SQL Server without using a loop (SQL Server 2014). In other words, for each row in a result set, how to update every row between the first occurrence (with one criterion) and the second occurrence (with different criteria). I think part of the issue is trying to run a TOP N query for every row in the query.
Specifically:
In the example starting table below, how can I update the last 2 columns of dates where:
Update rows between the null Category rows and the last consecutive "M" Category row if the null Category row is preceded by a "S" Category. Category can contain any order of "S", "M", or null.
Set StartDate = IDEndDate+1 day of the "S" row preceding the null row.
Set EndDate = IDEndDate of the last row with a "M" Category.
Here is a SQLFiddle.
Notes: I have done this in the past with a loop (fetch..) but I am trying to do this with a few queries instead kind of like:
step 1: Get work: select all valid null rows (beginning of range)
step 2: for each row above, select the related last "M" row (end of range) and then run a query to update the StartDate, EndDates in each range.
Starting Table:
ID IDStartDate IDEndDate Category
------------------------------------
11 2017-01-01 2017-01-31 S
11 2017-02-02 2017-02-03 null
11 2017-02-03 2017-03-31 M
11 2017-04-01 2017-04-30 M
22 2017-05-01 2017-06-15 S
22 2017-06-16 2017-06-20 null
22 2017-06-21 2017-06-25 M
22 2017-06-26 2017-06-27 null
22 2017-06-28 2017-06-29 S
22 2017-06-30 2017-07-05 M
33 2017-06-30 2017-07-14 M
33 2017-07-15 2017-07-20 S
33 2017-07-21 2017-07-25 null
44 2018-06-30 2018-07-14 S
44 2018-07-15 2018-07-20 M
44 2018-07-21 2018-07-25 null
Desired Ending Table:
ID IDStartDate IDEndDate Category StartDate EndDate
----------------------------------------------------------
11 2017-01-01 2017-01-31 S
11 2017-02-02 2017-02-03 null 2017-02-01 2017-04-30
11 2017-02-03 2017-03-31 M 2017-02-01 2017-04-30
11 2017-04-01 2017-04-30 M 2017-02-01 2017-04-30
22 2017-05-01 2017-06-15 S
22 2017-06-16 2017-06-20 null 2017-06-16 2017-06-25
22 2017-06-21 2017-06-25 M 2017-06-16 2017-06-25
22 2017-06-26 2017-06-27 null
22 2017-06-28 2017-06-29 S
22 2017-06-30 2017-07-05 M
33 2017-06-30 2017-07-14 M
33 2017-07-15 2017-07-20 S
33 2017-07-21 2017-07-25 null
44 2018-06-30 2018-07-14 S
44 2018-07-15 2018-07-20 M
44 2018-07-21 2018-07-25 null
Below is some SQL to create the table and view the query results that I have started. I tried cte, cross apply, outer apply, inner joins... with no luck.
thanks so much!
CREATE TABLE test (
ID INT,
IDStartDate date,
IDEndDate date,
Category VARCHAR (2),
StartDate date,
EndDate date
);
INSERT INTO test (ID, IDStartDate, IDEndDate, Category)
VALUES
(11, '2017-01-01', '2017-01-31', 'S')
,(11, '2017-02-02', '2017-02-03', null)
,(11, '2017-02-03', '2017-03-31', 'M')
,(11, '2017-04-01', '2017-04-30', 'M')
,(22, '2017-05-01', '2017-06-15', 'S')
,(22, '2017-06-16', '2017-06-20', null)
,(22, '2017-06-21', '2017-06-25', 'M')
,(22, '2017-06-26', '2017-06-27', null)
,(22, '2017-06-28', '2017-06-29', 'S')
,(22, '2017-06-30', '2017-07-05', 'M')
,(33, '2017-06-30', '2017-07-14', 'M')
,(33, '2017-07-15', '2017-07-20', 'S')
,(33, '2017-07-21', '2017-07-25', null)
,(44, '2018-06-30', '2018-07-14', 'S')
,(44, '2018-07-15', '2018-07-20', 'M')
,(44, '2018-07-21', '2018-07-25', null);
--**************************
--results: shows first rows of each range
--**************************
;with cte as
(
select *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS RowNum
,LAG(IDEndDate) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastIDEndDate
,LAG(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastCategory
,LEAD(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS nextCategory
from test
)
select * --select first row of each range to update
from cte
where Category is null and lastCategory = 'S' and nextCategory = 'M'
--*******************************
--6 of 8 "new" values are correct (missing NewEndDate for first range)
--*******************************
;with cte as
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS RowNum
,LAG(IDEndDate) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastIDEndDate
,LAG(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastCategory
,LEAD(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS nextCategory
FROM test
), cte2 as
(
select * --find the first/start row of each range
,LAG(RowNum) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastRowNum
,IIF(Category is null and lastCategory = 'S' and nextCategory = 'M', DateAdd(day, 1, lastIDEndDate), null) as NewStartDate
,IIF(Category is null and lastCategory = 'S' and nextCategory = 'M', RowNum, null) as NewStartRowNum
from cte
)
select t1.*, t3.*
from cte2 t1
outer apply
(
select top 1 --find the last/ending row of each range
t2.lastIDEndDate as NewEndDate
,t2.lastRowNum as NewEndRowNum
from cte2 t2
where t1.ID = t2.ID
and t1.NewStartRowNum < t2.RowNum
and t2.nextCategory <> 'M'
order by t2.ID, t2.RowNum
) t3
order by t1.ID, t1.RowNum
Here's an attempt on this SQL puzzle.
Basically, it updates from a CTE.
First it calculates a Cummulative sum. To create some kind of ranking.
Then only for rank 2 & 3 it'll calculate the dates.
;WITH CTE AS
(
SELECT ID, IDStartDate, IDEndDate, Category, StartDate, EndDate,
DATEADD(day,1, FIRST_VALUE(IDEndDate) OVER (PARTITION BY ID ORDER BY IDStartDate)) AS NewStartDate,
FIRST_VALUE(IDEndDate) OVER (PARTITION BY ID ORDER BY IDStartDate DESC) AS NewEndDate
FROM
(
SELECT ID, IDStartDate, IDEndDate, Category, StartDate, EndDate,
SUM(CASE WHEN Category = 'S' THEN 2 WHEN Category IS NULL THEN 1 END) OVER (PARTITION BY ID ORDER BY IDStartDate) AS cSum
FROM test t
) q
WHERE cSum IN (2, 3)
)
UPDATE CTE
SET
StartDate = NewStartDate,
EndDate = NewEndDate
WHERE (Category IS NULL OR Category = 'M');
A test on rextester here
I answered my own question. I had two major errors:
1) A Cross Apply (or Outer Apply) is needed for the Top N query to work properly.
Using a cross apply, the Top N query will be run for each row from the inner query.
Using an inner join (or left join), all rows will be returned first from the inner query and the Top N query runs only once.
2) Filtering on "[column] <> 'M'" messed me up as it did not exclude NULL's. I had to use instead "[column] = 'S' or [column] is null"
Final SQL found in rextester
Working code below:
;with cte as
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS RowNum
,LAG(IDEndDate) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastIDEndDate
,LAG(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS lastCategory
,LEAD(Category) OVER(PARTITION BY ID ORDER BY ID, IDStartDate, IDEndDate) AS nextCategory
FROM test
), cte2 as
(
select t1.ID, t1.IDStartDate, t1.IDEndDate --find the first/start row of the range
,IIF(Category is null and lastCategory = 'S' and nextCategory = 'M', DateAdd(day, 1, lastIDEndDate), null) as NewStartDate
,IIF(Category is null and lastCategory = 'S' and nextCategory = 'M', RowNum, null) as NewStartRowNum
,t3.*
from cte t1
cross apply
(
select top 1 --find the last/ending row of the range
t2.IDEndDate as NewEndDate
,t2.RowNum as NewEndRowNum
from cte t2
where t1.ID = t2.ID
and t1.RowNum < t2.RowNum
and (t2.nextCategory ='S' or t2.nextCategory is null)
order by t1.ID, t1.RowNum
) t3
where Category is null and lastCategory = 'S' and nextCategory = 'M'
)
update t4
set StartDate = NewStartDate
,EndDate = NewEndDate
from cte t4
inner join cte2 t5
on t4.ID = t5.ID
and t4.RowNum Between NewStartRowNum and NewEndRowNum
select * from test

Show all RowNumber records for duplicates?

I have a table of string resources :
;WITH cte AS
(
SELECT 1 AS id , 'john' AS name, 10 AS age
UNION
SELECT 2 AS id , 'john' AS name, 10 AS age
UNION
SELECT 3 AS id , 'john' AS name, 12 AS age
UNION
SELECT 4 AS id , 'paul' AS name, 6 AS age
UNION
SELECT 5 AS id , 'paul ' AS name, 6 AS age
UNION
SELECT 6 AS id , 'paul different' AS name, 7 AS age
UNION
SELECT 7 AS id , 'ringo' AS name, 2 AS age
)
So the name "john" has age of 10.
Later on , someone else (not me) also added "john" with age 10.
So I want to clean all duplicates.
But that's not the problem. Before I delete I want to see all duplicates.
So I did this :
SELECT *
FROM (
SELECT ID,
name,
age,
ROW_NUMBER() OVER(PARTITION BY name, age ORDER BY id) AS rn
FROM cte
) a WHERE a.rn>1
ORDER BY
name,
age,
a.rn
Result :
Which basically shows me duplicates. But I want to see also where rn=1 only if there's more version for the current value.
Question
In other words : How can I enhance my query so :
Show all versions for a record ( all row numbers , rn) only if there are versions for this record
Desired result :
ID name age rn
1 john 10 1
2 john 10 2
4 paul 6 1
5 paul 6 2
Sql online - demo
NB I know i can do it with rescanning the table for the same name and age . bUt I thought if there's more elegant way of doing it.
Use exists operator to find the name which are duplicated. Try this.
;WITH cte AS
(
SELECT 1 AS id , 'john' AS name, 10 AS age
UNION
SELECT 2 AS id , 'john' AS name, 10 AS age
UNION
SELECT 3 AS id , 'john' AS name, 12 AS age
UNION
SELECT 4 AS id , 'paul' AS name, 6 AS age
UNION
SELECT 5 AS id , 'paul ' AS name, 6 AS age
UNION
SELECT 6 AS id , 'paul different' AS name, 7 AS age
UNION
SELECT 7 AS id , 'ringo' AS name, 2 AS age
)
, cte1
AS (SELECT ID,
name,
age,
Row_number() OVER(PARTITION BY name, age ORDER BY id) AS rn
FROM cte)
SELECT *
FROM cte1 a
WHERE EXISTS (SELECT 1
FROM cte1 b
WHERE a.name = b.name and a.age=b.age
AND b.rn > 1)
ORDER BY name, age, a.rn
or use Inner Join
SELECT a.id,a.name,a.age
FROM cte1 a
JOIN cte1 b
ON a.name = b.name
AND a.age = b.age
AND b.rn > 1
ORDER BY a.name, a.age, a.rn
Or To do it in single table scan use Dense_Rank plus window function
;WITH cte AS
(
SELECT 1 AS id , 'john' AS name, 10 AS age
UNION
SELECT 2 AS id , 'john' AS name, 10 AS age
UNION
SELECT 3 AS id , 'john' AS name, 12 AS age
UNION
SELECT 4 AS id , 'paul' AS name, 6 AS age
UNION
SELECT 5 AS id , 'paul ' AS name, 6 AS age
UNION
SELECT 6 AS id , 'paul different' AS name, 7 AS age
UNION
SELECT 7 AS id , 'ringo' AS name, 2 AS age
)
, cte1
AS (SELECT ID,
name,
age,
count(age) over (partition by name,age) cnt,
dense_rank() OVER(PARTITION BY name ORDER BY age) AS rn
FROM cte)
SELECT *
FROM cte1
WHERE rn = 1
AND cnt > 1

How to transpose this table in postgresql

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

Resources