Is it Possible to Use IF/Else in SQL? - sql

Is it possible to use if/else in SQL? If I have a table called supplier with columns: sid -> primary key, sname and city.
Then I wish to:
select sid from supplier where city="taipei" if not empty.
Or select sid from supplier where city="tainan"

Yes, you can. I don't know about other DBMS but I have used such things in Microsot SQL Server in my Stored Procedure like this;
IF EXISTS
(SELECT [sid] FROM [supplier] WHERE [city]= "taipei")
select sid from supplier where city="taipei" // your true condition query
ELSE
select sid from supplier where city="tainan"
In MySQL From this link, it turns out that is also possible. see;
IF EXISTS(SELECT * FROM tbl_name WHERE category_code ='some-category-code') THEN UPDATE tbl_name SET active='0' WHERE category_code = 'some-category-code' END IF

It was unclear what you want to do (I leave my previous hypotheses below).
You want to associate a priority to your suppliers, so that the one for Taipei is selected, but if it is unavailable, then Tainan gets selected instead.
In this specific case you can just use:
SELECT sid FROM supplier WHERE city = (
SELECT MAX(city) FROM supplier WHERE city IN ('Taipei', 'Tainan')
);
The inner sub-SELECT will retrieve Taipei or, if unavailable, Tainan.
This uses the fact that Taipei is lexicographically greater than Tainan, but if you wanted a more flexible solution, MAX would not work. In that case you would change the subselect to sort cities in order of desirability (missing cities are of course undesirable) and then fetch the one most desirable:
SELECT sid FROM supplier WHERE city = (
SELECT city FROM supplier ORDER BY CASE
WHEN city = 'Taipei' THEN 1
WHEN city = 'Tainan' THEN 2
WHEN city = 'New York' THEN 3
ELSE 4
END
LIMIT 1
);
The subselect now will retrieve first Taipei, but missing Taipei it will get to Tainan and so on.
Note that if you want only one SID, you can do it much more simply:
SELECT sid FROM supplier ORDER BY CASE
WHEN city = 'Taipei' THEN 1
WHEN city = 'Tainan' THEN 2
WHEN city = 'New York' THEN 3
ELSE 4
END
LIMIT 1
This will retrieve all suppliers, but the one from Taipei, if available, will come out first; and the LIMIT 1 will truncate the response to that first row.
The solutions below do not apply
This will get sid from supplier where city is Taipei or Tainan (which of course means that city is not empty!):
SELECT sid FROM supplier WHERE city IN ('Taipei', 'Tainan');
This will get sid from supplier as above, provided sid is not empty:
SELECT sid FROM supplier WHERE city IN ('Taipei', 'Tainan') AND sid IS NOT NULL;
This will get sid from supplier as above, and replace sid if it is empty.
SELECT CASE WHEN sid IS NULL then 'Empty' ELSE sid END AS sid
FROM supplier WHERE city IN ('Taipei', 'Tainan');
Maybe you should provide two or three sample rows with the expected results.
Edit: sorry, I see now that sid is a primary key, which means it should never be empty. This means that cases 2 and 3 can never apply.
Then perhaps you mean that sname is not empty?:
SELECT sid FROM supplier WHERE city IN ('Taipei', 'Tainan')
AND sname IS NOT NULL AND sname != '';

The following selects a supplier if there is one in taipei, otherwise it selects the one in Tainan. If none of them exists, nothing will be returned.
select sid
from supplier
where city = 'Taipei'
union all
select sid
from supplier
where city = 'Tainan'
and not exists (select 1 from supplier where city = 'taipei')

Related

pick group by from a col where col2 is NULL

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

sql join with condition

I've a table EMPLOYEE which has columns like these
EmpId FName LName
I have another table ADDRESS which has columns like these
EmpId AddressType Address Phone Email
AddressType column has 2 possible types, Residential and Official and an Emp can have both types of address. I need a query which will join these 2 tables using EmpId. It also needs to fetch one address which has phone not null. If both addresses has phone, then fetch any one, if none has phone, still fetch any one. Please help.
The trick is to first decide which Address would be best for the Employee, based on your Phone-rule. After the prefered Address has been found, indicated by PhonePreference = 1, you can JOIN the correct Address on the Employee.
WITH AddressCTE AS (
SELECT *
, ROW_NUMBER() OVER (
PARTITION BY EmpId
ORDER BY CASE WHEN Phone IS NOT NULL THEN 1 ELSE 2 END, Phone
) PhonePreference
FROM Address
)
SELECT *
FROM Employee E
JOIN AddressCTE A ON E.EmpId = A.EmpId AND A.PhonePreference = 1

How to select only one row that satisfies one of multiple ordered conditions

