Query to identify records without certain values - sql

I have a table of data that looks something like this:
ID Num | Code
-------------
1 | A
1 | B
1 | C
1 | D
2 | A
2 | B
3 | A
3 | B
3 | D
4 | B
5 | A
5 | B
5 | E
And I need to be able to write an SQL query to show me all ID Numbers that do not have Codes C or D associated with them. (Which in this example would be ID Numbers 2, 4, & 5.)
Thanks in advance for any help you can provide!

I would use NOT IN:
SELECT DISTINCT ID_Num
FROM t
WHERE ID_Num NOT IN
(SELECT ID_Num
FROM t
WHERE code = 'C'
OR code = 'D')

I like to approach this type of question using group by and having:
select id_num
from t
group by id_num
having sum(case when code in ('C', 'D') then 1 else 0 end) = 0;

you can use 'not exists' (often more performant of not in)
SELECT DISTINCT ID_Num
FROM yourtable f1
WHERE not exists
(
SELECT * FROM yourtable f2
WHERE f2.code in ('C', 'D') and f2.ID_Num=f1.ID_Num
)

you can use 'left outer join lateral' and take not founded row like this:
SELECT DISTINCT f1.ID_Num
FROM yourtable f1
LEFT OUTER JOIN LATERAL
(
SELECT f2.ID_Num FROM yourtable f2
WHERE f2.code in ('C', 'D') AND f2.ID_Num=f1.ID_Num
FETCH FIRST ROWS ONLY
) f3 on 1=1
WHERE f3.ID_Num is null

Related

How to groupby by aggregating different keys in SQL

I have tables like below.
I would like to groupby by generating new keys like D,Dmeans AorB
In this case,countin D is 2 becauseAandBhas 1 record each.
Are there any way to generate new keys and groupby by using this?
product sex age
A M 10
B M 20
C F 30
My desired result is like below.
product count
C 1
D (A orB) 2
If you have same experience please let me know.
Thanks
Instead of the column product you must group by a derived column that matches your condition:
select
case when product in ('A', 'B') then 'D' else product end product,
count(*)
from tablename
group by case when product in ('A', 'B') then 'D' else product end
See the demo.
Results:
| newproduct | count(*) |
| ---------- | -------- |
| C | 1 |
| D | 2 |
ANSI SQL compliant query, use a case expression in a derived table to put A and B into D. GROUP BY its result:
select product, count(*)
from
(
select case when product in ('A', 'B') then 'D' else product end product
from tablename
) dt
group by product

SQL query for fetching a single column with multiple values

Consider the below table:
Table1
id | status
------------
1 | A
2 | B
3 | C
1 | B
4 | B
5 | C
4 | A
Desired output is 1 and 4 as they are having status as both 'A' and 'B'.
Can we write an query for this? I tried to query it using conditions like 'AND', 'UNION' and 'OR', but it did not return me the desired result.
If you want the ids with more than 1 statuses:
select id
from tablename
group by id
having count(distinct status) > 1
You can use aggregation:
select id
from t
where status in ('A', 'B')
group by id
having count(*) = 2;
If the table allows duplicates, then use count(distinct status) = 2.
Try this one, you can do without using having() as well
select
id
from
(
select
id,
count(distinct status) as cnt
from yourTable
group by
id
) val
where cnt > 1

bitwise comparison in bit columns

