How to select groups which has only the values we want and not select it if it also has other values in SQL - sql

id
code
value
A
cod
2
A
buy
34
A
cod
4
B
cod
44
B
F
23
C
thk
45
C
cod
33
C
F
31
D
cod
22
In this table for example, I want those groups of id which has 'code' column value as ONLY cod or F. so query should return values of id = B and nothing else. ( Not even values with id = C because id=C also has 'thk' in code , not even id= D, and output should have ids with ONLY the mentioned two values)
expected output
id
code
value
B
cod
44
B
F
23

You want all rows for the ID of which not exists a forbidden row:
select id, code, value
from mytable
where not exists
(
select null
from mytable forbidden_row
where forbidden_row.id = mytable.id
and forbidden_row.code not in ('cod', 'F')
);

One of approaches with nested query
SELECT ID,Code, value FROM (
select ID, Code,
(SELECT count(*) FROM TableA a where Code = 'cod' and a.ID = TableA.ID) Cod,
(SELECT count(*) FROM TableA a where Code = 'F' and a.ID = TableA.ID) F,
(SELECT count(*) FROM TableA a where Code not in ('F','cod') and a.ID = TableA.ID) Other,
Value
from TableA
) SOURCE
WHERE Cod <> 0 AND F <> 0 and Other = 0

We can achieve this using CTE. Check this,
-- Split the two record category first, then check cod Or F condition.
WITH Count2 AS (
SELECT id
FROM YourTable
GROUP BY id
HAVING COUNT(id) = 2
),
codORF AS (
SELECT id, code, COUNT(id) FROM YourTable T1
LEFT JOIN Count2 T2 On T1.id = T2.id
WHERE code = 'cod' OR code = 'F'
GROUP BY id, code
Having COUNT(id) = 1
)
-- Finally to take all values
SELECT T1.*
FROM YourTable T1
INNER JOIN codORF T2 ON T1.id = T2.id

with main as (
select *, count(id) over(partition by id order by id) as total_rows
from sample
), next_and_before as (
select *,
COALESCE(lag(code) over(partition by id order by id),lead(code) over(partition by id order by id)) as before_next
from main where total_rows <= 2
)
select * from next_and_before
where lower(trim(concat(code,before_next)))in('codf','fcod','cod','f')
Its a bit of hacky solution:
first you are filtering out all the rows that have less than or equal to 2 rows, since there could be cases where you only have one row per id with a code value = 'f' or 'cod', if you don't want that then simply change the last part to: in ('codf','fcod')
then out of two rows, you are looking at the next and before value and checking if it contains other than 'f' or 'cod'
where clause will filter those out if they exist
Test Results from the link below:
Results of sample data

Related

How to know if an element matches every in another table in SQL

I have the following problem in SQL:
I have a table with only 1 column containing different values: [A, B, C, D] for example
And in other table I have 2 columns with:
1 | A
1 | C
2 | D
1 | B
2 | D
1 | D
...
I need to return 1, because is the only item that matches every value in the other table, how do I do this? Thank you :)
here is the solution , you compare number of rows in your second table with number of rows in your first table,
I'm using distinct to make sure if there is a duplicate it wouldn't be counted:
SELECT id
FROM
table2
WHERE word in (select word from table1)
GROUP BY id
HAVING COUNT(DISTINCT word) = ( SELECT COUNT(*) FROM table1)
You can use aggregation:
select col1
from table1 t1
where col2 in (select col2 from table2)
group by col1
having count(*) = (select count(*) from table2);
This assumes that the col1/col2 columns are unique in table1. If not, use count(distinct) in the having clause.
Yet another option is to use left outer join and analytical function as follows:
Select id, word from
(Select t2.*,
Count(distinct t1.word) over () as total_words,
Count(distinct t2.word) over (partition by t2.id) as total_words_per_id
From table1 t1
Left Join table2 t2 on t1.word = t2.word)
Where total_words = total_words_per_id

Grouping of column

Group Error
1 a
1 b
1 c
2 a
2 b
3 a
I want write an SQL query to get that record only which has either only a or b as an error or both a and b
Output should be Group 2, 3 as
Group 2 contains both a and b , Group 3 contains only a.
Any Group that contains Error apart from a,b should not be returned.
I'd group and use a condition on a count of errors that aren't a or b:
SELECT [group] -- Assuming MS SQL Syntax, like Ross Presser did in his answer
FROM mytable
GROUP BY [group]
HAVING COUNT(CASE WHEN [error] NOT IN ('a', 'b') THEN 1 END) = 0
Unsure of the DBMS and so the following may not be applicable, but to offer another option using a correlated subquery:
SELECT DISTINCT a.Group
FROM Table1 a
WHERE NOT EXISTS (SELECT 1 FROM Table1 b WHERE a.Group = b.Group AND b.Error NOT IN ('a','b'))
Or using a LEFT JOIN on a query of Groups containing at least one Error code not equal to a or b:
SELECT DISTINCT a.Group
FROM
Table1 a LEFT JOIN
(
SELECT DISTINCT t.Group
FROM Table1 t
WHERE t.Error <> 'a' AND t.Error <> 'b'
) b
ON a.Group = b.Group
WHERE b.Group IS NULL
And one more very specific to your example, but just for fun -
SELECT t.Group
FROM Table1 t
GROUP BY t.Group
HAVING MIN(t.Error) >= 'a' AND MAX(t.Error) <= 'b'
Replace Table1 with the name of your table in all of the above.

