pick group by from a col where col2 is NULL - sql

I have to write a report by doing some SQL in MS SQL server. The data I have is like this:
UserID,Country, CommNumber
00001, IN, 1001
00002, IN, NULL
00003, US, 1002
00004, US, 1003
00005, DE, NULL
00006, DE, NULL
00007, US, NULL
Now I want to pull up the list of countries where all CommNumbers are NULL. Even if one user has a CommNumber in that country, I don't want that country to be in list. So looking at above only DE has all two users with NULL on CommNumber. US and IN have atleast one user where the CommNumber is not NULL.
Hope this question makes sense.
My attempt is:
SELECT
[COUNTRY]
,COUNT(*) AS 'COMMNUMBER_USERS'
FROM
<TABLENAME>
WHERE [COMMNUMBER] IS NULL
GROUP BY [C]
ORDER BY [COMMNUMBER_USERS]
The above is not giving me the correct results. I understand why because I don't have way to tell it that I only want countries where all commnumbers are null.

I would use NOT EXISTS :
SELECT t.*
FROM table t
WHERE NOT EXISTS (SELECT 1 FROM table t1 WHERE t1.Country = t.Country AND t1.CommNumber IS NOT NULL);
If you want only Country then you can do aggregation :
select country
from table t
group by country
having max(commnumber) is null;

You can simply use group by and having:
select country
from t
group by country
having max(commnumber) is null;

You can try using correlated subquery
select * from tablename a where not exists
(select 1 from tablename b where a.country=b.country and b.commnumber is not null)

SELECT Country
FROM Tablename
GROUP BY Country
HAVING sum(ISNULL(Commnumber, 0)) = 0
You can use this one as well

Related

finding value in a list created via subquery

Thank you Stack-Community,
This is probably obvious for most of you but I just don't understand why it doesn't work.
I am using the Northwind database and lets say I am trying to find the countries that or not occurring twice but are listed either more than twice or less often.
I already figured out other ways of doing it with a having statement, so I am not looking for alternatives but trying to understand why my initial attempt is not working.
I look at it and look at it and it makes perfect sense to me. Can someone explain what's the problem?
SELECT country, count(country)
FROM Customers
WHERE 2 not in (SELECT count(country) FROM Customers GROUP BY country)
GROUP BY country
;
You need correlated subquery:
SELECT country, count(country)
FROM Customers c
WHERE 2 not in (SELECT count(country) FROM Customers c2
WHERE c2.country = c.country )
GROUP BY country;
Otherwise you get something like:
SELECT country, count(country)
FROM Customers c
WHERE 2 not in (1,2,3) -- false in every case and empty resultset
GROUP BY country;
Imagine that you have:
1, 'UK' -- 1
2, 'DE' -- 2
3, 'DE'
4, 'RU' -- 1
Now you will get equivalent of
SELECT country, count(country)
FROM Customers c
WHERE 2 not in (1,2,1) -- false in every case and empty resultset
GROUP BY country;
-- 0 rows selected

DB2 SQL Getting distinct value when grouping rows

