select where a and next is c, skipping b - sql

I am wondering if it is possible in SQL to return a single row to show, using the table below as an example, only a row for id 2:
table1 ( id 2 and 4 are missing value b)
id value
1 a
1 b
1 c
1 d
2 a
2 c
2 d
3 a
3 b
3 c
3 d
4 a
4 c
4 d
i basically want to find all instances where 'b' does not exist but 'a' still does exist for any id and return a single row for that any given id. i have tried something like this, but its not working as i would want it to:
select * from table1
where not exists (select distinct value from table1 where value b)
i would like the end result to be something this, identifying the values where 'b' does not exist but 'a' does(not showing the value, is unneeded for final goal):
result table
id
2
4

SELECT id
FROM table1 t1
WHERE
value = 'a'
AND NOT EXISTS (
SELECT *
FROM table1 sub
WHERE sub.id = t1.id AND sub.value = 'b'
)

This should do the job:
select distinct id
from table1 t
where not exists (
select 1
from table1 tt
where t.id = tt.id and tt.vallue = 'b'
)
and exists (
select 1
from table1 tt
where t.id = tt.id and tt.vallue = 'a'
)
Below you have shorter form. It may perform better and distinct keyword may be unnecessary if the pair (id, value) is unique.
select distinct id
from table1 t
left join table1 tt
on t.id = tt.id and tt.value = 'b'
where t.value = 'a'
and tt.id is null

Haven't tested, but I think something like this would work.
SELECT id FROM table1
WHERE value='a' AND id NOT IN(SELECT id FROM table1 WHERE value='b')
GROUP BY id;

EDIT: Apologies to Dooh. I just noticed that this answer is essentially a duplicate of Dooh's second query. I'll leave it as a runnable example.
It may be enlightening to compare execution plans for the various queries.
declare #table1 as table ( id int, value varchar(10) )
insert into #table1 ( id, value ) values
( 1, 'a' ), ( 1, 'b' ), ( 1, 'c' ), ( 1, 'd' ),
( 2, 'a' ), ( 2, 'c' ), ( 2, 'd' ),
( 3, 'a' ), ( 3, 'b' ), ( 3, 'c' ), ( 3, 'd' ),
( 4, 'a' ), ( 4, 'c' ), ( 4, 'd' ),
( 5, 'a' ), ( 5, 'a' ), ( 5, 'b' ), -- Duplicate 'a's.
( 6, 'a' ), ( 6, 'a' ) -- Duplicate 'a's.
select distinct L.id
from #table1 as L left outer join
#table1 as R on R.id = L.id and R.value = 'b'
where R.id is NULL and L.value = 'a'

Related

Oracle SQL - Count based on a condition to include distinct rows with zero matches

Is there a "better" way to refactor the query below that returns the number occurrences of a particular value (e.g. 'A') for each distinct id? The challenge seems to be keeping id = 2 in the result set even though the count is zero (id = 2 is never related to 'A'). It has a common table expression, NVL function, in-line view, distinct, and left join. Is all of that really needed to get this job done? (Oracle 19c)
create table T (id, val) as
select 1, 'A' from dual
union all select 1, 'B' from dual
union all select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 2, 'B' from dual
union all select 3, 'A' from dual
;
with C as (select id, val, count(*) cnt from T where val = 'A' group by id, val)
select D.id, nvl(C.cnt, 0) cnt_with_zero from (select distinct id from T) D left join C on D.id = C.id
order by id
;
ID CNT_WITH_ZERO
---------- -------------
1 2
2 0
3 1
A simple way is conditional aggregation:
select id,
sum(case when val = 'A' then 1 else 0 end) as num_As
from t
group by id;
If you have another table with one row per id, you I would recommend:
select i.id,
(select count(*) from t where t.id = i.id and t.val = 'A') as num_As
from ids i;

How to compare column in one table with array from another table in BigQuery?

