Find What Column i Joined on when using OR - sql

In My database I have a row with multiple Codes, EG:
ID,Code1,Code2,Code3,Code4
These codes reference a Procedure Name in another table EG:
Code1 = 'Procedure one'
Code2 = 'Procedure two'
ect.
I needed to convert this single row to show one line for every code, and corresponding procedure name EG:
ID,ProcedureName
1,'Procedure One'
1,'Procedure two'
2,'Procedure one'
To get this to work I'm using an outer join with an OR statement, not the most performance effective, but since the ProcedureName Table isn't that large, i'm not too fussed about overhead at the moment, more about getting it to work.
FROM Events EV
LEFT JOIN ProcedureName PN
ON (PN.CODE = Ev.Code1)
OR (PN.CODE = Ev.Code2)
OR (PN.CODE = Ev.Code3)
OR (PN.CODE = Ev.Code4)
This works, however Now I have the problem of being able to tell What procedure is the Primary, and Secondary. Usually the Primary/secondary is denoted purely by whatever one is in the first Code. IE the primary would be whatever is in Code1, secondary in code2, ect.
However since I have now Joined using an OR, i now have no idea what Code that the procedure has joined to.
I've thought of just doing a case statement
CASE
WHEN PN.CODE = Ev.Code1 THEN
'(Primary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code2 THEN
'(Secondary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code3 THEN
'(Tertiary) ' + ISNULL(PN.NAME, '')
WHEN PN.CODE = Ev.Code4 THEN
'(Quaternary) ' + ISNULL(PN.NAME, '')
END AS ProcedureName,
However this has the major issue of, on the off chance, that both code1 and code2 are the same code. Which means they will both show up as primary.
Can anyone give me any hints as to how to find out what the OR join actually Joined on? did it join on code1, code2? is there perhaps a better way to write the join that will allow me to have multiple lines per ID (depending on amount of codes) whilst still allowing me to find out where they are Code1 or code2?

I would reword the question slightly. In reality the it doesn't "join on a column", it joins on the result of a boolean expression.
So, what you want is to find out which parts of the boolean expression are true or not...
SELECT
*,
CASE WHEN PN.CODE = Ev.Code1 THEN 1 ELSE 0 END AS MatchingCode1,
CASE WHEN PN.CODE = Ev.Code2 THEN 1 ELSE 0 END AS MatchingCode2,
CASE WHEN PN.CODE = Ev.Code3 THEN 1 ELSE 0 END AS MatchingCode3,
CASE WHEN PN.CODE = Ev.Code4 THEN 1 ELSE 0 END AS MatchingCode4
FROM
Events EV
LEFT JOIN
ProcedureName PN
ON PN.CODE IN (Ev.Code1, Ev.Code2, Ev.Code3, Ev.Code4)
If you want that as a single column, you could use binary arithmetic.
SELECT
*,
CASE WHEN PN.CODE = Ev.Code1 THEN 1 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code2 THEN 2 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code3 THEN 4 ELSE 0 END +
CASE WHEN PN.CODE = Ev.Code4 THEN 8 ELSE 0 END AS MatchingCodes
FROM
Events EV
LEFT JOIN
ProcedureName PN
ON PN.CODE IN (Ev.Code1, Ev.Code2, Ev.Code3, Ev.Code4)
Here a value of 1 in MatchingCodes means that Code1 is a match. Similarly a value of 3 means Code1 and Code2 both match, or a value of 15 means that all the codes match.
EDIT: (After making it clear that you want multiple rows)
This is similar to Gordon's answer, but has slightly different behaviour; you get 1 row per match instead of 4 rows all the time, or one row with NULLs if there is no match.
SELECT
*
FROM
Events EV
OUTER APPLY
(
SELECT 1 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code1
UNION ALL
SELECT 2 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code2
UNION ALL
SELECT 3 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code3
UNION ALL
SELECT 4 AS MatchedCode, * FROM ProcedureName WHERE CODE = EV.Code4
)
PN

