Find out if column contains two distinct values for each group - sql

I want to select the rows which satisfy both conditions of the same column. Below is the table schema.
Security Table
Id RoleId CompId SecurityToken Accesstype
1 1 10 abc 2
2 1 10 xyz 2
3 12 10 abc 2
4 16 12 abc 2
5 16 12 xyz 2
6 30 13 abc 2
7 1 10 efg 2
8 1 10 lmn 0
I want "All rows for each RoleID/CompID combination where accesstype = 2 and there is both a row containing securitytoken "abc" and a row containing SecurityToken "xyz" for that role/compID combination"
Output should be
Id RoleId CompId SecurityToken Accesstype
1 1 10 abc 2
2 1 10 xyz 2
4 16 12 abc 2
5 16 12 xyz 2

I believe the following query will produce the desired output:
SELECT *
FROM testdata
WHERE Accesstype = 2
AND SecurityToken IN ('abc', 'xyz')
AND EXISTS (
SELECT 1
FROM testdata AS tmp
WHERE RoleId = testdata.RoleId
AND CompId = testdata.CompId
AND Accesstype = testdata.AccessType
AND SecurityToken IN ('abc', 'xyz')
HAVING COUNT(DISTINCT SecurityToken) = 2
)
SQL Fiddle
In order to eliminate sets that contain extra security tokens (such as efg and lmn) change the WHERE and HAVING clause to:
WHERE RoleId = testdata.RoleId
AND CompId = testdata.CompId
AND Accesstype = testdata.AccessType
HAVING COUNT(DISTINCT SecurityToken) = 2
AND COUNT(DISTINCT SecurityToken) = COUNT(CASE WHEN SecurityToken IN ('abc', 'xyz') THEN 1 END)

