Optimize TSQL query with 3 tables - sql-server

I need to get all the runs from the database, but need to mark if there is an error for this run.
3 Tables:
Runs: contains the runs)
Runfiles: contains the file ids that were processed during a run
Messages: contains errors, warnings, ...
Can this query be optimized any further?
SELECT TOP 1000 runid,
start,
end,
userid,
CASE
WHEN EXISTS(SELECT rf.fk_fileid
FROM runfiles rf
WHERE rf.fk_runid = r.runid
AND EXISTS(SELECT m.messageid
FROM messages m
WHERE m.fk_fileid =
rf.fk_fileid
AND m.fk_statusid = 4))
THEN 1
ELSE 0
END AS ContainsError
FROM runs r
ORDER BY start DESC
Please don't comment on the table names, they were translated for this question.
Thanks!

Try this:
SELECT TOP 1000
r.runid
,r.start
,r.[end]
,r.userid
,CASE WHEN m.messageid IS NOT NULL THEN 1 ELSE 0 END AS ContainsError
FROM runs r
LEFT JOIN runfiles rf
ON rf.fk_runid = r.runid
LEFT JOIN [messages] m
ON m.fk_fileid = rf.fk_fileid
AND m.fk_statusid = 4
ORDER BY r.start DESC
Anything in the select list is ran for each row in the result set. This means that the nested subquery in your CASE statement is being executed for each of those TOP 1000 rows.
Using left joins and a CASE statement to check if the primary key is null allow the entire statement to be evaluated as a set, which SQL Server is built to do. It should perform better this way.

Related

The multi-part identifier "[column name]" could not be bound in UPDATE of TEMP Table

