What is a better alternative to procedural approach to produce a string based on columns? - sql

I am working on making string based on column values. Example:
SELECT #Registered = a.hasRegistered
,#Subscribed = a.hasSubscribed
FROM AuthorData a
WHERE a.authorId = 10 --#AUTHORID
IF (#Registered = 1)
SET #ReturnString = #ReturnString + '0,'
IF (#Subscribed = 1)
SET #ReturnString = #ReturnString + '1,'
IF EXISTS (
SELECT TOP 1 name
FROM AUTHORDATA AS A
INNER JOIN AUHTORPROFILE B on A.AUTHORID=B.AUTHORID
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE ISNULL(AUTHORDATA.authorId, 0) = 10 --#AUTHORID
)
BEGIN
SET #ReturnString = #ReturnString + '10,'
END
select CONVERT(NVARCHAR(50), STUFF(#ReturnString, LEN(#ReturnString), 1, ''))
This performs the calculation for 1 author (based on WHERE clause IN 2 places -> authorId=10)
I can make this into a function and then call from a select query as follows:
SELECT *,FN_CALCUALTE_OPTIONS(AUTHORID)
FROM AUTHORDATA
I want to ask if there is any way so I can do the calculations in the above SELECT query rather than create the function?
I have tried:
SELECT *,CASE WHEN A.hasRegistered=1 THEN '0,' ELSE '' END +
CASE WHEN A.hasSubscribed=1 THEN '1,' ELSE '' END
FROM AUTHORDATA as A
How can I have the exists part of select?

Pretty sure you can put them all together into a single query as follows. If you didn't need the final stuff you would just have a regular query, but because you need to use the results twice in the stuff CROSS APPLY is a convenient way to calculate it once and use it twice. Also you can correlate your sub-query since you are using the same AuthorData record.
SELECT CONVERT(NVARCHAR(50), STUFF(X.ReturnString, LEN(X.ReturnString), 1, ''))
FROM AuthorData a
CROSS APPLY (
VALUES
(
CASE WHEN a.hasRegistered = 1 THEN '0,' ELSE '' END
+ CASE WHEN a.hasSubscribed = 1 THEN '1,' ELSE '' END
+ CASE WHEN EXISTS (
SELECT 1
FROM AUHTORPROFILE B
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID = C.AUTHORPROFILEID
WHERE B.AUTHORID = a.AuthorId
)
THEN '10,' else '' end
)
) AS X (ReturnString)
WHERE a.AuthorId = 10 --#AUTHORID

The easiest way IMO is to move your criteria being found via EXISTS to an OUTER APPLY. Like so:
SELECT
CONVERT(NVARCHAR(50), STUFF(calc.ReturnString, LEN(calc.ReturnString), 1, ''))
FROM
AUTHORDATA AS A
OUTER APPLY (SELECT TOP (1)
1 AS Found
FROM
AUHTORPROFILE AS B
INNER JOIN AUTHORHISTORY AS C ON B.AUTHORPROFILEID = C.AUTHORPROFILEID
WHERE
B.authorId = A.authorId) AS lookup_author
/*outer apply here just for readibility in final select*/
OUTER APPLY (SELECT
CONCAT(CASE WHEN A.hasRegistered = 1 THEN '0,' ELSE '' END
,CASE WHEN A.hasRegistered = 1 THEN '1,' ELSE '' END
,CASE WHEN lookup_author.Found = 1 THEN '10,' ELSE '' END) AS ReturnString) AS calc;
Then you can use lookup_author.Found = 1 to determine that it was found in your lookup. From there, you just have to apply the rest of your conditions correctly via CASE statements and then use your final SELECT over the result.

You can put a single CASE statement and get data as given below. Make sure that you are covering every possible condition.
SELECT *, CASE WHEN a.Registered =1 AND a.Subscribed =1
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '0,1,10'
WHEN a.Registered =1 AND a.Subscribed =0
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '0,10'
WHEN a.Registered =0 AND a.Subscribed =1
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '1,10'
WHEN a.Registered =0 AND a.Subscribed =0
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '10'
END AS CalculateOptions
FROM AUTHORDATA AS a

Related

Case with SELECT - not returning expected results

I am trying to return "Y" if the ContractID in SC appears in AT_TMP_, and "N" if it does not.
Here is my current code - currently, "Y" is returned regardless.
Note: T-SQL I'm fine with, PL/SQL & Oracle is still relatively new to me
WITH AT_TMP_ AS (
SELECT CONTRACT_ID FROM SC_SERVICE_CONTRACT_CFV WHERE
EXISTS(
SELECT 1
FROM SC_SRV_CONTRACT_INVPLN_cfv cs
WHERE SC_SERVICE_CONTRACT_cfv.CONTRACT_ID = cs.contract_id
AND cs.cf$_invoice_date is null
AND CS.CF$_INV_NOT_REQ_DB = 'FALSE'
))
SELECT SC.CONTRACT_ID, CASE
WHEN
SC.CONTRACT_ID IS NOT NULL THEN 'Y'
ELSE 'N'
END AS YES_NO
FROM SC_SERVICE_CONTRACT_cfv SC
LEFT OUTER JOIN
AT_TMP_
ON AT_TMP_.Contract_ID = SC.Contract_ID;
You need to do the case in AT_TMP_. SC is never going to be null because it is on the left side of the left join. AT_TMP can be null as it's on the right side of the left join.
WITH AT_TMP_ AS (
SELECT CONTRACT_ID FROM SC_SERVICE_CONTRACT_CFV WHERE
EXISTS(
SELECT 1
FROM SC_SRV_CONTRACT_INVPLN_cfv cs
WHERE SC_SERVICE_CONTRACT_cfv.CONTRACT_ID = cs.contract_id
AND cs.cf$_invoice_date is null
AND CS.CF$_INV_NOT_REQ_DB = 'FALSE'
))
SELECT SC.CONTRACT_ID, CASE
WHEN
AT_TMP_.CONTRACT_ID IS NOT NULL THEN 'Y'
ELSE 'N'
END AS YES_NO
FROM SC_SERVICE_CONTRACT_cfv SC
LEFT OUTER JOIN
AT_TMP_
ON AT_TMP_.Contract_ID = SC.Contract_ID;
Your case expression isn't referring to at_tmp_ at the moment; you do an outer join but then don't do anything with the result of that join. You can change the case expression to check the value from the CTE instead, as #banana_99 showed.
But you don't really need the CTE. You can use the exists check directly:
SELECT SC.CONTRACT_ID,
CASE
WHEN EXISTS (
SELECT null
FROM SC_SRV_CONTRACT_INVPLN_cfv SCI
WHERE SCI.CONTRACT_ID = SC.contract_id
AND SCI.cf$_invoice_date is null
AND SCI.CF$_INV_NOT_REQ_DB = 'FALSE'
)
THEN 'Y' ELSE 'N' END AS YES_NO
FROM SC_SERVICE_CONTRACT_cfv SC;
I think you need EXISTS instead of LEFT JOIN -
WITH AT_TMP_ AS ( SELECT CONTRACT_ID
FROM SC_SERVICE_CONTRACT_CFV
WHERE EXISTS(SELECT 1
FROM SC_SRV_CONTRACT_INVPLN_cfv cs
WHERE cs.CONTRACT_ID = cs.contract_id
AND cs.cf$_invoice_date is null
AND CS.CF$_INV_NOT_REQ_DB = 'FALSE')
)
SELECT SC.CONTRACT_ID,
CASE WHEN EXISTS (SELECT 1 FROM AT_TMP_
WHERE AT_TMP_.Contract_ID = SC.Contract_ID) THEN 'Y'
ELSE 'N' END AS YES_NO
FROM SC_SERVICE_CONTRACT_cfv SC;

How do you properly query the result of a complex join statement in SQL?

New to advanced SQL!
I'm trying to write a query that returns the COUNT(*) and SUM of the resulting columns from this query:
DECLARE #Id INT = 1000;
SELECT
*,
CASE
WHEN Id1 >= 6 THEN 1
ELSE 0
END AS Tier1,
CASE
WHEN Id1 >= 4 THEN 1
ELSE 0
END AS Tier2,
CASE
WHEN Id1 >= 2 THEN 1
ELSE 0
END AS Tier3
FROM (
SELECT
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName,
MAX(AppSubmitU_Level.Id1) AS Id1
FROM Org
INNER JOIN AppEmployment
ON AppEmployment.OrgID = Org.OrgID
INNER JOIN App
ON App.AppID = AppEmployment.AppID
INNER JOIN AppSubmit
ON App.AppID = AppSubmit.AppID
INNER JOIN AppSubmitU_Level
ON AppSubmit.LevelID = AppSubmitU_Level.Id1
INNER JOIN AppEmpU_VerifyStatus
ON AppEmpU_VerifyStatus.VerifyStatusID = AppEmployment.VerifyStatusID
WHERE AppSubmitU_Level.SubmitTypeID = 1 -- Career
AND AppEmpU_VerifyStatus.StatusIsVerified = 1
AND AppSubmit.[ExpireDate] IS NOT NULL
AND AppSubmit.[ExpireDate] > GETDATE()
AND Org.OrgID = #Id
GROUP BY
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName
) employees
I've tried to do so by moving the #Id outside the original query, and adding a SELECT(*), SUM, and SUM to the top, like so:
DECLARE #OrgID INT = 1000;
SELECT COUNT(*), SUM(employees.Tier1), SUM(employees.Tier2), SUM(employees.Tier3)
FROM
(SELECT *,
...
) AS employees
);
When I run the query, however, I'm getting the errors:
The multi-part identifier employees.Tier1 could not be bound
The same errors appear for the other identifiers in my SUM statements.
I'm assuming this has to do with the fact that the Tier1, Tier2, and Tier3 columns are being returned by the inner join query in my FROM(), and aren't values set by the existing tables that I'm querying. But I can't figure out how to rewrite it to initialize properly.
Thanks in advance for the help!
This is a scope problem: employees is defined in the subquery only, it is not available in the outer scope. You basically want to alias the outer query:
DECLARE #OrgID INT = 1000;
SELECT COUNT(*), SUM(employees.Tier1) TotalTier1, SUM(employees.Tier2) TotalTier2, SUM(employees.Tier3) TotalTier3
FROM (
SELECT *,
...
) AS employees
) AS employees;
--^ here
Note that I added column aliases to the outer query, which is a good practice in SQL.
It might be easier to understand what is going on if you use another alias for the outer query:
SELECT COUNT(*), SUM(e.Tier1), SUM(e.Tier2), SUM(e.Tier3)
FROM (
SELECT *,
...
) AS employees
) AS e;
Note that you don't actually need to qualify the column names in the outer query, since column names are unambigous anyway.
And finally: you don't actually need a subquery. You could write the query as:
SELECT
SUM(CASE WHEN Id1 >= 6 THEN 1 ELSE 0 END) AS TotalTier1,
SUM(CASE WHEN Id1 >= 4 THEN 1 ELSE 0 END) AS TotalTier2,
SUM(CASE WHEN Id1 >= 2 THEN 1 ELSE 0 END) AS TotalTier3
FROM (
SELECT
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName,
MAX(AppSubmitU_Level.Id1) AS Id1
FROM Org
INNER JOIN AppEmployment
ON AppEmployment.OrgID = Org.OrgID
INNER JOIN App
ON App.AppID = AppEmployment.AppID
INNER JOIN AppSubmit
ON App.AppID = AppSubmit.AppID
INNER JOIN AppSubmitU_Level
ON AppSubmit.LevelID = AppSubmitU_Level.Id1
INNER JOIN AppEmpU_VerifyStatus
ON AppEmpU_VerifyStatus.VerifyStatusID = AppEmployment.VerifyStatusID
WHERE AppSubmitU_Level.SubmitTypeID = 1 -- Career
AND AppEmpU_VerifyStatus.StatusIsVerified = 1
AND AppSubmit.[ExpireDate] IS NOT NULL
AND AppSubmit.[ExpireDate] > GETDATE()
AND Org.OrgID = #Id
GROUP BY
Org.OrgID,
App.AppID,
App.FirstName,
App.LastName
) employees

sql function case returns more than one row

Going to use this query as a subquery, the problem is it returns many rows of duplicates. Tried to use COUNT() instead of exists, but it still returns a multiple answer.
Every table can only contain one record of superRef.
The below query I`ll use in SELECT col_a, [the CASE] From MyTable
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = myTable.sysno AND A_specAttr = 'value')
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo WHERE C_superRef = myTable.sysno AND b_type = 2)
THEN 2
ELSE (SELECT C_intType FROM C
WHERE C_superRef = myTable.sysno)
END
FROM A, B, C
result:
3
3
3
3
3
3...
What if you did this? Because Im guessing you are getting an implicit full outer join A X B X C then running the case statement for each row in that result set.
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM ( SELECT COUNT(*) FROM A ) --This is a hack but should work in ANSI sql.
--Your milage my vary with different RDBMS flavors.
DUAL is what I needed, thanks to Thorsten Kettner
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM DUAL

SQL how to deal with no values

When I run the query below
SELECT
COUNT(COALESCE(CASE PARTPPHY.TITRE
WHEN 1 THEN 'Monsieur '
WHEN 2 THEN 'Madame '
WHEN 3 THEN 'Madame '
ELSE ''
END + PARTPPHY.IDENTITE, PARTPPHY.IDENTITE)) AS ident,
RESPONSABLES.DIR_LOC,
PARTPPHY.IU_PART_PP,
COALESCE(CASE PARTPPHY.TITRE
WHEN 1 THEN 'Monsieur '
WHEN 2 THEN 'Madame '
WHEN 3 THEN 'Madame '
ELSE ''
END + PARTPPHY.IDENTITE,PARTPPHY.IDENTITE) AS IDENTITE,
FONCTIONS_ECO.LIBEL AS LIBEL_FONCTION
FROM
RESPONSABLES
INNER JOIN
ETABLISSEMENTS ON ETABLISSEMENTS.IU_ETS = RESPONSABLES.IU_ETS
LEFT OUTER JOIN
PARTPPHY ON RESPONSABLES.IU_PART_PP = PARTPPHY.IU_PART_PP
LEFT OUTER JOIN
FONCTIONS_ECO ON FONCTIONS_ECO.IU_FONC_ECO = RESPONSABLES.IU_FONC
WHERE
ETABLISSEMENTS.IU_ETS = 14783
AND RESPONSABLES.GESTDEL = 1
GROUP BY
RESPONSABLES.DIR_LOC,
PARTPPHY.IU_PART_PP,
COALESCE(CASE PARTPPHY.TITRE WHEN 1 THEN 'Monsieur ' WHEN 2 THEN 'Madame ' WHEN 3 THEN 'Madame ' ELSE '' END + PARTPPHY.IDENTITE,PARTPPHY.IDENTITE),
FONCTIONS_ECO.LIBEL
This is my result
Yet when I'm using this query
SELECT COUNT(COALESCE(CASE PARTPPHY.TITRE
WHEN 1 THEN 'Monsieur '
WHEN 2 THEN 'Madame '
WHEN 3 THEN 'Madame '
ELSE '' END + PARTPPHY.IDENTITE,PARTPPHY.IDENTITE)) AS ident,
RESPONSABLES.DIR_LOC,
PARTPPHY.IU_PART_PP,
COALESCE(CASE PARTPPHY.TITRE WHEN 1 THEN 'Monsieur ' WHEN 2 THEN 'Madame ' WHEN 3 THEN 'Madame ' ELSE '' END + PARTPPHY.IDENTITE,PARTPPHY.IDENTITE) AS IDENTITE,
FONCTIONS_ECO.LIBEL AS LIBEL_FONCTION
FROM RESPONSABLES
INNER JOIN ETABLISSEMENTS ON ETABLISSEMENTS.IU_ETS = RESPONSABLES.IU_ETS
LEFT OUTER JOIN PARTPPHY ON RESPONSABLES.IU_PART_PP = PARTPPHY.IU_PART_PP
LEFT OUTER JOIN FONCTIONS_ECO ON FONCTIONS_ECO.IU_FONC_ECO = RESPONSABLES.IU_FONC
WHERE ETABLISSEMENTS.IU_ETS = 1
AND RESPONSABLES.GESTDEL = 1
GROUP BY RESPONSABLES.DIR_LOC,
PARTPPHY.IU_PART_PP,
COALESCE(CASE PARTPPHY.TITRE WHEN 1 THEN 'Monsieur ' WHEN 2 THEN 'Madame ' WHEN 3 THEN 'Madame ' ELSE '' END + PARTPPHY.IDENTITE,PARTPPHY.IDENTITE),
FONCTIONS_ECO.LIBEL
I have this
My questions are the following:
It does not seem to be a null nor an empty space: What is it?
I want to have a value in my count: What should I do to have this?
Thanks
While using a COUNT without a GROUP BY clause you will always return a row, even if the WHERE clause is not equal to any of the records in your table. But while using a GROUP BY clause , it will return the records only if the WHERE clause condition is true.
Please check the following test scripts
DECLARE #TEST TABLE(ID INT)
INSERT INTO #TEST VALUES(10)
--1.
SELECT COUNT(*) FROM #TEST WHERE ID = 11 --Result is 0
--2.
SELECT COUNT(*),ID FROM #TEST WHERE ID = 11 GROUP BY ID --No Result
Here the first query always return a result
(No column name)
0
But second query not.
You can modify your second query something like the below script to get a '0' result
SELECT ISNULL(( SELECT COUNT(*) FROM #TEST WHERE ID = 11 GROUP BY ID ),0)
There is one more valid option. In both cases you are using the INNER JOIN, which is exclusive join meaning, if the join condition is not satisfied then no data is return, or more precise your result is an empty set.
Your INNER JOIN condition is on ETABLISSEMENTS.IU_ETS = RESPONSABLES.IU_ETS
In the first query your where statement says
WHERE ETABLISSEMENTS.IU_ETS = 14783 AND RESPONSABLES.GESTDEL = 1
This is where you have an empty data set
And in the second query your where statement is
WHERE ETABLISSEMENTS.IU_ETS = 1 AND RESPONSABLES.GESTDEL = 1
So to answer your first query:
Possibly there is no matching record, which has the ETABLISSEMENTS.IU_ETS = 14783 AND RESPONSABLES.GESTDEL = 1 therefore we are getting an empty set
In the second case the where statement is satisfied and there is a corresponding entry in both tables therefore we have a result.
Depending which side of the Join you are interested in, i.e. in which table yo will expect the row to be present you can switch the
JOIN to OUTER JOIN (LEFT or RIGHT)
The outer join is inclusive, meaning that it will return all rows from the table left or right to the join even if there is no record in corresponding table.
Then apply count on the corresponding table, i.e. id column. If there is no record then you will get 0
Example to illustrate
CREATE TABLE #Left
(
id INT
,col1 CHAR(3)
)
CREATE TABLE #Right
(
id INT
,col2 CHAR(3)
)
INSERT INTO #Right
( id, col2)
VALUES
(1,'c1')
,(2,'c2')
,(3,'c4')
,(4,'c5')
INSERT INTO #Left
( id, col1)
VALUES
(1,'c1')
,(2,'c2')
,(3,'c3')
,(4,'c5')
-- empty set
SELECT
*
FROM #Left L
INNER JOIN #Right R
ON L.col1 = R.col2
WHERE col1 = 'c3'
-- matching rows
SELECT
*
FROM #Left L
INNER JOIN #Right R
ON L.col1 = R.col2
WHERE col1 = 'c2'
-- Left Join
SELECT
L.*
,COUNT(R.col2)
FROM #Left L
LEFT JOIN #Right R
ON L.col1 = R.col2
WHERE col1 = 'c3'
GROUP BY L.id, L.col1

How to join SELECT statements within a CASE statement to outer queries?

In the following I need to somehow join the fields within the case statement with the outer tables so that the fldNumb is selected for the current PK and CIA values. I am really stuck, can anyone help?
INSERT INTO #CTE (sMedNum)
SELECT T1.sMedNum
FROM #CTE INNER JOIN
(SELECT
(CASE WHEN (Charindex('.', CAST(rInd AS NVARCHAR(30))) > 0)
THEN CAST(( SELECT fldNumb
FROM #CTE
WHERE sCtr = FLOOR(rInd)) AS FLOAT) +
CAST(( SELECT fldNumb
FROM #CTE
WHERE Ind = FLOOR(rInd+1)) AS FLOAT) / 2
ELSE CAST(( SELECT fldNumb
FROM #CTE
WHERE sCtr = rInd) AS FLOAT)
END) AS sMedNum, fldPK, fldCIA
) T1
ON
#CTE.fldPK = T2.fldPK AND
#CTE.fldCIA = T2.fldCIA
Not having your database I cannot help you terribly much, but it looks like you need to be aliasing your tables. If you are trying to get a value from a table outside a subquery, you have to call it something different.
here is an example
select case
when a1.value = 1 then (select a2.id from tableA a2 where a1.val2 = a2.val2)
when a1.value = 2 then (select a3.id from tableA a3 where a1.val2 = a3.val2)
when a1.value = 3 then (select a4.id from tableA a4 where a1.val2 = a4.val2)
end as new_id
from tableA a1
hope this makes sense, if it doesn't, then that is what questions are for ;)