(Oracle) SQL: Column must contain a list of values - sql

I'm trying to figure out an efficient (Oracle) SQL statement that verifies whether a column contains a specific list of values at least once.
One option would be to filter for that list, output all distinct values and then count them. So, something like this:
SELECT count(*)
FROM (
SELECT DISTINCT columnname
FROM table
WHERE columnname in ('a', 'b', 'c')
)
;
(And then check whether count(*) returns the number 3)
The problem with this is that the DISTINCT statement looks at the whole table, which is very bad performance-wise. All three values of my list could be at the very beginning, so i don't need to look at the millions of other rows. I only want to know that the column contains 'a', 'b' and 'c'.
Does anyone has an idea to solve this efficiently?
Thanks in advance!

It might be more efficient to look for each value individually:
select (case when exists (select 1 from t where col = 'a') and
exists (select 1 from t where col = 'b') and
exists (select 1 from t where col = 'c')
then 1 else 0
end) as has_all_three_flag
from dual;
This will be better particularly with an index on t(col).

If you wish to get rid of distinct, then try the below, Group by has better performance than distinct, See here.
SELECT count(*)
FROM (
SELECT columnname
FROM table
WHERE columnname in ('a', 'b', 'c')
GROUP BY columnname
)
;
Or you can avoid the usage of subquery by
SELECT count(DISTINCT columnname)
FROM table
WHERE columnname in ('a', 'b', 'c');

Related

Oracle else in where clause

I need to create a query look like this
select *
from users
where name = 'A'
if and only if no result THEN
name = 'B';
I don't want to get the two rows where first row name = A and second row name = B
I need just one of them
is there any way to do that in sql ?
I tried to achive that with not exists or case but couldn't make it work
Thank you
Since 'A' is before 'B' alphabetically then, from Oracle 12, you can use:
SELECT *
FROM users
WHERE name IN ('A', 'B')
ORDER BY name
FETCH FIRST ROW WITH TIES;
If you do not want to rely on alphabetic ordering then:
SELECT *
FROM users
WHERE name IN ('A', 'B')
ORDER BY CASE name WHEN 'A' THEN 1 ELSE 2 END
FETCH FIRST ROW WITH TIES;
If you want to apply a different ORDER BY clause then use a sub-query:
SELECT *
FROM (
SELECT *
FROM users
WHERE name IN ('A', 'B')
ORDER BY CASE name WHEN 'A' THEN 1 ELSE 2 END
FETCH FIRST ROW WITH TIES
)
ORDER BY col1, col2;
In earlier versions, you can use the RANK analytic function (or DENSE_RANK):
SELECT *
FROM (
SELECT u.*,
RANK() OVER (ORDER BY CASE name WHEN 'A' THEN 1 ELSE 2 END) AS rnk
FROM users u
WHERE name IN ('A', 'B')
)
WHERE rnk = 1
db<>fiddle here

Dividing two counts Oracle SQL

I am trying to write what I thought would be a fairly simple query but is proving to be harder than I thought. I want to divide the results of two select statements:
Select count (*) from tableA where columnA = 'A'
/
Select count (*) from tableA where columnA in ('1','2')
I am using Oracle SQL Developer
Thanks for any guidance
I would recommend conditional aggregation . . . but with sum() not count():
Select (sum(case when columnA = 'A' then 1 else 0 end) /
sum(case when columnA in ('1', '2') then 1 end)
)
from tableA;
Note the lack of else in the denominator. This handles the divide-by-zero case.
You could treat the, both as subqueries within a single main query:
select
(select count (*) from tableA where columnA = 'A')
/
(select count (*) from tableA where columnA in ('1', '2'))
from dual
or if the table and column names are actually the same you could do a single query with conditional counts, something like:
select count (case when columnA = 'A' then columnA end)
/
count (case when columnA in ('1', '2') then columnA end)
from tableA
where columnA in ('A', '1', '2')
I've fixed the single quotes in both, not sure if those were just an issue with posting your question.
You may also need to add logic to handle the second count being zero, if that could happen - as that would cause a runtime error.

Why does count( distinct ) with NULL columns return 0 in Hive SQL?

