Need help with a complex SQL query - sql

I have a stored procedure that uses this select statement:
SELECT dbo.VendorProgram.id,
dbo.VendorProgram.CODE,
dbo.Programs.ProgramName
+ '-' + dbo.Divisions.Division
+ '-' + dbo.Vendors.Source
+ '-' + dbo.Mediums.Medium
+ '-' + dbo.VendorProgram.content
AS SourceDetail,
dbo.Vendors.Source,
dbo.Programs.ProgramName,
dbo.Divisions.Division,
dbo.Mediums.Medium,
dbo.VendorProgram.content,
dbo.VendorProgram.url,
dbo.VendorProgram.cost,
dbo.VendorProgram.Notes,
dbo.VendorProgram.StartDate,
dbo.VendorProgram.EndDate
FROM dbo.Programs
RIGHT OUTER JOIN dbo.VendorProgram
ON dbo.Programs.id = dbo.VendorProgram.programID
LEFT OUTER JOIN dbo.Vendors
ON dbo.VendorProgram.vendorID = dbo.Vendors.mappingID
LEFT OUTER JOIN dbo.Divisions
ON dbo.VendorProgram.divisionID = dbo.Divisions.id
LEFT OUTER JOIN dbo.Mediums
ON dbo.VendorProgram.mediumID = dbo.Mediums.id
Basically we have a system that put together a code for a vendor. It is made up of 5 IDs pulled from 5 separate tables. Those tables have actual text, for a vendor's name, a type of medium, etc... that match to each ID. So far nothing truly ground breaking in use here I think.
What I need to do is be able to write a query that uses paramaters, in text, to search those 5 separate tables, and find all the "vendor mappings" that could match. I am still new in the SQL world so I am not quite sure what that query would look like.
As an example of how I want to search. I enter into my search form the text "Face" for the vendor field. I would then expect the query runs a select against the vendor table itself to first find all possible vendor IDs with "face" in the name. Then it would need to select all rows from the combined table that have any of those IDs in them.
Hopefully this makes sense, and is possible. As always thanks for any help.

SELECT vp.id, vp.CODE,
p.ProgramName + '-' + d.Division + '-' + v.Source + '-' + m.Medium + '-' + vp.content AS SourceDetail,
v.Source,
p.ProgramName,
d.Division,
m.Medium,
vp.content,
vp.url,
vp.cost,
vp.Notes,
vp.StartDate,
vp.EndDate
FROM dbo.Vendors v
LEFT OUTER JOIN dbo.VendorProgram vp vp.vendorID = v.mappingID
LEFT OUTER JOIN dbo.Programs p ON p.id = vp.programID
LEFT OUTER JOIN dbo.Divisions d ON vp.divisionID = d.id
LEFT OUTER JOIN dbo.Mediums m ON vp.mediumID = m.id
WHERE v.Name LIKE '%Face%'

