ORDER BY select statement using UNION & DISTINCT - sql-server

I'm using the following query to populate a dropdown list of values.
select 'Select a City' as City, 'All' as Value
UNION ALL
select distinct City, City as Value from BND_Listing
I'd like to sort A-Z the results. I've tried the following:
select 'Select a City' as City, 'All' as Value
UNION ALL
select distinct City, City as Value from BND_Listing
ORDER BY City ASC
But am getting an error:
Incorrect syntax near the keyword 'Union'.
Additionally this query is pulling "Blank or NULL" values and displaying a blank space at the top of the drop-down. I'd like to hide that if possible. Not display any null value?

You want to add a row to your result, which is always on top and carries a NULL as ID?
Try this:
SELECT *
FROM
(
SELECT NULL AS col1,'select an object' AS col2,0 AS SortInx
UNION ALL
SELECT TOP 10 object_id,name,ROW_NUMBER() OVER(ORDER BY name)
FROM sys.objects
) AS Sortable
ORDER BY SortInx
Short explanation: ROW_NUMBER() start with 1, so the first row gets 0 as sort index. The numbers from 1 to x represent the sorted name's order.
The outer SELECT will sort the result-set again making sure, that 0 is in front and 1 to x following...

I agree with most of the comments here where the best approach is to actually have the "Select a Value" row added in the application itself. It's probably best to have the database only delivering "actual" data to your application and handle things like that in the code.
I'm also not sure what this project is for, but if you have access, I would strongly recommend creating views and/or stored procedures at the database level to abstract any database schema and logic changes from your application.
Just out of curiosity, why are you selecting the same field twice with different aliases? I'm assuming you're setting the display value and the actual value in a simple HTML dropdown, but in this case, the values are the same, so you could only have one field in your result set and reference that value twice in the application. Doing this would solve the problem of your original question, as well as simplify your query (although a query this simple is going to have negligible cost anyway) to look like this:
SELECT DISTINCT City
FROM BND_Listing (NOLOCK)
WHERE City IS NOT NULL
ORDER BY 1 ASC
Depending on the data, DB config, etc, you may need to account for empty strings and/or leading/trailing spaces with something like this:
SELECT DISTINCT LTRIM(RTRIM(City)) AS City
FROM BND_Listing (NOLOCK)
WHERE LTRIM(RTRIM(City)) <> ''
AND City IS NOT NULL
ORDER BY 1
Sorry...I know that may have been a little overkill, but you said you were new to SQL, so I thought I'd just share that in case your NULL results were actually empty strings.

Thank you everyone for the responses it gave me a lot of insight on where to look for my problem. The original query with the addition of the below achieved the proper result.
working query:
select 'Select a City' as City, 'All' as Value
UNION ALL
select distinct City, City as Value from BND_Listing
where isnull(City,'') <> ''
Order by City ASC
with 'Select a City' always at the top of the dropdown. Credit to #scsimon on my other post for this.
with cte as(
select 'Select a City' as City, 'All' as Value
UNION ALL
select distinct City, City as Value from BND_Listing
where isnull(City,'') <> '')
select * from cte Order by case when City = 'Select a City' then 1 else 2 end, City ASC

Related

Same query giving different results