Just continue from the answer for my previous question.
I want to get all values from table b (in rows) if there is any difference between values in arrays from table a by same ids
WITH a as (SELECT 1 as id, ['123', 'abc', '456', 'qaz', 'uqw'] as value
UNION ALL SELECT 2, ['123', 'wer', 'thg', '10', '200']
UNION ALL SELECT 3, ['200']
UNION ALL SELECT 4, null
UNION ALL SELECT 5, ['140']),
b as (SELECT 1 as id, '123' as value
UNION ALL SELECT 1, 'abc'
UNION ALL SELECT 1, '456'
UNION ALL SELECT 1, 'qaz'
UNION ALL SELECT 1, 'uqw'
UNION ALL SELECT 2, '123'
UNION ALL SELECT 2, 'wer'
UNION ALL SELECT 2, '10'
UNION ALL SELECT 3, null
UNION ALL SELECT 4, 'wer'
UNION ALL SELECT 4, '234'
UNION ALL SELECT 5, '140'
UNION ALL SELECT 5, '121'
)
SELECT * EXCEPT(flag)
FROM (
SELECT b.*, COUNTIF(b.value IS NULL) OVER(PARTITION BY id) flag
FROM a LEFT JOIN a.value
FULL OUTER JOIN b
USING(id, value)
)
WHERE flag > 0
AND NOT id IS NULL
It works well for all ids except 5.
In my case I need to return all values if there is any difference.
In example array with id 5 from table a has only one value is '140' while there are two rows with values by id 5 from table b. So in this case all values by id 5 from table b also must appear in expected output
How need to modify this query to get what I want?
UPDATED
Seems like it works for me. But I can not be sure for 100%
SELECT * EXCEPT(flag)
FROM (
SELECT b.*, COUNTIF((b.value IS NULL AND a.value IS NOT NULL) OR (b.value IS NOT NULL AND a.value IS NULL)) OVER(PARTITION BY id) flag
FROM a LEFT JOIN a.value
FULL OUTER JOIN b
USING(id, value)
)
WHERE flag > 0
AND NOT id IS NULL
#standardSQL
SELECT *
FROM table_b
WHERE id IN (
SELECT id FROM table_a a
JOIN table_b b USING(id)
GROUP BY id
HAVING STRING_AGG(IFNULL(b.value, 'NULL') ORDER BY b.value) !=
IFNULL(ANY_VALUE((SELECT STRING_AGG(IFNULL(value, 'NULL') ORDER BY value) FROM a.value)), 'NULL')
)

Exists - Not exists - Exclude records those are having status in 0 ignoring other status associated with that record

