Change result set order based on another column being null - sql

I have a query that sorts the results based on the second column.
select decode(name, null, 'n/a', name) name, value1
from tableA
group by name
order by 2
How can I change it so a result where the first column is null is always the last row in the result set, without changing the ordering for the rest of the results?
One solution I have in my mind is to use union two queries, one which excludes nulls and one which only has nulls, something like:
select decode(name, null, 'n/a', name) name, value1
from tableA
where name is not null
group by name
union
select decode(name, null, 'n/a', name) name, value1
from tableA
where name is null
group by name
order by 2
Is there a better way to do this?

Your union approach won't get the result you want; the order by is applied after the two queries are unioned, so they will be sorted the same as if you had a single query. You could add a flag column in each brach of the union and include that in the ordering, but the you need to exclude it from the final select list.
You can handle this in the order by clause, using a case statement (or decode if you prefer) against column 1 which treats all not-null values as one priority regardless of the actual value, and all null values as a different priority; and then further orders by column 2:
select decode(a.name, null, 'n/a', a.name) name, value1
from tableA a
order by case when a.name is null then 1 else 0 end, value1 desc
I've used a table alias and included that in the case to avoid confusion between the original table value and the column alias of the same name. This will put all the results with null ('n/a') names after all of those which are not null; and within each category all the results will still be sorted by the second column.
With some sample data:
with tableA (name, value1) as (
select 'Joe', 3 from dual
union all select 'Anne', 10 from dual
union all select null, 4 from dual
union all select 'Sarah', 2 from dual
union all select 'Bill', 5 from dual
union all select 'Mary', 7 from dual
)
... ordering just by the second column gets:
select decode(a.name, null, 'n/a', a.name) name, value1
from tableA a
order by value1 desc;
NAME VALUE1
----- ----------
Anne 10
Mary 7
Bill 5
n/a 4
Joe 3
Sarah 2
Adding this case clause puts your 'n/a' row last in the result set:
select decode(a.name, null, 'n/a', a.name) name, value1
from tableA a
order by case when a.name is null then 1 else 0 end, value1 desc;
NAME VALUE1
----- ----------
Anne 10
Mary 7
Bill 5
Joe 3
Sarah 2
n/a 4
(I've ignored the group by clause in your example because you don't have any aggregates, and neither of your queries is valid; but you can still group and do this in your real query if you need to.)

Related

SQL Server - Conditional Aggregate - Sum only if ID is not null else dont sum

Please refer table below:
enter image description here
ID
Value
1
10
1
20
1
20
2
25
2
15
3
30
Null
5
Null
10
I have column ID and Value in my table and ID can be duplicate.
I would like to sum value column only if ID column is not null. If ID is Null, dont sum and show as it is.
I know I can do Union like below:
select ID, sum(Value)
from table where id is not null
group by ID
UNION
select ID, Value
from table where id is null
But I would like to achieve the same result using IIF or CASE or any function without UNION
something like:
iif(ID is not null,sum([Value]),[Value]) as Value
but this is not working.
You might try out grouping sets to see if that produces a better plan. This does assume that values (column 2) in the null subset are themselves unique:
select id, sum(val) from T
group by grouping sets ((id), (id, val))
having id is null and val is not null or id is not null and val is null;
And then there's always an approach that generates a new id for the null id so they can remain distinct during grouping. Here I'm assuming that negative values are open for this purpose but you could probably adjust if necessary:
with data as (
select coalesce(id, -row_number() over (order by id)) as id, val
from T
)
select case when id >= 0 then id end as id, sum(val)
from data group by id;
https://dbfiddle.uk/fbGbpyEf

How to select values from a column that have only specific values from another column and not other values?

I have a pgsql schema having a table that has two columns among others: id and status. status values are of varchar type ranging from '1' to '6'. I want to select values of id that have only specific status, precisely, one id having only one status ('1'), then another having two values ('1' ands '2'), then another having only three values ('1', '2' and '3') and so on.
This is for a pgsql database. I have tried using inner query joining with the same table.
select *
from srt s
join ( select id
from srt
group by id
having count(distinct status) = 2
) t on t.id = s.id
where srt.status in ('1', '2')
limit 10
I used this to get the IDs having only status values 1 and 2 (and not having any rows with status values 3, 4, 5, 6) but didn't get the expected result
The expected result would be something like this
id status
123 1
234 1
234 2
345 1
345 2
345 3
456 1
456 2
456 3
456 4
567 1
567 2
567 3
567 4
567 5
678 1
678 2
678 3
678 4
678 5
678 6
Move your where condition inside sub-query -
select *
from srt s
join ( select id
from srt
where status in ('1', '2')
group by id
having count(distinct status) = 2
) t on t.id = s.id
limit 10
To identify the ids with consecutive statuses, you can do:
select id, max(status) as max_status
from srt s
group by id
having min(status) = 1 and
max(status::int) = count(*);
Then, you can narrow this down to one example using distinct on and use join to bring in your results:
select s.*
from srt s join
(select distinct on (max(status)) id, max(status) as max_status
from srt s
group by id
having min(status) = 1 and
max(status::int) = count(*)
order by max_status asc
) ss
on ss.id = s.id
order by ss.max_status, s.status;
This is a tricky one. My solution is to first specify a list of the "target statuses" you want to match:
with target_statuses(s) as ( values (1),(2),(3) )
Then JOIN your srt table to it and count the occurrences grouped by id.
with target_statuses(s) as ( values (1),(2),(3) )
select id, count(*), row_number() OVER (partition by count(*) order by id) rownum
from srt
join target_statuses on status=s
group by id
)
This query also captures a row number, which we'll later use to limit it to the first id that has one match, the first id that has two matches, etc. Note the order by clause... I assume you want the alphabetically lowest id first in each case, but you may change that.
Since you can't put a window function in a HAVING clause, I wrap up the whole result at ids_and_counts_of_statuses and perform a follow-up query that rejoins it with the srt table to output it:
with ids_and_counts_of_statuses as(
with target_statuses(s) as ( values (1),(2),(3) )
select id, count(*), row_number() OVER (partition by count(*) order by id) rownum
from srt
join target_statuses on status=s
group by id
)
select srt.id, srt.status
from ids_and_counts_of_statuses
join srt on ids_and_counts_of_statuses.id=srt.id
where rownum=1;
Note that I have changed your varchar values to integers just so I didn't have to type quite so much punctuation. It works, here's an example: https://www.db-fiddle.com/f/wwob31uiNgr9aAkZoe1Jgs/0