I think apply does what you want:
select e.id, v.which, v.code
from Events e cross apply
(values ('procedure1', code1), ('procedure2', code2), ('procedure3', code3), ('procedure4', code4)
) v(which, code)
If you want to filter out codes that are NULL, then add:
where v.code is null

Related

Check and Change for empty or null value of column in SQL?

How can I change column text Not Exists when it is empty or null ?
My query :
Select TOP 1 ISNULL(NULLIF(DR.Name,''),'Not Exists') as Name,
DR.Name as Name ,Coalesce(NullIf(rtrim(DR.Name),''),'Not Exist') as Name,
Name = case when DR.Name is null then 'Not Exists'
when DR.Name='' then 'Not Exists' else DR.Name end
from Transfer TR
join Driver DR on DR.OID=TR.DriverID
WHERE TR.TruckID=51 AND TR.Statues<>7 and TR.DateScheduled<GETDATE()
AND TR.DateScheduled>=DATEADD(DAY,-7,GETDATE()) ORDER BY TR.OID DESC
Result :
If you just need a single column, then you can use a sub-select, this way when no rows are returned by the query you will still get not exists:
SELECT Name = ISNULL(( SELECT TOP 1 NULLIF(DR.Name,'')
FROM Transfer AS TR
INNER JOIN Driver AS DR
ON DR.OID = TR.DriverID
WHERE TR.TruckID = 51
AND TR.Statues <> 7
AND TR.DateScheduled < GETDATE()
AND TR.DateScheduled >= DATEADD(DAY, -7, GETDATE())
ORDER BY TR.OID DESC), 'Not Exists');
If you need multiple columns then you could union your Not Exists record to the bottom of the query, place all this inside a subquery then select the top 1 again, ensuring that your actual value takes precedence (by adding the column SortOrder):
SELECT TOP 1 Name, SomeOtherColumn
FROM ( SELECT TOP 1
Name = NULLIF(DR.Name,''),
SomeOtherColumn,
SortOrder = 0
FROM Transfer AS TR
INNER JOIN Driver AS DR
ON DR.OID = TR.DriverID
WHERE TR.TruckID = 51
AND TR.Statues <> 7
AND TR.DateScheduled < GETDATE()
AND TR.DateScheduled >= DATEADD(DAY, -7, GETDATE())
ORDER BY TR.OID DESC
UNION ALL
SELECT 'Not Exists', NULL, 1
) AS t
ORDER BY SortOrder;
I'm not entirely sure I understand your question, but if you are trying to catch nulls and empty strings "in one go", try this:
select TOP 1
case when length(trim(coalesce(DR.Name, ''))) = 0 then
'Not Exists'
else
DR.Name
as Name
....
The coalesce catches the NULLs and sets a replacement value. The trim gets rid of any padding and the length checks if what is left is an empty string --> so this covers nulls, padded- and non-padded trivial strings.
Assuming the value has regular spaces, the following would keep your approach:
Select TOP 1 ISNULL(NULLIF(ltrim(rtrim((DR.Name))), ''), 'Not Exists') as Name,
I would probably go with the more explicit:
select top 1 (case when ltrim(rtrim((DR.Name)) = '' or DR.Name is null then 'Not Exists'
else DR.Name end) as Name
Unless you also wanted the spaces removed from Name in the output.
If you have other characters, then you can use ASCII() to find them. Something like:
select ASCII(LEFT(DR.Name, 1))
. . .
where LEFT(DR.Name, 1) NOT LIKE '[a-zA-Z0-9 ]' -- you can expand this list of allowed characters
It seems to me you are not actually looking for a way to replace an empty string with 'Not Exists', but an empty result set.
In other words: It looks like you are looking for a way to show 'Not Exists' in case your query returns no rows. If it is this what you are looking for, then first "add" a 'Not Exists' record to your result set and then show the best row, i.e. the desired row in case such a row exists, else your 'Not Exists' row.
select top 1 name
from
(
select name, tr.oid
from transfer tr
join driver dr on dr.oid=tr.driverid
where tr.truckid=51 and tr.statues<>7 and tr.datescheduled<getdate()
and tr.datescheduled>=dateadd(day,-7,getdate())
union all
select 'Not Exists', -1
)
order by oid desc;
I chose -1 for the dummy OID. It must be a value smaller than any real OID. So if you have negative values, make that value even smaller.

Issues with counting records in secondary table based on complex criteria

I need to be able to count the number of records in a secondary table tblOptyRecordsHistorical which are related to the main table tblOptyRecordsCurrent.
The tables are exactly the same, the main contains the current 'daily snapshot', the secondary table contains previous daily snapshots.
I have a number of flags which use the following basic syntax:
(SELECT COUNT(OpportunityRecordID) AS Expr1
FROM dbo.tblOptyRecordsHistorical AS hist
WHERE (OpportunityGlobalCRMId = curr.OpportunityGlobalCRMId))
AS prevEntries,
This works fine. But one flag, I need to count the number of records in the historical table, but the logic is more complicated and depends on values from the main table:
SELECT OpportunityGlobalCRMId,
(SELECT SUM(CASE WHEN curr.PartnerGlobalCRMID IS NULL THEN CASE WHEN
hist.IgnoreOpportunity != 0 THEN 1 ELSE 0 END ELSE CASE
WHEN curr.CustomerAccountID IS NULL THEN CASE WHEN hist.IgnoreOpportunity = 1 AND
hist.PartnerGlobalCRMID = curr.PartnerGlobalCRMID THEN 1 ELSE 0 END ELSE CASE WHEN
hist.IgnoreOpportunity = 1 AND CONVERT(varchar, hist.CustomerAccountID) +
hist.PartnerGlobalCRMID = CONVERT(varchar, curr.CustomerAccountID) +
curr.PartnerGlobalCRMID AND hist.OpptyIncentiveCreatedDate =
curr.OpptyIncentiveCreatedDate THEN 1 ELSE 0 END END END) AS Expr1 FROM
dbo.tblOptyRecordsHistorical AS hist WHERE (OpportunityGlobalCRMId =
curr.OpportunityGlobalCRMId)) AS prevIgnored
FROM dbo.tblOptyRecordsCurrent AS curr
I've omitted the other flags and fields except for the initial OpportunityGlobalCRMID. This results in the following error: Multiple columns are specified in an aggregated expression containing an outer reference. If an expression is being aggregated contains an outer reference, then that outer reference must be the only column referenced in the expression.
SQL Server does not like mixing of inner (hist table) and outer (curr table) in a aggregate subquery expression. Some explanation is available here.
The proposed solutuon is to re-include the outer table in the sub-query, joining on it's key, in order to make all references inner. In your case, that would mean putting the tblOptyRecordsCurrent table inside the subquery, like this:
SELECT OpportunityGlobalCRMId,
(SELECT SUM(CASE
WHEN curr2.PartnerGlobalCRMID IS NULL
THEN CASE WHEN hist.IgnoreOpportunity != 0 THEN 1 ELSE 0 END
ELSE CASE
WHEN curr2.CustomerAccountID IS NULL
THEN CASE
WHEN hist.IgnoreOpportunity = 1 AND hist.PartnerGlobalCRMID = curr2.PartnerGlobalCRMID THEN 1 ELSE 0 END
ELSE CASE
WHEN hist.IgnoreOpportunity = 1
AND CONVERT(varchar, hist.CustomerAccountID) + hist.PartnerGlobalCRMID
= CONVERT(varchar, curr2.CustomerAccountID) + curr2.PartnerGlobalCRMID
AND hist.OpptyIncentiveCreatedDate = curr2.OpptyIncentiveCreatedDate
THEN 1
ELSE 0
END
END
END) AS Expr1
FROM dbo.tblOptyRecordsHistorical AS hist
inner join dbo.tblOptyRecordsCurrent AS curr2 on curr2.OpportunityGlobalCRMId = hist.OpportunityGlobalCRMId
WHERE curr2.OpportunityGlobalCRMId = curr.OpportunityGlobalCRMId) AS prevIgnored
FROM dbo.tblOptyRecordsCurrent AS curr
Haven't tested the code however.

Sorting based on multiple conditions

I am doing a exercise
I'll fire a query on the DB and get some 500 results. Now i want to sort this list based on some conditions and present the sorted list in client side.
I am using Java/Java EE and MySQL server 5.5
Conditions are like this,
Example: Consider a table having listed with cars
So, i ll fire a query on the table and it will list some 500 cars. now i want to sort this list based on user criteria.
conditions are age of car, colour of car and facilities of cars. List should be sorted like this
First appears the list of cars which satisfies all three conditions ie., same age as mentioned by end user, same colour and with all facilities user selected.
Second appears any 2 conditions satisfying cars list and one condition not satifying.
Third appears any one condition satisfying cars list and not the other two.
And finally appears the list of cars of which no conditions are satisfied.
How can i achieve this. I have searched in google, asked in irc channels regarding this. Couldn't get any help.
I have tried using RANK function by defining the CASES and finally order by RANK. It works for me while the conditions fields (columns) are of same table. In my case the fields are from a parent table as well as its child tables which has many to one relationship with its parent. Like in this example, age and color of the cars are stored in parent table and facilities that cars has are stored in another table. I tried doing the same using inner join, but no luck.
I tried something like this:
Query:
select distinct t0.id,t0.name,t0.price,
CASE
WHEN
t1.age='2' AND t1.colour='Red' AND t2.facilities_id=9 THEN 1
WHEN
t1.age='2' AND t1.colour='Red' AND t2.facilities_id!=9 THEN 2
WHEN
t1.age='2' AND t1.colour!='Red' AND t2.facilities_id=9 THEN 3
WHEN
t1.age!='2' AND t1.colour='Red' AND t2.facilities_id=9 THEN 4
WHEN
t1.age!='2' AND t1.colour='Red' AND t2.facilities_id!=9 THEN 5
WHEN
t1.age='2' AND t1.colour!='Red' AND t2.facilities_id!=9 THEN 6
WHEN *
t1.age!='2' AND t1.colour!='Red' AND t2.facilities_id=9 THEN 7
ELSE 8
END as pre_status
from cars_listing t0
inner join
cars_listing_details t1
on t0.id=t1.mg_listing_id
inner join
cars_facilities_listing t2
on t1.cars_listing_id=t2.listing_id
where t0.type='new_cars'
order by pre_status
Thanks in advance for helping.
try ordering by something like...
order by
case when first_condition then 1 else 0 end
+ case when second_condition then 1 else 0 end
+ case when third_condition then 1 else 0 end DESC
select distinct
t0.id,
t0.name,
t0.price,
case when t1.age = '2' then 1 else 0 end as MatchedAge,
case when t1.colour='Red' then 1 else 0 end as MatchedColor,
case when t2.facilities_id = 9 THEN 1 else 0 end as MatchedFacility
from
cars_listing t0
inner join cars_listing_details t1
on t0.id = t1.mg_listing_id
inner join cars_facilities_listing t2
on t1.cars_listing_id = t2.listing_id
where
t0.type = 'new_cars'
order by
case when t1.age = '2' then 1 else 0 end
+ case when t1.colour='Red' then 1 else 0 end
+ case when t2.facilities_id = 9 THEN 1 else 0 end DESC
If one field is a higher priority -- such as a red car, you could even give that more weight than the other in the order by... So a Red car at Facility 5 would show before a Blue car at facility 9 just by changing the order by to something like
order by
case when t1.age = '2' then 1 else 0 end
+ case when t1.colour='Red' then 5 else 0 end <-- applyi higher Wgt to color match vs other criteria
+ case when t2.facilities_id = 9 THEN 1 else 0 end DESC
Well, I have done Dynamic sql where condition in my project. It might help you. I have created a stored procedure for SELECT query. (I have done it in SQL Server 2008 R2). Tell me if you need more help.
USE [DATABASE_NAME]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[PROCEDURE_NAME]
#Id int = NULL,
#Requester varchar(20) = NULL,
#Suggester varchar(20) = NULL
AS
BEGIN
DECLARE #sql nvarchar(4000)
SELECT #sql='SELECT Id, Suggester, Requester from DATABASE_NAME.dbo.TABLE_NAME WHERE 1=1 '
If (#Id) IS NOT NULL
SELECT #sql=#sql + ' AND Id=(#Id) '
If (#Suggester) IS NOT NULL
SELECT #sql=#sql + ' AND Suggester like (#Suggester) '
If (#Requester) IS NOT NULL
SELECT #sql=#sql + ' AND Requester like (#Requester) '
EXEC sp_executesql #sql, N'#id int, #Requester varchar(20), #Suggester varchar(20)',
#Id, #Requester, #Suggester
END
GO
Here in this SP; Id,Requester,Suggester are field names.

Replacing In clause with exists

HI Gurus,
I'm looking to replace an IN clause with exists, but despite reading other similar cases on here I've not been able to apply them to my dataset.
I am looking to add in a column to my main query which tells me if a fund is found within a separate list, and if it does then label it 'emergency' and if not then 'non-emergency'
The list is defined like so:
select
f.id
FROM _audit a
INNER JOIN _fund f ON a.article_id = f.id
WHERE a.entity_name = 'Fund'
AND a.Changes LIKE
'%finance_code2%OldValue>3%'
)
UNION
(
select
id AS fund_reference
FROM _fund
WHERE (finance_code2 LIKE '3%'
OR finance_code2 LIKE '9%')
AND finance_code2 IS NOT NULL
And so what I am looking for is essentially something like:
SELECT
...Main query here...
,CASE WHEN fund_id IN (list_details) THEN 'emergency' else 'non-emergency' end
I know that it would be more efficient to do something like
SELECT
...Main query here...
,SELECT CASE WHEN EXISTS
(SELECT fund_id FROM list_details WHERE fund_id IS NOT NULL) THEN 'emergency' else 'non-emergency' END
But every time I try it keeps returning false values (saying that funds are contained within the list when they are not)
In case it helps I'm using sql server 2005 and the main query is listed below, where the list_details result (id) is joined onto donation_fund_allocation on list_details.id = donation_fund_allocation.fund_id
As always any clue would be massively appreciated :)
Thanks!
Main query
SELECT
don.supporter_id AS contact_id
,don.id AS gift_id
,YEAR(don.date_received) AS calendar_year
,YEAR(don.date_received) - CASE WHEN MONTH(don.date_received) < 4 THEN 1 ELSE 0 END AS financial_year
,don.date_received AS date_received
,don.event_id AS event_id
,SUM(CASE WHEN don.gift_aid_status <> 4 THEN don.value_gross * ((dfa.percentage) / 100)
WHEN don.gift_aid_status = 4 AND don.value_net > don.value_gross
AND don.value_net <> 0 THEN don.value_net * ((dfa.percentage) / 100)
ELSE don.value_gross * ((dfa.percentage) / 100)
END
) AS donation_value
--**List details query to go in here**
FROM donation don WITH (nolock)
INNER JOIN donation_fund_allocation dfa WITH (nolock) ON dfa.donation_id = don.id
WHERE don.supporter_id IS NOT NULL
AND don.status = 4
AND don.value_gross <> 0
GROUP BY don.supporter_id
,don.id
,don.date_received
,don.event_id
You need to correlate the exists call with the outer query. As written you are just asking if there exist any rows in list_details where fund_id isn't null
So, what you actually want is
SELECT
...Main query here...
,SELECT CASE WHEN EXISTS
(SELECT 1 FROM list_details WHERE fund_id = outer.fund_id) THEN 'emergency' else 'non-emergency' END
Where outer is the table alias for where fund_id can be found in your main select
You could write a function which takes the fund_id and returns an appropriate string value of "emergency" or "non-emergency".

SQL Server query - loop question

I'm trying to create a query that would generate a cross-check table with about 40 custom columns that show Y or N. Right now I have
SELECT DISTINCT [Company],
[Option1],
[Option2],
[Option3],
CASE
WHEN [Table1].[ID1] IN (SELECT ID2 FROM Table2 WHERE Variable = 1 AND Bit = 1) THEN
'Y'
ELSE 'N'
END AS 'CustomColumn1:',
CASE
WHEN [Table1].[ID1] IN (SELECT ID2 FROM Table2 WHERE Variable = 2 AND Bit = 1) THEN
'Y'
ELSE 'N'
END AS 'CustomColumn1:',
CASE
WHEN [Table1].[ID1] IN (SELECT ID2 FROM Table2 WHERE Variable = 3 AND Bit = 1) THEN
'Y'
ELSE 'N'
END AS 'CustomColumn1:',
.............
-- REPEAT ANOTHER 40 times
FROM [Table1]
WHERE [Table1].[OtherCondition] = 'True'
ORDER BY [Company]
So my question is, how do I create a loop (while? for?) that will loop on variable and assign Y or N to the row based on the condition, rather than creating 40+ Case statements?
You couldn't use a loop, but you could create a stored procedure/function to perform the sub-select and case expression and call that 40 times.
Also, you could improve performance of the sub-select by changing it to
SELECT 1 FROM Table2 WHERE EXISTS [Table2].[ID2] = [Table1.ID1] AND Variable = 3 AND Bit = 1
A loop (that is, iterating through a cursor) works on rows, not columns. You will still have to have 40 expressions, one for each column, and the performance will be terrible.
Let SQL Server do its job. And do your bit by telling exactly what you need and creating proper indices. That is, replace
CASE WHEN [Table1].[ID1] IN (SELECT ID2 FROM Table2 WHERE Variable = 2 AND Bit = 1)
with
CASE WHEN EXISTS (SELECT 0 FROM Table2 WHERE ID2 = [Table1].[ID1] AND Variable = 2 AND Bit = 1)
If the output is so vastly different than the schema, there is a question as to whether the schema properly models the business requirements. That said, I would recommend just writing the SQL. You can simplify the SQL like so:
Select Company
, Option1, Option2, Option3
, Case When T2.Variable = 1 Then 'Y' Else 'N' End As CustomCol1
, Case When T2.Variable = 2 Then 'Y' Else 'N' End As CustomCol2
, Case When T2.Variable = 3 Then 'Y' Else 'N' End As CustomCol3
, Case When T2.Variable = 4 Then 'Y' Else 'N' End As CustomCol4
...
From Table1 As T1
Left Join Table2 As T2
On T2.ID2 = T1.ID
And T2.Bit = 1
Where T1.OtherCondition = 'True'
Group By T1.Company
Order By T1.Company
If you want to write something that can help you auto-gen those Case statements (and you are using SQL Server 2005+), you could do something like:
With Numbers As
(
Select 0 As Value
Union All
Select Value + 1
From Numbers
Where Value < 41
)
Select ', Case When T2.Variable = ' + Cast(N.Value As varchar(10)) + ' Then ''Y'' Else ''N'' End As CustomCol' + Cast(N.Value As varchar(10))
From Numbers As N
You would run the query and copy and paste the results into your procedure or code.
One way could have been to use Pivot statement, which is in MS SQL 2005+. But even in that you have to put 1 ... 40 hardcoded columns in pivot statement.
Other way i can think of is to create dynamic SQL, but it is not so much recommended, So what we can do is we can create a dynamic sql query by running a while loop on table and can create the big sql and then we can execute it by using sp_execute. So steps would be.
int #loopVar
SET #loopVar = 0
int #rowCount
varchar #SQL
SET #SQl = ''
Select #rowcount = Count(ID2) from Table2
WHILE(#loopVar <= #rowCount)
BEGIN
// create ur SQL here
END
sp_execute(#SQL)