SQL Server : Subselect where IS NULL THEN 'VALUE' - sql-server

I am trying to return a string result when a value IS NULL in SQL Server.
An example of what I am doing is below. This is a sub select query...
SELECT
stockitems.code AS StockCode,
stockitems.description AS
ShortDescription,
(SELECT
CASE remaining
WHEN NULL
THEN 'NO LEVEL'
ELSE remaining
END
FROM StockItemAlertLevels
WHERE StockItems.ID = StockItemAlertLevels.StockItemID) AS RemainingLevel
FROM
stockitems
WHERE
StockItems.Attribute1 = '1'
ORDER BY
StockItems.Code
No matter what I do, I cannot get the output result of the subselect to change if the result is NULL. Not all returned results from the SELECT statement will have returned results in the subselect - this is where I need the set the output to "NO LEVEL". The subselect will never return more than one result.
Hope this makes sense.

I think you want COALESCE():
SELECT si.code As StockCode, si.description AS ShortDescription,
(SELECT COALESCE(remaining, 'NO LEVEL')
FROM StockItemAlertLevels sal
WHERE si.ID = sal.StockItemID
) as RemainingLevel
FROM stockitems si
WHERE si.Attribute1 = '1'
ORDER BY si.Code;
I suspect, though, that your problem is not that remaining is NULL, but that there are no matches. You can move the COALESCE() outside the subquery, but I would propose a LEFT JOIN:
SELECT si.code As StockCode, si.description AS ShortDescription,
COALESCE(remaining, 'NO LEVEL') as RemainingLevel
FROM stockitems si LEFT JOIN
StockItemAlertLevels sal
ON si.ID = sal.StockItemID
WHERE si.Attribute1 = '1'
ORDER BY si.Code;

Related

how to optimize the inner query?

SELECT mark.student_id,
.
.
.
MAX(mark.SAVE_TIME) AS SAVE_TIME,
(SELECT tamil FROM mark WHERE SAVE_TIME = (MAX(mark.SAVE_TIME))) AS tamilmark,
(SELECT english FROM mark WHERE SAVE_TIME = (MAX(mark.SAVE_TIME))) AS englishmark
FROM
(.......
)
Above query will return the my expected data. but the problem is two times I'm fetching the data from same table. How can i achieve with single select statement?
Here is one way that could be rewritten:
SELECT MySubTable.*, mark.tamil, mark.english
(
SELECT student_id,
MAX(mark.SAVE_TIME) AS SAVE_TIME
FROM Table
GROUP BY student_id
) MySubTable
INNER JOIN mark
ON mark.SAVE_TIME = MySubTable.SAVE_TIME
You can use one select statement to get all values, but you also will get NULLs (in my example I gave them 'n/a' value). To get rid of them you will have to select this subquery:
SELECT *
FROM (
SELECT DISTINCT mark.student_id,
MAX(MARK.SAVE_TIME) OVER() AS SAVE_TIME,
CASE WHEN SAVE_TIME = (MAX(mark.SAVE_TIME) OVER()) THEN tamil ELSE 'n/a' END AS tamilmark,
CASE WHEN SAVE_TIME = (MAX(mark.SAVE_TIME) OVER()) THEN english ELSE 'n/a' END AS englishmark
FROM MARK
) AS t
WHERE tamilmark != 'n/a'

SQL combine two queries result into one dataset