I am still new to working in databases, so please have patience with me. I have read through a number of similar questions, but none of them seem to be talking about the same issue I am facing.
Just a bit of info on what I am doing, I have a table filled with contact information, and some of the contacts are duplicated, but most of the duplicated rows have a truncated phone number, which makes that data useless.
I wrote the following query to search for the duplicates:
WITH CTE (CID, Firstname, lastname, phone, email, length, dupcnt) AS
(
SELECT
CID, Firstname, lastname, phone, email, LEN(phone) AS length,
ROW_NUMBER() OVER (PARTITION BY Firstname, lastname, email
ORDER BY Firstname) AS dupcnt
FROM
[data.com_raw]
)
SELECT *
FROM CTE
WHERE dupcnt > 1
AND length <= 10
I assumed that this query would find all records that have duplicates based on the three columns that I have specified, and select any that have the dupcnt greater than 1, and a phone column with a length less than or equal to 10. But when I run the query more than once I get different result sets each execution. There must be some logic that I am missing here, but I am completely baffled by this. All of the columns are of varchar datatype, except for CID, which is int.
Instead of ROW_NUMBER() use COUNT(*), and remove the ORDER BY since that's not necessary with COUNT(*).
The way you have it now, you are chunking up records into similar groups/partitions of records by firstname/lastname/email. Then you are ORDERING each group/partition by firstname. Firstname is part of the partition, meaning every firstname in that group/partition is identical. You will get different results depending on how SQL Server fetches the results from storage (which record it found first is 1, what it found second is 2). Every time it fetches records (every time you run this sql) it may fetch each record from disk or cache at a different order.
Count(*) will return ALL duplicate rows
So instead:
COUNT(*) OVER (PARTITION BY Firstname, lastname, email ) AS dupcnt
Which will return the number of records that share the same firstname, lastname, and email. You then keep any record that is greater than 1.
ORDER BY Firstname is non-deterministic here as they all have the same Firstname from the partition by
If CID is unique you could use that for the order by but I suspect you really want count.
I believe you are getting different results with every run would be because (a) unless clearly specified in the query, you can assume nothing about the order in which SQL return data in a query, and (b) the only ordering criteria you provide is by FirstName, which is far less precise than your grouping (Firstname, lastname, email).
As for the query itself, as written it assumes that the first item found in a given partition contains a valid phone number. Without specifying the order, you cannot know this will be true… and what if all items in a given grouping have invalid phone numbers? Below is my stab at pulling out the data you're looking for, in a hopefully useful format.
WITH CTE -- Sorry, I'm lazy and generally don't list the columns
AS
(
SELECT
Firstname
,lastname
,phone
,count(*) HowMany -- How many in group
,sum(case len(phone) when 10 then 1 else 0 end) BadLength -- How many "bad" in group
from data.com_raw
group by
Firstname
,lastname
,phone
having count(*) <> sum(case len(phone) when 10 then 1 else 0 end)
and count(*) > 1 -- Remove this to find singletons with invalid phone numbers
)
select
cr.CID
,cr.Firstname
,cr.lastname
,case len(cr.phone) when 10 then '' else 'Bad' end) IsBad
,cr.phone
,cr.email
from data.com_raw cr
inner join CTE
on CTE.Firstname = cr.Firstname
and CTE.lastname = cr.lastname
and CTE.phone = cr.phone
order by
cr.CID
,cr.Firstname
,cr.lastname
,case len(cr.phone) when 10 then '' else 'Bad' end)
,cr.phone
(Yes, if there are no indexes to support this, you will end up with a table scan.)
SELECT Firstname, lastname,email, COUNT(*)
FROM [data.com_raw]
GROUP BY Firstname, lastname,email HAVING COUNT(*)>1
WHERE LEN(PHONE)<= 10

Exclude multiple values using <>

I am trying to create a query that returns results excluding 4 specific values from a columns.
Here is my code:
SELECT CustomerID,
ContactName,
Country
FROM Customers
WHERE CustomerID IN (SELECT CustomerID
FROM Customers
WHERE (Country <> ('UK')('Australia')('Canada')));
This works if I exclude only one country but not multiple.
Thanks in advance for any help you can provide.
Use NOT IN operator
The list should be separated by comma no need to have open/close parenthesis for each value inside the list. Also you don't need sub-query just add the filter Where clause
SELECT CustomerID,
ContactName,
Country
FROM Customers
WHERE Country NOT IN ( 'USA', 'UK', 'Australia', 'Canada' )
Make sure you dont pass any NULL values inside the list because NOT IN fails when there is a NULL value present in the list
Select
CustomerID, ContactName, Country
From Customers
Where CustomerID NOT IN ('USA','UK','Australia','Canada');
You may consider defining excluded countries in a special table (e.g. ExcludedCountry). This will separate your data (excluded countries) from your logic (select customer not in excluded countries). In this case, your query can look like:
select CustomerID, ContactName, Country
from Customers C
left join ExcludedCountry EC ON EC.Name = C.CustomerID
where CustomerID IS NOT NULL
or
-- may be faster than above
select CustomerID, ContactName, Country
from Customers C
where not exists (
select 1
from ExcludedCountry EC
where EC.Name = C.CustomerID)
The advantage of this solution is an easier management of your excluded items.

Joining two selects - different column names

