Checking using nvl2 with multiple group by - sql

I have a table like
------------------------
S.No Name Amount Imp_Num
1 A 10 12345
2 B 20
3 A 30
4 C 40 4555
5 B 50
--------------------------
and I want something like
---------------------------------------
Name Total_Amount Imp_Num Imp_Num_Present
A 40 12345 Y
B 70 null N
C 40 4555 Y
---------------------------------------
The important_number_present column should be Y if the important number is present for the particular name at least once and the important number should be captured. The important number for a particular name is assumed to be the same.If different the latest one should be displayed as imp_numb. (But this is of secondary priority).
I tried something like
Select sum(amount) as total_amount, imp_num, nvl2(imp_num,'Y','N') from sampletable group by imp_num;
But name can't be retrieved and the data doesn't make sense without the name. I might be doing something totally wrong. Can a feasible solution be done in SQL rather than in pl/sql.
Group by with name is returning the name with a null entry and imp_num entry.
I am cracking my head on this. Would be of great help, if someone solves it.
Thanks in advance

You could use a (fake) aggregation function on imp_num and group by name
Select Name, sum(amount) as total_amount, max(imp_num), nvl2( max(imp_num),'Y','N')
from sampletable
group by Name;

EDIT: Another solution with COUNT function. DEMO
SELECT name
,SUM(amount) AS total_amount
,MAX(imp_num) AS Imp_Num
,CASE
WHEN Count(imp_num) > 0
THEN 'Y'
ELSE 'N'
END AS Imp_Num_Present
FROM yourtable
GROUP BY name
You may also use a MAX( CASE ) block
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE yourtable
(S_No int, Name varchar2(1), Amount int, Imp_Num varchar2(5))
;
INSERT ALL
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (1, 'A', 10, '12345')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (2, 'B', 20, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (3, 'A', 30, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (4, 'C', 40, '4555')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (5, 'B', 50, NULL)
SELECT * FROM dual
;
Query 1:
SELECT Name,
SUM (amount) AS total_amount,
MAX (imp_num) AS Imp_Num,
CASE
WHEN MAX (CASE WHEN imp_num IS NOT NULL THEN 1 ELSE 0 END) = 1
THEN
'Y'
ELSE
'N'
END
AS Imp_Num_Present
FROM yourtable
GROUP BY Name
Results:
| NAME | TOTAL_AMOUNT | IMP_NUM | IMP_NUM_PRESENT |
|------|--------------|---------|-----------------|
| A | 40 | 12345 | Y |
| B | 70 | (null) | N |
| C | 40 | 4555 | Y |

Related

Get the min of one column but select multiple columns

I have a table as following:
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I want the min(Amount) for each ID but I still want to display its Name. So I want this:
ID NAME min(AMOUNT)
______________________
1 A 3
2 C 18
4 I 2
ID's can occur multiple times, Names too. I tried this:
SELECT ID, NAME, min(AMOUNT) FROM TABLE
GROUP BY ID
But of course its an error because I have to
GROUP BY ID, NAME
But then I get
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I understand why, it looks for the min(AMOUNT) for each combination of ID + NAME. So my question is basically, how can I select multiple column (ID, NAME, AMOUNT) and get the minimum for only one column, still displaying the others?
Im new to SQL but I cant seem to find an answer..
If you are using PostgreSQL, SQL Server, MySQL 8.0 and Oracle then try the following with window function row_number().
in case you have one id with similar amount then you can use dense_rank() instead of row_number()
Here is the demo.
select
id,
name,
amount
from
(
select
*,
row_number() over (partition by id order by amount) as rnk
from yourTable
) val
where rnk = 1
Output:
| id | name | amount |
| --- | ---- | ------ |
| 1 | A | 3 |
| 2 | C | 18 |
| 4 | I | 2 |
Second Option without using window function
select
val.id,
t.name,
val.amount
from myTable t
join
(
select
id,
min(amount) as amount
from myTable
group by
id
) val
on t.id = val.id
and t.amount = val.amount
You did not specify your db vendor. If it is luckily Postgres, the problem can be also solved without nested subquery using proprietary distinct on clause:
with t(id,name,amount) as (values
(1, 'A', 3),
(1, 'B', 4),
(1, 'W', 3),
(2, 'C', 18),
(4, 'I', 2),
(4, 'P', 9)
)
select distinct on (id, name_of_min) id
, first_value(name) over (partition by id order by amount) as name_of_min
, amount
from t
order by id, name_of_min
Just for widening knowledge. I don't recommend using proprietary features. first_value is standard function but to solve problem in simple query is still not enough. #zealous' answer is perfect.
In many databases, the most efficient method uses a correlated subquery:
select t.*
from t
where t.amount = (select min(t2.amount) from t t2 where t2.id = t.id);
In particular, this can take advantage of an index on (id, amount).

How to SUM column 1 and select column 2 by condition?

I've stuck with how to sum column A and select column B with a condition if column B >= 50 select this row id.
Example Table Like this
+----+-----------+---------+
| ID | PRICE | PERCENT |
+----+-----------+---------+
| 1 | 5 | 5 |
| 2 | 18 | 20 |
| 3 | 7 | 50 |
| 4 | 16 | 56 |
| 5 | 50 | 87 |
| 6 | 17 | 95 |
| 7 | 40 | 107 |
+----+-----------+---------+
SELECT ID, SUM(PRICE) AS PRICE, PERCENT FROM Table
Column ID and PERCENT, I want to select from a row with PERCENT >= 50
The result should be
Any suggestions?
Try below query:
declare #tbl table(ID int, PRICE int, [PERCENT] int);
insert into #tbl values
(1, 5, 5),
(2, 18, 20),
(3, 7, 50),
(4, 16, 56),
(5, 50, 87),
(6, 17, 95),
(7, 40, 107);
select top 1 ID,
(select sum(PRICE) from #tbl) PRICE,
[PERCENT]
from #tbl
where [PERCENT] > 50
You could include the total in a subquery in the SELECT clause of your query like this:
SELECT
[ID],
(SELECT SUM([PRICE]) FROM T) AS [PRICE],
[PERCENT]
FROM
T
WHERE
[PRICE] >= 50
However, it remains unclear which of the five valid records should be picked. You indicated it should be the record where PERCENT has value 56, but IMHO value 50 would be possible too, just like 87, 95, and 107 (?). It is unclear why you pick value 56 as the correct one. If it doesn't matter, you could use TOP (1) in the SELECT clause, but if it does matter, you should extend the WHERE clause with appropriate conditions/filters.
Mixing aggregate data from groups back with individual elements/records like this is often fuzzy. I consider it to be a "code smell" and here in your question on StackOverflow, it might indicate an XY-problem. Anyway, these query results might get misinterpreted quite easily if you are not careful. Always remember that such aggregated data in the result (in this case the PRICE field) has practically nothing to do with the detail data in the result (in this case the ID and PERCENT fields). Unless you want to combine your aggregate data with your detail data (in a calculation for example), but you do not indicate you want anything like that in your question...
you can do this Trick to have a result of 2 queries in 1 query:
select ID as ID,T.[PERCENT] AS B, 0 as sumA
from Table_1 as T
where T.[PERCENT]>=50
union All
select 0 as ID,0 AS B, sum(t.[PRICE]) as sumA
from Table_1 as T
Am not sure why you need this but certainly, You can Archive Above Output using below query
Sample Data
declare #data table
(Id int, Price int, [Percent] int)
insert #data
VALUES (1,5,5),
(2,18,20),
(3,7,50),
(4,16,56),
(5,50,87),
(6,17,95),
(7,40,107)
Query
select top 1 ID, (select sum(price) from #data) as Price, [Percent ]
from #data
where [Percent ] >50
You can try the following code:
SELECT TOP (1) [ID], SUM(PRICE) OVER (), [PERCENT]
FROM #tbl
ORDER BY CASE WHEN [PERCENT] > 50 THEN 0 ELSE 1 END, [ID];
I am using OVER clause in order to extract/read data from the table only once - one table scan.

Check the conditions on the rows having same ids in sql server

I have a table as below
Id | CompanyName
--- | ---
100 | IT
100 | R&D
100 | Financial
100 | Insurance
110 | IT
110 | Financial
110 | Product Based
111 | R&D
111 | IT
The table contains the data like the above structure but contains thousands of ids like these
I want to find out all the ids in which all the company names are IT and R&D.If for any of the ids the company name is not in neither IT not R&D then dont consider those ids.
e.g. id 100 cannot be in this list because it has extra company name as Financial but id 111 will be considered because all the companies are IT and R&D
Any help?
select id,
sum(case when CompanyName='IT' OR CompanyName='R&D' then 1 else 0 end) as c1
from t
group by id
having count(*)=2 and c1=2
I would do this as:
select id
from t
having sum(case when CompanyName not in ('IT', 'R&D') then 1 else 0 end) = 0 and
sum(case when CompanyName in ('IT', 'R&D') then 1 else 0 end) = 2; -- use "> 0" if you want either one.
Note: This assumes that you don't have duplicates in the table.
SELECT DISTINCT Id
FROM companies
WHERE
Id NOT IN (
SELECT DISTINCT Id
FROM companies
WHERE
CompanyName != 'IT'
AND CompanyName != 'R&D'
)
;
Subquery: Select all records - only Id, distinctly - with CompanyName different from "IT" and "R&D".
Main query: Select all records - only Id, distinctly - which are not in the prior list.
Results: 111
Tested on sqlfiddle.com with option "MS SQL Server 2014" and schema syntax:
CREATE TABLE companies
([Id] int, [CompanyName] varchar(255))
;
INSERT INTO companies
([Id], [CompanyName])
VALUES
(100, 'IT'),
(100, 'R&D'),
(100, 'Financial'),
(100, 'Insurance'),
(110, 'IT'),
(110, 'Financial'),
(110, 'Product Based'),
(111, 'R&D'),
(111, 'IT')
;

List the names of people that have never scored above 3

From a table like this:
Name | Score
------ | ------
Bill | 1
Bill | 2
Bill | 1
Steve | 1
Steve | 4
Steve | 1
Return the names of people that have never scored above 3
Answer would be:
Name |
------ |
Bill |
The key is to get the maximum score for each person, then filter to those whose maximum is less than 3. To get the maximum you need to do an aggregate (GROUP BY and MAX). Then to apply filters to aggregates you must use HAVING rather than WHERE. So you would end up with:
SELECT Name, MAX(Score) AS HighScore
FROM Table
GROUP BY Name
HAVING MAX(Score) <= 3;
one solution would be:
SELECT DISTINCT name
FROM mytable
WHERE Name NOT IN
( SELECT Name
FROM mytable
WHERE score > 3
)
sample table :
DECLARE #Table1 TABLE
(Name varchar(5), Score int)
;
INSERT INTO #Table1
(Name, Score)
VALUES
('Bill', 1),
('Bill', 2),
('Bill', 1),
('Steve', 1),
('Steve', 4),
('Steve', 1)
;
Script :
;with CTE AS (
select Name,Score from #Table1
GROUP BY Name,Score
HAVING (Score) > 3 )
Select
NAME,
Score
from #Table1 T
where not EXISTS
(select name from CTE
where name = T.Name )
Result :
NAME Score
Bill 1
Bill 2
Bill 1
SELECT name
FROM table_name
WHERE score < 3

SQL query using aggregate function

Taking the following table EXAMPLE:
Name | List | FlagByList
---------------------------
A | 1 | Y
A | 2 | Y
B | 1 | Y
B | 2 | N
C | - | -
C | - | -
I want to return the Names which have 'Y' in all Lists AND the Names which are not present in any list.
A simple aggregate query can do this.
SELECT Name
FROM Table
GROUP BY Name
HAVING COUNT(1) = COUNT(CASE FlagByList WHEN 'Y' THEN 1 END) --counts all rows with Y as Value
OR COUNT(1) = COUNT(CASE WHEN FlagByList IS NULL THEN 1 END); --counts all rows with NULL as value
With decode
select name from example
group by Name
having sum(decode( FlagByList, 'Y',1, 0)) = count(*)
OR sum(decode(List, NULL, 0, 1)) = count(*)
Please make use of the below code. Its working fine with SQL server 2012.
DECLARE #Table TABLE (Name char(2),List char(2) , FlagByList char(2))
INSERT #Table
(Name,List,FlagByList)
VALUES
('A','1','Y'),
('A','2','Y'),
('B','1','Y'),
('B','2','N'),
('C','-','-'),
('C','-','-')
SELECT DISTINCT(Name) FROM #Table WHERE FlagByList ='Y'
UNION
SELECT DISTINCT(Name) FROM #Table WHERE FlagByList ='-'
EXCEPT
SELECT DISTINCT(Name) FROM #Table WHERE FlagByList ='N'