Can I eliminate the inner query from my query - sql

I would like to know if I can improve this query:
SELECT _c.*,
_o.Status,
(SELECT TOP 1 _o.PlacedOn
FROM Orders _o1
JOIN Client _c1 on _c1.Id = _o1.Client_Id
WHERE _c1.Id = 3
ORDER BY PlacedOn DESC) as LastOrderDate
FROM Client _c
JOIN Orders _o on _c.[Id] = _o.[Client_Id]
WHERE _c.Id = 3 AND _o.[Status] = 'Draft'
The outer JOIN is needed in order to get information from client and also some information on he's order.
The inner JOIN, gets the last placed order of that client.
I would like to know if I can remove the inner join and how.
Client.tbl
Id int
Gender nvarchar(255)
DateOfBirth nvarchar(255)
ContactDetails nvarchar(MAX)
FirstName nvarchar(255)
MiddleName nvarchar(255)
LastName nvarchar(255)
Order.tbl
Id int
Status nvarchar(255)
PaymentMethod nvarchar(255)
PlacedOn datetime2(7)
CancelledOn datetime2(7)
PaidOn datetime2(7)
OrderNumber nvarchar(255)
Client_Id int
Client test data:
Id Gender DateOfBirth ContactDetails FirstName MiddleName LastName
1 F. 10/16/1991 NULL Mia M. Brown
Order test data:
Id Status PaymentMethod PlacedOn CancelledOn PaidOn OrderNumber Client_Id
1 Done Cash 11/11/1996 NULL NULL NULL 1
2 Done Cash 11/11/2007 NULL NULL NULL 1
3 Draft NULL NULL NULL NULL NULL 1
Expected result:
client information FirstName for example
the Id of order where status is 'Draft'
the PlacedOn where its value is max, so in this case 11/11/2007
Result:
FirstName DraftId LastPlacedOn
Mia 3 11/11/2007

I suppose you can use MAX() OVER () to find the maximum value from the result set:
WITH cte AS (
SELECT _c.*,
_o.Status,
MAX(_o.PlacedOn) OVER (PARTITION BY _c.id) as LastOrderDate
FROM Client _c
JOIN Orders _o on _c.[Id] = _o.[Client_Id]
)
SELECT *
FROM cte
WHERE Id = 1 AND [Status] = 'Draft'

Related

Showing NULL values in WHERE clause

