COALESCE function in OVER statement not working - sql

Can someone please tell me why the COALESCE is working on the first SELECT here and not the other two? I'm still getting NULL values on the second two statements.
(SELECT COALESCE(DEFax, NULL, '') FROM Debtor d WHERE d.DEIsPrimary = 1 AND d.CApKey = c.CApKey) AS FaxNumberOne,
(SELECT COALESCE(DEFax, NULL, '') FROM (SELECT ROW_NUMBER() OVER (ORDER BY DEpKey ASC)
AS rownumber, DEFax FROM Debtor d WHERE d.CApKey = c.CApKey AND d.DEIsPrimary <> 1)
AS foo WHERE rownumber = 1) AS FaxNumberTwo,
(SELECT COALESCE(DEFax, NULL, '') FROM (SELECT ROW_NUMBER() OVER (ORDER BY DEpKey ASC)
AS rownumber, DEFax FROM Debtor d WHERE d.CApKey = c.CApKey AND d.DEIsPrimary <> 1)
AS foo WHERE rownumber = 2) AS FaxNumberThree
Thanks!

Sample data and desired results would really help.
But a scalar subquery is a subquery that returns one column and zero or one rows. If it returns zero rows, then the value is NULL regardless of the expression in the SELECT. In other words, the COALESCE() needs to go outside, something like this:
coalesce( (select . . . . ),
''
)
Including NULL in the coalesce() list is not a good practice. It is unnecessary and misleading -- and always ignored.

Related

How to pivot two rows into two columns

I have the following SQL Query:
select
distinct
Equipment_Reserved.Equipment_Attached_To,
Equipment.Name
from
Equipment,
Studies,
Equipment_Reserved
where
Studies.Study = 'MAINT19-01'
and
Equipment.idEquipment = Equipment_Reserved.Equipment_idEquipment
and
Studies.idStudies = Equipment_Reserved.Studies_idStudies
and
Equipment.Type = 'Probe'
This query produces the following results:
Equipment_Attached_To Name
2297 R1-P1
2297 R1-P2
2299 R1-P3
I would like to change it to the following:
Equipment_Attached_To Name1 Name2
2297 R1-P1 R1-P2
2299 R1-P3 NULL
Thanks for your help!
I'd first change your query from the old, legacy JOIN syntax to an explicit join as it makes the query easier to understand:
SELECT
DISTINCT
Equipment_Reserved.Equipment_Attached_To,
Equipment.Name
FROM
Equipment
INNER JOIN Equipment_Reserved ON Equipment_Reserved.Equipment_idEquipment = Equipment.idEquipment
INNER JOIN Studies ON Studies.idStudies = Equipment_Reserved.Studies_idStudies
WHERE
Studies.Study = 'MAINT19-01'
AND
Equipment.Type = 'Probe'
I don't think you actually need a PIVOT - I think you can do this with a nested query with the ROW_NUMBER function. I've seen that PIVOT queries often have worse query execution plans than nested-queries.
Let's add ROW_NUMBER (which require an ORDER BY as it's a windowing-function) and a matching ORDER BY in the whole query to make it consistent). Let's also use PARTITION BY so it resets the row-number for each Equipment_Attached_To value:
SELECT
DISTINCT
Equipment_Reserved.Equipment_Attached_To,
Equipment.Name,
ROW_NUMBER() OVER (PARTITION BY Equipment_Attached_To ORDER BY [Name]) AS RowNumber
FROM
Equipment
INNER JOIN Equipment_Reserved ON Equipment_Reserved.Equipment_idEquipment = Equipment.idEquipment
INNER JOIN Studies ON Studies.idStudies = Equipment_Reserved.Studies_idStudies
WHERE
Studies.Study = 'MAINT19-01'
AND
Equipment.Type = 'Probe'
ORDER BY
Equipment_Attached_To,
[Name]
This will give output like this:
Equipment_Attached_To Name RowNumber
2297 R1-P1 1
2297 R1-P2 2
2299 R1-P3 1
This can then be split out into explicit columns like so below. The use of MAX() is arbitrary (we could use MIN() instead) and only because we're dealing with a GROUP BY and because the CASE WHEN... restricts the input set to just 1 row anyway.
SELECT
Equipment_Attached_To,
MAX( CASE WHEN RowNumber = 1 THEN [Name] END ) AS Name1,
MAX( CASE WHEN RowNumber = 2 THEN [Name] END ) AS Name2
FROM
(
-- the query from above
)
GROUP BY
Equipment_Attached_To
ORDER BY
Equipment_Attached_To,
Name1,
Name2
So the final query is:
SELECT
Equipment_Attached_To,
MAX( CASE WHEN RowNumber = 1 THEN [Name] END ) AS Name1,
MAX( CASE WHEN RowNumber = 2 THEN [Name] END ) AS Name2
FROM
(
SELECT
DISTINCT
Equipment_Reserved.Equipment_Attached_To,
Equipment.Name,
ROW_NUMBER() OVER (PARTITION BY Equipment_Attached_To ORDER BY [Name]) AS RowNumber
FROM
Equipment
INNER JOIN Equipment_Reserved ON Equipment_Reserved.Equipment_idEquipment = Equipment.idEquipment
INNER JOIN Studies ON Studies.idStudies = Equipment_Reserved.Studies_idStudies
WHERE
Studies.Study = 'MAINT19-01'
AND
Equipment.Type = 'Probe'
)
GROUP BY
Equipment_Attached_To
ORDER BY
Equipment_Attached_To,
Name1,
Name2
Let's start with some basics.
To facilitate reading the code, I added alias to the tables using their initials.
Then, I converted the old join syntax which is partly deprecated to use the standard syntax since 1992 (27 years and people still use the old syntax).
Finally, since there are only 2 possible values, we can use MIN and MAX to separate them in 2 columns.
And because we're using aggregate functions, we remove the DISTINCT and use GROUP BY
The code now looks like this:
SELECT er.Equipment_Attached_To,
--Gets the first row for the id
MIN( e.Name) AS Name1,
--If the MAX is equal to the MIN, returns a NULL. If not, it returns the second value.
NULLIF( MAX(e.Name), MIN( e.Name)) AS Name2
FROM Equipment e
JOIN Studies s ON s.idStudies = er.Studies_idStudies
JOIN Equipment_Reserved er ON e.idEquipment = er.Equipment_idEquipment
WHERE s.Study = 'MAINT19-01'
AND e.Type = 'Probe'
GROUP BY er.Equipment_Attached_To;

