How to Select table for Join using Case in SQL - sql

i have a table attribute_name in which a column c_type indicate what type of value we have like 1,2,3,4 so that base on that value i decide which table to join .
so i select that table first Join (case statment) On (case statment)
but i does not work.
SELECT attribute_names.*,attributes_trans_name.*,
(CASE
WHEN attribute_names.c_type=1
THEN attribute_values_text.c_fk_files_id
WHEN attribute_names.c_type=3
THEN attribute_values_longtext.c_fk_files_id
WHEN attribute_names.c_type=8
THEN attribute_values_file.c_fk_files_id
END) as file_id
From attributes_trans_name,
attribute_names JOIN
(CASE
WHEN attribute_names.c_type=1
THEN attribute_values_text
WHEN attribute_names.c_type=3
THEN attribute_values_longtext
WHEN attribute_names.c_type=8
THEN attribute_values_file
END)
ON
(CASE
WHEN attribute_names.c_type=1
THEN attribute_values_text.c_fk_attribute_names_id
WHEN attribute_names.c_type=3
THEN attribute_values_longtext.c_fk_attribute_names_id
WHEN attribute_names.c_type=8
THEN attribute_values_file.c_fk_attribute_names_id
END) = attribute_names.c_id
WHERE
attribute_names.c_id=attributes_trans_name.c_fk_attribute_names_id

With proper JOIN/LEFT JOIN context, you can do in single query. Left join means I want the record from the left side always, but OPTIONAL if there is a match on the right side. So, I have adjusted your query to reflect. I have also rewritten to use "alias" names for the file names so it is shorter for read and write than bulky long table names.
So, the main table is the attribute_names as that appears to be the basis of all the joins with the C_ID column into each of the others. Notice indentation helps me know / follow what is linked to what, and not just all tables listed in bulk.
Now, by having each of the left-joins in place, it will ALWAYS TRY to link to their respective other tables by the foreign key, but as you know your data, only one of them will really have the piece of information you need. So your CASE construct is simplified down. If = 1, then look at the ATV (alias) table and its column, otherwise AVLT alias if = 3 and finally AVF if = 8
SELECT
AN.*,
ATN.*,
CASE WHEN AN.c_type = 1
THEN ATV.c_fk_files_id
WHEN AN.c_type = 3
THEN AVLT.c_fk_files_id
WHEN AN.c_type = 8
THEN AVF.c_fk_files_id END as file_id
From
attribute_names AN
JOIN attributes_trans_name ATN
ON AN.c_id = ATN.c_fk_attribute_names_id
LEFT JOIN attribute_values_text AVT
ON AN.c_id = AVT.c_fk_attribute_names_id
LEFT JOIN attribute_values_longtext AVLT
ON AN.c_id = AVLT.c_fk_attribute_names_id
LEFT JOIN attribute_values_file AVF
ON AN.c_id = AVF.c_fk_attribute_names_id

Related

SQL Query Pull data from another table IF NULL