I have customers that have multiple addresses. Each customer/address combination has its own line in the database (Oracle) table.
I am trying to achieve a query in which, if the customer has a 'Main Address', I display only the Main Address,
otherwise if he has a Shipment Address, I display only the Shipment Address,
otherwise if he has a 'Secondary Address', I display the Secondary Address,
otherwise I display nothing.
This order is important, and the problem is that the entries in the database are in no specific order, meaning that the same customer might be found to have a Shipment Address first, and a Main Address later on. Therefore I don't simply need the first row that satisfies one of the conditions...
I tried this, but it returns a result for each line, e.g. multiple results for one person:
CASE WHEN ADR = 'MAIN' THEN 'MAIN'
WHEN ADR = 'SHIPMENT' THEN 'SHIPMENT'
WHEN ADR = 'SECONDARY' THEN 'SECONDARY'
ELSE null
END AS Adressart
To clarify, the input looks as follows:
CUSTOMER_NR ADDRESS_TYPE
1 SHIPMENT
1 MAIN
2 SHIPMENT
3 SECONDARY
3 SHIPMENT
4 SECONDARY
The results would look like this:
CUSTOMER_NR ADDRESS_TYPE
1 MAIN
2 SHIPMENT
3 SHIPMENT
4 SECONDARY
I think you could use multiple joins to the address table and by using coalesce return only the one address from the order
The query would look something like:
SELECT customer_nr, coalesce(a1.address, a2.address, a3,address) AS address
FROM customer AS c
LEFT JOIN address AS a1 where a1.customer_nr = c.customer_nr and a1.address_type = 'MAIN'
LEFT JOIN address AS a2 where a2.customer_nr = c.customer_nr and a2.address_type = 'SHIPMENT'
LEFT JOIN address AS a3 where a3.customer_nr = c.customer_nr and a3.address_type = 'SECONDARY'
The following query would get you the desired result:
SELECT
CUSTOMER_NR,
ADDRESS_TYPE
FROM
(
select
customer_nr,
address_type,
row_number () over (partition by customer_nr order by
case address_type
when 'MAIN' then 1
when 'SHIPMENT' then 2
when 'SECONDARY' THEN 3
else 4
end) rn
from addresses
)
WHERE rn = 1;
First, the addresses are sorted using the priority with a CASE statement. Then, only the address types that have rn = 1 (the address type of highest priority) are selected.
SQL Fiddle demo

DB2 SQL Join and Max value

The database I'm accessing has two tables I need to query using DB2 SQL, shown here as nametable and addresstable. The query is for finding all of the people with a certain balance due. The addresses are stored in a separate table to keep track of address changes. In addresstable, the latest address is determined by a sequence number (ADDRSEQUENCE). The AddressID field is present in both tables, and is what ties each person to specific addresses. The highest sequence number is the current address. I need that current address for each person and only that one. I know I'm going to have to use MAX somewhere for the sequence number, but I can't figure out how to position it given the join. Here's my current query, which of course returns all addresses...
SELECT NAMETABLE.ACCTNUM AS ACCOUNTNUMBER,
NAMETABLE.NMELASTBUS AS LASTNAME,
NAMETABLE.NAME_FIRST AS FIRSTNAME,
NAMETABLE.BALDUE AS BALANCEDUE,
ADDRESSTABLE.STREETNAME AS ADDR,
ADDRESSTABLE.ADDRLINE2 AS
ADDRLINE2,ADDRESSTABLE.CITYPARISH AS CITY,
ADDRESSTABLE.ADDRSTATE AS STATE,
ADDRESSTABLE.ZIPCODE AS ZIP,
ADDRESSTABLE.ADDIDSEQNO AS ADDRSEQUENCE
FROM NAMETABLE JOIN ADDRESSTABLE ON NAMETABLE.ADDRESSID = ADDRESSTABLE.ADDRESSID
WHERE NAMETABLE.BALANCEDUE >= '50.00'
You can do a sub-select on the MAX(ADDRSEQUENCE) like so:
SELECT
N.ACCTNUM AS ACCOUNTNUMBER
,N.NMELASTBUS AS LASTNAME
,N.NAME_FIRST AS FIRSTNAME
,N.BALDUE AS BALANCEDUE
,A.STREETNAME AS ADDR,
,A.ADDRLINE2 AS
,A.ADDRLINE2
,A.CITYPARISH AS CITY,
,A.ADDRSTATE AS STATE,
,A.ZIPCODE AS ZIP,
FROM NAMETABLE AS N
JOIN ADDRESSTABLE AS A
ON N.ADDRESSID = A.ADDRESSID
WHERE N.BALANCEDUE >= '50.00'
AND A.ADDRSEQUENCE = (
SELECT MAX(ADDRSEQUENCE)
FROM ADDRESSTABLE AS A2
WHERE A.ADDRESSID = A2.ADDRESSID
)
This is pretty quick in DB2.
You can use a row_number and partition by to do this. Something like this:
with orderedaddress as (
select row_number() over (partition by ADDRESSID order by ADDRSEQUENCE desc) as rown,
STREETNAME,ADDRESSID, ... from ADDRESSTABLE
)
select NAMETABLE.ACCTNUM AS ACCOUNTNUMBER,
...
oa.STREETNAME
...
from NAMETABLE JOIN orderedaddress oa on NAMETABLE.ADDRESSID = oa.ADDRESSID
where oa.rown = 1
and NAMETABLE.BALANCEDUE >= '50.00'