I've put together the below example to explain what I'm trying to do.
I'm trying to within the query when #FeatureID is NULL then return all the records in the#Temp table for that ClientID. If a #FeatureID contains a 1 for example then returning the 1 record and the NULL record.
Where have I gone wrong in my where clause?
CREATE TABLE #Temp
(
ClientID int,
FeatureID int
)
Insert into #Temp
(
ClientID,
FeatureID
)
SELECT
1,
1
UNION
SELECT
1,
2
UNION
SELECT
1,
3
UNION
SELECT
1,
NULL
Declare #ClientID int = 1
Declare #FeatureID int = NULL
--should return all 4 records
select * from #Temp
where ClientID = 1 and
FeatureID = IIF(#FeatureID IS NULL, FeatureID, #FeatureID)
Set #ClientID = 1
Set #FeatureID = 1
--should return the 1,1 record and the 1,NULL record
select * from #Temp
where ClientID = 1 and
FeatureID = IIF(#FeatureID IS NULL, FeatureID, #FeatureID)
drop table #Temp
An alternative formulation that might be a little simpler:
select * from #Temp
where ClientID = 1 and
(ISNULL(#FeatureID, FeatureID) = FeatureID or FeatureID is NULL)
If #FeatureID is null, then FeatureID = FeatureID, which is always true. Otherwise, it will check for #FeatureID = FeatureID.
It will always return the rows where FeatureID is null.
You're effectively trying to compare NULL = NULL in your where clause which doesn't work. NULL does not compare equal to another NULL.
For your first query what you need to do is only compare the feature id column when #FeatureID is not null. This can be accomplished by testing the variable and using an OR condition.
--should return all 4 records
select * from #Temp
where ClientID = 1 and
(#FeatureID IS NULL OR FeatureID = #FeatureID)
In the second query you need to compare the feature ID column to both #FeatureID and NULL to get both rows.
--should return the 1,1 record and the 1,NULL record
select * from #Temp
where ClientID = 1 and
(#FeatureID IS NOT NULL AND (FeatureID IS NULL OR FeatureID=#FeatureID))
To handle both cases in a single query, use two conditions joined by OR that branched based on whether the variable is null or not.
select * from #Temp
where ClientID = 1 and
(
#FeatureID IS NULL
OR (#FeatureID IS NOT NULL AND (FeatureID IS NULL OR FeatureID=#FeatureID))
)
If you want to combine them then this should work:
select * from #Temp
where ClientID = 1 and
(#FeatureID is null
or
(#FeatureID is not null
and (FeatureID is null or FeatureID=#FeatureID)))
This will select all the records when #FeatureID = null and return 2 results {(1, null), (1, 1)} when #FeatureID = 1

Getting more rows back than what I should while splitting tables into seperate tables

I have a temp customer table
create table #tmpCustomer
(
ID int identity(1,1),
CustomerID nvarchar(128),
CustomerName nvarchar(50),
FirstName nvarchar(50),
LastName nvarchar(50),
DateCreated nvarchar(50),
CreatedBy int,
YearBuilt nvarchar(50),
IsActive bit,
CustTypeID nvarchar(128),
CustomerTypeID int,
CompanyID int,
Line1 nvarchar(50) not null,
Line2 nvarchar(50) null,
Line3 nvarchar(50) null,
City nvarchar(50) not null,
ZipCode nvarchar(15),
StateID int not null,
NewCounty nvarchar(20),
SubDivisionID int null
)
That I am populating from an originating customer table and am using this to populate the temp table
declare #separator char(1);
set #separator = ',';
insert into #tmpCustomer
(CustomerID, CustomerName, FirstName, LastName, DateCreated, CreatedBy, YearBuilt, IsActive, CustomerTypeID, CompanyID, Line1, Line2, Line3, City, ZipCode, StateID, CountyID, NewCounty, SubDivisionID)
select
c.CustomerID,
c.CustomerName,
LastName = Case
When CHARINDEX(#separator, c.CustomerName, 1) - 1 <= 0 Then c.CustomerName
Else SUBSTRING(c.CustomerName,1,CHARINDEX(#separator, c.CustomerName, 1) - 1)
End,
FirstName = Case
When CHARINDEX(#separator, c.CustomerName, 1) - 1 <= 0 Then NULL
Else SUBSTRING(c.CustomerName,CHARINDEX(#separator, c.CustomerName, 1) + 1, Len(c.CustomerName) - (CHARINDEX(#separator, c.CustomerName, 1) ))
End,
GETDATE(),
1,
case when c.YearBuilt is NULL then 'N/A' else c.YearBuilt end,
c.EnabledInd,
case when CustomerTypeID = '4F0B6446-441D-46B8-81CB-B0E8A94624A7' then 1 else 2 end,
1,
c.Address1,
c.Address2,
null,
c.City,
c.ZipCode,
11,
null,
Case when c.County <> '' then County ELSE 'N/A' end as "County",
null
from [Customer] c
where c.CompanyID = '21DE6731-5E6C-11D5-AF81-00D0B74725F6'
This has a total count of 33165 records, which is exactly what it should be.
Next is that I am having a seperate table to hold the addresses
create table #tmpAddress
(
ID int identity(1,1),
Line1 nvarchar(50) not null,
Line2 nvarchar(50) null,
Line3 nvarchar(50) null,
City nvarchar(50) not null,
ZipCode nvarchar(15),
StateID int not null,
CountyID int null,
SubDivisionID int null
)
and if I run this
insert into #tmpAddress
(Line1, Line2, Line3, City, ZipCode, StateID, CountyID,SubDivisionID)
select cr.Line1, cr.Line2,cr.Line3, cr.City, cr.ZipCode, 11, 0, null from #tmpCustomer cr
Then I get the correct amoutn of addresses at 33165
The problem that I am running into is the County.
I have a table of counties, called County and the issue seems to be when I join the County table to get its ID's. Here what is returning more records than it should and it returns 34546 records, which is over a 1000 more than the other one.
insert into #tmpAddress
(Line1, Line2, Line3, City, ZipCode, StateID, ct.CountyID,SubDivisionID)
select cr.Line1, cr.Line2,'', cr.City, cr.ZipCode, 11, ct.CountyID, null from #tmpCustomer cr
inner join Exo.dbo.County ct on ct.County = cr.NewCounty
I don't know what is going wrong and maybe someone could point it out to me so I can get the 33165 records for the tmpAddress table
As alluded to by unfinishedmonkey, it looks like you have duplicate values in Exo.dbo.County.County, you can check with a query like:
SELECT c.County, COUNT(*)
FROM Exo.dbo.County c
GROUP BY c.County
HAVING COUNT(*) > 1
ORDER BY c.County
If this returns any records, then you have duplicate records in Exo.dbo.County with the same value in the County field, which in turn is leading to you getting multiple rows in your resultset for a single row from #tmpCustomer.
You could solve this in a couple of ways, firstly by removing records from Exo.dbo.County so that all County values in that table are unique, or by amending your SELECT clause:
SELECT DISTINCT cr.Line1, cr.Line2,'', cr.City, cr.ZipCode, 11, ct.CountyID, NULL
FROM #tmpCustomer cr
INNER JOIN Exo.dbo.County ct ON ct.County = cr.NewCounty
If this query still returns more records than you get from SELECT Line1, Line2, cr.City, cr.ZipCode FROM #tmpCustomer, I'm not sure what the problem could be. If it returns less records, then you must have some records in #tmpCustomer which have a NewCounty value which doesn't appear at all in Exo.dbo.County.County, so you might want to consider a LEFT OUTER JOIN if that's the case.

select results from two tables but choose one field over another

I have two tables.
tblEmployee tblExtraOrMissingInfo
id nvarchar(10) id nvarchar(10)
Name nvarchar(50) Name nvarchar(50)
PreferredName nvarchar(50)
UsePreferredName bit
The data (brief example)
tblEmployee tblExtraOrMissingInfo
id Name id Name PreferredName UsePreferredName
AB12 John PN01 Peter Tom 1
LM22 Lisa YH76 Andrew Andy 0
PN01 Peter LM22 Lisa Liz 0
LK655 Sarah
I want a query to produce the following result
id Name
AB12 John
LM22 Lisa
PN01 Tom
YH76 Andrew
LK655 Sarah
So what I want is all the records from tblEmployee returned and any records in tblExtraOrMissingInfo that are not already in tblEmployee.
If there is a record in both tables with the same id I would like is if the UsePreferredName field in tblExtraOrMissingInfo is 1 for the PreferredName to be used rather than the Name field in the tblEmployee, please see the record PN01 in the example above.
It is slightly faster to use a left join and coalesce than to use the case statement (most servers are optimized for coalesce).
Like this:
SELECT E.ID, COALESCE(P.PreferredName,E.Name,'Unknown') as Name
FROM tblemployee E
LEFT JOIN tblExtraOrMissingInfo P ON E.ID = P.ID AND P.UsePreferredName = 1
The ,'Unknown' is not needed to answer your question, but I added
here to show that you can enhance this query to handle cases where the
name is not available in both tables and you don't want nulls in your result
left join on the employee table and use a case expression for name.
select e.id
,case when i.UsePreferredName = 1 then i.PreferredName else e.name end as name
from tblemployee e
left join tblExtraOrMissingInfo i on i.id=e.id
You can LEFT OUTER JOIN the tables together, then use a CASE statement and COALESCE() formula to get this:
SELECT
tblEmployee.id,
CASE WHEN UsePreferredName = 1 THEN COALESCE(PreferredName, tblEmployee.Name) ELSE tblEmployee.name
FROM
tblEmployee
LEFT OUTER JOIN tblExtraOrMissingInfo
ON tblEmployee.id = tblExtraOrMissingInfo.id
CASE checks that usePreferredName value, and the COALESCE() then grabs PreferredName unless it's NULL. Then it grabs the employees name from tblEmployee.
select id ,name
from
(
select id ,name from tblEmployee
union all
select id ,name from tblExtraOrMissingInfo
)group by id,name
This is an other way you can achieve the results. You can also use CTE to get the same result.
CREATE TABLE #tblEmployee
(
id NVARCHAR(10)
, Name NVARCHAR(50)
);
CREATE TABLE #tblExtraOrMissingInfo
(
id NVARCHAR(10)
, Name NVARCHAR(50)
, PreferredName NVARCHAR(50)
, UsePreferredName BIT
);
INSERT INTO #tblEmployee
( id, Name )
VALUES ( N'AB12' -- id - nvarchar(10)
, N'John' -- Name - nvarchar(50)
),
( N'LM22' -- id - nvarchar(10)
, N'Lisa' -- Name - nvarchar(50)
),
( N'PN01' -- id - nvarchar(10)
, N'Peter' -- Name - nvarchar(50)
),
( N'LK655' -- id - nvarchar(10)
, N'Sarah' -- Name - nvarchar(50)
);
INSERT INTO #tblExtraOrMissingInfo
( id, Name, PreferredName, UsePreferredName )
VALUES ( N'PN01' -- id - nvarchar(10)
, N'Peter' -- Name - nvarchar(50)
, N'Tom' -- PreferredName - nvarchar(50)
, 1 -- UsePreferredName - bit
),
( N'YH76' -- id - nvarchar(10)
, N'Andrew' -- Name - nvarchar(50)
, N'Andy' -- PreferredName - nvarchar(50)
, 0 -- UsePreferredName - bit
),
( N'LM22' -- id - nvarchar(10)
, N'Lisa' -- Name - nvarchar(50)
, N'Liz' -- PreferredName - nvarchar(50)
, 0 -- UsePreferredName - bit
);
SELECT R.id
, CASE WHEN tbl.UsePreferredName = 1 THEN tbl.PreferredName
ELSE R.Name
END AS NAME
FROM #tblExtraOrMissingInfo tbl
RIGHT JOIN ( SELECT id
, Name
FROM #tblEmployee
UNION
SELECT id
, Name
FROM #tblExtraOrMissingInfo
) AS R
ON R.id = tbl.id
AND R.Name = tbl.Name;

SQL Server, Merge two records in one record

We have these tables
CREATE TABLE tbl01
(
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
CREATE TABLE tbl02
(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL REFERENCES tbl01(id),
[val] nvarchar(50) NULL,
[code] int NULL
)
If we run this query:
SELECT
tbl01.id, tbl01.name, tbl02.val, tbl02.code
FROM
tbl01
INNER JOIN
tbl02 ON tbl01.id = tbl02.id
we get these results:
-------------------------------
id | name | val | code
-------------------------------
1 | one | FirstVal | 1
1 | one | SecondVal | 2
2 | two | YourVal | 1
2 | two | OurVal | 2
3 | three | NotVal | 1
3 | three | ThisVal | 2
-------------------------------
You can see that each two rows are related to same "id"
The question is: we need for each id to retrieve one record with all val, each val will return in column according to the value of column code
if(code = 1) then val as val-1
else if (code = 2) then val as val-2
Like this:
-------------------------------
id | name | val-1 | val-2
-------------------------------
1 | one | FirstVal | SecondVal
2 | two | YourVal | OurVal
3 | three | NotVal | ThisVal
-------------------------------
Any advice?
Use can use MAX and Group By to achieve this
SELECT id,
name,
MAX([val1]) [val-1],
MAX([val2]) [val-2]
FROM ( SELECT tbl01.id, tbl01.name,
CASE code
WHEN 1 THEN tbl02.val
ELSE ''
END [val1],
CASE code
WHEN 2 THEN tbl02.val
ELSE ''
END [val2]
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
) Tbl
GROUP BY id, name
Is it the PIVOT operator (http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx) that you are looking for?
You've already got a few answers, but heres one using PIVOT as an alternative. The good thing is this approach is easy to scale if there are additional columns required later
-- SETUP TABLES
DECLARE #t1 TABLE (
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
DECLARE #t2 TABLE(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL,
[val] nvarchar(50) NULL,
[code] int NULL
)
-- SAMPLE DATA
INSERT #t1 ( id, name )
VALUES ( 1, 'one'), (2, 'two'), (3, 'three')
INSERT #t2
( subId, id, val, code )
VALUES ( 1,1,'FirstVal', 1), ( 2,1,'SecondVal', 2)
,( 3,2,'YourVal', 1), ( 4,2,'OurVal', 2)
,( 5,3,'NotVal', 1), ( 6,3,'ThisVal', 2)
-- SELECT (using PIVOT)
SELECT id, name, [1] AS 'val-1', [2] AS 'val-2'
FROM
(
SELECT t2.id, t1.name, t2.val, t2.code
FROM #t1 AS t1 JOIN #t2 AS t2 ON t2.id = t1.id
) AS src
PIVOT
(
MIN(val)
FOR code IN ([1], [2])
) AS pvt
results:
id name val-1 val-2
---------------------------------
1 one FirstVal SecondVal
2 two YourVal OurVal
3 three NotVal ThisVal
If there are always only two values, you could join them or even easier, group them:
SELECT tbl01.id as id, Min(tbl01.name) as name, MIN(tbl02.val) as val-1, MAX(tbl02.val) as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
GROUP BY tbl02.id
note: this query will always put the lowest value in the first column and highest in the second, if this is not wanted: use the join query:
Join query
If you always want code 1 in the first column and code 2 in the second:
SELECT tbl01.id as id, tbl01.name as name, tbl02.val as val-1, tbl03.val as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
ON tbl02.code = 1
INNER JOIN tbl03 ON tbl01.id = tbl03.id
ON tbl03.code = 2
Variable amount of columns
You cannot get an variable amount of columns, only when you do this by building your query in code or t-sql stored procedures.
My advice:
If its always to values: join them in query, if not, let your server-side code transform the data. (or even better, find a way which makes it not nessecery to transform data)
Try this - it uses a pivot function but it also creates creates the dynamic columns dependent on code
DECLARE #ColumnString varchar(200)
DECLARE #sql varchar(1000)
CREATE TABLE #ColumnValue
(
Value varchar(500)
)
INSERT INTO #ColumnValue (Value)
SELECT DISTINCT '[' + 'value' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id )) + ']'
FROM Test
SELECT #ColumnString = COALESCE(#ColumnString + ',', '') + Value
FROM #ColumnValue
Drop table #ColumnValue
SET #sql =
'
SELECT *
FROM
(
SELECT
id,name,val,''value'' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id ))as [values]
FROM Test
) AS P
PIVOT
(
MAX(val) FOR [values] IN ('+#ColumnString+')
) AS pv
'
--print #sql
EXEC (#sql)

Make multiple SQL into one Query

I have a table in SQL Server 2005 as shown below
DECLARE #Promotions TABLE (PromoCode VARCHAR(10),
DeptID VARCHAR(10),
VerticalID VARCHAR(10),
BuildingID VARCHAR(10)
primary key (DeptID, VerticalID, BuildingID)
)
INSERT INTO #Promotions VALUES ('P1_20%','101','501','1001')
INSERT INTO #Promotions VALUES ('P2_10%','101','501','All')
INSERT INTO #Promotions VALUES ('P3_5%','101','All','All')
INSERT INTO #Promotions VALUES ('P1_50%','111','606','1002')
We need to find out promotion code value based on the matching values in DeptID, VerticalID and BuildingID columns. If there is not match with these 3 columns, look for a match with the input value for DeptID, VerticalID and a default value ("All") for BuildingID. Still if there is no result found, look for a match by considering input DeptID, default value for VerticalID ("All") and default value for BuildingID ("All").
The following query works fine – however it is using a 3 part approach. Can we achieve this in a single SQL query?
EXISTING CODE
DECLARE #PromoCode VARCHAR(10)
--Part 1
SELECT #PromoCode = PromoCode
FROM #Promotions
WHERE DeptID = #inputDeptID
AND VerticalID = #inputVerticalID
AND BuildingID = #inputBuildingID
--Part 2
IF #PromoCode IS NULL
BEGIN
SELECT #PromoCode = PromoCode
FROM #Promotions
WHERE DeptID = #inputDeptID
AND VerticalID = #inputVerticalID
AND BuildingID = 'All'
END
--Part 3
IF #PromoCode IS NULL
BEGIN
SELECT #PromoCode = PromoCode
FROM #Promotions
WHERE DeptID = #inputDeptID
AND VerticalID = 'All'
AND BuildingID = 'All'
END
--Result
SELECT #PromoCode
Testing
DECLARE #inputDeptID VARCHAR(10)
DECLARE #inputVerticalID VARCHAR(10)
DECLARE #inputBuildingID VARCHAR(10)
SET #inputDeptID = '101'
SET #inputVerticalID = '501'
SET #inputBuildingID = '3003'
Expected Result
P2_10%
You can approach this as a "prioritization". That is, look for all possible matches and then choose the one that is best, using order by and top:
set #PromoCode = (SELECT top 1 PromoCode
FROM #Promotions
WHERE DeptID = #inputDeptID AND
(VerticalID = #inputVerticalID OR VerticalId = 'ALL') AND
(BuildingID = #inputBuildingID OR BuildingID = 'ALL')
ORDER BY (case when Building = 'ALL' then 1 else 0 end),
(case when VerticalId = 'ALL' then 1 else 0 end)
);
There are other ways to express this. If performance is an issue and you have indexes on the columns, then a union all approach with order by may work better than ors in the where clause.