Bizarre Join with comma - sql

I'm looking at someone else's code and find this bizarre join:
SELECT
SUM(
(
intUnitOverheadCost + intUnitLaborCost + intUnitMaterialCost + intUnitSubcontractCost
+ intUnitDutyCost + intUnitFreightCost + intUnitMiscCost
)
*
(
(
CASE
WHEN imtSource = 3
THEN - 1
ELSE 1
END
) * intQuantity
)
)
FROM PartTransactions --imt
INNER JOIN PartTransactionCosts --int
ON imtPartTransactionID = intPartTransactionID
LEFT JOIN Warehouses --imw
ON imtPartWarehouseLocationID = imwWarehouseID
, ProductionProperties --xap <-- weird join
WHERE imtJobID = jmpJobID
AND imtSource IN (2,3)
AND imtReceiptID = ''
AND Upper(imtTableName) <> 'RECEIPTLINES'
AND imtNonInventoryTransaction <= {?CHECKBOXGROUP_4_ShowNonInventory}
AND imtJobType IN (1, 3)
AND imtTransactionDate < DATEADD(d, 1, {?PROMPT_1_TODATE})
AND (
imtNonNettable = 0
OR (
imtNonNettable <> 0
AND ISNULL(imwDoNotIncludeInJobCosts, 0) = 0
)
)
AND intCostType = (
CASE -- Always 1
WHEN xapIMCostingMethod = 1
THEN 1
WHEN xapIMCostingMethod = 2
THEN 2
WHEN xapIMCostingMethod = 3
THEN 3
ELSE 4
END
)
There is only one record in table ProductionProperties and the result of select xapIMCostingMethod from ProductionProperties is always 1.
There are always 4 enumerated results in PartTransactionCosts, but only 1 result is allowed.
ProductionProperties.xapIMCostingMethod is implicitly joining to PartTransactionCosts.intCostType
My specific question is what is really going on with this comma join? It looks like it has to be a cross-join, later filtered in the WHERE clause with one possible result.

Agree with the previous answer. It is a cartesian join but since the rows are 1 it doesn't cause an issue.
I'm thinking if you added rows to ProductionProperties then it would serve as a multiplier for your sum. I did a little experiment to show the issue:
declare #tableMoney table (
unit int,
Product char(5),
xapIMPCostingMethod int,
Cost money
)
declare #tableProdProperties table (
xapIMPCostingMethod int
)
insert #tableMoney (unit, Product, xapIMPCostingMethod, Cost)
values
(1,'bike',1, 2.00),
(1,'car',1, 2.25),
(2,'boat',2, 4.50)
insert #tableProdProperties (xapIMPCostingMethod)
values (1),
(2)
select sum(Cost)
from #tableMoney, #tableProdProperties
I also don't like to use joins where it isn't clear what is joining to what so I always use an alias:
select sum(Cost)
from #tableMoney tbm join #tableProdProperties tpp
on tbm.xapIMPCostingMethod = tpp.xapIMPCostingMethod

Related

CTE self join slow down the execution