Select exactly one row for each employee using unordered field as criteria

I have a data set that looks like the following.
EMPLID PHONE_TYPE PHONE
------ ---------- --------
100 HOME 111-1111
100 WORK 222-2222
101 HOME 333-3333
102 WORK 444-4444
103 OTHER 555-5555
I want to select exactly one row for each employee using the PHONE_TYPE field to establish preferences. I want the HOME phone number if the employee has one as is the case for employee 100 and 101. If the HOME number is not present, I want the WORK number (employee 102), and as a last resort I'll take the OTHER number as with employee 103. In reality my table has about a dozen values for the PHONE_TYPE field, so I need to be able to extend any solution to include more than just the three values I've shown in the example. Any thoughts? Thanks.
You need to add a phone_types table (Phone_Type TEXT(Whatever), Priority INTEGER). In this table, list each Phone_Type value once and assign a priority to it (in your example, HOME would be 1, WORK 2, OTHER 3 and so on).
Then, create a view that joins the Priority column from Phone_Types to your Phone_Numbers table (imagine we call it Phone_Numbers_Ex).
Now, you have several options for how to get record from Phone_Numbers_Ex with the MIN(Priority) for a given emplID, of which probably the clearest is:
SELECT * FROM Phone_Numbers_Ex P1 WHERE NOT EXISTS
(SELECT * FROM Phone_Numbers_Ex P2 WHERE P2.EmplID = P1.EmplID AND P2.Priority < P1.Priority)
Another way is to declare another view, or inner query, along the lines of SELECT EmplID, MIN(Priority) AS Priority FROM Phone_Numbers_Ex GROUP BY EmplID and then joining this back Phone_Numbers_Ex on both EmplID and Priority.
I forget, does Server 2000 support Coalesce? If it does, I think this will work:
Select Distinct EmplID, Coalesce(
(Select Phone from Employees where emplid = e1.emplid and phone_type = 'HOME'),
(Select Phone from Employees where emplid = e1.emplid and phone_type = 'WORK'),
(Select Phone from Employees where emplid = e1.emplid and phone_type = 'OTHER')
) as Phone
From Employees e1
Your requirements may not be complete if an employee is allowed to have more than one phone number for a given phone type. I've added a phone_number_id just to make things unique and assumed that you would want the lowest id if the person has two phones of the same type. That's pretty arbitrary, but you can replace it with your own business logic.
I've also assumed some kind of a Phone_Types table that includes your priority for which phone number should be used. If you don't already have this table, you should probably add it. If nothing else, it lets you constrain the phone types with a foreign key.
SELECT
PN1.employee_id,
PN1.phone_type,
PN1.phone_number
FROM
Phone_Numbers PN1
INNER JOIN Phone_Types PT1 ON
PT1.phone_type = PN1.phone_type
WHERE
NOT EXISTS
(
SELECT *
FROM
Phone_Numbers PN2
INNER JOIN Phone_Types PT2 ON
PT2.phone_type = PN2.phone_type AND
(
(PT2.priority < PT1.priority)
--OR (PT2.priority = PT1.priority AND PN2.phone_number_id > PN1.phone_number_id)
)
)
You could also implement this with a LEFT JOIN instead of the NOT EXISTS or you could use TOP if you were looking for the phone number for a single employee. Just do a TOP 1 ORDER BY priority, phone_number_id.
Finally, if you were to move up to SQL 2005 or SQL 2008, you could use a CTE with ROWNUMBER() OVER (ORDER BY priority, phone_number, PARTITION BY employee_id) <- I think my syntax may be slightly off with the parentheses on that, but hopefully it's clear enough. That would allow you to get the top one for all employees by checking that ROWNUMBER() = 1.
As an alternative g.d.d.c's answer that uses queries in the Select clause you could use left joins. You might get better perf, but you should test of course.
SELECT
e1.iD,
Coalesce(phoneHome.Phone,phoneWork.Phone,phoneOther) phone
FROm
employees e1
LEFT JOIN phone phoneHome
ON e1.emplId = phoneHome
and phone_type = 'HOME'
LEFT JOIN phone phoneWork
ON e1.emplId = phoneWork
and phone_type = 'WORK'
LEFT JOIN phone phoneWork
ON e1.emplId = phoneOTHER
and phone_type = 'OTHER'