SQL query that finds repeated data in one column with different data in a second column

Let's say I have data that looks like this:
ID Date Data
A D1 123
A D1 456
A D2 123
What I'm looking for is a select statement that will pull all rows where ID and Date are repeated as a pair, but the data doesn't match. In this case, it would return the top two rows, because ID A and Date D1 are repeated as a pair, but the Data is different. The third row would not be returned because the ID and Date combination are not repeated.
There are several ways to do this. Here's one option using exists:
select *
from yourtable t
where exists (
select 1
from yourtable t2
where t.id = t2.id and t.date = t2.date and t.data <> t2.data
)
SELECT d.Id ,
d.Date ,
d.Data
FROM YourTable d
INNER JOIN ( SELECT Id ,
Date
FROM YourTable
GROUP BY Id ,
Date
HAVING COUNT(*) > 1
) a ON a.Id = d.Id

Can criteria be re-used in a T-SQL query?

I have a SQL query that looks something like this:
SELECT
o.name,
o.type_id,
(SELECT COUNT(*) FROM a WHERE type_id = o.type_id AND id IN ('1, 2, 3 ... 1000')) AS count_a,
(SELECT COUNT(*) FROM b WHERE type_id = o.type_id AND id IN ('1, 2, 3 ... 1000')) AS count_b,
(SELECT COUNT(*) FROM c WHERE type_id = o.type_id AND id IN ('1, 2, 3 ... 1000')) AS count_c
FROM o
In the subqueries (count_a, count_b and count_c) the criteria specified in the IN clause is the same for each, but its a REALLY long list of numbers (that aren't in fact sequential) and im concerned that:
a) Im slowing the query down by making it too long
b) Its going to get too long and cause an error eventually
Is there a way to alias/reference that list of criteria (perhaps as a variable?) so that it can be re-used in each of the three places it appears in the query? Or am I worrying for nothing?
UPDATE
Given the suggestion of using a CTE, I have changed the query above to work like this for now:
WITH id_list AS (SELECT id FROM source WHERE id IN ('1, 2, 3 ... 1000'))
SELECT
o.name,
o.type_id,
(SELECT COUNT(*) FROM a WHERE type_id = o.type_id AND id IN (SELECT id FROM id_list)) AS count_a,
(SELECT COUNT(*) FROM b WHERE type_id = o.type_id AND id IN (SELECT id FROM id_list)) AS count_b,
(SELECT COUNT(*) FROM c WHERE type_id = o.type_id AND id IN (SELECT id FROM id_list)) AS count_c
FROM o
This cuts the overall length of the query down to about a third of what it was, and although the DB appears to take a couple of milliseconds longer to execute the query, at least I wont run into an error based on the length of query being too long.
QUESTION: Is there a quick way to break a comma separated list of numbers (1, 2, 3 ... 1000) into a result set that could be used as the CTE?
You could use a common-table-expression(CTE):
WITH Numbers AS
(
SELECT N
FROM (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 1000)
AS T(N)
)
SELECT
o.name,
o.type_id,
(SELECT COUNT(*) FROM a WHERE type_id = o.type_id AND id IN (SELECT N FROM Numbers)) AS count_a,
(SELECT COUNT(*) FROM b WHERE type_id = o.type_id AND id IN (SELECT N FROM Numbers)) AS count_b,
(SELECT COUNT(*) FROM c WHERE type_id = o.type_id AND id IN (SELECT N FROM Numbers)) AS count_c
FROM o
If your ids are foreign keys into some other table, then you could make a table variable from your list, and then join into that repeatedly.
DECLARE #id_list TABLE (
id INT
)
INSERT INTO #id_list
SELECT id
FROM pk_location
WHERE id IN ('1, 2, 3 ... 1000')
SELECT
o.name,
o.type_id,
(SELECT COUNT(*) FROM a INNER JOIN #id_list i ON a.id = i.id WHERE type_id = o.type_id) AS count_a,
(SELECT COUNT(*) FROM b INNER JOIN #id_list i ON b.id = i.id WHERE type_id = o.type_id) AS count_b,
(SELECT COUNT(*) FROM c INNER JOIN #id_list i ON c.id = i.id WHERE type_id = o.type_id) AS count_c
FROM o

Select on records based on two column criterias

I would like to do an SQL query to select from the following table:
id type num
1 a 3
1 b 4
2 a 5
2 c 6
In the case where they have the same 'id' and be type 'a or b', so the result would look something like this:
id type num
1 a 3
1 b 4
Any one has any idea how that can be accomplished?
SELECT table1.*
FROM table1,
(
SELECT COUNT(*) as cnt, id
FROM (
SELECT *
FROM table1
WHERE type = 'a' OR type = 'b'
) sub1
GROUP BY id
HAVING cnt > 1
)sub2
WHERE table1.id = sub2.id
Tested here: http://sqlfiddle.com/#!2/4a031/1 seems to work fine.
Method 1:
select a.*
from some_table t
join some_table a on a.id = t.id and a.type = 'a'
join some_table b on b.id = t.id and b.type = 'b'
Method 2:
select *
from some_table t
where exists ( select *
from some_table x
where x.id = t.id
and x.type = 'a'
)
and exists ( select *
from some_table x
where x.id = t.id
and x.type = 'b'
)
The first technique offers the possibilities of duplicate rows in the results set, depending on the cardinality of id and type. The latter is guaranteed to provide a proper subset of the table.
Either query, assuming you have reasonable indices defined on the table should provide pretty equivalent performance.