I am using the following query in SP.
DECLARE #DateFrom datetime = '01/01/1753',
#DateTo datetime = '12/31/9999'
BEGIN
WITH tmpTethers
AS
(
SELECT TL.str_systemid AS SystemCode,
ISNULL(ml.name, ml.location) AS [System],
TL.dte_created AS [Date],
TL.str_LengthId AS TetherRegId,
0 AS LengthCut,
ISNULL(TL.dbl_newlength, 0) AS LengthAdded,
CAST(0 AS FLOAT) AS RemainingLength,
1 AS Mode,
UT.description AS UOM
FROM OP_TetherLength AS TL
INNER JOIN master_location AS ML ON ML.location = TL.str_systemid
LEFT JOIN udc_type AS UT ON TL.lng_lengthuom = UT.udc
WHERE (TL.dte_dateadded BETWEEN #DateFrom AND #DateTo)
UNION ALL
SELECT RR.systemcode AS SystemCode,
ISNULL(ML.name, ML.location) AS [System],
RR.datecreated AS [Date],
RR.oms_repairid AS TetherRegId,
ISNULL(RR.cutlength, 0) AS LengthCut,
0 AS LengthAdded,
0 AS RemainingLength,
0 AS Mode,
UT.description AS UOM
FROM Repair_Registration AS RR
INNER JOIN master_location AS ML ON RR.systemcode = ml.location
LEFT JOIN udc_type AS UT ON RR.cutlength_uomid = UT.udc
WHERE --RR.cut_umbilical_tether = 0 AND
RR.cutbackrequired = 1 AND
(RR.datecreated BETWEEN #DateFrom AND #DateTo)
),
tmpOrderedTethers
AS
(
SELECT TOP 1000
SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
Mode,
UOM,
ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] ) AS RowNumber
FROM tmpTethers
ORDER BY SystemCode
),
tmpFinalTethers
AS
(
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
CASE
WHEN Mode = 1 THEN LengthAdded
ELSE 0 - LengthCut
END AS RemainingLength,
Mode,
UOM,
RowNumber
FROM tmpOrderedTethers
WHERE RowNumber = 1
UNION ALL
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
),
---- FT - Previous
---- OT - Current
SELECT SystemCode,
[System],
[Date],
TetherRegId,
LengthCut,
LengthAdded,
RemainingLength,
UOM,
RowNumber
,ROW_NUMBER() OVER(PARTITION BY SystemCode ORDER BY [Date] desc) AS SortNumber
FROM tmpGetFinalTethers
ORDER BY SystemCode, SortNumber
OPTION (MAXRECURSION 1000)
END
In above query when I am commenting the following part then execution time reduced and data come fast:
SELECT tmpOT.SystemCode,
tmpOT.[System],
tmpOT.[Date],
tmpOT.TetherRegId,
tmpOT.LengthCut,
tmpOT.LengthAdded,
CASE
WHEN tmpOT.Mode = 1 THEN /*tmpFT.RemainingLength +*/ tmpOT.LengthAdded
ELSE tmpFT.RemainingLength - tmpOT.LengthCut
END AS RemainingLength,
CASE
WHEN tmpOT.Mode = 1 OR tmpFT.Mode = 1 THEN 1
ELSE 0
END AS Mode,
tmpOT.UOM,
tmpOT.RowNumber
FROM tmpOrderedTethers AS tmpOT
INNER JOIN tmpFinalTethers AS tmpFT ON tmpFT.SystemCode = tmpOT.SystemCode AND
tmpFT.RowNumber = tmpOT.RowNumber - 1
Please let me know how I can refine this.
It seems like you have row by row processing in your [tmpFinalTethers] and [tmpGetFinalTethers] cte's.
Each row returned in [tmpFinalTethers] is based on [tmpOrderedTethers] and [tmpOrderedTethers]'s data is based on [tmpTethers]. Therefore the logic which contains in [tmpOrderedTethers] and [tmpTethers] will be executed n times, where n is a number of rows returned by [tmpFinalTethers].
The reason is because cte's are not materialized objects. They are not get stored in memory or disc, so they're executing each time you reference them outside of declaration.
Loading the resultset of [tmpOrderedTethers] to temp table may help if you really need row by row processing for your task and don't have other options.
Also it seems like your [tmpFinalTethers] and [tmpGetFinalTethers] have the same logic inside. I am not sure what the purpose for it. Mb you can do final select from [tmpFinalTethers] and get rid of [tmpGetFinalTethers].
Edited:
Try smth like this:
;WITH tmpTethers AS (...),
tmpOrderedTethers AS (...)
SELECT * INTO #tmpOrderedTethers FROM tmpOrderedTethers
;WITH tmpFinalTethers (
SELECT ... FROM #tmpOrderedTethers WHERE ...
UNION ALL
SELECT ... FROM #tmpOrderedTethers tmpOT INNER JOIN ...
)
Edited 2:
As you have OPTION (MAXRECURSION 1000) I suppose you always get 1000<= number of rows. For such amount of rows your solution with recursive cte combined with temp table will probably work. At least it would be better than cursor, because it consumes some resources in addition to row by row processing. But if you will need to process let's say 10 000 of rows then row by row processing is definitely not appropriate solution and you should find another one.