BUSINESSTABLE looks like this:
HOTEL_CHAIN HOTEL_LOCATION HOTEL_OWNER
_____________________________________________________
Marriott Las Vegas Nelson
Best Western New York Richards
Best Western San Francisco Smith
Marriott New York Nelson
Hilton Boston James
I'm trying to execute an SQL statement in a DB2 database that groups these entries by HOTEL_CHAIN. If the rows that are grouped together contain the same HOTEL_LOCATION or HOTEL_OWNER, that info should be preserved. Otherwise, a value of 'NULL' should be displayed. For example, both Marriott hotels have the same owner, Nelson, so I want to display that information in the new table. However, each Marriott hotel is in a different location, so I'd like to display 'NULL' in that column.
The resulting table (HOTELTABLE) should look like this:
HOTEL_CHAIN HOTEL_LOCATION HOTEL_OWNER
_____________________________________________________
Marriott NULL Nelson
Best Western NULL NULL
Hilton Boston James
I'm trying to use the following SQL statement to accomplish this:
INSERT INTO HOTELTABLE(HOTEL_CHAIN,HOTEL_LOCATION,HOTEL_OWNER)
SELECT
HOTEL_CHAIN,
CASE COUNT(DISTINCT(HOTEL_LOCATION)) WHEN 1 THEN HOTEL_LOCATION ELSE 'NULL' END,
CASE COUNT(DISTINCT(HOTEL_OWNER)) WHEN 1 THEN HOTEL_OWNER ELSE 'NULL' END,
FROM BUSINESSTABLE GROUP BY HOTEL_CHAIN
I get an SQL error SQLCODE-119 A COLUMN OR EXPRESSION IN A HAVING CLAUSE IS NOT VALID. It seems to be complaining about the 2nd HOTEL_LOCATION and the 2nd HOTEL_OWNER within my case statements. I also tried using DISTINCT(HOTEL_LOCATION) and that threw another error. Can someone please explain the correct way to code this? Thank you!
Don't use COUNT(DISTINCT). Use MIN() and MAX():
INSERT INTO HOTELTABLE(HOTEL_CHAIN,HOTEL_LOCATION,HOTEL_OWNER)
SELECT HOTEL_CHAIN,
(CASE WHEN MIN(HOTEL_LOCATION) = MAX(HOTEL_LOCATION)
THEN MIN(HOTEL_LOCATION) ELSE 'NULL'
END),
(CASE WHEN MIN(HOTEL_OWNER) = MAX(HOTEL_OWNER)
THEN MIN(HOTEL_OWNER) ELSE 'NULL'
END)
FROM BUSINESSTABLE
GROUP BY HOTEL_CHAIN;
Notes:
Why not COUNT(DISTINCT)? It is generally much more expensive than MIN() and MAX() because it needs to maintain internal lists of all values.
I don't approve of a string value called 'NULL'. Seems like it is designed to foster confusion. Perhaps just NULL the value itself?
I agree Gordon for the null (gj Gordon).
other method
INSERT INTO HOTELTABLE(HOTEL_CHAIN,HOTEL_LOCATION,HOTEL_OWNER)
select distinct f1.HOTEL_CHAIN,
case when f2.HasDiffLocation is not null then 'NULL' else f1.HOTEL_LOCATION end as HOTEL_LOCATION,
case when f3.HasDiffOwner is not null then 'NULL' else f1.HOTEL_OWNER end as HOTEL_OWNER
from BUSINESSTABLE f1
left outer join lateral
(
select 1 HasDiffLocation from BUSINESSTABLE f2b
where f1.HOTEL_CHAIN=f2b.HOTEL_CHAIN and f1.HOTEL_LOCATION<>f2b.HOTEL_LOCATION
fetch first rows only
) f2 on 1=1
left outer join lateral
(
select 1 HasDiffOwner from BUSINESSTABLE f3b
where f1.HOTEL_CHAIN=f3b.HOTEL_CHAIN and f1.HOTEL_OWNER<>f3b.HOTEL_OWNER
fetch first rows only
) f3 on 1=1
or like this :
INSERT INTO HOTELTABLE(HOTEL_CHAIN,HOTEL_LOCATION,HOTEL_OWNER)
select distinct f1.HOTEL_CHAIN,
ifnull(f2.result, f1.HOTEL_LOCATION) as HOTEL_LOCATION,
ifnull(f3.result, f1.HOTEL_OWNER) as HOTEL_LOCATION,
from BUSINESSTABLE f1
left outer join lateral
(
select 'NULL' result from BUSINESSTABLE f2b
where f1.HOTEL_CHAIN=f2b.HOTEL_CHAIN and f1.HOTEL_LOCATION<>f2b.HOTEL_LOCATION
fetch first rows only
) f2 on 1=1
left outer join lateral
(
select 'NULL' result from BUSINESSTABLE f3b
where f1.HOTEL_CHAIN=f3b.HOTEL_CHAIN and f1.HOTEL_OWNER<>f3b.HOTEL_OWNER
fetch first rows only
) f3 on 1=1

SQL: multiple counts from same table

