Better way of writing this query - sql-server

I'm wondering if I could replace the below with a join and reduce the need for the ISNULL wrapped around the whole select?
SELECT ISNULL(
(
SELECT Locale FROM Users WHERE UserGuid = #UserGuid),
(SELECT Locale FROM Companies WHERE CompanyGuid =
(SELECT CompanyGuid FROM UserCompany WHERE UserGuid = #UserGuid)
))

I think this would be the equivalent:
SELECT ISNULL(u.Locale, c.Locale)
FROM Companies c
INNER JOIN UserCompany uc ON c.CompanyGuid = uc.CompanyGuid
LEFT OUTER JOIN Users u ON uc.UserGuid = u.UserGuid
WHERE uc.UserGuid = #UserGuid
Note: this assumes the UserGuid is unique in Users and UserCompany.

Related

SQL. Select names of users who have certain set of skills

There are three tables:
Skills table has the following data:
I need to select names of users, who are skilled in BOTH Node.js AND JavaScript.
So basically I need to select ids of two skills and then filter Users by those skills.
Unfortunately I couldn't come up with a query.
You can use exists in a query:
with Skilled (UserId) as
(
select UserId
from
(select distinct us.UserId, us.SkillId
from UserSkills us
inner join Skills s on us.SkillId = s.Id
where s.Name in ('Node.js', 'Javascript')) tmp
group by UserId
having count(*) = 2
)
select Id, Name
from Users u
where exists (select * from Skilled s where s.userId = u.Id);
DBFiddle demo
;with E1 as
(
select us.UserId
from UserSkills us
where SkillId in (1, 2)
group by UserId
having count(*) = 2;
)
SELECT Name FROM User AS us
LEFT JOIN E1 AS e ON e.UserId=us.UserId
THATS IT

Improve Inner-Join/Temp Table Query

I have this query and it's returning the results I expect, however, as you can see it's really crude & difficult to read.
I tried Inner Joins and FIRST_VALUE approaches but wasn't getting the right results so resorted to temp tables (just because I find it easier to verify results that way).
The issue is I get a StartWayPoint and EndWayPoint from dbo.Trips which is actually just the first and last points entered into the GPSWaypoints table (for a particular trip / vehicle).
Those points aren't entered in UTCTime order so I need to take the StartWayPoint, EndWayPoint & Vehicle, get results BETWEEN those waypoint values from dbo.GPSWaypoints table, sort that by UTCTime desc and the first Location string there is my true EndLocationString.
Hope that makes sense, I may have over complicated it ....I feel as though the answer is either an Inner Join or a SubQuery, my SQL skills aren't that hot though so any help appreciated.
DROP TABLE IF EXISTS #temp1, #temp2
USE XXX
DECLARE #StartWayPoint bigint, #EndWaypoint bigint, #Vehicle smallint, #TripId smallint = 9863;
SELECT t.Id,
t.Date,
t.StartWayPoint,
t.EndWayPoint,
t.Distance,
t.Alarms,
t.FuelConsumption,
t.Vehicle,
gpsStart.UtcTime as TripStart,
gpsEnd.UtcTime as TripEnd,
gpsStart.LocationString as StartLocationString,
gpsEnd.LocationString as EndLocationString
INTO #temp1
FROM dbo.Trips t
LEFT JOIN dbo.GPSWaypoints gpsStart on StartWaypoint = gpsStart.Id
LEFT JOIN dbo.GPSWaypoints gpsEnd on EndWaypoint = gpsEnd.Id
LEFT JOIN dbo.Operators o on Driver = o.Id
LEFT JOIN dbo.Vehicles v on t.Vehicle = v.Id
WHERE t.id = #TripId
SELECT #StartWayPoint = (SELECT StartWaypoint FROM #temp1), #EndWaypoint = (SELECT EndWaypoint FROM #temp1), #Vehicle = (SELECT Vehicle FROM #temp1)
SELECT TOP 1 g.Id,
g.LocationString,
g.Vehicle
INTO #temp2
FROM dbo.GPSWaypoints g
WHERE Id BETWEEN #StartWayPoint AND #EndWaypoint
AND Vehicle = #Vehicle
order by UtcTime desc
SELECT t1.*,
t2.Id as TRUE_EndWayPoint,
t2.LocationString as TRUE_EndLocationString
FROM #temp1 t1
LEFT JOIN #temp2 t2 on t2.Vehicle = t1.
Results from #temp1
Results from #temp2 (with TRUE EndWayPoint and EndLocationString
Think I've answered it for myself with a nested query, must have just had an error in my earlier attempts ....still open to suggestions for improvement though.
select t.Id,
t.Date,
t.StartWayPoint,
t.EndWayPoint,
t.Distance,
t.Alarms,
t.FuelConsumption,
t.Vehicle,
gpsStart.UtcTime as TripStart,
gpsEnd.UtcTime as TripEnd,
gpsStart.LocationString as StartLocationString,
( select top 1 (g.LocationString)
from dbo.GPSWaypoints g
where Id BETWEEN gpsStart.Id AND gpsEnd.Id
AND Vehicle = v.Id
order by UtcTime desc
) as EndLocationString
from dbo.Trips t
left join dbo.GPSWaypoints gpsStart on StartWaypoint = gpsStart.Id
left join dbo.GPSWaypoints gpsEnd on EndWaypoint = gpsEnd.Id
left join dbo.Operators o on Driver = o.Id
left join dbo.Vehicles v on t.Vehicle = v.Id
where t.id = 9863

how to use aggregate query result as column in another query having joins using stored procedures in SQL Server

i have a inner join query in stored procedure which is working fine. i need to inject a aggregate query in it so that it show an aggregated result in a new column
https://drive.google.com/file/d/1tAIEACvEnG7sAisSoE2crYRrzCjIcvST/view?usp=sharing
i tried to inject aggregate query as a column TotalQty in my query
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName, dbo.SOD.SalePrice
,TotalQty = (select SUM(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
But it says
Column 'dbo.SO.Id' is invalid in the select list because it is not
contained in either an aggregate function or the GROUP BY clause.
or any other good Technique suggested will be appreciated.
so change AS :
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName, dbo.SOD.SalePrice
,(select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B') AS TotalQty
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
As in the internal query, your characteristic is you used the o.SOId field on where and in other hand used count aggregate function so you should:
SELECT dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name AS ProductName,
dbo.SOD.SalePrice
,count(dbo.SOD.Quantity) AS TotalQty
FROM dbo.SOD INNER JOIN
dbo.Product ON dbo.SOD.ProductId = dbo.Product.Id RIGHT JOIN
dbo.SO ON dbo.SOD.SOId = dbo.SO.Id INNER JOIN
dbo.Customer ON dbo.SO.CustomerId = dbo.Customer.Id
WHERE (dbo.SO.Id = '68BD0F69-B957-439F-9AD0-180DF23EF42B')
group by
dbo.SO.Id,dbo.Customer.Name, dbo.Product.Name , dbo.SOD.SalePrice
Which will have the same output.
Generally speaking, you encourage others to help if you provide a MVCE. Using cryptic table names (are they tables? or views perhaps?) is not a healthy practice. In addition, it is not clear what you are trying to achieve with your subquery. You attempted to use count but you label the value as "TotalQty" and you replied to a suggestion using "sum". Very confusing.
So since we don't have your tables, I used the common MS sample database AdventureWorks. Below are two examples of counting the quantity values from the detail table.
select Ord.SalesOrderID, Det.SalesOrderDetailID,
Cust.AccountNumber as CustName, -- too lazy to get actual name
Prd.Name as ProductName,
Det.UnitPrice
,Counted.TotalQty
-- TotalQty = (select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
from Sales.SalesOrderHeader as Ord
inner join Sales.SalesOrderDetail as Det on Ord.SalesOrderID = Det.SalesOrderID
inner join Production.Product as Prd on Det.ProductID = Prd.ProductID
inner join Sales.Customer as Cust on Ord.CustomerID = Cust.CustomerID
cross apply (select count(DetCnt.OrderQty) as TotalQty from Sales.SalesOrderDetail as DetCnt where DetCnt.SalesOrderID = Det.SalesOrderID) as Counted
where Ord.SalesOrderID = 43659
select Ord.SalesOrderID, Det.SalesOrderDetailID,
Cust.AccountNumber as CustName, -- too lazy to get actual name
Prd.Name as ProductName,
Det.UnitPrice
, TotalQty = (select count(DetCnt.OrderQty) from Sales.SalesOrderDetail as DetCnt where DetCnt.SalesOrderID = Det.SalesOrderID)
-- TotalQty = (select count(dbo.SOD.Quantity) from [sod] o where o.SOId='68BD0F69-B957-439F-9AD0-180DF23EF42B' )
from Sales.SalesOrderHeader as Ord
inner join Sales.SalesOrderDetail as Det on Ord.SalesOrderID = Det.SalesOrderID
inner join Production.Product as Prd on Det.ProductID = Prd.ProductID
inner join Sales.Customer as Cust on Ord.CustomerID = Cust.CustomerID
where Ord.SalesOrderID = 43659
I think that interpretation is correct but I don't know your schema. I added the PK of the detail table to help "see" the relationship between Order and Detail.
Examine the code closely. Notice how the query only refers to the specific PK value once (this would be your procedure's parameter). You use correlations and joins to limit the results as needed. And notice how much easier it is to understand the query since it uses names that are actual words - SalesOrder vs. SO. I don't think it makes much sense to right join your Detail table to the Order table - seems like a mistake. Your aggregation attempt is odd so I can't say if the value computed by these queries is correct.
I'll also note that you should not be passing the PK value of your table using a nvarchar parameter. Use the correct datatype to avoid the possibility that someone attempts to pass an actual string (e.g., N'Pick me') instead of a GUID value.

SQL Server left join if condition met

I have a problem with my query. I have to do left outer join only if condition is true. If condition is false do another left outer join.
I tried with this but not successfully:
select
*
from
works with(nolock)
if work.type = 1
begin
left outer join
users with(nolock) on users.id = work.owner
else
left outer join
groups with(nolock) on groups.id = work.owner
end
How can I solve this problem?
you should try left join both of them, but inside of the select, take what you want depending on the use case.
SELECT
*,
CASE work.type WHEN '1' THEN 'a.owner' ELSE 'b.owner' END AS owner
FROM
blahblah
left join users on blahblah.user_id = users.id as a,
left join groups as blahblah.groups_id = groups.id as b
You can try below query.
First method:
select
works.*, isnull(users.id, groups.id)
from
works with(nolock)
left outer join
users with(nolock) on users.id = works.owner and work.type = 1
left outer join
groups with(nolock) on groups.id = works.owner
Second method:
if exists (select 1 from works with (nolock) where works.type = 1)
select *
from works with(nolock)
left outer join users with(nolock) on users.id = works.owner
else
select *
from works with(nolock)
left outer join groups with(nolock) on groups.id = works.owner
third method: use dynamic SQL to build query at runtime.
Considering that you want to display all columns (*), then you can conditionally join against both tables by checking the work.type as a join condition:
select
*
from
works
left join users on
users.id = work.owner and
work.type = 1
left join groups on
groups.id = work.owner and
(work.type <> 1 OR work.type IS NULL)
A particular row from the works table can only have a particular value for type, so it will join against users or against groups, but never both. The problem with this solution becomes the displayed columns, since we are joining against both tables, now you have to unify groups and users columns.
You can do this with a bunch of ISNULLs:
select
works.*,
Column1 = ISNULL(users.Column1, groups.Column1),
Column2 = ISNULL(users.Column2, groups.Column2)
from
works
left join users on
users.id = work.owner and
work.type = 1
left join groups on
groups.id = work.owner and
(work.type <> 1 OR work.type IS NULL)
If you need to use the same select repeatedly, you can create a table-valued function to wrap this up so you don't have to code it every time. I'll use the table example to show another alternative, using UNION ALL.
CREATE FUNCTION dbo.GetWorkData (#owner INT) -- assuming its a INT
RETURNS TABLE
AS
RETURN
SELECT
-- Your wanted columns here
FROM
works AS W
INNER JOIN users AS U ON W.owner = U.owner
WHERE
W.owner = #owner AND
W.type = 1
UNION ALL
SELECT
-- Your wanted columns here (must be same data type and order of previous SELECT)
FROM
works AS W
INNER JOIN groups AS U ON W.owner = U.owner
WHERE
W.owner = #owner AND
(W.type <> 1 OR W.type IS NULL)
You can use the function with APPLY:
SELECT
D.*
FROM
works AS W
CROSS APPLY dbo.GetWorkData(W.owner) AS D -- User "OUTER APPLY" if you want works that have no users or groups
You can look into Dynamic SQL. The main idea is that the SQL statement is constructed and compiled at runtime.
You can start here:
Link 1 - MSSQL tips.
or here: Link 2 - official microsoft documentation.

Get distinct rows when filtering and using dynamic order by

My first version of the question was confusing, I need to make smaller chunks.
If a user can filter products from a website, one product should occur only once in the list.
Because of joins this code gives me two same products, how do I solve that?
I think I need a solution without using distinct because it will give me headache later on.
code from AW2012:
declare #safetystocklevel int
set #safetystocklevel = 1000
declare #status int
set #status = 2
select * from Production.Product p
inner join Purchasing.ProductVendor pv on p.ProductID = pv.ProductID
inner join Purchasing.Vendor v on v.BusinessEntityID = pv.BusinessEntityID
inner join Production.ProductDocument pd on p.ProductID = pd.ProductID
inner join Production.Document d on d.DocumentNode = pd.DocumentNode
WHERE
(#safetystocklevel = '' or p.SafetyStockLevel = #safetystocklevel)
and (#status = '' or d.Status = #status)
output:
ProductId Name
506 Reflector
506 Reflector
Edit:
Thanks, I now use Group by to get distinct rows.
Yeah, maybe using group by works for me, Im gonna do some testing now.....
Hi again
I want all products to be searchable, so I guess I need left outer joins to achieve that.
When I add dynamic order by I get into trouble, more rows are added.
Probably because I must add poh.Status to the group by.
There are 504 rows in the product table, this query returns 776 rows.
(I have removed the filtering in WHERE since it is not interesting now, and Im joining to other tables now just to get more rows to play with)
Code:
declare #sortType nvarchar(50)
set #sortType = 'Status'
select p.ProductID,
CASE WHEN #sortType = 'Status' THEN poh.Status END as Status,
CASE WHEN #sortType = 'ProductId' THEN p.ProductID END as ProductId
from Production.Product p
left outer join Purchasing.PurchaseOrderDetail pod on p.ProductID = pod.ProductID
left outer join Purchasing.PurchaseOrderHeader poh on poh.PurchaseOrderID = pod.PurchaseOrderID
left outer join Production.ProductDocument ppd on ppd.ProductID = p.ProductID
left outer join Production.Document pd on pd.DocumentNode = ppd.DocumentNode
group by p.ProductID, poh.Status
ORDER BY
CASE WHEN #sortType = 'Status' THEN poh.Status END ASC,
CASE WHEN #sortType = 'ProductId' THEN p.ProductID END ASC
You can use Group By ProductId, Name, to select the single row, if you are not planning to include distinct. But I'll prefer "distinct" if you are not using any aggregate value in select clause.
select p.ProductId, p.Name from Production.Product p
inner join Purchasing.ProductVendor pv on p.ProductID = pv.ProductID
inner join Purchasing.Vendor v on v.BusinessEntityID = pv.BusinessEntityID
inner join Production.ProductDocument pd on p.ProductID = pd.ProductID
inner join Production.Document d on d.DocumentNode = pd.DocumentNode
WHERE
(#safetystocklevel = '' or p.SafetyStockLevel = #safetystocklevel)
and (#status = '' or d.Status = #status)
GROUP BY p.ProductId, p.Name

Resources