How to get the result in below format in SQL Server?

I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n

Select column value that matches a combination of other columns values on the same table

I have a table called Ads and another Table called AdDetails to store the details of each Ad in a Property / Value style, Here is a simplified example with dummy code:
[AdDetailID], [AdID], [PropertyName], [PropertyValue]
2 28 Color Red
3 28 Speed 100
4 27 Color Red
5 28 Fuel Petrol
6 27 Speed 70
How to select Ads that matches many combinations of PropertyName and PropertyValue, for example :
where PropertyName='Color' and PropertyValue='Red'
And
where PropertyName='Speed' and CAST(PropertyValue AS INT) > 60
You are probably going to do stuff like this a lot so I would start out by making a view that collapses all of the properties to a single row.
create view vDetail
as
select AdID,
max(case PropertyName
when 'Color' then PropertyValue end) as Color,
cast(max(case PropertyName
when 'Speed' then PropertyValue end) as Int) as Speed,
max(case PropertyName
when 'Fuel' then PropertyValue end) as Fuel
from AdDetails
group by AdID
This approach also solves the problem with casting Speed to an int.
Then if I select * from vDetails
This makes it easy to deal with when joined to the parent table. You said you needed a variable number of "matches" - note the where clause below. #MatchesNeeded would be the count of the number of variables that were not null.
select *
from Ads a
inner join vDetails v
on a.AdID = v.AdID
where case when v.Color = #Color then 1 else 0 end +
case when v.Spead > #Speed then 1 else 0 end +
case when v.Fuel = #Fuel then 1 else 0 end = #MatchesNeeded
I think you have two main problems to solve here.
1) You need to be able to CAST varchar values to integers where some values won't be integers.
If you were using SQL 2012, you could use TRY_CAST() ( sql server - check to see if cast is possible ). Since you are using SQL 2008, you will need a combination of CASE and ISNUMERIC().
2) You need an efficient way to check for the existence of multiple properties.
I often see a combination of joins and where clauses for this, but I think this can quickly get messy as the number of properties that you check gets over... say one. Instead, using an EXISTS clause tends to be neater and I think it provides better clues to the SQL Optimizer instead.
SELECT AdID
FROM Ads
WHERE 1 = 1
AND EXISTS (
SELECT 1
FROM AdDetails
WHERE AdID = Ads.AdID
AND ( PropertyName='Color' and PropertyValue='Red' )
)
AND EXISTS (
SELECT 1
FROM AdDetails
WHERE AdID = Ads.AdID
AND PropertyName='Speed'
AND
(
CASE
WHEN ISNUMERIC(PropertyValue) = 1
THEN CAST(PropertyValue AS INT)
ELSE 0
END
)
> 60
)
You can add as many EXISTS clauses as you need without the query getting particularly difficult to read.
Something like this might work for 2 conditions, you would have to adapt depending on the number of conditions
select a.*
from ads as a
join addetails as d1 on d1.adid = a.id
join addetails as d2 on d2.adid = a.id
where (d1.PropertyName='Color' and d1.PropertyValue='Red')
and (d2.PropertyName='Speed' and d2.CAST(PropertyValue AS INT) > 60)
DECLARE #AdDetails TABLE
(
AdDetailID INT,
AdID INT,
PropertyName VARCHAR(20),
PropertyValue VARCHAR(20)
)
INSERT INTO #AdDetails
( AdDetailID, AdID, PropertyName, PropertyValue )
VALUES
(2, 28, 'Color', 'Red'),
(3, 28, 'Speed', '100'),
(4, 27, 'Color', 'Red'),
(5, 28, 'Fuel', 'Petrol'),
(6, 27, 'Speed', '70');
--Col1
DECLARE #ColorValue VARCHAR(20) = 'Red'
--Col2
DECLARE #SpeedValue INT = 90
DECLARE #SpeedType VARCHAR(2) = '>'
--Col3
DECLARE #FuelValue VARCHAR(20) = null
SELECT DISTINCT a.AdID FROM #AdDetails a
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #ColorValue IS NULL
OR #ColorValue = PropertyValue
) Color
ON Color.AdID = a.AdID
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #SpeedType IS NULL
UNION
SELECT *
FROM #AdDetails
WHERE PropertyName = 'Speed'
AND ((#SpeedType = '>' AND CONVERT(INT, PropertyValue) > #SpeedValue)
OR (#SpeedType = '<' AND CONVERT(INT, PropertyValue) < #SpeedValue)
OR (#SpeedType = '=' AND CONVERT(INT, PropertyValue) = #SpeedValue))
) AS Speed
ON Speed.AdID = a.AdID
INNER JOIN
(
SELECT *
FROM #AdDetails
WHERE #FuelValue IS NULL
OR (#FuelValue = PropertyValue)
) AS Fuel
ON Fuel.AdID = a.AdID
I add one inner join clause per property type (with some overrides), your sql query would pass all of the possible property type info in one go nulling out whatever they don't want. very ugly code though as it grows.

Why is this query with a nested select faster when I include the where clause twice

I had a large sql query that had a nested select in the from clause.
Similar to this:
SELECT * FROM
( SELECT * FROM SOME_TABLE WHERE some_num = 20)
WHERE some_num = 20
In my sql query if I remove the outer "some_num" = 20 it takes 5 times as long . Shouldent these querys run in almost exactly the same time, if not wouldn't having the the additional where slow it down slightly?
What am I not understanding about how sql querys work?
Here is the original query in question
SELECT a.ITEMNO AS Item_No,
a.DESCRIPTION AS Item_Description,
UNITPRICE / 100 AS Retail_Price,
b.UNITSALES AS Units_Sold,
( Dollar_Sales ) AS Dollar_Sales,
( Dollar_Cost ) AS Dollar_Cost,
( Dollar_Sales ) - ( Dollar_Cost ) AS Gross_Profit,
( Percent_Page * c.PAGECOST ) AS Page_Cost,
( Dollar_Sales - Dollar_Cost - ( Percent_Page * c.PAGECOST ) ) AS Net_Profit,
Percent_Page * 100 AS Percent_Page,
( CASE
WHEN UNITPRICE = 0 THEN NULL
WHEN Percent_Page = 0 THEN NULL
WHEN ( Dollar_Sales - Dollar_Cost - ( Percent_Page * c.PAGECOST ) ) > 0 THEN 0
ELSE ( ceiling(abs(Dollar_Sales - Dollar_Cost - ( Percent_Page * c.PAGECOST )) / ( UNITPRICE / 100 )) )
END ) AS Break_Even,
b.PAGENO AS Page_Num
FROM (SELECT PAGENO,
OFFERITEM,
UNITSALES,
UNITPRICE,
( DOLLARSALES / 100 ) AS Dollar_Sales,
( DOLLARCOST / 10000 ) AS Dollar_Cost,
(( CAST(STUFF(PERCENTPAGE, 2, 0, '.') AS DECIMAL(9, 6)) )) AS Percent_Page
FROM OFFERITEMS
WHERE LEFT(OFFERITEM, 6) = 'CH1301'
AND PERCENTPAGE > 0) AS b
INNER JOIN ITEMMAST a
ON a.EDPNO = 1 * RIGHT(OFFERITEM, 8)
LEFT JOIN OFFERS c
ON c.OFFERNO = 'CH1301'
WHERE LEFT(OFFERITEM, 6) = 'CH1301'
ORDER BY Net_Profit DESC
Notice the two
WHERE left(OFFERITEM,6) = 'CH1301'
If I remove the outer Where then the query takes 5 times as long
As requested the Execution plan excuse the crappy upload
http://i.imgur.com/1PqmpVf.png
Is the column OFFERITEM in an index but PERCENTPAGE is not?
In your inner query you reference both these columns, in the outer query you only reference OFFERITEM.
Difficult to say without seeing the execution plan, but it could be that the outer query is causing the optimizer to run an 'index scan' whereas the inner query would cause a full table scan.
On a separate note, you should definitely modify:
WHERE left(OFFERITEM,6) ='CH1301'
to:
where offeritem like 'CH1301%'
As this will allow an index seek if there is an index on offeritem.

How to get the deepest levels of a hierarchical sql query

I'm using SQLServer 2008.
Say I have a recursive hierarchy table, SalesRegion, whit SalesRegionId and ParentSalesRegionId. What I need is, given a specific SalesRegion (anywhere in the hierarchy), retrieve ALL the records at the BOTTOM level.
I.E.:
SalesRegion, ParentSalesRegionId
1, null
1-1, 1
1-2, 1
1-1-1, 1-1
1-1-2, 1-1
1-2-1, 1-2
1-2-2, 1-2
1-1-1-1, 1-1-1
1-1-1-2, 1-1-1
1-1-2-1, 1-1-2
1-2-1-1, 1-2-1
(in my table I have sequencial numbers, this dashed numbers are only to be clear)
So, if the user enters 1-1, I need to retrieve al records with SalesRegion 1-1-1-1 or 1-1-1-2 or 1-1-2-1 (and NOT 1-2-2). Similarly, if the user enters 1-1-2-1, I need to retrieve just 1-1-2-1
I have a CTE query that retrieves everything below 1-1, but that includes rows that I don't want:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, 1 AS Level /*Added as a workaround*/
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, Level + 1 AS Level
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
Thanks in advance!
Alejandro.
I found a quick way to do this, but I'd rather the answer to be in a single query. So if you can think of one, please share! If I like it better, I'll vote for it as the best answer.
I added a "Level" column in my previous query (I'll edit the question so this answer is clear), and used it to get the last level and then delete the ones I don't need.
INSERT INTO #SaleLocales
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
SELECT #LowestLevel = MAX(Level)
FROM #SaleLocales
DELETE #SaleLocales
WHERE Level <> #LowestLevel
Building off your post:
; WITH CTE AS
(
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
)
SELECT
FROM CTE a
JOIN
(
SELECT MAX(level) AS level
FROM CTE
) b
ON a.level = b.level
There were a few edits in there. Kept hitting post...
Are you looking for something like this:
declare #SalesRegion as table ( SalesRegion int, ParentSalesRegionId int )
insert into #SalesRegion ( SalesRegion, ParentSalesRegionId ) values
( 1, NULL ), ( 2, 1 ), ( 3, 1 ),
( 4, 3 ), ( 5, 3 ),
( 6, 5 )
; with CTE as (
-- Get the root(s).
select SalesRegion, CAST( SalesRegion as varchar(1024) ) as Path
from #SalesRegion
where ParentSalesRegionId is NULL
union all
-- Add the children one level at a time.
select SR.SalesRegion, CAST( CTE.Path + '-' + cast( SR.SalesRegion as varchar(10) ) as varchar(1024) )
from CTE inner join
#SalesRegion as SR on SR.ParentSalesRegionId = CTE.SalesRegion
)
select *
from CTE
where Path like '1-3%'
I haven't tried this on a serious dataset, so I'm not sure how it'll perform, but I believe it solves your problem:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
WHERE HasChildren = 0