I am having a real problem trying to get a query with the data I need. I have tried a few methods without success. I can get the data with 4 separate queries, just can't get hem into 1 query. All data comes from 1 table. I will list as much info as I can.
My data looks like this. I have a customerID and 3 columns that record who has worked on the record for that customer as well as the assigned acct manager
RecID_Customer___CreatedBy____LastUser____AcctMan
1-------1374----------Bob Jones--------Mary Willis------Bob Jones
2-------1375----------Mary Willis------Bob Jones--------Bob Jones
3-------1376----------Jay Scott--------Mary Willis-------Mary Willis
4-------1377----------Jay Scott--------Mary Willis------Jay Scott
5-------1378----------Bob Jones--------Jay Scott--------Jay Scott
I want the query to return the following data. See below for a description of how each is obtained.
Employee___Created__Modified__Mod Own__Created Own
Bob Jones--------2-----------1---------------1----------------1
Mary Willis------1-----------2---------------1----------------0
Jay Scott--------2-----------1---------------1----------------1
Created = Counts the number of records created by each Employee
Modified = Number of records where the Employee is listed as Last User
(except where they created the record)
Mod Own = Number of records for each where the LastUser = Acctman
(account manager)
Created Own = Number of Records created by the employee where they are
the account manager for that customer
I can get each of these from a query, just need to somehow combine them:
Select CreatedBy, COUNT(CreatedBy) as Created
FROM [dbo].[Cust_REc] GROUP By CreatedBy
Select LastUser, COUNT(LastUser) as Modified
FROM [dbo].[Cust_REc] Where LastUser != CreatedBy GROUP By LastUser
Select AcctMan, COUNT(AcctMan) as CreatePort
FROM [dbo].[Cust_REc] Where AcctMan = CreatedBy GROUP By AcctMan
Select AcctMan, COUNT(AcctMan) as ModPort
FROM [dbo].[Cust_REc] Where AcctMan = LastUser AND NOT AcctMan = CreatedBy GROUP By AcctMan
Can someone see a way to do this? I may have to join the table to itself, but my attempts have not given me the correct data.
The following will give you the results you're looking for.
select
e.employee,
create_count=(select count(*) from customers c where c.createdby=e.employee),
mod_count=(select count(*) from customers c where c.lastmodifiedby=e.employee),
create_own_count=(select count(*) from customers c where c.createdby=e.employee and c.acctman=e.employee),
mod_own_count=(select count(*) from customers c where c.lastmodifiedby=e.employee and c.acctman=e.employee)
from (
select employee=createdby from customers
union
select employee=lastmodifiedby from customers
union
select employee=acctman from customers
) e
Note: there are other approaches that are more efficient than this but potentially far more complex as well. Specifically, I would bet there is a master Employee table somewhere that would prevent you from having to do the inline view just to get the list of names.
this seems pretty straight forward. Try this:
select a.employee,b.created,c.modified ....
from (select distinct created_by from data) as a
inner join
(select created_by,count(*) as created from data group by created_by) as b
on a.employee = b.created_by)
inner join ....
This highly inefficient query may be a rough start to what you are looking for. Once you validate the data then there are things you can do to tidy it up and make it more efficient.
Also, I don't think you need the DISTINCT on the UNION part because the UNION will return DISTINCT values unless UNION ALL is specified.
SELECT
Employees.EmployeeID,
Created =(SELECT COUNT(*) FROM Cust_REc WHERE Cust_REc.CreatedBy=Employees.EmployeeID),
Mopdified =(SELECT COUNT(*) FROM Cust_REc WHERE Cust_REc.LastUser=Employees.EmployeeID AND Cust_REc.CreateBy<>Employees.EmployeeID),
ModOwn =
CASE WHEN NOT Empoyees.IsManager THEN NULL ELSE
(SELECT COUNT(*) FROM Cust_REc WHERE AcctMan=Employees.EmployeeID)
END,
CreatedOwn=(SELECT COUNT(*) FROM Cust_REc WHERE AcctMan=Employees.EmployeeID AND CReatedBy=Employees.EMployeeID)
FROM
(
SELECT
EmployeeID,
IsManager=CASE WHEN EXISTS(SELECT AcctMan FROM CustRec WHERE AcctMan=EmployeeID)
FROM
(
SELECT DISTINCT
EmployeeID
FROM
(
SELECT EmployeeID=CreatedBy FROM Cust_Rec
UNION
SELECT EmployeeID=LastUser FROM Cust_Rec
UNION
SELECT EmployeeID=AcctMan FROM Cust_Rec
)AS Z
)AS Y
)
AS Employees
I had the same issue with the Modified column. All the other columns worked okay. DCR example would work well with the join on an employees table if you have it.
SELECT CreatedBy AS [Employee],
COUNT(CreatedBy) AS [Created],
--Couldn't get modified to pull the right results
SUM(CASE WHEN LastUser = AcctMan THEN 1 ELSE 0 END) [Mod Own],
SUM(CASE WHEN CreatedBy = AcctMan THEN 1 ELSE 0 END) [Created Own]
FROM Cust_Rec
GROUP BY CreatedBy

SQL query - Selecting distinct values from a table

I have a table in which i have multiple entries against a FK. I want to find out the value of FK which do not have certain entries e.g
my table has following entries.
PK----------------FK-----------------Column entries
1----------------100-----------------ab1
2----------------100-----------------ab2
3----------------100-----------------ab4
4----------------200-----------------ab1
5----------------200-----------------ab2
6----------------200-----------------ab3
7----------------300-----------------ab1
8----------------300-----------------ab2
9----------------300-----------------ab3
10---------------300-----------------ab4
Now, from this table i want to filter all those FK which do not have ab3 or ab4 in them. Certainly, i expect distinct values i.e. in this case result would be FK= 100 and 200.
The query which i am using is
select distinct(FK)
from table1
where column_entries != 'ab3'
or column_entries != 'ab4';
Certainly, this query is not fetching the desired result.
try the following :-
select distinct fk_col from table1
minus
(select distinct fk_col from table1 where col_entry='ab3'
intersect
select distinct fk_col from table1 where col_entry='ab4')
This will show all those FKs which do not have 'ab3' and 'ab4'. i.e. 100 and 200 in your case
The below script may be the solution if I got your question in a right way.
SELECT DISTINCT(TableForeignKey)
FROM Test
WHERE TableForeignKey NOT IN (
SELECT T1.TableForeignKey
FROM Test T1 INNER JOIN Test T2 ON T1.TableForeignKey = T2.TableForeignKey
WHERE T1.TableEntry = 'ab3' AND T2.TableEntry = 'ab4')
SQLFiddle Demo
You could use GROUP BY with conditional aggregation in HAVING:
SELECT FK
FROM table1
GROUP BY FK
HAVING COUNT(CASE column_entries WHEN 'ab3' THEN 1 END) = 0
OR COUNT(CASE column_entries WHEN 'ab4' THEN 1 END) = 0
;
The two conditional aggregates count 'ab3' and 'ab4' entries separately. If both end up with results greater than 0, then the corresponding FK has both 'ab3' and 'ab4' and is thus not returned. If at least one of the counts evaluates to 0, then FK is considered satisfying the requirements.