I am trying to combine two SQL queries the first is
SELECT
EAC.Person.FirstName,
EAC.Person.Id,
EAC.Person.LastName,
EAC.Person.EmployeeId,
EAC.Person.IsDeleted,
Controller.Cards.SiteCode,
Controller.Cards.CardCode,
Controller.Cards.ActivationDate,
Controller.Cards.ExpirationDate,
Controller.Cards.Status,
EAC.[Group].Name
FROM
EAC.Person
INNER JOIN
Controller.Cards ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN
EAC.GroupPersonMap ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN
EAC.[Group] ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id
And the second one is
SELECT
IsActive, ActivationDateUTC, ExpirationDateUTC,
Sitecode + '-' + Cardcode AS Credential, 'Badge' AS Type,
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GetUTCDate()
THEN 'Pending'
WHEN ExpirationDAteUTC < GetUTCDate()
THEN 'Expired'
ELSE 'Active'
END AS Status
FROM
EAC.Credential
JOIN
EAC.WiegandCredential ON Credential.ID = WiegandCredential.CredentialId
WHERE
PersonID = '32'
Where I would like to run the second query for each user of the first query using EAC.Person.Id instead of the '32'.
I would like all the data to be returned in one Dataset so I can use it in Report Builder.
I have been fighting with this all day and am hoping one of you smart guys can give me a hand. Thanks in advance.
Based on your description in the comments, I understand that the connection between the two datasets is actually the PersonID field, which exists in both EAC.Credential and EAC.Person; however, in EAC.Credential, duplicate values exist for PersonID, and you want only the most recent one for each PersonID.
There are a few ways to do this, and it will depend on the number of rows returned, the indexes, etc., but I think maybe you're looking for something like this...?
SELECT
EAC.Person.FirstName
,EAC.Person.Id
,EAC.Person.LastName
,EAC.Person.EmployeeId
,EAC.Person.IsDeleted
,Controller.Cards.SiteCode
,Controller.Cards.CardCode
,Controller.Cards.ActivationDate
,Controller.Cards.ExpirationDate
,Controller.Cards.Status
,EAC.[Group].Name
,X.IsActive
,X.ActivationDateUTC
,X.ExpirationDateUTC
,X.Credential
,X.Type
,X.Status
FROM EAC.Person
INNER JOIN Controller.Cards
ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN EAC.GroupPersonMap
ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN EAC.[Group]
ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id
CROSS APPLY
(
SELECT TOP 1
IsActive
,ActivationDateUTC
,ExpirationDateUTC
,Sitecode + '-' + Cardcode AS Credential
,'Badge' AS Type
,'Status' =
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GETUTCDATE()
THEN 'Pending'
WHEN ExpirationDateUTC < GETUTCDATE()
THEN 'Expired'
ELSE 'Active'
END
FROM EAC.Credential
INNER JOIN EAC.WiegandCredential
ON EAC.Credential.ID = EAC.WiegandCredential.CredentialId
WHERE EAC.Credential.PersonID = EAC.Person.PersonID
ORDER BY EAC.Credential.ID DESC
) AS X
-- Optionally, you can also add conditions to return specific rows, i.e.:
-- WHERE EAC.Person.PersonID = 32
This option uses a CROSS APPLY, which means that every row of the first dataset will return additional values from the second dataset, based on the criteria that you described. In this CROSS APPLY, I'm joining the two datasets based on the fact that PersonID exists in both EAC.Person (in your first dataset) as well as in EAC.Credential. I then specify that I want only the TOP 1 row for each PersonID, with an ORDER BY specifying that we want the most recent (highest) value of ID for each PersonID.
The CROSS APPLY is aliased as "X", so in your original SELECT you now have several values prefixed with the X. alias, which just means that you're taking these fields from the second query and attaching them to your original results.
CROSS APPLY requires that a matching entry exists in both subsets of data, much like an INNER JOIN, so you'll want to check and make sure that the relevant values exist and are returned correctly.
I think this is pretty close to the direction you're trying to go. If not, let me know and I'll update the answer. Good luck!
Try like this;
select Query1.*, Query2.* from (
SELECT
EAC.Person.FirstName,
EAC.Person.Id as PersonId,
EAC.Person.LastName,
EAC.Person.EmployeeId,
EAC.Person.IsDeleted,
Controller.Cards.SiteCode,
Controller.Cards.CardCode,
Controller.Cards.ActivationDate,
Controller.Cards.ExpirationDate,
Controller.Cards.Status,
EAC.[Group].Name
FROM
EAC.Person
INNER JOIN
Controller.Cards ON EAC.Person.Id = Controller.Cards.PersonId
INNER JOIN
EAC.GroupPersonMap ON EAC.Person.Id = EAC.GroupPersonMap.PersonId
INNER JOIN
EAC.[Group] ON EAC.GroupPersonMap.GroupId = EAC.[Group].Id)
Query1 inner join (SELECT top 100
IsActive, ActivationDateUTC, ExpirationDateUTC,
Sitecode + '-' + Cardcode AS Credential, 'Badge' AS Type,
CASE
WHEN isActive = 0
THEN 'InActive'
WHEN ActivationDateUTC > GetUTCDate()
THEN 'Pending'
WHEN ExpirationDAteUTC < GetUTCDate()
THEN 'Expired'
ELSE 'Active'
END AS Status
FROM
EAC.Credential
JOIN
EAC.WiegandCredential ON Credential.ID = WiegandCredential.CredentialId
ORDER BY EAC.Credential.ID DESC) Query2 ON Query1.PersonId = Query2.PersonID
Just select two queries to join them like Query1 and Query2 by equaling PersonId data.

SQL Case Statement - If first 'when' returns null then complete the 2nd when