add
WHERE dbo.Vendors.ID in
(select ID From dbo.Vendors where dbo.Vendors.Field LIKE '%'+ #QueryText + '%'

If you want only the rows related to your search, switch to inner joins (or include some where not nulls) and in your join to Vendors, include:
AND Vendor.VendorName LIKE #PassedInParam
Where #PassedInParam would be passed into your proc and would be your search string with a '%' on either side, ie '%face%'

Related

Conditional self join SQL Server

I'm trying to self join with conditions.
Is there a way where I could do this without using Union? (union works fine but the query doubles in size which I'm trying to avoid)
Below is the query I've written (any suggestion or guidance would be much appreciated!)
Select
concat(de.EthnicityText ,' - ', dec2.EthnicityText) as 'Ethnicity'
from
dl.DimEthnicity de
inner join
(select dec1.EthnicityParentId, dec1.EthnicityText
from dl.dimethnicity dec1) as dec2 on de.EthnicityID = dec2.EthnicityParentId
union all
select de.EthnicityText as 'Ethnicity'
from dl.DimEthnicity de
where de.EthnicityParentId is null
Assuming you're wanting to return all values, even if ParentID is null.
select a.EthnicityText + case when b.EthnicityText is null then '' else ' - ' + b.EthnicityText end
from DimEthnicity a
left join DimEthnicity b on b.EthnicityID = a.EthnicityParentID
Left join allows for the condition you're looking for (only return the self join row when it exists). More info on left joins: https://www.w3schools.com/sql/sql_join_left.asp

SQL to combine multiple rows into a single row

I am currently writing a SQL script - takes a business term, and all related synonyms. What it does is creates multiple rows (because there are multiple synonyms (can have other columns that could be multiple values as well.
What I am trying to do is to create a single row for every business term, and concatenate values (, delimited) so that I get one line item for each business term only.
Currently my SQL script is:
SELECT dbo.TblBusinessTerm.BusinessTerm, dbo.TblBusinessTerm.BusinessTermLongDesc,
dbo.TblBusinessTerm.DomainCatID, dbo.TblSystem.SystemName,
dbo.TblDomainCat.DataSteward, dbo.TblDomainCat.DomainCatName,
dbo.TblField.GoldenSource, dbo.TblField.GTS_table,
dbo.TblTableOwner.TableOwnerName, dbo.TblBusinessSynonym.Synonym
FROM dbo.TblTableOwner INNER JOIN
dbo.TblBusinessTerm INNER JOIN
dbo.TblBusinessSynonym ON dbo.TblBusinessTerm.BusinessTermID = dbo.TblBusinessSynonym.BusinessTermID INNER JOIN
dbo.TblField ON dbo.TblBusinessTerm.BusinessTermID = dbo.TblField.BusinessTermID INNER JOIN
dbo.TblSystem INNER JOIN
dbo.TblTable ON dbo.TblSystem.SystemID = dbo.TblTable.SystemID ON dbo.TblField.TableID = dbo.TblTable.TableID INNER JOIN
dbo.TblDomainCat ON dbo.TblBusinessTerm.DomainCatID = dbo.TblDomainCat.DomainCatID ON dbo.TblTableOwner.TableOwnerID = dbo.TblDomainCat.DataSteward
Is there an easy way to do this that takes performance into consideration - am new to SQL.
Thank you
I have managed to create a with statement that now concatenates my rows:
With syn as (
select [BusinessTermID],
syns = STUFF((SELECT ', ' + dbo.TblBusinessSynonym.Synonym
FROM dbo.TblBusinessSynonym
WHERE [BusinessTermID] = x.[BusinessTermID]
AND dbo.TblBusinessSynonym.Synonym <> ''
FOR XML PATH ('')),1,2,'')
FROM dbo.TblBusinessSynonym AS x
GROUP BY [BusinessTermID]
)
select * from syn
But now how can I use it in the above query where everything links?
Would want to replace dbo.TblBusinessSynonym.Synonym with the results from syn
Any SQL 2014 developers that can assist?
Write your with statement at the very top, without the select.
Then write your upper query as it is and change
INNER JOIN dbo.TblBusinessSynonym ON dbo.TblBusinessTerm.BusinessTermID = dbo.TblBusinessSynonym.BusinessTermID
to
INNER JOIN syn ON syn.BusinessTermID = dbo.TblBusinessTerm.BusinessTermID
That's it
With syn as (
select [BusinessTermID],
syns = STUFF((SELECT ', ' + dbo.TblBusinessSynonym.Synonym
FROM dbo.TblBusinessSynonym
WHERE [BusinessTermID] = x.[BusinessTermID]
AND dbo.TblBusinessSynonym.Synonym <> ''
FOR XML PATH ('')),1,2,'')
FROM dbo.TblBusinessSynonym AS x
GROUP BY [BusinessTermID]
)
SELECT dbo.TblBusinessTerm.BusinessTerm,
dbo.TblBusinessTerm.BusinessTermLongDesc,
dbo.TblBusinessTerm.DomainCatID, dbo.TblSystem.SystemName,
dbo.TblDomainCat.DataSteward, dbo.TblDomainCat.DomainCatName,
dbo.TblField.GoldenSource, dbo.TblField.GTS_table,
dbo.TblTableOwner.TableOwnerName, syn.syns
FROM dbo.TblTableOwner INNER JOIN
dbo.TblBusinessTerm INNER JOIN
syn ON dbo.TblBusinessTerm.BusinessTermID = syn.BusinessTermID INNER JOIN
dbo.TblField ON dbo.TblBusinessTerm.BusinessTermID = dbo.TblField.BusinessTermID INNER JOIN
dbo.TblSystem INNER JOIN
dbo.TblTable ON dbo.TblSystem.SystemID = dbo.TblTable.SystemID ON dbo.TblField.TableID = dbo.TblTable.TableID INNER JOIN
dbo.TblDomainCat ON dbo.TblBusinessTerm.DomainCatID = dbo.TblDomainCat.DomainCatID ON dbo.TblTableOwner.TableOwnerID = dbo.TblDomainCat.DataSteward
Please use STRING_AGG function. It combines record items in field ans set them in one record separated with specified delimiter.
Details are here:
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017
Your query is complicated, so I will just post here sample data and how to deal with it in a manner you want. The operation is string aggregation with concatenation, in latest version there's string_agg function, that does the work for us. But, as you can't use this function, here's workaround:
select * into #tt
from (values (1, '1'),(1, '2'),(2, '1'),(2, '2')) A(id, someStr)
select id, (select someStr + ',' from #tt where id = [t].id for xml path('')) [grouped]
from #tt [t] group by id
Above query groups by Id and concaenates all corresponding rows in someStr column.

SQL using table from join in subqueries

I am writing a query to be used as databases view, it looks now like this:
SELECT
contact.*,
contact_users.names AS user_names,
contact_status.status_id AS status_id,
status_translation.name AS status_name,
status_translation.lang_id AS lang_id
FROM contacts as contact
LEFT JOIN contact_status AS contact_status ON contact_status.status_id = contact.status
LEFT JOIN contact_status_translation AS status_translation ON status_translation.id = contact.status
LEFT JOIN (
SELECT
contacts_users.contact_id,
string_agg(users.fullname || ', ' || users.id, ' | ') as names
FROM v_contacts_users as contacts_users
LEFT JOIN v_users as users on users.id = contacts_users.user_id
WHERE users.lang_id = status_translation.lang_id
GROUP BY contacts_users.contact_id
) AS contact_users ON contact_users.contact_id = contact.id
WHERE contact.deleted = FALSE
Everything works as expected except the WHERE condition in the last LEFT JOIN - the WHERE users.lang_id = status_translation.lang_id part says that status_translation cannot be referenced in this part of the query? Why is that? I tried to reference this table with various always but the result is still the same.Thing is that v_users is translated as well so I need to have only one result from this table.
Insert LATERAL between LEFT JOIN and the opening parenthesis if you want to reference previous FROM list entries.

SQL joining on names

I am querying two tables and joining them together on people's names. I am trying to produce all employees who have not filled out a form within the past month. The problem I am encountering is I am receiving an overlap of names when people list their short name (Joe rather than Joseph, or Mike rather than Michael). How can I still produce the list of people without overlap, even when they use their short names?
This is the query I have as of now:
SELECT DISTINCT ge.employeeNo,
(ge.firstName + ' ' + ge.lastName) AS empName,
ge.email
FROM dbo.hist_Employees ge
INNER JOIN dbo.ctrl_Sites cs ON ge.locationID = cs.ID
WHERE (ge.firstName + ' ' + ge.lastName) NOT IN
(SELECT sc.recordedBy
FROM GRSTOPS.dbo.hist_StopCard sc
INNER JOIN dbo.ctrl_Area a ON sc.area = a.ID
INNER JOIN dbo.ctrl_Site s ON a.site = s.ID
WHERE sc.recorded BETWEEN '10/01/2013' AND '10/30/2013'
AND s.code = 'gre')
AND cs.Abbreviation = 'gre'
AND ge.employmentStatus = 1
AND ge.primaryDept <> 3
It would be better not to join on peoples names as they are not unique.
You should join using a primary key like ID/employeeNo etc.

SQL Merge Cells from many rows into a single cell

Similar to the question here:
http://forums.asp.net/t/1580379.aspx/1
I'm trying to merge common cells into a single comma-delimited cell, however across an inner join.
My SQL is:
SELECT DISTINCT tb_Order.OrderNumber, tb_Order.OrderId,
tb_Order.orderDate, tb_Order.OrderTotal,
tb_OrderStatus.OrderStatus, tb_Order.GroupOrderId,
tb_Venue.Title AS Venue
FROM tb_Order INNER JOIN tb_OrderItem ON tb_Order.OrderId = tb_OrderItem.OrderId
INNER JOIN tb_Show ON tb_OrderItem.ShowId = tb_Show.showId
INNER JOIN tb_OrderStatus ON tb_Order.OrderStatusId = tb_OrderStatus.OrderStatusID
INNER JOIN tb_Venue ON tb_Show.VenueId = tb_Venue.id
WHERE (tb_Order.OrderId = 705)
I need the [venue] to be comma-delimited like:
"Interactive Seating Chart Advanced, Interactive Seating Chart Mode Multi-Click"
If you have SQL Server 2017 (14.x) and later, you can use the STRING_AGG function.
SELECT tb_Order.OrderNumber, tb_Order.OrderId,
tb_Order.orderDate, tb_Order.OrderTotal,
tb_OrderStatus.OrderStatus, tb_Order.GroupOrderId,
STRING_AGG(tb_Venue.Title, ',') AS Venue
FROM tb_Order INNER JOIN tb_OrderItem ON tb_Order.OrderId = tb_OrderItem.OrderId
INNER JOIN tb_Show ON tb_OrderItem.ShowId = tb_Show.showId
INNER JOIN tb_OrderStatus ON tb_Order.OrderStatusId = tb_OrderStatus.OrderStatusID
INNER JOIN tb_Venue ON tb_Show.VenueId = tb_Venue.id
WHERE (tb_Order.OrderId = 705)
GROUP BY tb_Order.OrderNumber, tb_Order.OrderId,
tb_Order.orderDate, tb_Order.OrderTotal,
tb_OrderStatus.OrderStatus, tb_Order.GroupOrderId
One way to do this is by using a side effect of row processing order to populate a variable iteratively, like so. The downside of this is that it won't work in a simple query context and it's not the most efficient solution.
DECLARE #venues varchar(max)
SET #venues = ''
SELECT #venues =
CASE WHEN #venues = '' THEN v.Title
ELSE #venues + ',' + v.Title END
FROM tb_Venue v
SELECT #venues
A second way to do this is with STUFF and SQL Server XML extensions, like so:
SELECT DISTINCT v.Title,
(STUFF(
(SELECT ',' + v2.Title
FROM tb_Venue v2
-- uncomment this line if you are going
-- to aggregate only by something in the outer query
-- WHERE v2.GroupKey = v.GroupKey
ORDER BY v2.Title
FOR XML PATH(''), TYPE, ROOT
).value('root[1]','varchar(max)'),1,1,'')) as Aggregation
FROM tb_Venue v
A CLR-based solution is usually the most performant for this use case, and one of those is described here along with a boatload of other less ideal solutions...
Your problem is not one rooted in set logic so there isn't a clean SQL solution...