How to select a row for certain (or give preference in the selection) in mysql?

Need your help guys in forming a query.
Example.
Company - Car Rental
Table - Cars
ID NAME STATUS
1 Mercedes Showroom
2 Mercedes On-Road
Now, how do I select only one entry from this table which satisfies the below conditions?
If Mercedes is available in Showroom, then fetch only that row. (i.e. row 1 in above example)
But If none of the Mercedes are available in the showroom, then fetch any one of the rows. (i.e. row 1 or row 2) - (This is just to say that all the mercedes are on-road)
Using distinct ain't helping here as the ID's are also fetched in the select statement
Thanks!
Here's a common way of solving that problem:
SELECT *,
CASE STATUS
WHEN 'Showroom' THEN 0
ELSE 1
END AS InShowRoom
FROM Cars
WHERE NAME = 'Mercedes'
ORDER BY InShowRoom
LIMIT 1
Here's how to get all the cars, which also shows another way to solve the problem:
SELECT ID, NAME, IFNULL(c2.STATUS, c1.STATUS)
FROM Cars c1
LEFT OUTER JOIN Cars c2
ON c2.NAME = c1.NAME AND c2.STATUS = 'Showroom'
GROUP BY NAME
ORDER BY NAME
You would want to use the FIND_IN_SET() function to do that.
SELECT *
FROM Cars
WHERE NAME = 'Mercedes'
ORDER BY FIND_IN_SET(`STATUS`,'Showroom') DESC
LIMIT 1
If you have a preferred order of other statuses, just add them to the second parameter.
ORDER BY FIND_IN_SET(`STATUS`,'On-Road,Showroom' ) DESC
To fetch 'best' status for all cars you can simply do this:
SELECT *
FROM Cars
GROUP BY NAME
ORDER BY FIND_IN_SET(`STATUS`,'Showroom') DESC
SELECT * FROM cars
WHERE name = 'Mercedes'
AND status = 'Showroom'
UNION SELECT * FROM cars
WHERE name = 'Mercedes'
LIMIT 1;
EDIT Removed the ALL on the UNION since we only want distinct rows anyway.
MySQL doesn't have ranking/analytic/windowing functions, but you can use a variable to simulate ROW_NUMBER functionality (when you see "--", it's a comment):
SELECT x.id, x.name, x.status
FROM (SELECT t.id,
t.name,
t.status,
CASE
WHEN #car_name != t.name THEN #rownum := 1 -- reset on diff name
ELSE #rownum := #rownum + 1
END AS rank,
#car_name := t.name -- necessary to set #car_name for the comparison
FROM CARS t
JOIN (SELECT #rownum := NULL, #car_name := '') r
ORDER BY t.name, t.status DESC) x --ORDER BY is necessary for rank value
WHERE x.rank = 1
Ordering by status DESC means that "Showroom" will be at the top of the list, so it'll be ranked as 1. If the car name doesn't have a "Showroom" status, the row ranked as 1 will be whatever status comes after "Showroom". The WHERE clause will only return the first row for each car in the table.
The status being a text based data type tells me your data is not normalized - I could add records with "Showroom", "SHOWroom", and "showROOM". They'd be valid, but you're looking at using functions like LOWER & UPPER when you are grouping things for counting, sum, etc. The use of functions would also render an index on the column useless... You'll want to consider making a CAR_STATUS_TYPE_CODE table, and use a foreign key relationship to make sure bad data doesn't get into your table:
DROP TABLE IF EXISTS `example`.`car_status_type_code`;
CREATE TABLE `example`.`car_status_type_code` (
`car_status_type_code_id` int(10) unsigned NOT NULL auto_increment,
`description` varchar(45) NOT NULL default '',
PRIMARY KEY (`car_status_type_code_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;