I need to build a SELECT statement showing all columns for JV1 - JV6.
How would I go about getting JV2, JV3, etc populated?
SELECT
Id as [JV1],
vp_pct as [JV1 Per],
vl_role as [JV1 Role],
[status] as [JV1 Status]
FROM era_project_allications_m
WHERE era_project_allications_m.jv_row_id = '1'
UNION ALL
SELECT
Id as [JV2],
vp_pct as [JV2 Per],
vl_role as [JV2 Role],
[status] as [JV2 Status]
FROM era_project_allications_m
WHERE era_project_allications_m.jv_row_id = '2'
I need the results to display columns containing the JV1, JV2 information.
Thanks for any advice!
Join and union are 2 different things. You can't have different column names in union but you can add a column that will hold the source table name (hard coded) to help you understand where the row comes from:
SELECT
Id,
vp_pct,
vl_role,
[status],
'1' as sourceId
FROM era_project_allications_m
WHERE jv_row_id = '1'
UNION ALL
SELECT
Id,
vp_pct,
vl_role,
[status],
'2'
FROM era_project_allications_m
WHERE jv_row_id = '2'
though based on your sample code you might just want to do this:
SELECT
Id,
vp_pct,
vl_role,
[status],
jv_row_id as sourceId
FROM era_project_allications_m
WHERE jv_row_id IN('1', '2'...'n')
I Figured this one out.
I simply used 6 select statements, giving them each an aliases and a unique ID (project ID as Select_1_ID, Select_2_ID) and LEFT JOIN'd them together using the Select 1's ID).
Rather long-winded but it works - I suspect it isn't very efficient though, Thankfully it only returns 24k rows.

Summarizing count of multiple talbes in one row or column

I've designed a migration script and as the last sequence, I'm running the following two lines.
select count(*) from Origin
select count(*) from Destination
However, I'd like to present those numbers as cells in the same table. I haven't decided yet if it's most suitable to put them as separate rows in one column or adjacent columns on one row but I do want them in the same table.
How can I select stuff from those selects into vertical/horizontal line-up?
I've tried select on them both with and without parentheses but id didn't work out (probably because of the missing from)...
This questions is related to another one but differs in two aspects. Firstly, it's much more to-the-point and clearer states the issue. Secondly, it asks about both horizontal and vertical line-up of the selected values whereas the linked questions only regards the former.
select
select count(*) from Origin,
select count(*) from Destination
select(
select count(*) from Origin,
select count(*) from Destination)
You need to nest the two select statements under a main (top) SELECT in order to get one row with the counts of both tables:
SELECT
(select count(*) from Origin) AS OriginCount,
(select count(*) from Destination) AS DestinationCount
SQLFiddle for the above query
I hope this is what you are looking for, since the "same table" you are mentioning is slightly confusing. (I'm assuming you're referring to result set)
Alternatively you can use UNION ALL to return two cells with the count of both tables.
SELECT COUNT(*), 'Origin' 'Table' FROM ORIGIN
UNION ALL
SELECT COUNT(*), 'Destination' 'Table' FROM Destination
SQLFiddle with UNION ALL
SQLFiddle with UNION
I recommend adding the second text column so that you know the corresponding table for each number.
As opposed to simple UNION the UNION ALL command will return two rows everytime. The UNION command will generate a single result (single cell) if the count of rows in both tables is the same (the same number).
...or if you want vertical...
select 'OriginalCount' as Type, count(*)
from origin
union
select 'DestinationCount' as Type, count(*)
from destination

dblookupcombobox has a null row

I have a question about DBLookupComboBox.
I have a program that has a database I wrote. It has everything, except when I open DBLookupComboBox it must have a row with null value for when user wants to select nothing. But there isn't one. How can I make a null row show up?
You must either add a row that says 'Nothing' or 'All', which ever suits. The usual solution is a UNION query, which can be used as the RowSource of the combobox. The Union query can be used to add virtual fields.
If the combo comtains only unique values, you can say:
SELECT "Nothing" As Description
FROM ATable
UNION
SELECT Description
FROM ATable
UNION eliminates duplicates, UNION ALL< does not, so if there are matching rows, you can say:
SELECT DISTINCT "Nothing" As Description
FROM ATable
UNION ALL
SELECT Description
FROM ATable
If you want "Nothing" to sort first, you must juggle a little and use " Nothing", or "-Nothing", but if you have an ID or Key column you can get a nice sort, like so:
SELECT 0 As ID, "Nothing" As Description
FROM ATable
UNION
SELECT ID, Description
FROM ATable

Resources