SQL LEFT() not working as expected when used with GROUP BY and Partition

I have codes that are like 1231231A, 1231231A, 3453454B etc
I need to group them by their number (ignoring the char which is a version) and just get one of each. I also need to drop the last char. My code works in grouping them and returning one of each, but it returns the last char.
Why is it returning the last char when i chop it off?
Expected output is
1231231
3453454
What I'm getting is
1231231A
3453454B
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY T.fldProductDescrip
ORDER BY T.fldEffectiveDate DESC) AS rn
FROM (
-- Insert statements for procedure here
SELECT JST.flduid
,JST.fldEffectiveDate
,(CASE
WHEN RIGHT(fldProductDescrip, 1) LIKE '[0-9]'
THEN fldProductDescrip
ELSE LEFT(fldProductDescrip, DATALENGTH(fldProductDescrip) - 1)
END) as fldProductDescrip
,(
CASE
WHEN PE.fldLogoutDateTime IS NULL
THEN PE.fldESigUser
ELSE ''
END
) AS LoggedIn
,(
CASE
WHEN PE.fldLogoutDateTime IS NULL
THEN PE.fldLoginDateTime
ELSE ''
END
) AS LoggedInDateTime
FROM tblJSJobSheetTemplates JST
INNER JOIN tblJSProducts JP ON JST.fldProductUID = JP.fldUID
INNER JOIN tblJSProductEsig PE ON JP.fldProductDescrip = PE.fldProduct
) AS T
WHERE LoggedIn <> ''
)AS G WHERE rn = 1

Declare, Set, and Use Variable in SQL Query (not a stored proc)