I have a system running a SQL Server Express database and I need to pull some data from it. I have the basic SQL query created but I have found that some data is located elsewhere.
The basic premise is I have a database of Repair Orders, Vehicles And Customers. The Vehicles are usually added via a VIN decoder so they have ID's associated from a MAKE and MODEL table. However in the case of a VIN not decoding the application allows the user to manually enter this information and then it is stored in another table named "UserVehicleAttributes". In this table there is the VehicleID, AttributeName, & AttributeValue.
UserAttributeId VehicleId AttributeName AttributeValue
-----------------------------------------------------------
364 6829 Model Sedona
365 6830 Make Kia
366 6830 Model Sedona
So what I need is if the Make or Model comes up as NULL from the Vehicle table, I can display what as manually entered in.
I found that there is an existing function in the DB that looks to be able to do what I need but I don't know how to use it as part of my query.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [SM].[fnVehicleModelName]()
RETURNS TABLE
AS
RETURN
(
SELECT DISTINCT v.VehicleId,
CASE
WHEN v.SubModelId IS NULL THEN ISNULL(ua.[AttributeValue],'')
ELSE smm.[Name]
END as Model
FROM SM.Vehicle v (NOLOCK)
LEFT OUTER JOIN
(SELECT sm.SubModelId, m.[Name] + ' ' + sm.[Name] as Name
FROM DMV.SubModel (NOLOCK) sm
INNER JOIN DMV.Model m (NOLOCK)
ON sm.ModelId = m.ModelId ) as smm
ON v.SubModelId = smm.SubModelId
LEFT OUTER JOIN SM.UserVehicleAttributes ua (NOLOCK)--
ON v.VehicleId = ua.VehicleId and ua.AttributeName = 'Model'
Any help is greatly appreciated. I am not very good with SQL (obviously) but I am trying to figure this one out.
I'm not sure why you're making this a function with no parameters - that's kinda the same thing as a view. Consider if using a view here might simplify the situation.
You're correct that ISNULL is what you want to use here, but I think the join should be more simple. Your situation is basically "pull the column value from whichever table has a non-null value, giving preference to one table first"
In the outer join, all the columns from the outer joined tables will be null if there's not a match, and if there is a match, all the values should be filled in. Knowing that... you should be able to do something like this... (as an example to clarify how this concept works, not solving your query for you)
select v.VehicleId,
VehicleName = isnull(Model.Name, UserVehicle.Name)
from Vehicle v
left outer join Model on Model.VehicleID = Vehicle.VehicleID
left outer join UserVehicle on UserVehicle.VehicleID = Vehicle.VehicleId
So, what that does is join the possible rows from either table, and the ISNULL macro selects whichever value is non-null. Do that for the rest of the columns, and fix the join condition to whatever your conditions are, and you should be golden.
That function has no parameters, if you want to use it rewrite it as a view, but it only shows model, so you can use subqueries like this:
SELECT
VehicleId,
CASE
WHEN Make IS NULL
THEN ( SELECT AttributeValue FROM UserVehicleAttributes
WHERE VehicleId = Vehicles.VehicleId
AND AttributeName = 'Make' )
ELSE Make
END AS Make,
CASE
WHEN Model IS NULL
THEN ( SELECT AttributeValue FROM UserVehicleAttributes
WHERE VehicleId = Vehicles.VehicleId
AND AttributeName = 'Model' )
ELSE Model
END AS Model
FROM
Vehicles

Can I do a left join without returning the conditional columns?

New to SQL but I want to be able to optimize my query by bringing just the right amount of data. I am doing a left join on CS Rep Name and WE, which are two columns present in both tables. I find that if I don't bring in CS Rep Name and WE in the TECDR table, the query would error. Is there a workaround to this? Since it is a left join, I don't need redundant data.
SELECT *
FROM Tish_Email_CSAT_Dump AS TECD
LEFT JOIN (SELECT CS_Rep_Name,
Team_Leader,
Operations_Manager,
Tenure,
WE,
FileName
FROM Tish_Email_CSAT_Dump_Roster) AS TECDR
ON TECD.CS_Rep_Name = TECDR.CS_Rep_Name
AND TECD.WE = TECDR.WE
When you embed a SELECT inside a query in place of a table, the result of a select (projection) behave like a table visible only inside the query.
In your case, the join is the same as if there were a table called TECDR with the columns that you select. Hence, if you leave out some columns of Tish_Email_CSAT_Dump_Roster from your SELECT, these columns would not be available for joining or selection.
However, in your case this is unnecessary: all you need to do is joining to the underlying table, like this:
SELECT
TECD.*
, TECDR.Team_Leader
, TECDR.Operations_Manager
, TECDR.Tenure
, TECDR.FileName
FROM Tish_Email_CSAT_Dump AS TECD
LEFT JOIN Tish_Email_CSAT_Dump_Roster AS TECDR
ON TECD.CS_Rep_Name = TECDR.CS_Rep_Name AND TECD.WE = TECDR.WE
select
<place the columns you want here>
from
Tish_Email_CSAT_Dump as TECD
Left join Tish_Email_CSAT_Dump_Roster as TECDR
On TECD.CS_Rep_Name = TECDR.CS_Rep_Name and TECD.WE = TECDR.WE
Hope the following helps or else please share the query that errors:
select TECD.Column1, TECD.Column2, TECDR.Column1, TECDR.Column2
from Tish_Email_CSAT_Dump as TECD
Left join Tish_Email_CSAT_Dump_Roster as TECDR
On TECD.CS_Rep_Name = TECDR.CS_Rep_Name and TECD.WE = TECDR.WE

Setting up database structure

I am trying to figure out the best way to restructure my database as I didn't plan ahead and now I am a little stuck on this part :)
I have a Table called Campaigns and a Table called Data Types.
Each campaign is a unique record that holds about 10 fields of data.
The data types contains 3 fields - ID, Type, Description
When You create a campaign, you can select as many data types as you would like.
1, 2 or all 3 of them.
My concern / question is - How can I store what the user selected with the campaign record?
I need to be able to pull in the campaign details but also know which data types were selected.
How I originally had it set up was the data types were in 1 field, comma separated but learned is not ideal to do that.
What would be the best way to accomplish this? Storing the data as XML ?
UPDATE -
Here is an example of the query I was trying to get to work (its probably way off).
BEGIN
SET NOCOUNT ON;
BEGIN
SELECT *
FROM (SELECT A.[campaignID] as campaignID,
A.[campaignTitle],
A.[campaignDesc],
A.[campaignType],
A.[campaignStatus],
A.[duration],
A.[whoCreated],
B.[campaignID],
B.[dataType],
(SELECT *
FROM Tags_Campaign_Settings
WHERE campaignID = #campaignID) AS dataTypes
FROM Tags_Campaigns AS A
INNER JOIN
Tags_Campaign_Settings AS B
ON A.[campaignID] = B.[campaignID]
WHERE A.[campaignID] = #campaignID
) AS a
FOR XML PATH ('campaigns'), TYPE, ELEMENTS, ROOT ('root');
END
END
Create a join table called Campain_DataType with campaignId and dataTypeId. Make sure they're foreign key constrained to the respective tables. When you query for campaign data, you can either create a separate query to get the data type information based on the campaignId, or you can do a left outer join to fetch campaigns and their data types together.
If you want to collapse the 3 data types into the same row, then give the following a shot. It's definitely on the hacky side, and it'll only work with a fixed number of data types. If you add another data type, you'll have to update this query to support it.
SELECT
Campaign.ID,
Campaign.foo,
Campaign.bar,
dataType1.hasDataType1,
dataType2.hasDataType2,
dataType3.hasDataType3
FROM
Campaign
LEFT OUTER JOIN
( SELECT
1 as hasDataType1,
Campaign_DataType.campaignID
FROM
DataType
INNER JOIN Campaign_DataType ON Campaign_DataType.dataTypeId = DataType.id
WHERE
DataType.Type = 'Type1'
) dataType1 ON dataType1.campaignID = Campaign.ID
LEFT OUTER JOIN
( SELECT
1 as hasDataType2,
Campaign_DataType.campaignID
FROM
DataType
INNER JOIN Campaign_DataType ON Campaign_DataType.dataTypeId = DataType.id
WHERE
DataType.Type = 'Type2'
) dataType2 ON dataType2.campaignID = Campaign.ID
LEFT OUTER JOIN
( SELECT
1 as hasDataType3,
Campaign_DataType.campaignID
FROM
DataType
INNER JOIN Campaign_DataType ON Campaign_DataType.dataTypeId = DataType.id
WHERE
DataType.Type = 'Type3'
) dataType3 ON dataType3.campaignID = Campaign.ID
The record you receive for each Campaign will have three fields: hasDataType1, hasDataType2, hasDataType3. These columns will be 1 for yes, NULL for no.
Looks to me like what you want here is a crosstab query. Take a look at:
Sql Server 2008 Cross Tab Query

Left Join With Where Clause

I need to retrieve all default settings from the settings table but also grab the character setting if exists for x character.
But this query is only retrieving those settings where character is = 1, not the default settings if the user havent setted anyone.
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1'
So i should need something like this:
array(
'0' => array('somekey' => 'keyname', 'value' => 'thevalue'),
'1' => array('somekey2' => 'keyname2'),
'2' => array('somekey3' => 'keyname3')
)
Where key 1 and 2 are the default values when key 0 contains the default value with the character value.
The where clause is filtering away rows where the left join doesn't succeed. Move it to the join:
SELECT `settings`.*, `character_settings`.`value`
FROM `settings`
LEFT JOIN
`character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1'
When making OUTER JOINs (ANSI-89 or ANSI-92), filtration location matters because criteria specified in the ON clause is applied before the JOIN is made. Criteria against an OUTER JOINed table provided in the WHERE clause is applied after the JOIN is made. This can produce very different result sets. In comparison, it doesn't matter for INNER JOINs if the criteria is provided in the ON or WHERE clauses -- the result will be the same.
SELECT s.*,
cs.`value`
FROM SETTINGS s
LEFT JOIN CHARACTER_SETTINGS cs ON cs.setting_id = s.id
AND cs.character_id = 1
If I understand your question correctly you want records from the settings database if they don't have a join accross to the character_settings table or if that joined record has character_id = 1.
You should therefore do
SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT OUTER JOIN `character_settings`
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1' OR
`character_settings`.character_id is NULL
You might find it easier to understand by using a simple subquery
SELECT `settings`.*, (
SELECT `value` FROM `character_settings`
WHERE `character_settings`.`setting_id` = `settings`.`id`
AND `character_settings`.`character_id` = '1') AS cv_value
FROM `settings`
The subquery is allowed to return null, so you don't have to worry about JOIN/WHERE in the main query.
Sometimes, this works faster in MySQL, but compare it against the LEFT JOIN form to see what works best for you.
SELECT s.*, c.value
FROM settings s
LEFT JOIN character_settings c ON c.setting_id = s.id AND c.character_id = '1'
For this problem, as for many others involving non-trivial left joins such as left-joining on inner-joined tables, I find it convenient and somewhat more readable to split the query with a with clause. In your example,
with settings_for_char as (
select setting_id, value from character_settings where character_id = 1
)
select
settings.*,
settings_for_char.value
from
settings
left join settings_for_char on settings_for_char.setting_id = settings.id;
The way I finally understand the top answer is realising (following the Order Of Execution of the SQL query ) that the WHERE clause is applied to the joined table thereby filtering out rows that do not satisfy the WHERE condition from the joined (or output) table. However, moving the WHERE condition to the ON clause applies it to the individual tables prior to joining. This enables the left join to retain rows from the left table even though some column entries of those rows (entries from the right tables) do not satisfy the WHERE condition.
The result is correct based on the SQL statement. Left join returns all values from the right table, and only matching values from the left table.
ID and NAME columns are from the right side table, so are returned.
Score is from the left table, and 30 is returned, as this value relates to Name "Flow". The other Names are NULL as they do not relate to Name "Flow".
The below would return the result you were expecting:
SELECT a.*, b.Score
FROM #Table1 a
LEFT JOIN #Table2 b
ON a.ID = b.T1_ID
WHERE 1=1
AND a.Name = 'Flow'
The SQL applies a filter on the right hand table.

Conditional JOIN

I'm wondering if it's possible to accomplish this in MS Access 2007:
A client gave me several tables, and they asked me for some queries. One of them has to get a field value from a table, depending on the value of a field of each record. This means, depending on the region, it has to look at one table, a second, or a third one.
So, I was wondering if I could do something like this:
SELECT
table2.some_value
FROM
table1
INNER JOIN table2
ON CASE table1.SOME_VALUE THEN table3.id = table2.some_id ELSE
CASE table1.SOME_VALUE THEN table4.id = table2.some_id ELSE
table5.id = table2.some_id END END
Is it clear? IF not, just ask and I'll answer your doubts.
EDIT:
I think I was not clear enough. I have a several joins in my query, but I have this last one, in which its ON statement will be different, depending on the data. For example:
I have a record in a table that has a State field, with three possibilities: CA, TX, FL.
If the value is CA, the ON statement of that JOIN should be CA_Standard_table.field = myTable.field.
If it's TX, the ON statement of that JOIN should be TX_Standard_table.field = myTable.field
And the same logic goes for FL.
How can I accomplish that?
EDIT 2:
Here is the query code, the last JOIN is the one that matters for this. The three possibilities of tables to join with in the ON statement are:
EU_Accepted_Standards
CA_Accepted_Standards
NZ_Accepted_Standards
It will decide for one of them, depending of which of the following fields are checked:
CAStandard: it should take CA_Accepted_Standards.
EUSelStandard:it should take EU_Accepted_Standards.
NZ_Accepted_Standards: it should take NZ_Accepted_Standards
Query
SELECT
Projects.COMPAS_ID,
Projects.[Opportunity Name],
IIf([VolCap]=True,1) AS [Volume Cap],
IIf([DelGuarantee]=True,1) AS [Delivery Guarantee],
Projects.Tech_Level_Name,
Counterparty.CPExpertise,
Counterparty.CPFinStrength,
Geographic_Location.Country_RiskLevel,
Project_Stage_Risk.ProStaRiskLevel,
Counterparty.CPExperience,
Projects.Country_Name,
IIf([EU ETS]=True,1) AS EU,
IIf([CA ETS]=True,1) AS CA,
IIf([NZ ETS]=True,1) AS NZ,
IIf([Australia ETS]=True,1) AS Australia,
IIf([CAProjectType] is not null, CA_Accepted_Projects.CAPTRiskLevel,
IIf([EUSelProjType] is not null, EU_ETS_Standards.EUPTRiskLevel,
IIf([NZSelProjType] is not null, NZ_Accepted_Projects.NZPTRiskLevel))) as [Risk Level],
IIf([CAStandard] is not null, CA_Accepted_Standards.CAStanRiskLevel,
IIf([EUSelStandard] is not null, EU_Accepted_Standards.EUStanRiskLevel,
IIf([NZSelStandard] is not null, NZ_Accepted_Standards.NZStanRiskLevel))) as [Standard Risk]
FROM
Project_Stage_Risk
INNER JOIN (((((((((Counterparty
INNER JOIN Projects
ON Counterparty.CPID = Projects.[Counter Party])
INNER JOIN Geographic_Location
ON Projects.Country_Name = Geographic_Location.Country_Name)
left JOIN CA_Accepted_Projects
ON Projects.CAProjectType = CA_Accepted_Projects.CA_ProjectTypes)
left JOIN NZ_Accepted_Projects
ON Projects.NZSelProjType = NZ_Accepted_Projects.NZ_StandardID)
left JOIN EU_ETS_Standards
ON Projects.EUSelProjType = EU_ETS_Standards.EU_StandardID)
left JOIN CA_Accepted_Standards
ON Projects.CAStandard = CA_Accepted_Standards.ID)
left JOIN NZ_Accepted_Standards
ON Projects.NZSelStandard = NZ_Accepted_Standards.ID)
left JOIN EU_Accepted_Standards
ON Projects.EUSelStandard = EU_Accepted_Standards.ID)
left join Emissions_Trading_Systems
ON Emissions_Trading_Systems.ETS = EU_Accepted_Standards.ETS)
ON Project_Stage_Risk.ProStaID = Projects.[Project Stage];
cross join the two sets in a view, put the condition in the select. make 2 views of this view. Join the 2 views together.
You could create a UNION query that unions together the three tables you want to conditionally join to, including a "Some_Value" column that will contain the item on which you want to join. Essentially, for each table you include in the UNION, set the value of the "Some_Value" column to a value you can use in a where clause to differentiate things. Then create an overall query that joins (in your example, table2) to the union query and use a WHERE clause to limit the records to the ones you need. I have done similar things myself on projects in the past with great success.
Thanks for the answers. I know it was not well explained though, but in the end, I could solve this problem by writing a subquery.
Join all five tables together, and use that CASE expression inside the SELECT clause to choose the appropriate field from all tables.
SELECT
CASE table1.some_value
WHEN 'a' THEN table2.some_value
WHEN 'b' THEN table3.some_value
WHEN 'c' THEN table4.some_value
WHEN 'd' THEN table5.some_value
END