Below is my data.
with cte as(
select 'A' name, 0 status
union all select 'A' name, 1 status
union all select 'B' name, 1 status
union all select 'C' name, 2 status
union all select 'D' name, 1 status
)
I want to get only B, C, D as output from the query. Lets say, 0 is status-complete & I want to ignore records associated with it.
This I am able to achieve using the not in clause as below.
select * from cte c
where c.name not in (select cf.name from cte cf where cf.status=0)
But I want to achieve this using exists or not exists clause in where condition.
Could you please share the logic ?
thanks,
Can you please try with this:
SELECT * FROM cte c
WHERE NOT EXISTS (SELECT cf.name
FROM cte cf WHERE c.name = cf.name AND cf.status = 0)
For this we don't need any column in the where clause because we are addressing that conditional column as comparison in WHERE of sub query.
Please try this
with cte as(
select 'A' name, 0 status
union all select 'A' name, 1 status
union all select 'B' name, 1 status
union all select 'C' name, 2 status
union all select 'D' name, 1 status
)
Select * from cte c
where NOT EXISTS (select 1 from cte cf where cf.status=0 AND c.name = cf.name)
With NOT EXISTS
with cte as(
select 'A' name, 0 status
union all select 'A' name, 1 status
union all select 'B' name, 1 status
union all select 'C' name, 2 status
union all select 'D' name, 1 status
)
select * from cte out where NOT EXISTS
(select inn.name from cte inn WHERE out.name = inn.name and inn.status=0)
DECLARE #tbl1 AS TABLE
(
Name VARCHAR(50),
Status INT
)
INSERT INTO #tbl1 VALUES('A',0)
INSERT INTO #tbl1 VALUES('A',1)
INSERT INTO #tbl1 VALUES('B',1)
INSERT INTO #tbl1 VALUES('C',1)
INSERT INTO #tbl1 VALUES('D',1)
INSERT INTO #tbl1 VALUES('E',0)
With Not EXISTS:
SELECT
*
FROM #tbl1 T1
WHERE NOT EXISTS( SELECT T2.Name FROM #tbl1 T2 WHERE T2.Status=0 AND T1.Name=T2.Name)
With EXISTS:
SELECT
*
FROM #tbl1 T1
WHERE EXISTS( SELECT T2.Name FROM #tbl1 T2 WHERE T1.Name=T2.Name AND T1.Status=1 GROUP BY T2.Name having count(T2.Status)=1 )
Output:

Select query select based on a priority

Someone please change my title to better reflect what I am trying to ask.
I have a table like
Table (id, value, value_type, data)
ID is NOT unique. There is no unique key.
value_type has two possible values, let's say A and B.
Type B is better than A, but often not available.
For each id if any records with value_type B exists, I want all the records with that id and value_type B.
If no record for that id with value_Type B exists I want all records with that id and value_type A.
Notice that if B exists for that id I don't want records with type A.
I currently do this with a series of temp tables. Is there a single select statement (sub queries OK) that can do the job?
Thanks so much!
Additional details:
SQL Server 2005
RANK, rather than ROW_NUMBER, because you want ties (those with the same B value) to have the same rank value:
WITH summary AS (
SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM TABLE t
WHERE t.value_type IN ('A', 'B'))
SELECT s.id,
s.value,
s.value_type,
s.data
FROM summary s
WHERE s.rank = 1
Non CTE version:
SELECT s.id,
s.value,
s.value_type,
s.data
FROM (SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM TABLE t
WHERE t.value_type IN ('A', 'B')) s
WHERE s.rank = 1
WITH test AS (
SELECT 1 AS id, 'B' AS value_type
UNION ALL
SELECT 1, 'B'
UNION ALL
SELECT 1, 'A'
UNION ALL
SELECT 2, 'A'
UNION ALL
SELECT 2, 'A'),
summary AS (
SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM test t)
SELECT *
FROM summary
WHERE rank = 1
I get:
id value_type rank
----------------------
1 B 1
1 B 1
2 A 1
2 A 1
SELECT *
FROM table
WHERE value_type = B
UNION ALL
SELECT *
FROM table
WHERE ID not in (SELECT distinct id
FROM table
WHERE value_type = B)
The shortest query to do the job I can think of:
SELECT TOP 1 WITH TIES *
FROM #test
ORDER BY Rank() OVER (PARTITION BY id ORDER BY value_type DESC)
This is about 50% worse on CPU as OMG Ponies' and Christoperous 5000's solutions, but the same number of reads. It's the extra sort that is making it take more CPU.
The best-performing original query I've come up with so far is:
SELECT *
FROM #test
WHERE value_type = 'B'
UNION ALL
SELECT *
FROM #test T1
WHERE NOT EXISTS (
SELECT *
FROM #test T2
WHERE
T1.id = T2.id
AND T2.value_type = 'B'
)
This consistently beats all the others presented on CPU by about 1/3rd (the others are about 50% more) but has 3x the number of reads. The duration on this query is often 2/3rds the time of all the others. I consider it a good contender.
Indexes and data types could change everything.
declare #test as table(
id int , value [nvarchar](255),value_type [nvarchar](255),data int)
INSERT INTO #test
SELECT 1, 'X', 'A',1 UNION
SELECT 1, 'X', 'A',2 UNION
SELECT 1, 'X', 'A',3 UNION
SELECT 1, 'X', 'A',4 UNION
SELECT 2, 'X', 'A',5 UNION
SELECT 2, 'X', 'B',6 UNION
SELECT 2, 'X', 'B',7 UNION
SELECT 2, 'X', 'A',8 UNION
SELECT 2, 'X', 'A',9
SELECT * FROM #test x
INNER JOIN
(SELECT id, MAX(value_type) as value_type FROM
#test GROUP BY id) as y
ON x.id = y.id AND x.value_type = y.value_type
Try this (MSSQL).
Select id, value_typeB, null
from myTable
where value_typeB is not null
Union All
Select id, null, value_typeA
from myTable
where value_typeB is null and value_typeA is not null
Perhaps something like this:
select * from mytable
where id in (select distinct id where value_type = "B")
union
select * from mytable
where id in (select distinct id where value_type = "A"
and id not in (select distinct id where value_type = "B"))
This uses a union, combining all records of value B with all records that have only A values:
SELECT *
FROM mainTable
WHERE value_type = B
GROUP BY value_type UNION SELECT *
FROM mainTable
WHERE value_type = A
AND id NOT IN(SELECT *
FROM mainTable
WHERE value_type = B);

Interesting SQL issue

I have a SQL problem I am trying to digest. I am using SQL Server 2005.
In a table I have data as such:
ID Type
1 A
2 A
3 A
3 B
4 B
I need to find all of the IDs that have a Type of both A and B.
Use the INTERSECT operator:
SELECT DISTINCT ID FROM [Table] WHERE Type = 'A'
INTERSECT
SELECT DISTINCT ID FROM [Table] WHERE Type = 'B'
select distinct a.id
from table a
join table b on a.id=b.id
where a.type='A'
and b.type='B';
With a semi-join (no sorting, only index seek on B):
select a.id from table a
where a.type = 'A'
and exists (select * from table b where a.id = b.id and b.type = 'B')
If you want to abstract the problem a little bit and find cases where rows with the same id contain different values in the type column, you can check for <> like this:
DECLARE #TestTable TABLE (thisid int, thisval varchar(1))
INSERT INTO #TestTable VALUES (1, 'A')
INSERT INTO #TestTable VALUES (2, 'A')
INSERT INTO #TestTable VALUES (3, 'A')
INSERT INTO #TestTable VALUES (3, 'B')
INSERT INTO #TestTable VALUES (4, 'B')
SELECT DISTINCT thisid
FROM #TestTable a
WHERE EXISTS
( SELECT *
FROM #TestTable b
WHERE a.thisid=b.thisid AND a.thisval<>b.thisval)
-- www.caliberwebgroup.com
This returns:
3
select id, count(type = 'A') as a_count, count(type = 'B') as b_count
from your_table
group by 1
having a_count > 0 and b_count > 0;
At least, this works in sane SQL environments. Dunno if it works in yours.
I was not looking at other answers, but still posting. lol
SELECT distinct t1.ID
FROM table1 AS t1
WHERE exists
(select t2.ID from table1 t2 where t2.type="A" and t2.ID=t1.ID)
and exists
(select t3.ID from table1 t3 where t3.type="B" and t3.ID=t1.ID);
SELECT Id FROM tableX AS x, tableX AS y
WHERE x.id = y.id AND x.type = 'A' AND y.type = 'B'
This is very simple
Declare #t table([ID] INT, [Type] VARCHAR(2))
INSERT INTO #t SELECT 1, 'A' UNION ALL SELECT 2,'A' UNION ALL SELECT 3,'A'
UNION ALL SELECT 3,'B' UNION ALL SELECT 4,'B' UNION ALL SELECT 5,'A' UNION ALL SELECT 5,'A'
;WITH CTE AS
(
SELECT Rn = Row_NUMBER() OVER(PARTITION BY [ID],[TYPE] ORDER BY [ID])
,*
FROM #t
)
SELECT ID
FROM CTE
WHERE Rn =1 AND ([Type]='A' or [Type]='B')
GROUP BY [ID]
HAVING (COUNT([ID])>1)
Output:
id
3
this would help if there are "unknown" amounts of types and you want to find all IDs which have all of types
select id from yourtable group by id having count(*)=(select count(distinct type) from yourtable)
select id
from idtypedata
group by id
having
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 1 = 1
And
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 2 = 2