Maybe I'm missing something simple, but is there a way to declare a variable and use it within a single SQL query? That way, if you have a piece of information that is generated using a large nested query, you don't have to keep running that same query over and over again.
This would be a decent example illustrating the problem:
SELECT id, CASE WHEN LEN(HUGESUBQUERY) > 10 THEN
LEFT(HUGESUBQUERY, LEN(HUGESUBQUERY)-2) ELSE HUGESUBQUERY END AS result FROM table
With everything added in, it gets really long. Here is the actual query I am working with. (of note, I am running this from Dynamics AX, so I have no flexibility on doing anything other than everything in one SELECT statement. None of the solutions with DECLARE, SET, etc. are available in this environment.)
SELECT (CAST (
CASE WHEN
RIGHT(
(CONCAT(COALESCE((SELECT TOP 1 SalesId FROM CustInvoiceJour WHERE
CustInvoiceJour.InvoiceId = T1.INVOICE), 'NONE'),
'-', ROW_NUMBER () OVER (PARTITION BY T1.ACCOUNTNUM, (SELECT TOP 1 SalesId
FROM CustInvoiceJour
WHERE CustInvoiceJour.InvoiceId = T1.INVOICE) ORDER BY T1.DUEDATE)-1))
, 2) = '-0'
THEN
LEFT(
(CONCAT(COALESCE((SELECT TOP 1 SalesId FROM CustInvoiceJour WHERE
CustInvoiceJour.InvoiceId = T1.INVOICE), 'NONE'),
'-', ROW_NUMBER () OVER (PARTITION BY T1.ACCOUNTNUM, (SELECT TOP 1 SalesId
FROM CustInvoiceJour
WHERE CustInvoiceJour.InvoiceId = T1.INVOICE) ORDER BY T1.DUEDATE)-1)),
LEN(
(CONCAT(COALESCE((SELECT TOP 1 SalesId FROM CustInvoiceJour WHERE
CustInvoiceJour.InvoiceId = T1.INVOICE), 'NONE'),
'-', ROW_NUMBER () OVER (PARTITION BY T1.ACCOUNTNUM, (SELECT TOP 1 SalesId
FROM CustInvoiceJour
WHERE CustInvoiceJour.InvoiceId = T1.INVOICE) ORDER BY T1.DUEDATE)-1))
)-2
)
ELSE
(CONCAT(COALESCE((SELECT TOP 1 SalesId FROM CustInvoiceJour WHERE
CustInvoiceJour.InvoiceId = T1.INVOICE), 'NONE'),
'-', ROW_NUMBER () OVER (PARTITION BY T1.ACCOUNTNUM, (SELECT TOP 1 SalesId
FROM CustInvoiceJour
WHERE CustInvoiceJour.InvoiceId = T1.INVOICE) ORDER BY T1.DUEDATE)-1))
END
AS NVARCHAR(21))) AS DOC_ID,
T1.ACCOUNTNUM AS CUSTOMER_ID,T1.AMOUNTCUR AS DOCTOTAL,T1.DUEDATE AS
DUEDATE,T1.DOCUMENTDATE AS DOCDATE,
T1.RECID AS ID,T1.DATAAREAID AS DATAAREAID,T1.PARTITION AS
PARTITION,T1.RECID AS RECID,
(CAST ((T1.RECID + T1.RECVERSION) AS BIGINT)) AS SWX_COMPUTEDVERSION FROM
CUSTTRANS T1 WHERE (AMOUNTCUR>0)
ORDER BY DOC_ID
Yes you can considering that the query is not Correlated. Try this
DECLARE #HUGESUBQUERY VARCHAR(500)
SET #HUGESUBQUERY = (SELECT TOP 1 something
FROM somewhere
INNER JOIN table2
ON table2.id = somwhere.id
INNER JOIN table3
ON table3.id = table2.id
WHERE condition1 = condition1
AND condition2 = condition2)
SELECT id,
CASE #HUGESUBQUERY
WHEN Len(#HUGESUBQUERY) > 10 THEN LEFT(#HUGESUBQUERY,
Len(#HUGESUBQUERY) - 2)
END AS result
FROM table
For a correlated subquery, you can use CROSS APPLY. Since we don't have a real query, I'll just put one that demonstrates the technique:
select
*,cname
from
sys.objects so
cross apply
(select top 1 sc.name from sys.columns sc where sc.object_id = so.object_id) t (cname)
And now cname can be used throughout the SELECT clause to refer to the name value returned from sys.columns.

Include `WHERE` in a joined sql sequence

I have the following working sql sequence:
SELECT *, films.category AS filmCategory
FROM ( SELECT *
FROM films
ORDER BY `unique` ASC
LIMIT 6, 4) films
LEFT OUTER JOIN items ON items.unique = films.ref
ORDER BY films.unique ASC
This works well and selects the correct four elements from the DB. However, I have some rules that I check for using WHERE, that I can't get working. I have done the following:
SELECT *, films.category AS filmCategory
FROM ( SELECT *
FROM films
ORDER BY `unique` ASC
LIMIT 6, 4) films
LEFT OUTER JOIN items ON items.unique = films.ref
WHERE films.youtube IS NOT NULL AND films.youtube <> ''
ORDER BY films.unique ASC
where the only difference is the added line with the WHERE clause. But this doesn't work - in fact it makes no difference from before but returns the same rows.
How can I include these WHERE rules correctly in this sql sentence?
Note
The line films.youtube IS NOT NULL AND films.youtube <> '' is checking if a specific cell is empty. This is made with help from this question
Perhaps you are looking for a where clause in the subquery? That way, the limit will be applied after your where clause.
SELECT *, films.category AS filmCategory
FROM ( SELECT *
FROM films
WHERE films.youtube IS NOT NULL AND films.youtube <> ''
ORDER BY `unique` ASC
LIMIT 6, 4) films
LEFT OUTER JOIN items ON items.unique = films.ref
ORDER BY films.unique ASC
A small additional suggestion. You can simplify:
WHERE films.youtube IS NOT NULL AND films.youtube <> ''
to
WHERE films.youtube > ''
because null > '' is not true (but unknown.) Or perhaps more readable:
WHERE length(films.youtube) > 0

Reuse subquery result in WHERE-Clause for INSERT

i am using Microsoft SQL Server 2008
i would like to save the result of a subquery to reuse it in a following subquery.
Is this possible?
What is best practice to do this? (I am very new to SQL)
My query looks like:
INSERT INTO [dbo].[TestTable]
(
[a]
,[b]
)
SELECT
(
SELECT TOP 1 MAT_WS_ID
FROM #TempTableX AS X_ALIAS
WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
)
,(
SELECT TOP 1 MAT_WS_NAME
FROM #TempTableY AS Y_ALIAS
WHERE Y_ALIAS.MAT_WS_ID = MAT_WS_ID
--(
--SELECT TOP 1 MAT_WS_ID
--FROM #TempTableX AS X_ALIAS
--WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
--)
)
FROM [dbo].[LASERTECHNO] AS OUTERBASETABLE
My question is:
Is this correct what i did.
I replaced the second SELECT Statement in the WHERE-Clause for [b] (which is commented out and exactly the same as for [a]), with the result of the first SELECT Statement of [a] (=MAT_WS_ID).
It seems to give the right results.
But i dont understand why!
I mean MAT_WS_ID is part of both temporary tables X_ALIAS and Y_ALIAS.
So in the SELECT statement for [b], in the scope of the [b]-select-query, MAT_WS_ID could only be known from the Y_ALIAS table. (Or am i wrong, i am more a C++, maybe the scope things in SQL and C++ are totally different)
I just wannt to know what is the best way in SQL Server to reuse an scalar select result.
Or should i just dont care and copy the select for every column and the sql server optimizes it by its own?
One approach would be outer apply:
SELECT mat.MAT_WS_ID
, (
SELECT TOP 1 MAT_WS_NAME
FROM #TempTableY AS Y_ALIAS
WHERE Y_ALIAS.MAT_WS_ID = mat.MAT_WS_ID
)
FROM [dbo].[LASERTECHNO] AS OUTERBASETABLE
OUTER APPLY
(
SELECT TOP 1 MAT_WS_ID
FROM #TempTableX AS X_ALIAS
WHERE OUTERBASETABLE.LT_ALL_MATERIAL = X_ALIAS.MAT_RM_NAME
) as mat
You could rank rows in #TempTableX and #TempTableY partitioning them by MAT_RM_NAME in the former and by MAT_WS_ID in the latter, then use normal joins with filtering by rownum = 1 in both tables (rownum being the column containing the ranking numbers in each of the two tables):
WITH x_ranked AS (
SELECT
*,
rownum = ROW_NUMBER() OVER (PARTITION BY MAT_RM_NAME ORDER BY (SELECT 1))
FROM #TempTableX
),
y_ranked AS (
SELECT
*,
rownum = ROW_NUMBER() OVER (PARTITION BY MAT_WS_ID ORDER BY (SELECT 1))
FROM #TempTableY
)
INSERT INTO dbo.TestTable (a, b)
SELECT
x.MAT_WS_ID,
y.MAT_WS_NAME
FROM dbo.LASERTECHNO t
LEFT JOIN x_ranked x ON t.LT_ALL_MATERIAL = x.MAT_RM_NAME AND x.rownum = 1
LEFT JOIN y_ranked y ON x.MAT_WS_ID = y.MAT_WS_ID AND y.rownum = 1
;
The ORDER BY (SELECT 1) bit is a trick to specify an indeterminate ordering, which, accordingly, would result in indeterminate rownum = 1 rows picked by the query. That is to more or less duplicate your TOP 1 without an explicit order, but I would recommend you to specify a more sensible ORDER BY clause to make the results more predictable.