I have a database table with columns shaped as following:
| ID | name | A | B | C | D |
| 1 | foo | 1 | 0 | 0 | 1 |
| 2 | bar | 0 | 0 | 1 | 1 |
| 3 | foo | 1 | 1 | 0 | 0 |
| 4 | bar | 1 | 1 | 0 | 0 |
A, B, C and D are bit columns.
I need to get the name values of the rows of which there at least two and that both have at least one identical bit column set to true. the result set I want to get for the given example is as following:
| name |
| foo |
I can do the following:
SELECT l.name
FROM dummy l
INNER JOIN dummy r ON l.name = r.name
WHERE (l.A = 1 AND r.A = 1)
OR (l.B = 1 AND r.B = 1)
OR (l.C = 1 AND r.C = 1)
OR (l.D = 1 AND r.D = 1)
GROUP BY l.name
HAVING COUNT(*) > 1
But this gets unreadable soon since the table is massive. I was wondering if there was a bitwise solution to solve this
I suspect that your data model is wrong. It feels like A-D represent the same "type" of thing and so the data ought to be represented using a single column that contains the data values A-D and (if necessary) one column to store the 1 or 0, with separate rows for each A-D value. (But then, of course, we can use the presence of a row to indicate a 1 and the absence of the row to represent a 0).
We can use UNPIVOT to get this "better" structure for the data and then the query becomes trivial:
declare #t table (ID int not null, name char(3) not null, A bit not null, B bit not null,
C bit not null, D bit not null)
insert into #t(ID,name,A,B,C,D) values
(1,'foo',1,0,0,1),
(2,'bar',0,0,1,1),
(3,'foo',1,1,0,0),
(4,'bar',1,1,0,0)
;With ProperLayout as (
select ID,Name,Property,Value
from #t t
unpivot (Value for Property in (A,B,C,D)) u
where Value = 1
)
select name,Property
from ProperLayout
group by name,Property
having COUNT(*) > 1
Result:
name Property
---- ---------
foo A
(Note also that the top of my script is not much different in size to the sample data in your question but has the massive benefit that it's runnable)
In similar way you could also use Apply opertaor
SELECT a.name FROM table t
CROSS APPLY (
VALUES (name, 'A', A), (name, 'B', B), (name, 'C', C), (name, 'D', D)
)a(name , names , value)
WHERE a.value = 1
GROUP BY a.name, a.Names, a.value
HAVING COUNT(*) > 1
From your description, you seem to want:
SELECT l.name
FROM dummy l
GROUP BY l.name
HAVING SUM( CAST(A as int) ) >= 2 OR
SUM( CAST(B as int) ) >= 2 OR
SUM( CAST(C as int) ) >= 2 OR
SUM( CAST(D as int) ) >= 2 ;
This is based on the description. I don't know what the same result row has to do with the question.
It is not hard to read. It is just long.
This would be more efficient:
SELECT distinct l.name
FROM dummy l
INNER JOIN dummy r
ON l.name = r.name
and l.id < r.id
and ( (l.A = 1 AND r.A = 1)
OR (l.B = 1 AND r.B = 1)
OR (l.C = 1 AND r.C = 1)
OR (l.D = 1 AND r.D = 1)
)
order by l.name
You could build it up reading sys.columns
I don't think TSQL has any bitwise operators.

Oracle SQL Get unique symbols from table

I have table with descriptions of smth. For example:
My_Table
id description
================
1 ABC
2 ABB
3 OPAC
4 APEЧ
I need to get all unique symbols from all "description" columns.
Result should look like that:
symbol
================
A
B
C
O
P
E
Ч
And it shoud work for all languages, so, as I see, regular expressions cant help.
Please help me. Thanks.
with cte (c,description_suffix) as
(
select substr(description,1,1)
,substr(description,2)
from mytable
where description is not null
union all
select substr(description_suffix,1,1)
,substr(description_suffix,2)
from cte
where description_suffix is not null
)
select c
,count(*) as cnt
from cte
group by c
order by c
or
with cte(n) as
(
select level
from dual
connect by level <= (select max(length(description)) from mytable)
)
select substr(t.description,c.n,1) as c
,count(*) as cnt
from mytable t
join cte c
on c.n <= length(description)
group by substr(t.description,c.n,1)
order by c
+---+-----+
| C | CNT |
+---+-----+
| A | 4 |
| B | 3 |
| C | 2 |
| E | 1 |
| O | 1 |
| P | 2 |
| Ч | 1 |
+---+-----+
Create a numbers table and populate it with all the relevant ids you'd need (in this case 1..maxlength of string)
SELECT DISTINCT
locate(your_table.description, numbers.id) AS symbol
FROM
your_table
INNER JOIN
numbers
ON numbers.id >= 1
AND numbers.id <= CHAR_LENGTH(your_table.description)
SELECT DISTINCT(SUBSTR(ll,LEVEL,1)) OP --Here DISTINCT(SUBSTR(ll,LEVEL,1)) is used to get all distinct character/numeric in vertical as per requirment
FROM
(
SELECT LISTAGG(DES,'')
WITHIN GROUP (ORDER BY ID) ll
FROM My_Table --Here listagg is used to convert all values under description(des) column into a single value and there is no space in between
)
CONNECT BY LEVEL <= LENGTH(ll);

SQL:Query to check if a column meets certain criteria, if it does perform one action if it doesn't perform another

I have found it quite hard to word what I want to do in the title so I will try my best to explain now!
I have two tables which I am using:
Master_Tab and Parts_Tab
Parts_Tab has the following information:
Order_Number | Completed| Part_Number|
| 1 | Y | 64 |
| 2 | N | 32 |
| 3 | Y | 42 |
| 1 | N | 32 |
| 1 | N | 5 |
Master_Tab has the following information:
Order_Number|
1 |
2 |
3 |
4 |
5 |
I want to generate a query which will return ALL of the Order_Numbers listed in the Master_Tab on the following conditions...
For each Order_Number I want to check the Parts_Tab table to see if there are any parts which aren't complete (Completed = 'N'). For each Order_Number I then want to count the number of uncompleted parts an order has against it. If an Order_Number does not have uncompleted parts or it is not in the Parts_Table then I want the count value to be 0.
So the table that would be generated would look like this:
Order_Number | Count_of_Non_Complete_Parts|
1 | 2 |
2 | 1 |
3 | 0 |
4 | 0 |
5 | 0 |
I was hoping that using a different kind of join on the tables would do this but I am clearly missing the trick!
Any help is much appreciated!
Thanks.
I have used COALESCE to convert NULL to zero where necessary. Depending on your database platform, you may need to use another method, e.g. ISNULL or CASE.
select mt.Order_Number,
coalesce(ptc.Count, 0) as Count_of_Non_Complete_Parts
from Master_Tab mt
left outer join (
select Order_Number, count(*) as Count
from Parts_Tab
where Completed = 'N'
group by Order_Number
) ptc on mt.Order_Number = ptc.Order_Number
order by mt.Order_Number
You are looking for a LEFT JOIN.
SELECT mt.order_number, count(part_number) AS count_noncomplete_parts
FROM master_tab mt LEFT JOIN parts_tab pt
ON mt.order_number=pt.order_number AND pt.completed='N'
GROUP BY mt.order_number;
It is also possible to put pt.completed='N' into a WHERE clause, but you have to be careful of NULLs. Instead of the AND you can have
WHERE pt.completed='N' OR pr.completed IS NULL
SELECT mt.Order_Number SUM(tbl.Incomplete) Count_of_Non_Complete_Parts
FROM Master_Tab mt
LEFT JOIN (
SELECT Order_Number, CASE WHEN Completed = 'N' THEN 1 ELSE 0 END Incomplete
FROM Parts_Tab
) tbl on mt.Order_Number = tbl.Order_Number
GROUP BY mt.Order_Number
Add a WHERE clause to the outer query if you need to filter for specific order numbers.
I think it's easiest to get a subquery in there. I think this should be self-explanitory, if not feel free to ask any questions.
CREATE TABLE #Parts
(
Order_Number int,
Completed char(1),
Part_Number int
)
CREATE TABLE #Master
(
Order_Number int
)
INSERT INTO #Parts
SELECT 1, 'Y', 64 UNION ALL
SELECT 2, 'N', 32 UNION ALL
SELECT 3, 'Y', 42 UNION ALL
SELECT 1, 'N', 32 UNION ALL
SELECT 1, 'N', 5
INSERT INTO #Master
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6
SELECT M.Order_Number, ISNULL(Totals.NonCompletedCount, 0) FROM #Master M
LEFT JOIN (SELECT P.Order_Number, COUNT(*) AS NonCompletedCount FROM #Parts P
WHERE P.Completed = 'N'
GROUP BY P.Order_Number) Totals ON Totals.Order_Number = M.Order_Number