Oracle query to find unique occurrences of a column in a table

Here's my table:
ID|2ndID|Value
1|ABC|103
2|ABC|102
3|DEF|103
4|XYZ|105
My query should return all instance of the ID that has only one Value=103 for the 2ndID. It shouldn't return Ids 1 and 2 because apart from 103, ABC has 102 too. 3|DEF on the other hand has only one Value = 103. And I need such rows back. I don't need the 4|XYZ also since value <> 103. Based on the above sample set my result should only be.
3|DEF|103
I can use a group by 2ndID having COUNT(*) =1 which will return all but I don't know how to filter it only to Value = 103.
Thanks in advance.
This should return all the row with a single 2ndId value
select * from
my_table where 2ndId in (
select 2ndId
from my_table
group by 2ndId
having count(*) =1
)
And if you need to enforce the filter for value 103
select * from
my_table where 2ndId in (
select 2ndId
from my_table
group by 2ndId
having count(*) =1
)
and value = 103
This is a standard application of the HAVING clause in aggregate queries. You want to group by the second id, and select only the groups that have only one row and where the min(value) is 103. MIN(value) will be the unique value in the unique row, in the groups that only have one row to begin with; and you don't care about any other groups.
COMMENT: This solution assumes that the combination (second_id, value) is unique - it can't appear in the table more than once, for different id's. I asked the OP in a Comment under the original question to clarify whether this is in fact the case.
with
mytable ( id, second_id, value ) as (
select 1, 'ABC', 103 from dual union all
select 2, 'ABC', 102 from dual union all
select 3, 'DEF', 103 from dual union all
select 4, 'XYZ', 105 from dual
)
-- End of SIMULATED inputs (for testing only, NOT PART OF THE SOLUTION).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select min(id) as id, second_id, min(value) as value
from mytable
group by second_id
having count(*) = 1 and min(value) = 103
;
ID SECOND_ID VALUE
-- --------- -----
3 DEF 103

Need to fetch only 1 record for the sql requirement

I have a requirement where I need to fetch only one record for the id provided.
If the id is present in the table it will fetch that particular record and if it is not present, it will fetch the NULL valued record
Example
Table-: demo_table
id
-----------
1
2
3
4
5
(null)
For example if I am passing id as 1 it should return only 1st row, similarly If i am passing id as 4 it should fetch only 4th row but if I am passing id that is not there in the table, it should give me the 6th record i.e. NULL valued record.
I want this to be in Single SQL query. Thanks for your help in advance.
Let me know if you need more information.
Also , no ROWNUM, ROWID concept. Should be in 1 query, i.e. no MINUS,UNION etc
If I got the requirement correctly you want to have either the ID back of NULL instead.
A very easy trick is the following:
SELECT MAX(id) FROM demo_table WHERE id = 4;
This will either return the id 4 or, if not present, it will return NULL instead. Given that you always include the ID in the WHERE clause, the MAX does not do much more than just handling the NO ROWS FOUND for you.
SELECT * FROM
(
select id from demo_table where id = 4
union
select null from dual
)
where rownum = 1
order by id DESC
select t.* from
( select * from demo_table
where id = 4 or id is null) as t
where t.id =
( IF EXISTS(SELECT Id FROM demo_table WHERE ID = 4)
SELECT 4
ELSE
SELECT null ) ;
Try the following
SELECT CASE WHEN id = 123 THEN 'NULL' ELSE id END id
FROM (SELECT TO_CHAR (NVL (MIN (id), '123')) AS id
FROM my_table
WHERE id = 6)
Update 1
SELECT CASE WHEN TO_CHAR (id) IS NULL THEN 'NULL' ELSE id END id,
CASE WHEN ename IS NULL THEN 'NULL' ELSE ename END ename
FROM (SELECT TO_CHAR (id) id, ename
FROM my_table
WHERE id = 6
UNION
SELECT NULL, NULL
FROM DUAL
WHERE NOT EXISTS
(SELECT id, ename
FROM my_table
WHERE id = 6))

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