I have struggled with an issue in Hive SQL and just found out what the issue was:
select distinct 'A', NULL;
returns 'A', NULL
select count(distinct 'A', NULL);
returns 0
select count(distinct 'A', coalesce(NULL,''));
returns 1.
I was using the select line inside of a larger query and filtered on the result (=1). With the outcome being 0, I lost a lot of rows.
How come that a row with NULL column does not contribute to the result of a count(distinct) query?
It's the interface of count in hive:
count(*) counts all rows
count(col1) counts all rows where col1 is not null
count(distinct col1,col2...) counts all distinct rows where the specified columns are not null
As a solution to your specific problem, you can try to have a nested query with the logic and use count(*) in the outer query:
select count(*) from (select distinct 'A', NULL) a;
returns 1

How to delete all records returned by a subquery?

I want to delete all records that are returned by a certain query, but I can't figure out a proper way to do this. I tried to DELETE FROM mytable WHERE EXISTS (subquery), however, that deleted all records from the table and not just the ones selected by the subquery.
My subquery looks like this:
SELECT
MAX(columnA) as columnA,
-- 50 other columns
FROM myTable
GROUP BY
-- the 50 other columns above
having count(*) > 1;
This should be easy enough, but my mind is just stuck right now. I'm thankful for any suggestions.
Edit: columnA is not unique (also no other column in that table is globally unique)
Presumably, you want to use in:
DELETE FROM myTable
WHERE columnA IN (SELECT MAX(columnA) as columnA
FROM myTable
GROUP BY -- the 50 other columns above
HAVING count(*) > 1
);
This assumes that columnA is globally unique in the table. Otherwise, you will have to work a bit harder.
DELETE FROM myTable t
WHERE EXISTS (SELECT 1
FROM (SELECT MAX(columnA) as columnA,
col1, col2, . . .
FROM myTable
GROUP BY -- the 50 other columns above
HAVING count(*) > 1
) t2
WHERE t.columnA = t2.columnA AND
t.col1 = t2.col1 AND
t.col2 = t2.col2 AND . . .
);
And, even this isn't guaranteed to work if any of the columns have NULL values (although the conditions can be easily modified to handle this).
Another solution if the uniqueness is only guaranteed by a set of columns:
delete table1 where (col1, col2, ...) in (
select min(col1), col2, ...
from table1
where...
group by col2, ...
)
Null values will be ignored and not deleted.
To achieve this, try something like
with data (id, val1, val2) as
(
select 1, '10', 10 from dual union all
select 2, '20', 21 from dual union all
select 2, null, 21 from dual union all
select 2, '20', null from dual
)
-- map null values in column to a nonexistent value in this column
select * from data d where (d.id, nvl(d.val1, '#<null>')) in
(select dd.id, nvl(dd.val1, '#<null>') from data dd)
If you need to delete all the rows of a table such that the value of a given field is in the result of a query, you can use something like
delete table
my column in ( select column from ...)

Counting the rows of a column where the value of a different column is 1

I am using a select count distinct to count the number of records in a column. However, I only want to count the records where the value of a different column is 1.
So my table looks a bit like this:
Name------Type
abc---------1
def----------2
ghi----------2
jkl-----------1
mno--------1
and I want the query only to count abc, jkl and mno and thus return '3'.
I wasn't able to do this with the CASE function, because this only seems to work with conditions in the same column.
EDIT: Sorry, I should have added, I want to make a query that counts both types.
So the result should look more like:
1---3
2---2
SELECT COUNT(*)
FROM dbo.[table name]
WHERE [type] = 1;
If you want to return the counts by type:
SELECT [type], COUNT(*)
FROM dbo.[table name]
GROUP BY [type]
ORDER BY [type];
You should avoid using keywords like type as column names - you can avoid a lot of square brackets if you use a more specific, non-reserved word.
I think you'll want (assuming that you wouldn't want to count ('abc',1) twice if it is in your table twice):
select count(distinct name)
from mytable
where type = 1
EDIT: for getting all types
select type, count(distinct name)
from mytable
group by type
order by type
select count(1) from tbl where type = 1
;WITH MyTable (Name, [Type]) AS
(
SELECT 'abc', 1
UNION
SELECT 'def', 2
UNION
SELECT 'ghi', 2
UNION
SELECT 'jkl', 1
UNION
SELECT 'mno', 1
)
SELECT COUNT( DISTINCT Name)
FROM MyTable
WHERE [Type] = 1