Just wondering if anyone can help me , I am running a case statement that references a different table. It needs to look up the make, model and year of a car as well as the position (FL,FR,BL,BR) and return the kit number.
Up to 4 entries can exist in the table for the same vehicle with the fitting position column specifying which kit number to be selected, in order to only return 1 result i believe i need to put this in the where section of the query, if i add it anywhere else more than 1 value is returned.
However 4 entries won't always exist for the vehicle. A kit can exist for FL & BL but not FR and BR. Because of me adding the position column into the where section 'null' is returned.Rather than it returning nothing i want it to return the next part of the case statement.
This is where the sql works because a kit is available for FL
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FL'
However when it changes to
SELECT CAST (CASE WHEN '002' != 'UNI' THEN T0.U_MPLFK ELSE 'NOKIT' END AS VARCHAR)
FROM
[#CSOL_MILFORD] T0 INNER JOIN [dbo].[#CSOL_VEHICLES] T1 ON T0.[U_VehicleRef] = T1.[U_VehicleRef]
WHERE
T1.U_Manufacturer = 'Ford'
AND
T1.U_Model = 'Galaxy'
AND
T0.U_MPLFK > 1
AND
T0.U_FittingPosition = 'FR'
I get no value retuned, i want it to return 'NOKIT'
Many Thanks,
Roisin
A left join returns a row with null columns if its on condition fails. So you could move the conditions to the on part of a left join. Something like:
...
FROM #CSOL_VEHICLES T1
LEFT JOIN
#CSOL_MILFORD T0
ON T0.U_VehicleRef = T1.U_VehicleRef
AND T0.U_MPLFK > 1
AND T0.U_FittingPosition = 'FR'
WHERE T1.U_Manufacturer = 'Ford'
AND T1.U_Model = 'Galaxy'
Having the condition in the on clause instead of the where clause means you'll get a row with nulls instead of no row.

Update row value only once when multiple results returned in CASE statement

I need to run an UPDATE script with a value returned from a CASE WHEN statement. The condition used might lead to have the same order (table ORDER) affetcted by different erorrs.
As example, order.id = 20 can be affected by Error 1 and Error 2, but I need to update it only once with the value Error 1 (the order in the CASE WHEN shows the priority). The goal would be to have only Error 1 to be set in the field STATUS. Is this possible to be achieved in TSQL? Or should I better implement the logic with a console application?
Thanks.
UPDATE O
SET STATUS = (
CASE
WHEN E.EXPID = 'E1' THEN 'Error1'
WHEN E.EXPID = 'E3' THEN 'Error2'
WHEN E.EXPID = 'E2' THEN 'Error3'
ELSE 'EX'
END )
FROM ORDER O
INNER JOIN EXCP E ON O.ID = E.ORDERID
I would use a CTE to grab the top "error" and join that result to your order table.
;WITH TopError
AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY OrderID ORDER BY ExpID ASC) AS ROWID, *
FROM Excp
)
SELECT *
-- UPDATE O SET STATUS = CASE WHEN T.EXPID = 'E1' THEN 'Error1' WHEN T.EXPID = 'E2' THEN 'Error2' WHEN T.EXPID = 'E3' THEN 'Error3' ELSE 'EX' END
FROM TopError T
JOIN Order O
ON T.ORDERID = O.ID
WHERE T.ROWID=1
Try it as a SELECT, if you're satisfied with the results, switch to an UPDATE.

Calculated summary field based on child table

I have two tables, Order and OrderItem. There is a one-to-many relationship on Order.Order_ID=OrderItem.Order_ID
I want a query to return a list showing the status of each Order, COMPLETE or INCOMPLETE.
A COMPLETE Order is defined as one where all the related OrderItem records have a non-NULL, non-empty value in the OrderItem.Delivery_ID field.
This is what I have so far:
SELECT Order.Order_ID, 'INCOMPLETE' AS Order_status
FROM Order
WHERE EXISTS
(SELECT *
FROM OrderItem
WHERE OrderItem.Order_ID=Order.Order_ID
AND (OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID=''))
UNION
SELECT Order.Order_ID, 'COMPLETE' AS Order_status
FROM Order
WHERE NOT EXISTS
(SELECT *
FROM OrderItem
WHERE OrderItem.Order_ID=Order.Order_ID
AND (OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID=''))
ORDER BY Order_ID DESC
It works, but runs a bit slow. Is there a better way?
(N.B. I've restated the problem for clarity, actual table and field names are different)
I would suggest you have a column status on your Order table and update the status to complete when all order items get delivered.
It will make simple your query to get status as well improve performance.
Put it into a subquery to try to make the case statement less confusing:
SELECT Order_ID,
CASE WHEN incomplete_count > 0 THEN 'INCOMPLETE' ELSE 'COMPLETE' END
AS Order_status
FROM ( SELECT o.Order_ID
,SUM( CASE WHEN OrderItem.Delivery_ID IS NULL OR OrderItem.Delivery_ID='' THEN 1 ELSE 0 END )
AS incomplete_count
FROM Order o
INNER JOIN OrderItem i ON (i.Order_ID = o.Order_ID)
GROUP by o.Order_ID
) x
ORDER BY ORder_ID DESC
The idea is to keep a counter every time you encounter a null item. If the sum is 0, there were no empty order items.
Try this one -
SELECT
o.Order_ID
, Order_status =
CASE WHEN ot.Order_ID IS NULL
THEN 'COMPLETE'
ELSE 'INCOMPLETE'
END
FROM dbo.[Order] o
LEFT JOIN (
SELECT DISTINCT ot.Order_ID
FROM dbo.OrderItem ot
WHERE ISNULL(ot.Delivery_ID, '') = ''
) ot ON ot.Order_ID = o.Order_ID

Resources