CREATE TABLE #Table1
([Id] int, [RoleId] int, [CompId] int, [SecurityToken] varchar(3), [Accesstype] int)
;
INSERT INTO #Table1
([Id], [RoleId], [CompId], [SecurityToken], [Accesstype])
VALUES
(1, 1, 10, 'abc', 2),
(2, 1, 10, 'xyz', 2),
(3, 12, 10, 'abc', 2),
(4, 16, 12, 'abc', 2),
(5, 16, 12, 'xyz', 2),
(6, 30, 13, 'abc', 2)
;
WITH cte AS (
SELECT *,ROW_NUMBER() OVER (PARTITION BY [ROLEID],[COMPID] ORDER BY ID) AS RN FROM #TABLE1
),
COUNTED AS (
SELECT
*,
COUNT(*) OVER (PARTITION BY [ROLEID],[COMPID]) AS CNT
FROM cte
)
SELECT
[ID], [ROLEID], [COMPID], [SECURITYTOKEN], [ACCESSTYPE]
FROM COUNTED
WHERE CNT >= 2
output
ID ROLEID COMPID SECURITYTOKEN ACCESSTYPE
1 1 10 abc 2
2 1 10 xyz 2
4 16 12 abc 2
5 16 12 xyz 2
or
WITH CTE AS
(
SELECT
*,
COUNT(*) OVER (PARTITION BY [ROLEID],[COMPID]) AS CNT
FROM #TABLE1)
SELECT [ID], [ROLEID], [COMPID], [SECURITYTOKEN], [ACCESSTYPE] FROM CTE WHERE CNT>=2

One method uses exists:
select t.*
from t
where t.Accesstype = 2 and
t.securityToken in ('abc', 'xyz') and
exists (select 1
from t t2
where t2.RoleId = t.RoleId and
t2.CompId = t.CompId and
t2.Accesstype = t.AccessType and
t2.SecurityToken in ('abc', 'xyz') and
t2.SecrityToken <> t.SecurityToken
);
Perhaps a simpler method uses window functions:
select t.*
from (select t.*,
min(securitytoken) over (partition by roleid, compid) as min_st,
min(securitytoken) over (partition by roleid, compid) as max_st
from t
where t.Accesstype = 2 and
t.SecurityToken in ('abc', 'xyz')
) t
where minsecuritytoken = 'abc' and
maxsecuritytoken = 'xyz;

Related

Match rows that include one of each at least once in SQL

I have a users table:
ID Name OID TypeID
1 a 1 1
2 b 1 2
3 c 1 3
4 d 2 1
5 e 2 1
6 f 2 2
7 g 3 2
8 h 3 2
9 i 3 2
for this table, I want to filter by OID and TypeID so that I get the rows that it is filtered by OID and that includes all 1, 2, and 3 in TypeID.
For example, where OID=1, we have 1, 2, and 3 in TypeID but I shouldn't get the rows with IDs 4-6 because for IDs 4-6, OIDs are the same but TypeID does not include all of each(1, 2, and 3).
You can do :
select oid
from table t
where typeid in (1,2,3)
group by oid
having count(*) = 3;
If, oid contain duplicate typeid then you can use count(distinct typeid) instead.
you could use exists
select oid from table t1
where exists ( select 1 from table t1 where t1.oid=t2.oid
group by t2.oid
having (distinct TypeID)=3
)
Asume TypeID 1,2,3
if you are using sql-server, you can try this.
DECLARE #SampleData TABLE(ID INT, Name VARCHAR(5), OID INT, TypeID INT)
INSERT INTO #SampleData VALUES
(1 , 'a', 1, 1),
(2 , 'b', 1, 2),
(3 , 'c', 1, 3),
(4 , 'd', 2, 1),
(5 , 'e', 2, 1),
(6 , 'f', 2, 2),
(7 , 'g', 3, 2),
(8 , 'h', 3, 2),
(9 , 'i', 3, 2)
SELECT * FROM #SampleData D
WHERE NOT EXISTS (
SELECT * FROM #SampleData D1
RIGHT JOIN (VALUES (1),(2),(3)) T(TypeID) ON D1.TypeID = T.TypeID
AND D.OID = D1.OID
WHERE D1.TypeID IS NULL
)
Result:
ID Name OID TypeID
----------- ----- ----------- -----------
1 a 1 1
2 b 1 2
3 c 1 3

T-SQL results in to columns

I have a table (t1) like below
Id Name RelId
1 a 2
2 b 3
3 c 4
4 d 3
5 e 6
The other table (t2)
Id data FK Order
1 aa 2 2
2 bb 2 3
3 cc 2 1
4 dd 2 4
5 ee 2 5
6 ff 3 3
7 gg 3 2
8 hh 3 1
9 ii 4 7
10 jj 4 4
11 kk 4 1
12 ll 4 3
13 mm 6 1
14 nn 6 2
15 oo 6 3
16 pp 6 4
My output result am looking for is
+----+------+-------+-------+------+----------+
| id | name | RelId | Col 1 | Col2 | Col-Oth |
+----+------+-------+-------+------+----------+
| 1 | a | 2 | cc | aa | bb,dd,ee |
| 2 | b | 3 | hh | gg | ff |
| 3 | c | 4 | kk | ll | jj,ii |
| 4 | d | 3 | hh | gg | ff |
| 5 | e | 6 | mm | nn | oo,pp |
+----+------+-------+-------+------+----------+
based on the Relid in T1 table join with FK column in T2 and populate col1 with the least order data, col2 with the next higher order data and col-oth with remaining data comma separated ordered.
Need your help on same.
SELECT id,name,RelId, (select data,rownumber() (partition by data order by order asc) from t2 inner join t1 on t1.relid= t2.FK) from t1
Try following query:
DECLARE #TEMP TABLE
(
Id INT,
Name VARCHAR(10),
RelId INT
)
INSERT INTO #TEMP VALUES (1,'a',2),(2,'b',3),(3,'c',4),(4,'d',3),(5,'e',6)
DECLARE #TEMP1 TABLE
(
Id INT,
Data varchar(10),
FK INT,
[order] INT
)
INSERT INTO #TEMP1 VALUES
(1 ,'aa',2,2),(2 ,'bb',2,3),(3 ,'cc',2,1),(4 ,'dd',2,4),(5 ,'ee',2,5),
(6 ,'ff',3,3),(7 ,'gg',3,2),(8 ,'hh',3,1),(9 ,'ii',4,7),(10,'jj',4,4),
(11,'kk',4,1),(12,'ll',4,3),(13,'mm',6,1),(14,'nn',6,2),(15,'oo',6,3),(16,'pp',6,4)
SELECT
t1.*,
(SELECT Data FROM (SELECT ROW_NUMBER() OVER(ORDER BY t2.[order]) As RowNo,Data FROM #TEMP1 t2 WHERE t2.FK = t1.RelId)t3 WHERE t3.RowNo=1),
(SELECT Data FROM (SELECT ROW_NUMBER() OVER(ORDER BY t2.[order]) As RowNo,Data FROM #TEMP1 t2 WHERE t2.FK = t1.RelId)t3 WHERE t3.RowNo=2),
STUFF((SELECT DISTINCT ',' + Data FROM (SELECT ROW_NUMBER() OVER(ORDER BY t2.[order]) As RowNo,Data FROM #TEMP1 t2 WHERE t2.FK = t1.RelId)t3 WHERE t3.RowNo > 2 FOR XML PATH ('')), 1, 1, '')
FROM
#TEMP t1
Using PIVOT:
DECLARE #t1 TABLE
(
ID INT ,
Name CHAR(1) ,
RelID INT
)
DECLARE #t2 TABLE
(
ID INT ,
Data CHAR(2) ,
RelID INT ,
Ordering INT
)
INSERT INTO #t1
VALUES ( 1, 'a', 2 ),
( 2, 'b', 3 ),
( 3, 'c', 4 ),
( 4, 'd', 3 ),
( 5, 'e', 6 )
INSERT INTO #t2
VALUES ( 1, 'aa', 2, 2 ),
( 2, 'bb', 2, 3 ),
( 3, 'cc', 2, 1 ),
( 4, 'dd', 2, 4 ),
( 5, 'ee', 2, 5 ),
( 6, 'ff', 3, 3 ),
( 7, 'gg', 3, 2 ),
( 8, 'hh', 3, 1 ),
( 9, 'ii', 4, 7 ),
( 10, 'jj', 4, 4 ),
( 11, 'kk', 4, 1 ),
( 12, 'll', 4, 3 ),
( 13, 'mm', 6, 1 ),
( 14, 'nn', 6, 2 ),
( 15, 'oo', 6, 3 ),
( 16, 'pp', 6, 4 );
WITH cte1
AS ( SELECT t1.ID ,
t1.Name ,
t1.RelID ,
t2.Data ,
ROW_NUMBER() OVER ( PARTITION BY t1.ID ORDER BY t2.Ordering ) AS rn
FROM #t1 t1
JOIN #t2 t2 ON t1.RelID = t2.RelID
),
cte2
AS ( SELECT ID ,
Name ,
RelID ,
Data ,
rn ,
STUFF(( SELECT ',' + Data
FROM cte1 ci
WHERE co.ID = ci.ID
AND rn > 2
FOR
XML PATH('')
), 1, 1, '') AS Col3
FROM cte1 co
)
SELECT ID ,
Name ,
RelID ,
[1] AS Col1 ,
[2] AS Col2 ,
Col3
FROM cte2 PIVOT( MAX(data) FOR rn IN ( [1], [2] ) ) p
Output:
ID Name RelID Col1 Col2 Col3
1 a 2 cc aa bb,dd,ee
2 b 3 hh gg ff
3 c 4 kk ll jj,ii
4 d 3 hh gg ff
5 e 6 mm nn oo,pp
Execution plan of my statement
Execution plan of accepted statement:
Which is better? :)

SQL : how to find leaf rows?

i have a self related table myTable like :
ID | RefID
----------
1 | NULL
2 | 1
3 | 2
4 | NULL
5 | 2
6 | 5
7 | 5
8 | NULL
9 | 7
i need to get leaf rows on any depth
based on the table above, the result must be :
ID | RefID
----------
3 | 2
4 | NULL
6 | 5
8 | NULL
9 | 7
thank you
PS: the depth may vary , here is very small example
Try:
SELECT id,
refid
FROM mytable t
WHERE NOT EXISTS (SELECT 1
FROM mytable
WHERE refid = t.id)
DECLARE #t TABLE (id int NOT NULL, RefID int NULL);
INSERT #t VALUES (1, NULL), (2, 1), (3, 2), (5, NULL),
(6, 5), (4, NULL), (7, 5), (8, NULL), (9, 8), (10, 7);
WITH CTE AS
(
-- top level
SELECT id, RefID, id AS RootId, 0 AS CTELevel FROM #t WHERE REfID IS NULL
UNION ALL
SELECT T.id, T.RefID, RootId, CTELevel + 1 FROM #t T JOIN CTE ON T.RefID = CTE.id
), Leafs AS
(
SELECT
id, RefID, DENSE_RANK() OVER (PARTITION BY CTE.RootId ORDER BY CTELevel DESC) AS Rn
FROM CTE
)
SELECT
id, RefID
FROM
Leafs
WHERE
rn = 1
select ID, RefId
from myTable t1 left join myTable t2 on t1.ID = t2.RefID
where t2.RefID is null
try this:
SELECT *
FROM
my_table
WHERE
id NOT IN
(
SELECT DISTINCT
refId
FROM
my_table
WHERE
refId IS NOT NULL
)

Aggregating several columns to single column

I have a table that looks like
id cat data
--------------------
1 1 foo
2 1 bar
3 1 baz
4 2 some
5 2 random
6 3 Data 1
7 2 data
8 3 Data 2
9 3 Data 3
And I want the last 3 ids and data of each category in a single row like
cat id1 data1 id2 data2 id3 data3
-----------------------------------------------------
1 1 foo 2 bar 3 baz
2 4 some 5 random 7 data
3 6 Data 1 8 Data 2 9 Data 3
I already tried the following:
Get the data with the highest id for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat
GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
Get the data with the 2nd highest ids for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat AND NOT EXISTS (
-- Not the highest value
SELECT 1 FROM tbl t3 WHERE t1.cat = t3.cat GROUP BY t3.cat
HAVING MAX(t3.id) = t2.id
) GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
Get the data with the 3rd highest id for each cat:
SELECT id, data FROM tbl t1 WHERE EXISTS (
SELECT 1 FROM tbl t2 WHERE t1.cat = t2.cat AND NOT EXISTS (
-- id is not 2nd highest
SELECT 1 FROM tbl t3 WHERE t1.cat = t3.cat AND NOT EXISTS (
-- id is not the highest
SELECT 1 FROM tbl t4 WHERE t1.cat = t4.cat GROUP BY t4.cat
HAVING MAX(t4.id) = t3.id
) GROUP BY t3.cat HAVING MAX(t3.id) = t2.id
) AND NOT EXIST (
-- not the highest id
SELECT 1 FROM tbl t5 WHERE t1.cat = t5.cat GROUP BY t5.cat
HAVING MAX(t5.id) = t2.id
) GROUP BY t2.cat HAVING MAX(t2.id) = t1.id
)
And now, joining the entire thing. But I believe that there exists a better solution. What is it?
PS: I have to do it with Informix
Not my answer, a coworker of mine came up with this:
create temp table t(
id smallint,
cat smallint,
data char(10)
) with no log;
insert into t values (1, 1, "foo");
insert into t values (2, 1, "bar");
insert into t values (3, 1, "baz");
insert into t values (4, 2, "some");
insert into t values (5, 2, "random");
insert into t values (6, 3, "Data 1");
insert into t values (7, 2, "data");
insert into t values (8, 3, "Data 2");
insert into t values (9, 3, "Data 3");
insert into t values (10, 4, "some");
insert into t values (11, 4, "more");
insert into t values (12, 4, "random");
insert into t values (13, 4, "data");
insert into t values (14, 4, "for");
insert into t values (15, 4, "testing");
insert into t values (16, 5, "one");
select
cat,
max(case when cnt = 3 then id end) as id1,
max(case when cnt = 2 then id end) as id2,
max(case when cnt = 1 then id end) as id3,
max(case when cnt = 3 then data end) as data1,
max(case when cnt = 2 then data end) as data2,
max(case when cnt = 1 then data end) as data3
from
(
select
a.cat,
a.id,
a.data,
count(*) as cnt
from
t a,
t b
where
a.cat = b.cat and
a.id <= b.id
group by
a.id,
a.cat,
a.data
having
count(*) <= 3
)
group by
1
order by
1;
cat id1 id2 id3 data1 data2 data3
1 1 2 3 foo bar baz
2 4 5 7 some random data
3 6 8 9 Data 1 Data 2 Data 3
4 13 14 15 data for testing
5 16 one
If you using Informix 11.50 or above , there is an option where isn't perfect, but maybe can help. Check the select at end bellow.
They will return a multiset datatype with char() data type... where probably will create a difficult to read it, depending of the program language are you using.
Thanks to Fernando Nunes who suggest this SQL into IIUG forum
At this moment I don't see other alternative besides complex SQLs.
drop table teste;
create temp table teste ( id smallint, cat smallint, data char(10));
insert into teste values ( 1, 1, 'foo ' );
insert into teste values ( 2, 1, 'bar ' );
insert into teste values ( 3, 1, 'baz ' );
insert into teste values ( 4, 2, 'some ' );
insert into teste values ( 5, 2, 'random ' );
insert into teste values ( 6, 3, 'Data 1 ' );
insert into teste values ( 7, 2, 'data ' );
insert into teste values ( 8, 3, 'Data 2 ' );
insert into teste values ( 9, 3, 'Data 3 ' );
insert into teste values ( 10, 3, 'Data 4 ' );
select * from teste;
select ms.*
from
(
SELECT MULTISET( SELECT ITEM t.id || ',' || t.cat || ',' || t.data m1 FROM
teste t WHERE t.cat = tout.cat) FROM (SELECT unique cat from teste) tout
) msdrop table teste;
will return :
expression MULTISET{'1,1,foo ','2,1,bar ','3,1,baz '}
expression MULTISET{'4,2,some ','5,2,random ','7,2,data '}
expression MULTISET{'6,3,Data 1 ','8,3,Data 2 ','9,3,Data 3 '}

Consolidating subsets in a table

I have a table in SqlServer 2008 with data of the form
UserID StartWeek EndWeek Type
1 1 3 A
1 4 5 A
1 6 10 A
1 11 13 B
1 14 16 A
2 1 5 A
2 6 9 A
2 10 16 B
I'd like to consolidate/condense the adjacent types so that the resulting table looks like this.
UserID StartWeek EndWeek Type
1 1 10 A
1 11 13 B
1 14 16 A
2 1 9 A
2 10 16 B
Does anyone have any suggestions as to the best way to accomplish this? I've been looking at using Row_number and Partition, but I can't get it to behave exactly as I'd like.
There's probably a neater way to do it, but this produces the correct result
DECLARE #t TABLE
(UserId TINYINT
,StartWeek TINYINT
,EndWeek TINYINT
,TYPE CHAR(1)
)
INSERT #t
SELECT 1,1,3,'A'
UNION SELECT 1,4,5,'A'
UNION SELECT 1,6,10,'A'
UNION SELECT 1,11,13,'B'
UNION SELECT 1,14,16,'A'
UNION SELECT 2,1,5,'A'
UNION SELECT 2,6,9,'A'
UNION SELECT 2,10,16,'B'
;WITH srcCTE
AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY t1.UserID, t1.Type
ORDER BY t1.EndWeek
) AS rn
FROM #t AS t1
)
,recCTE
AS
(
SELECT *
,0 AS grp
FROM srcCTE
WHERE rn = 1
UNION ALL
SELECT s.UserId
,s.StartWeek
,s.EndWeek
,s.TYPE
,s.rn
,CASE WHEN s.StartWeek - 1 = r.EndWeek
THEN r.grp
ELSE r.grp+ 1
END AS GRP
FROM srcCTE AS s
JOIN recCTE AS r
ON r.UserId = s.UserId
AND r.TYPE = s.TYPE
AND r.rn = s.rn - 1
)
SELECT UserId
,MIN(StartWeek) AS StartWeek
,MAX(EndWeek) AS EndWeek
,TYPE
FROM recCTE AS s1
GROUP BY UserId
,TYPE
,grp
Also using a CTE, but in a slightly different way
DECLARE #Consolidate TABLE (
UserID INTEGER, StartWeek INTEGER,
EndWeek INTEGER, Type CHAR(1))
INSERT INTO #Consolidate VALUES (1, 1, 3, 'A')
INSERT INTO #Consolidate VALUES (1, 4, 5, 'A')
INSERT INTO #Consolidate VALUES (1, 6, 10, 'A')
INSERT INTO #Consolidate VALUES (1, 14, 16, 'A')
INSERT INTO #Consolidate VALUES (1, 11, 13, 'B')
INSERT INTO #Consolidate VALUES (2, 1, 5, 'A')
INSERT INTO #Consolidate VALUES (2, 6, 9, 'A')
INSERT INTO #Consolidate VALUES (2, 10, 16, 'B')
;WITH ConsolidateCTE AS
(
SELECT UserID, StartWeek, EndWeek, Type
FROM #Consolidate
UNION ALL
SELECT cte.UserID, cte.StartWeek, c.EndWeek, c.Type
FROM ConsolidateCTE cte
INNER JOIN #Consolidate c ON
c.UserID = cte.UserID
AND c.StartWeek = cte.EndWeek + 1
AND c.Type = cte.Type
)
SELECT UserID, [StartWeek] = MIN(Startweek), EndWeek, Type
FROM (
SELECT UserID, Startweek, [EndWeek] = MAX(EndWeek), Type
FROM ConsolidateCTE
GROUP BY UserID, StartWeek, Type
) c
GROUP BY UserID, EndWeek, Type
ORDER BY 1, 2, 3