I have a table structure like this (there are actually more levels):
------------------------------------------
|region1|region2|region3|region4|postcode|
|-------|-------|-------|-------|--------|
|a |x |i | |1 |
|a |y |i | |2 |
|a |y |j | |2 |
|a |z |k | |3 |
|b |u |m | |4 |
|b | |n | |4 |
|c | | | |5 |
|c |q | | |6 |
------------------------------------------
So for example, a => x => i and a => y => i are different places but both are in the same region1 a.
I want to know which region each postcode can cover.
For example, code 2 covers areas a => y => i and a => y => j, so the common ancestor for those are a => y.
Here is the desired output of the query run on the example:
------------------------------------------
|postcode|region1|region2|region3|region4|
|--------|-------|-------|-------|-------|
|1 |a |x |i | |
|2 |a |y | | |
|3 |a |z |k | |
|4 |b | | | |
|5 |c | | | |
|6 |c |q | | |
------------------------------------------
I don't really know how to attack this problem. I thought about partitioning by the postcode, but that still leaves the problem of finding the common ancestor within each partition...
This is a really messy solution, but it does seem to give the correct answer. It would no doubt need quite a bit of work to fit your actual requirement, but maybe this could help point you in some sort of direction!
-- Setup a test table
DECLARE #tbl AS TABLE(R1 NVARCHAR(10), R2 NVARCHAR(10), R3 NVARCHAR(10), R4 NVARCHAR(10), PC NVARCHAR(10));
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('a','x','i',NULL,'1');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('a','y','i',NULL,'2');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('a','y','j',NULL,'2');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('a','z','k',NULL,'3');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('b','u','m',NULL,'4');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('b',NULL,'n',NULL,'4');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('c',NULL,NULL,NULL,'5');
INSERT INTO #tbl(R1,R2,R3,R4,PC) VALUES ('c','q',NULL,NULL,'6');
-- Calculate the result:
SELECT
PC,
CASE WHEN LVL1 = 1 THEN R1 ELSE NULL END AS R1,
CASE WHEN LVL2 = 1 THEN R2 ELSE NULL END AS R2,
CASE WHEN LVL3 = 1 THEN R3 ELSE NULL END AS R3,
CASE WHEN LVL4 = 1 THEN R4 ELSE NULL END AS R4
FROM
(
SELECT
PC,
MAX(R1) AS R1,
MAX(R2) AS R2,
MAX(R3) AS R3,
MAX(R4) AS R4,
COUNT(DISTINCT ISNULL(R1,'.')) AS LVL1,
COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.')) AS LVL2,
COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.') + ISNULL(R3,'.')) AS LVL3,
COUNT(DISTINCT ISNULL(R1,'.') + ISNULL(R2,'.') + ISNULL(R3,'.') + ISNULL(R4,'.')) AS LVL4
FROM #tbl
GROUP BY PC
) A
The end result matches the table in the question.
This question rather intrigued me and I came up with an alternative, which you might find useful:
-- Setup test table
DECLARE #InputTable TABLE (region1 varchar(2), region2 varchar(2), region3 varchar(2), region4 varchar(2), postcode varchar(2))
INSERT INTO #InputTable (region1, region2, region3, region4, postcode)
SELECT 'a','x','i',null,'1'
UNION ALL SELECT 'a','y','i',NULL,'2'
UNION ALL SELECT 'a','y','j',NULL,'2'
UNION ALL SELECT 'a','z','k',NULL,'3'
UNION ALL SELECT 'b','u','m',NULL,'4'
UNION ALL SELECT 'b',NULL,'n',NULL,'4'
UNION ALL SELECT 'c',NULL,NULL,NULL,'5'
UNION ALL SELECT 'c','q',NULL,NULL,'6'
-- Find the common ancestors
;with totals as (
select postcode, count(*) as postcodeCount from #InputTable group by postcode
)
, region4group as (
select postcode, region1, region2, region3, region4 from #InputTable in1
group by postcode, region1, region2, region3, region4 having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, region3group as (
select * from region4group
union
select in1.postcode, in1.region1, in1.region2, in1.region3, null from #InputTable in1
left outer join region4group on region4group.postcode=in1.postcode
where region4group.postcode is null
group by in1.postcode, in1.region1, in1.region2, in1.region3
having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, region2group as (
select * from region3group
union
select in1.postcode, in1.region1, in1.region2, null, null from #InputTable in1
left outer join region3group on region3group.postcode=in1.postcode
where region3group.postcode is null
group by in1.postcode, in1.region1, in1.region2
having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
, commonancestors as (
select * from region2group
union
select in1.postcode, in1.region1, null, null, null from #InputTable in1
left outer join region2group on region2group.postcode=in1.postcode
where region2group.postcode is null
group by in1.postcode, in1.region1
having count(*)=(select postCodeCount from totals where totals.postcode=in1.postcode)
)
select * from commonancestors
Related
I have this table:
Id |Name |ParentId
1 |John |Null
2 |Oscar |1
3 |Peter |2
4 |Abbey |3
5 |Adrian |4
6 |Barbara |5
and i want to make a select that will give me a new column that gets the previous Name with by the parentId to make a listName (Order by ParentID).
the final result in this example would be this:
Id |Name |ParentId | List
1 |John |Null | John
2 |Oscar |1 | John-Oscar
3 |Peter |2 | John-Oscar-Peter
4 |Abbey |3 | John-Oscar-Peter-Abbey
5 |Adrian |4 | John-Oscar-Peter-Abbey-Adrian
6 |Barbara |5 | John-Oscar-Peter-Abbey-Adrian-Barbara
Thnks for all the help!
You can use a recursive CTE to produce the desired result:
declare #t table (Id int, Name varchar(20), ParentId int)
insert #t values
( 1 ,'John' ,Null ),
( 2 ,'Oscar' ,1 ),
( 3 ,'Peter' ,2 ),
( 4 ,'Abbey' ,3 ),
( 5 ,'Adrian' ,4 ),
( 6 ,'Barbara' ,5 )
;with x as (
select *, cast(name as varchar(1000)) as list from #t where parentid is null
union all
select t.id, t.name, t.parentid, cast(x.list+'-'+t.name as varchar(1000)) from #t t join x on t.parentid = x.id
)
select * from x
This also works for multiple roots, of course.
This is same as concatenate columns to rows
select id,name,pid,
stuff((select '-'+name from yourtable n2 where n2.id<=n1.id for xml path('')),1,1,'') b
from yourtable n1
I have a parent child relation table as shown below:
ContractID ContractIdRef
---------- -------------
1 null
2 1
3 1
4 2
5 4
10 null
11 10
12 11
15 null
16 12
I want result like below:
ContractID ContractIdRef rw
----------- -------------- ---
1 null 1
2 1 1
3 1 1
4 2 1
5 4 1
10 null 10
11 10 10
12 11 10
15 null 15
16 12 10
In above result I want to specify each rows parent.
Thanks
As you mentioned in the TAGS Comman Table Expression is the way to go
;WITH REC_CTE
AS (SELECT [contractid],
[ContractIdRef],
[contractid] AS rw
FROM Yourtable
WHERE [contractidref] IS NULL
UNION ALL
SELECT T.[contractid],
T.[contractidref],
c.rw
FROM Yourtable AS T
INNER JOIN REC_CTE C
ON T.[contractidref] = c.[contractid]
WHERE T.[contractid] <> T.[contractidref])
SELECT [contractid],
[contractidref],
rw
FROM REC_CTE
ORDER BY [contractid]
Demo
Schema Setup
If object_id('tempdb.dbo.#Yourtable') is not null
DROP table #Yourtable
CREATE TABLE #Yourtable
([ContractID] INT, [ContractIdRef] INT);
Sample data
INSERT INTO #Yourtable
([ContractID], [ContractIdRef])
VALUES
('1', NULL),
('2', '1'),
('3', '1'),
('4', '2'),
('5', '4'),
('10', NULL),
('11', '10'),
('12', '11'),
('15', NULL),
('16', '12');
Query
;WITH REC_CTE
AS (SELECT [ContractID],
[ContractIdRef] as [ContractIdRef],
[ContractID] AS rw
FROM #Yourtable where [ContractIdRef] is null
UNION ALL
SELECT T.[ContractID],
T.[ContractIdRef],
c.rw
FROM #Yourtable AS T
INNER JOIN REC_CTE c
ON T.[ContractIdRef] = c.[ContractID]
WHERE T.[ContractID] <> T.[ContractIdRef])
SELECT [ContractID],
[ContractIdRef],
rw
FROM REC_CTE
ORDER BY [ContractID]
Result
+-----------+-------------+----+
|ContractID |ContractIdRef| rw |
+-----------+-------------+----+
|1 |NULL | 1 |
|2 |1 | 1 |
|3 |1 | 1 |
|4 |2 | 1 |
|5 |4 | 1 |
|10 |NULL | 10 |
|11 |10 | 10 |
|12 |11 | 10 |
|15 |NULL | 15 |
|16 |12 | 10 |
+-----------+-------------+----+
with Q as(
select ContractID, ContractIdRef, ContractID as root
from childs
where ContractIdRef is null
union all
select C.ContractID, C.ContractIdRef, Q.root
from Q, childs C
where C.ContractIdRef=Q.ContractID
)
select * from Q
order by ContractID
Tested on MS SQL 2014.
For Postgresql need add word 'recursive' after 'with'. Test on sqlfiddle.com
For Oracle first line writed as with Q(ContractID,ContractIdRef,root).
I have a table on ms access which has 13 columns.I want to group by column Name then check the latest one by comparing column id and take the record if the latest row has value if not take the previous record. the comparison will be done for each columns.
+-----+-----+-------+-------+-------+
| id |Name |colum1 |colum2 |colum3 |
+-----+-----+-------+-------+-------+
| 1 |a |x | |x |
+-----+-----+-------+-------+-------+
| 2 |b | |y |y |
+-----+-----+-------+-------+-------+
| 3 |a |z |z | |
+-----+-----+-------+-------+-------+
| 4 |a |m | | |
+-----+-----+-------+-------+-------+
Expected output
+-----+-----+-------+-------+-------+
| id |Name |colum1 |colum2 |colum3 |
+-----+-----+-------+-------+-------+
| 2 |b | |y |y |
+-----+-----+-------+-------+-------+
| 4 |a |m |z |x |
+-----+-----+-------+-------+-------+
You can do Self Join.
SELECT T1.*
FROM
table_name T1
INNER JOIN
(SELECT `Name`,MAX(`id`) AS ID FROM table_name GROUP BY `Name` ) T2
ON T1.`id`= T2.`ID` AND T1.`Name` = T2.`Name`
Hope this helps.
I'm not sure if it will work in MS Access. It works in SQL Server. Even if it does, it will be very slow.
SELECT
Groups.Name
,(
SELECT TOP(1) T.colum1
FROM T
WHERE T.Name = Groups.Name AND T.colum1 <> ''
ORDER BY T.ID DESC
) AS C1
,(
SELECT TOP(1) T.colum2
FROM T
WHERE T.Name = Groups.Name AND T.colum2 <> ''
ORDER BY T.ID DESC
) AS C2
,(
SELECT TOP(1) T.colum3
FROM T
WHERE T.Name = Groups.Name AND T.colum3 <> ''
ORDER BY T.ID DESC
) AS C3
FROM
(
SELECT Name
FROM T
GROUP BY Name
) AS Groups
Hi I have the following requirement
In Table A
CRD | RNo | J_NAME
-------------------------
DOS1 |1 | NULL
DOS2 |2 | Name 1
DOS3 |3 | Name 2
DOS4 |4 | Name 3
DOS5 |5 | Name 1
DOS6 |6 | Name 1
DOS7 |7 | Name 4
DOS8 |8 | Name 2
Out put should be
CRD | RNo | J_NAME
-------------------------
DOS1 |1 | NULL
DOS2 |2 | A
DOS3 |3 | B
DOS4 |4 | C
DOS5 |5 | A
DOS6 |6 | A
DOS7 |7 | D
DOS8 |8 | B
Null allays should be null, If the name already exist in the target table then It will be add the same name eg: J_Name = A and B, if the source value is not in the target table then it will get a new entry from the list.
Ho I can achieve this?
You can try somthing like this:-
SELECT CRD, RNo,
CASE WHEN J_NAME ='Name 1' THEN 'A'
WHEN J_NAME ='Name 2' THEN 'B'
WHEN J_NAME ='Name 3' THEN 'C'
WHEN J_NAME ='Name 4' THEN 'D'
ELSE 'NULL' END AS J_NAME
FROM TAB;
If you're sure you won't run out of letters (since you're not telling how to generate values beyond Z), you could just dense_rank your names and give them a letter corresponding to the rank;
WITH cte AS (
SELECT * FROM mytable UNION ALL SELECT '', 0, NULL FROM DUAL
)
SELECT crd, rno, CASE WHEN j_name IS NULL THEN NULL ELSE CHR(calc+63) END j_name
FROM (
SELECT crd, rno, j_name, DENSE_RANK() OVER (ORDER BY j_name NULLS FIRST) calc
FROM cte
)
WHERE rno > 0 ORDER BY rno;
It basically takes the table contents, adds a row with a null J_NAME value to make sure there's a rank for NULL, and uses DENSE_RANK() on the resulting j_names to get a value to generate the letter from.
An SQLfiddle to test with.
First I will show You architecture of tables.
Table "public.questionare"
Column | Type |
--------------------+-----------------------+
id | integer |
Table "public.questionareacceptance"
Column | Type |
-------------------------+-----------------------+
id | integer |
questionare_id | integer |
accept_id | integer |
impact_id | integer |
Table questionareacceptance contains:
id | questionare_id | accept_id| impact_id |
----+----------------+----------+------------------+
1 |1 |1 | |
2 |1 |1 | 1 |
3 |1 |1 | 1 |
4 |2 | | 1 |
5 |3 |1 | 1 |
6 |4 |1 | 1 |
7 |4 |1 | 1 |
What I am trying to get is a list of questionare ID where in each questionareacceptance fields accept_id and impact_id are not NULL
My query looks like:
SELECT q.id AS quest,
qa.id AS accepted
FROM questionare q,
questionareacceptance qa
WHERE q.id = qa.questionare_id
AND qa.accept_id IS NOT NULL
AND qa.impact_id IS NOT NULL;
But the result is as fallows:
quest | accepted |
--------------------+-----------------------+
1 |1 |
1 |2 |
1 |3 |
2 |4 |
3 |5 |
4 |6 |
4 |7 |
But the result that should be returned are only 3 and 4 others have impact_id or accept_id null.
Can anyone point me where I am doing the mistake?
your query could be written with not exists:
select
q.id as quest, qa.id as accepted
from questionare as q
inner join questionareacceptance as qa on qa.questionare_id = q.id
where
not exists (
select *
from questionareacceptance as tqa
where
tqa.questionare_id = q.id and
(tqa.accept_id is null or tqa.impact_id is null)
)
but I think faster one would using window functions:
with cte as (
select
q.id as quest, qa.id as accepted,
sum(case when qa.accept_id is not null and qa.impact_id is not null then 1 else 0 end) over(partition by q.id) as cnt1,
count(*) over(partition by q.id) as cnt2
from questionare as q
inner join questionareacceptance as qa on qa.questionare_id = q.id
)
select quest, accepted
from cte
where cnt1 = cnt2
actually looks like you don't need join at all:
with cte as (
select
qa.questionare_id as quest, qa.id as accepted,
sum(case when qa.accept_id is not null and qa.impact_id is not null then 1 else 0 end) over(partition by qa.questionare_id) as cnt1,
count(*) over(partition by qa.questionare_id) as cnt2
from questionareacceptance as qa
)
select quest, accepted
from cte
where cnt1 = cnt2;
sql fiddle demo