I am trying to create a stored procedure whereupon I input a (simple for now) query into a temp table, and then replace some of the data with data from a different table based on a key.
Here is the complete code:
CREATE PROCEDURE GetInquiryList
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.*,q.QuoteID INTO #Inq FROM Inquiries i left join Quotes q on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
--SELECT * FROM #Inq
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
SELECT c.CustomerName,
c.CustomerEmail,
c.CustomerPhone1,
c.CustomerBestTimetoCall,
c.customerDay,
c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
SELECT * FROM #Inq
END
I get the following error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I get this error for whatever column is placed first after the SET command.
Both query pieces of this work independently (the first select creating the temp table and the joined query at the bottom). The data returned is correct. I have tried using aliases (SELECT c.CustomerName AS Name, ...).
Originally, I used "#Inq i" in the second command, but changed to "j" out of an abundance of caution.
I have also run the command against the original table (substituting the Inquiry table for the temp table #Inq, and that fails as well).
Shortening it to this:
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
I get a different error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I'm sure it's probably something simple,(so simple that I can't find any references in any of my searches).
I'm sure it has something to do with the fact that you can't update the same instance of the table used in the join (I'm going to have to re-join again with a "k" alias). How do I do this?
data from the first query
data from the first query
data from the second select statement on the actual temp table
Here is what I updated the stored procedure to, which works exactly how I need it to:
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.* INTO #Inq FROM (
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
cust.CustomerName as InquiryCustomerName,
cust.CustomerEmail as InquiryCustomerEmail,
cust.CustomerPhone1 as InquiryCustomerPhone,
cust.CustomerBestTimeToCall as InquiryBestTimeToCall,
cust.CustomerDay as InquiryDay,
cust.CustomerNight as InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries inner join dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID and inquiries.InquiryCustomerID > 0
UNION ALL
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
InquiryCustomerName,
InquiryCustomerEmail,
InquiryCustomerPhone,
InquiryBestTimeToCall,
InquiryDay,
InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries WHERE inquiries.InquiryCustomerID = 0
) i
select i.*, q.QuoteID
FROM #Inq i left join dbo.Quotes as q
on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
END
Just stop using this pattern without a really good reason. Here it only appears to create more work for the database engine with no obvious benefit. Your procedure - as posted - has trivially simple queries so why bother with the temp table and the update?
It is also time to start learning and using best practices. Terminate EVERY statement - eventually it will be required. Does order of the rows in your resultset matter? Usually it does and that is only guaranteed when that resultset is produced by a query that includes an ORDER BY clause.
As a developing/debugging short cut, you can harness the power of CTEs to help you build a working query. In this case, you can "stuff" your first query into a CTE and then simply join the CTE to Customers and "adjust" the columns you need in that resultset.
WITH inquiries as (
select inq.*, qt.QuoteID
FROM dbo.Inquiries as inq left join dbo.Quotes as qt
on inq.InquiryId = qt.InquiryId
WHERE inq.YNDeleted = 0
)
select inquiries.<col>,
...,
cust.CustomerName as "InquiryCustomerName",
...
from inquiries inner (? guessing) dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID
order by ...
;
Schema names added as best practice. Listing the columns you actually need in your resultset is another best practice. Note I did not do that for the query in the CTE but you should. You can choose to create aliases for your resultset columns as needed. I listed one example that corresponds to your UPDATE attempt.
It is odd and very suspicious that all of the columns you intended to UPDATE exist in the Inquiries table. Are you certain you need to do that at all? Do they actually differ from the related columns in the Customer table? Also odd that the value 0 exists in InquiryCustomerID - suggesting you might have not a FK to enforce the relationship. Perhaps that means you need to outer join rather than inner join (as I wrote). If an outer join is needed, then you will need to use CASE expressions to "choose" which value (the CTE value or the Customer value) to use for those columns.
After learning a lot more about how things get bound to models, and how to further use sql, here is what my stored procedure looks like:
ALTER PROCEDURE [dbo].[GetInquiryList]
#InquiryID int = 0
AS
BEGIN
SET NOCOUNT ON
select i.InquiryId,
i.InquiryDateReceived,
i.InquiryCustomerID,
InquiryCustomerName =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerName
ELSE c.CustomerName
END,
InquiryCustomerEmail =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerEmail
ELSE c.CustomerEmail
END,
InquiryCustomerPhone =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerPhone
ELSE c.CustomerPhone1
END,
InquiryBestTimetoCall =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryBestTimetoCall
ELSE c.CustomerBestTimetoCall
END,
InquiryDay =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryDay
ELSE c.CustomerDay
END,
InquiryNight =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryNight
ELSE c.CustomerNight
END,
i.InquiryServiceType,
i.InquiryServiceID,
i.InquiryTimeframe,
i.InquiryProjectDescription,
i.InquiryDateResponded,
i.InquiryCustomerReply,
i.YNMigrated,
i.InquiryDateClosed,
i.YNClosed,
i.YNDeleted, ISNULL(q.QuoteId,0) AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
LEFT JOIN dbo.Customers c ON i.InquiryCustomerID = c.CustomerId
WHERE i.YNDeleted = 0
END
I'm sure there are additional enhancements that could be made, but avoiding the union is a big savings. Thanks, everyone.

SQL Server Sum Returning Invalid Number

I am using LEFT JOIN three times in a query to display user information. For some reason, the second last LEFT JOIN affects the output of the SUM function.
I have tried removing the second last LEFT JOIN statement which returns the correct value. I don't see why it would change the value.
SELECT
[tbl_users].[id],
[username],
COUNT([tbl_password_resets].[id]) as passwordresets,
SUM(CASE WHEN [tbl_files].[user_id] = [tbl_users].[id] THEN 1 ELSE 0 END) as uploads,
COUNT([tbl_downloads].[id]) as downloads,
CAST(SUM(CASE WHEN [tbl_downloads].[liked] = 1 OR [tbl_downloads].[disliked] = 1 THEN 1 ELSE 0 END) AS FLOAT) / NULLIF(COUNT([tbl_downloads].[id]), 0) as ratio,
[ban]
FROM
[tbl_users]
LEFT JOIN
[tbl_password_resets] ON [tbl_users].[id] = [tbl_password_resets].[user_id]
LEFT JOIN
[tbl_downloads] ON [tbl_users].[id] = [tbl_downloads].[user_id]
LEFT JOIN
[tbl_files] ON [tbl_files].[user_id] = [tbl_users].[id]
GROUP BY
[tbl_users].[id], [tbl_users].[username], [tbl_users].[ban]
The result for uploads is 9 instead of 3.
If any joined table is duplicating rows, that affects the result of COUNTs and SUMs. Comment out the aggregates and the GROUP BY and the JOINs for testing and see what happens with the row count when you build the query adding the JOINs one-by-one. The more rows fall into one group, the more values will be COUNTed and SUMmed.

SUM vs EXIST in SqlServer

The intent is to return all 'Unprocessed' TransactionSets if they have NO PaymentUid and NO ProcessStatus.value('/CPI/#ProcessItem)[1]'... relations, and also pick up 'No-Matched-Payments' TransactionSets if they have ANY PaymentUid AND ANY ProcessStatus.value('/CPI/#ProcessItem)[1]'... relations.
The SUM function in the having seem clunky and don't allow SQL to quit when it encounters any or none. So it seems like it's inefficient, and at the very least quite clunky to read and deal with. Is there a way to write this with something like an EXIST ?
select ts.TransactionSetUid
from TransactionSet ts
join TransactionHeader eh on ts.TransactionSet = eh.TransactionSet
join TransactionPayment tp on eh.TransactionHeaderUid = tp.TransactionHeaderUid
left join ServicePayment sp on tp.TransactionPaymentUid = sp.TransactionPaymentUid
where TransactionStatus in ('Unprocessed', 'No-Matched-Payments')
group by ts.TransactionSet
having (TransactionStatus = 'Unprocessed'
and SUM( CASE WHEN sp.TransactionItem is null THEN 0 ELSE 1 END) = 0
and SUM( CASE WHEN tp.ProcessStatus.value('(/CPI/#ProcessItem)[1]', 'varchar(50)') IS NULL THEN 0 ELSE 1 END) = 0)
or (ts.RuleStatus = 'No-Matched-Payments'
and (SUM( CASE WHEN sp.TransactionItem is null THEN 0 ELSE 1 END) <> 0
or SUM( CASE WHEN tp.ProcessStatus.value('(/CPI/#ProcessItem)[1]', 'varchar(50)') IS NULL THEN 0 ELSE 1 END) <> 0))
UPDATE to answer questions. The relationships between the TransactionSet is one to many with the other tables. There could be many TransactionPayment records but the query is only concerned with ProcessStatus.value that has an xml node at (/CPI/#processItem)[1]. But with ServicePayment, any non-null TransactionItem will do.
As I understand it, the group by is only in there because of the SUM functions. The intent is to flag any TransactionSet that meets one of two conditions.
The first condition is:
the Transaction Status is 'Unprocessed'
and
there are no Process Status values
and
there are no Transaction Items.
The second condition is:
the Transaction Status is 'No-Matched-Payments'
and
there is at least one Process Status value
or
there is at least one Transaction Item.
So the query was set up to use SUM to count the number of times the left join on ServicePayment comes up NULL or when the XML value in TransactionPayment doesn't contain a '/CPI/#processItem'.
It seems to me that instead of using a SUM, the query could instead use an EXIST or some other mechanism to short circuit the test condition. The value of the SUM is not really important, It just needs to know if there is at least one or if there are none.
--
Thank you to everyone: I know i'm not a database expert by any means, and I've been programming in the seven C's (C,C++,C#,Java,etc.) for so long that I sometimes forget that SQL is not an imperative language, or more likely, I just don't think in declarative terms.
I think something like this should do the trick:
select ts.TransactionSetUid
from TransactionSet ts
where CASE WHEN EXISTS(SELECT * FROM TransactionHeader eh
join TransactionPayment tp on eh.TransactionHeaderUid = tp.TransactionHeaderUid
left join ServicePayment sp on tp.TransactionPaymentUid = sp.TransactionPaymentUid
where ts.TransactionSet = eh.TransactionSet and
(
sp.TransactionItem is not null or
tp.ProcessStatus.value('(/CPI/#ProcessItem)[1]', 'varchar(50)') IS not NULL
)
) THEN 1 ELSE 0 END =
CASE TransactionStatus
WHEN 'Unprocessed' THEN 0
WHEN 'No-Matched-Payments' THEN 1
END
That is, I've put the EXISTS check in to test for either condition and put it inside a CASE expression so that we don't have to write it out twice for which result we want (for Unprocessed and No-Matched-Payments).
I've also crafted the second CASE expression to return 0, 1 or NULL so that if the TransactionStatus is something else, it doesn't matter what result the EXISTS produces.
I hope I've followed the correct chains of 0/1, true/false, and/or, NULL/NOT NULL logic here - if it's not 100%, it's hopefully just tweaks to those options. I've also assumed I can shift all of the tables except TransactionSet into the EXISTS - it may be that TransactionHeader has to stay outside if that's where TransactionStatus is coming from.
If this isn't correct, you should probably add bare-bones tables and sample data to your question, alongside the expected results.
Yes, this might work... -- your query did not include a select distinct, but if this this produces duplicate TransactionSetUids, add the keyword distinct...
select [distinct] ts.TransactionSetUid from TransactionSet ts
join TransactionHeader th
on th.TransactionSet = ts.TransactionSet
join TransactionPayment tp
on tp.TransactionHeaderUid = th.TransactionHeaderUid
where not exists
( Select * from ServicePayment
Where TransactionPaymentUid = tp.TransactionPaymentUid
and tp.ProcessStatus.value(
'(/CPI/#ProcessItem)[1]', 'varchar(50)') IS NULL
and TransactionStatus = 'Unprocessed')
Or exists
( Select * from ServicePayment
Where TransactionPaymentUid = tp.TransactionPaymentUid
and ts.RuleStatus = 'No-Matched-Payments'
and tp.ProcessStatus.value(
'(/CPI/#ProcessItem)[1]', 'varchar(50)') IS not NULL
and ts.RuleStatus = 'No-Matched-Payments')

SQL Server Remove unwanted rows from a CASE statement

I do a SELECT with a CASE statement with this following:
SELECT DISTINCT
n.NiveauId, n.Description,
CASE WHEN n.NiveauId NOT IN (SELECT ccs.idNiveau WHERE ccs.centreCout = 60001) THEN 0 ELSE 1 END AS attribue
FROM pa.dbo.Niveau n
JOIN BDC.dbo.CentreCoutSecteur ccs ON n.NiveauId = ccs.idNiveau
Explication :
In case "NiveauId" is not present in the other table, the value of "attribue" is 0. Else, if it's present, the value is 1.
This works, but every rows that contains a 1 also shows the same row with a 0.
Exemple:
How would I change the SELECT query to remove the unwanted duplicate rows that contain 0?
Thanks in advance!
Try wrapping your select in a max (if you only want the rows with the highest value for attribue.
SELECT b.NiveauID, b.Description, MAX(b.attribue)
FROM
(SELECT DISTINCT
n.NiveauId, n.Description,
CASE WHEN n.NiveauId NOT IN (SELECT ccs.idNiveau WHERE ccs.centreCout = 60001) THEN 0 ELSE 1 END AS attribue
FROM pa.dbo.Niveau n
JOIN BDC.dbo.CentreCoutSecteur ccs ON n.NiveauId = ccs.idNiveau) b
Group By b.NiveauID, b.Description

SQL server recursive query error.The maximum recursion 100 has been exhausted before statement completion

I have a recursive query that returns an error when I run it; in other databases (with more data) I have not the problem.
In my case this query returns 2 colums (ID_PARENT and ID_CHILD) doing a recursion because my tree can have more than one level, bit I wanna have only "direct" parent.
NOTE: I tried to put OPTION (MAXRECURSION 0) at the end of the query, but with no luck.
The following query is only a part of the entire query, I tried to put OPTION only at the end of the "big query" having a continous running query, but no errors displayed.
Error have in SQL Server:
"The statement terminated.The maximum recursion 100 has been exhausted before statement completion"
The query is the following:
WITH q
AS (SELECT ID_ITEM,
ID_ITEM AS ID_ITEM_ANCESTOR
FROM ITEMS_TABLE i
JOIN ITEMS_TYPES_TABLE itt
ON itt.ID_ITEM_TYPE = i.ID_ITEM_TYPE
UNION ALL
SELECT i.ID_ITEM,
q.ID_ITEM_ANCESTOR
FROM q
JOIN ITEMS_TABLE i
ON i.ID_ITEM_PADRE = q.ID_ITEM
JOIN ITEMS_TYPES_TABLE itt
ON itt.ID_ITEM_TYPE = i.ID_ITEM_TYPE)
SELECT ID_ITEM AS ID_CHILD,
ID_ITEM_ANCESTOR AS ID_PARENT
FROM q
I need a suggestion to re-write this query to avoid the error of recursion and see the data, that are few.
Ok, after some investigations, for some reason, there were a circular references in 2 records: ITEM 1 was child of ITEM 2 and ITEM 2 was child of ITEM 1.
Changing manually the values, query run perfectly.
WITH q AS ( SELECT ID_ITEM ,
ID_ITEM AS ID_ITEM_ANCESTOR
FROM ITEMS_TABLE i
JOIN ITEMS_TYPES_TABLE itt ON itt.ID_ITEM_TYPE = i.ID_ITEM_TYPE
UNION ALL
SELECT i.ID_ITEM ,
q.ID_ITEM_ANCESTOR
FROM q
JOIN ITEMS_TABLE i ON i.ID_ITEM_PADRE = q.ID_ITEM
JOIN ITEMS_TYPES_TABLE itt ON itt.ID_ITEM_TYPE = i.ID_ITEM_TYPE
)
SELECT ID_ITEM AS ID_CHILD ,
ID_ITEM_ANCESTOR AS ID_PARENT
FROM q
OPTION ( MAXRECURSION 500 )

Resources