Multi Column CASE WHEN in Teradata - sql

I'm trying to consolidate a few sub-queries to avoid hitting a massive table (42B rows) multiple times and getting
"[3771] Illegal Expression in WHEN clause of CASE expression."
,SUM(CASE
WHEN (oh.LOCN_NBR,oh.WK_NBR) IN (SELECT LOCN_NBR,START_WK FROM VT_STORES)
THEN oh.TTL_UN_QT
ELSE NULL
END) AS BEGINNING_OH
Is there any way to do multi-column IN statements within a CASE statement, or am I stuck putting these in the join/where in a subquery as it is currently?
Edit: Full Query as requested:
SELECT
oh.LOCN_NBR AS LOCN_NBR
,item.ITEM_ID AS ITEM_ID
,SUM(CASE
WHEN oh.WK_NBR = (SELECT WK_NBR FROM ALEX_ARP_VIEWS_PRD.REF_CUSTOM_TIME WHERE cust_time_id=2 )
THEN oh.TTL_UN_QT
ELSE NULL
END) AS SALEABLE_QTY
,SUM(CASE
WHEN oh.WK_NBR = (SELECT LY_WK_NBR FROM ALEX_ARP_VIEWS_PRD.REF_CUSTOM_TIME WHERE cust_time_id=2 )
THEN oh.TTL_UN_QT
ELSE NULL
END) AS SALEABLE_QTY_LY
,SUM(CASE
WHEN (oh.LOCN_NBR,oh.WK_NBR) IN (SELECT LOCN_NBR,PRI_START_WK FROM VT_STORES)
THEN oh.TTL_UN_QT
ELSE NULL
END) AS BEGINNING_OH_LY
,SUM(CASE
WHEN (oh.LOCN_NBR,oh.WK_NBR) IN (SELECT LOCN_NBR,START_WK FROM VT_STORES)
THEN oh.TTL_UN_QT
ELSE NULL
END) AS BEGINNING_OH
FROM
ALEX_ARP_VIEWS_PRD.FACT_WKLY_OPR_INS oh
INNER JOIN VT_STORES stores ON oh.LOCN_NBR = stores.LOCN_NBR
INNER JOIN VT_ITEM item ON oh.VEND_PACK_ID = item.VEND_PACK_ID
WHERE
INS_TYP_CD='H'
AND TTL_UN_QT <> 0
AND WK_NBR >= (SELECT MIN(PRI_START_WK) FROM VT_STORES)
GROUP BY
oh.LOCN_NBR
,item.ITEM_ID

You don't need to use IN. You can use exists:
SUM(CASE WHEN EXISTS (SELECT 1 FROM VT_STORES v WHERE oh.LOCN_NBR = v.LOCN_NBR AND oh.WK_NBR = v.START_WK)
THEN oh.TTL_UN_QT
END) AS BEGINNING_OH
However, I'm not 100% sure the problem is the IN. Many databases do not allow subqueries as arguments to aggregation functions. I'm not sure if Teradata allows this functionality.

I'm trying to consolidate a few sub-queries to avoid hitting a massive table multiple times
Multi-column Subqueries are only allowed in WHERE, but not in CASE. Rewriting to EXISTS will not probably not improve performance, the plan might actually be more complex.
You simply try hiding complexity, but it's still an Outer Join in the background, like this:
,Sum(CASE WHEN stores.LOCN_NBR IS NOT NULL THEN oh.TTL_UN_QT END) AS BEGINNING_OH
...
FROM oh LEFT JOIN
(
SELECT LOCN_NBR,START_WK FROM VT_STORES
) AS stores
ON stores.LOCN_NBR = oh.LOCN_NBR
AND stores.START_WK = oh.WK_NBR
Can you show your current query (at least those parts you try to optimize)?
Edit:
When VT_STORES.locn_nbr is unique this should return the same result:
SELECT
oh.LOCN_NBR AS LOCN_NBR
,item.ITEM_ID AS ITEM_ID
,Sum(CASE
WHEN oh.WK_NBR = (SELECT Min(WK_NBR) FROM ALEX_ARP_VIEWS_PRD.REF_CUSTOM_TIME WHERE cust_time_id=2 )
THEN oh.TTL_UN_QT
ELSE NULL
END) AS SALEABLE_QTY
,Sum(CASE
WHEN oh.WK_NBR = (SELECT Min(LY_WK_NBR) FROM ALEX_ARP_VIEWS_PRD.REF_CUSTOM_TIME WHERE cust_time_id=2 )
THEN oh.TTL_UN_QT
ELSE NULL
END) AS SALEABLE_QTY_LY
,Sum(CASE
WHEN oh.WK_NBR= stores.PRI_START_WK
THEN oh.TTL_UN_QT
END) AS BEGINNING_OH_LY
,Sum(CASE
WHEN oh.WK_NBR = stores.START_WK
THEN oh.TTL_UN_QT
END) AS BEGINNING_OH
FROM
ALEX_ARP_VIEWS_PRD.FACT_WKLY_OPR_INS oh
INNER JOIN VT_STORES stores ON oh.LOCN_NBR = stores.LOCN_NBR
INNER JOIN VT_ITEM item ON oh.VEND_PACK_ID = item.VEND_PACK_ID
WHERE
INS_TYP_CD='H'
AND TTL_UN_QT <> 0
AND WK_NBR >= (SELECT Min(PRI_START_WK) FROM VT_STORES)
GROUP BY
oh.LOCN_NBR
,item.ITEM_ID
The other Scalar Subqueries should be fine, because they return only a single row and the optimizer knows that.

re: Is there any way to do multi-column IN statements within a CASE statement
no.
re: to avoid hitting a massive table (42B rows) multiple times
which one is large? I don't see how, even if a compound IN clause worked, you would avoid hitting a large table multiple times compared to using a join/adding columns to a where clause.
have you tried something like:
SELECT
...
,SUM(CASE
WHEN SELECT COUNT(*) FROM VT_STORES
WHERE LOCN_NBR = oh.LOCN_NBR AND
PRI_START_WK = oh.WK_NBR > 1
THEN oh.TTL_UN_QT
END) AS BEGINNING_OH_LY
,SUM(CASE
WHEN SELECT COUNT(*) FROM VT_STORES
WHERE LOCN_NBR = oh.LOCN_NBR AND
START_WK = oh.WK_NBR > 1
THEN oh.TTL_UN_QT
END) AS BEGINNING_OH
FROM
...
?
If it's VT_STORES that's large, try:
ALEX_ARP_VIEWS_PRD.FACT_WKLY_OPR_INS oh INNER JOIN
(select
LOCN_NBR,
PRI_START_WK,
START_WK
from
VT_STORES) stores ON
oh.LOCN_NBR = stores.LOCN_NBR and
(PRI_START_WK = oh.WK_NBR OR
START_WK = oh.WK_NBR)
although I've seen performance hits from using OR clauses in joins, you might be better off with two inner joins, one for each week you want.

Related

Show a total value from a something that is referenced in other tables

I'm sorry if the question is confusing, but hopefully it makes sense once I explain it.
In the table "t_land" I have all the pieces of land I have with all the properties, and the two other tables "t_plantland" and "t_grassland" in which plants or animals grow respectively. I want to have a query which tells me what is the total amount of land used for each of those types of lands.
I hope it makes sense, cause this is difficult to explain for me. Thank you.
This could be a lot simpler like below
Select * from (
( SELECT sum(size) AS Plant_Tamt from
t_land
where land_id in (select
land_id from t_plantland)
)
JOIN
(SELECT sum(size) AS Grass_Tamt from t_land
where land_id in
(select land_id from t_grassland)
)
)
Well, a join comes to mind:
select sum(case when pl.landid is null and gl.landid is not null then l.area end) as grassland_area,
sum(case when pl.landid is not null and gl.landid is null then l.area end) as plantland_area,
sum(case when pl.landid is not null and gl.landid is not null then l.area end) as both_area,
sum(case when pl.landid is null and gl.landid is null then l.area end) as neither_area
from land l left join
plantland pl
on pl.landid = l.landid left join
grassland gl
on gl.landid = l.landid;

How can I prevent a Lazy Spool from happening in my query?

I been struggling to optimize this query,
SELECT
dbo.OE61BLIN.Order_Key
,dbo.OE61BLIN.Doc_Type
,dbo.OE61BHED.Doc__
,dbo.OE61BHED.Inv_Date
,dbo.OE61BHED.Cust__
,dbo.OE61BLIN.Line_Type
,dbo.OE61BLIN.Item__
,dbo.OE61BLIN.Description
,(CASE
WHEN dbo.OE61BLIN.Doc_Type = 'I' THEN dbo.OE61BLIN.Qty_Shipped * dbo.OE61BLIN.Unit_Factor
WHEN dbo.OE61BLIN.Doc_Type = 'C' AND
dbo.OE61BLIN.return_to_inventory_ = 1 THEN -dbo.OE61BLIN.Qty_Shipped * dbo.OE61BLIN.Unit_Factor
ELSE 0
END) AS QTY
,(CASE
WHEN dbo.OE61BLIN.Doc_Type = 'I' THEN dbo.OE61BLIN.Ext_Price
WHEN dbo.OE61BLIN.Doc_Type = 'C' THEN -dbo.OE61BLIN.Ext_Price
ELSE 0
END) * (CASE
WHEN ISNULL(dbo.OE61BHED.Inv_Disc__, 0) <> 0 THEN 1 - (dbo.OE61BHED.Inv_Disc__ / 100)
ELSE 1
END)
AS amount
,dbo.OE61BHED.Inv_Disc__
,dbo.OE61BLIN.ITEM_GROUP
,dbo.OE61BLIN.Category
,ISNULL(dbo.AR61ACST.intercompany, 0) AS intercompany
FROM dbo.OE61BHED
LEFT OUTER JOIN dbo.AR61ACST
ON dbo.OE61BHED.Cust__ = dbo.AR61ACST.Cust__
RIGHT OUTER JOIN dbo.OE61BLIN
ON dbo.OE61BHED.Order_Key = dbo.OE61BLIN.Order_Key
WHERE (dbo.OE61BLIN.Line_Type = 'R')
AND isnull(intercompany,0) != 1
AND (dbo.OE61BLIN.Doc_Type = 'C'
OR dbo.OE61BLIN.Doc_Type = 'I')
Complete estimated execution plan is here
https://www.brentozar.com/pastetheplan/?id=S1htt0rxN
Actual Exectuion Plan
https://www.brentozar.com/pastetheplan/?id=BymztxLgE
I use SQL Sentry Plan Explorer to optimaze it ,
and it suggested that I should add the following two indexes, which I have
But it doesnt improve much, It only removed RID Look Up from plan.
CREATE NONCLUSTERED INDEX [XI_LineTypeDocType_OE61BLIN_12172018]
ON [dbo].[OE61BLIN] ([Line_Type],[Doc_Type])
INCLUDE ([Order_Key],[Item__],[Description],[Category],[Return_to_Inventory_],[Unit_Factor],[Qty_Shipped],[Ext_Price],[ITEM_GROUP])
CREATE INDEX [XI_CustIntercompany_AR67ACST_12172018] ON [GarbageMark].[dbo].[AR61ACST]
([Cust__] ASC)
INCLUDE ([Intercompany])
I am completely stuck on how to aproach this problem.
I see that Lazy Spool is the most expensive operation but I dont know how to remove
or substitute.
Regrettably you don't prefix intercompany in the where clause with its table name so to some extent I'm guessing that the changes you see below. I am going to suggest that you re-arrange your query to avoid the use of right outer join and then, perhaps more importantly, place the intercompany <> 1 condition directly into the left join which
removes the use of ISNULL() from your where clause.
SELECT
dbo.OE61BLIN.Order_Key
, dbo.OE61BLIN.Doc_Type
, dbo.OE61BHED.Doc__
, dbo.OE61BHED.Inv_Date
, dbo.OE61BHED.Cust__
, dbo.OE61BLIN.Line_Type
, dbo.OE61BLIN.Item__
, dbo.OE61BLIN.Description
, (CASE
WHEN dbo.OE61BLIN.Doc_Type = 'I' THEN dbo.OE61BLIN.Qty_Shipped * dbo.OE61BLIN.Unit_Factor
WHEN dbo.OE61BLIN.Doc_Type = 'C' AND
dbo.OE61BLIN.return_to_inventory_ = 1 THEN -dbo.OE61BLIN.Qty_Shipped * dbo.OE61BLIN.Unit_Factor
ELSE 0
END) AS QTY
, (CASE
WHEN dbo.OE61BLIN.Doc_Type = 'I' THEN dbo.OE61BLIN.Ext_Price
WHEN dbo.OE61BLIN.Doc_Type = 'C' THEN -dbo.OE61BLIN.Ext_Price
ELSE 0
END) * (CASE
WHEN ISNULL( dbo.OE61BHED.Inv_Disc__, 0 ) <> 0 THEN 1 - (dbo.OE61BHED.Inv_Disc__ / 100)
ELSE 1
END)
AS amount
, dbo.OE61BHED.Inv_Disc__
, dbo.OE61BLIN.ITEM_GROUP
, dbo.OE61BLIN.Category
, ISNULL( dbo.AR61ACST.intercompany, 0 ) AS intercompany
FROM dbo.OE61BLIN
INNER JOIN dbo.OE61BHED ON dbo.OE61BLIN.Order_Key = dbo.OE61BHED.Order_Key
LEFT OUTER JOIN dbo.AR61ACST ON dbo.OE61BHED.Cust__ = dbo.AR61ACST.Cust__
AND dbo.AR61ACST.intercompany != 1
WHERE dbo.OE61BLIN.Line_Type = 'R'
AND dbo.OE61BLIN.Doc_Type IN ('C','I')
;
I believe the join between OE61BLIN and OE61BHED can be an inner join, if not try using a left join.

how to include several count functions and group byes in one line

I am trying to get the number of customers by their types and groups all in line as such:
GroupName | GroupNotes | Count(Type1) | Count(Type2) | Count(Type3)
but instead I can only get the groupid ,the typeid and the number of types in the group by using the following query
SELECT
CustomersGroups.idCustomerGroup , Customers.type , COUNT(*)
FROM
CustomersGroups
inner Join CustomersInGroup on CustomersGroups.idCustomerGroup = CustomersInGroup.idCustomerGroup
inner Join Customers on Customers.idCustomer = CustomersInGroup.idCustomer
Group by
CustomersGroups.idCustomerGroup, Customers.type
is there a way to show them in a single line , (and show the name of the group?)
This is a "pivot" query. Some databases directly support pivot syntax. In all, you can use conditional aggregation.
Perhaps more importantly, you should learn to use table aliases. These make queries easier to write and to read:
select cg.idCustomerGroup,
sum(case when c.type = 'Type1' then 1 else 0 end) as num_type1,
sum(case when c.type = 'Type2' then 1 else 0 end) as num_type2,
sum(case when c.type = 'Type3' then 1 else 0 end) as num_type3
from CustomersGroups cg inner Join
CustomersInGroup cig
on cg.idCustomerGroup = cig.idCustomerGroup inner Join
Customers c
on c.idCustomer = cig.idCustomer
Group by cg.idCustomerGroup;

Multiple CASE stamenet within SUM() in SQL Server 2008

I have a query where I need to add one more column check withing CASE statement. The check is_contractor = 0. The check means AND with column.
Being new to the use of CASE is not helping. So any help would be appreciated.
SELECT
ft.profit_center_id,
c.region,
SUM(CASE cc.is_nurse_cc WHEN 1 THEN ft.number_of_fte ELSE 0 END) AS sum_number_of_fte_nurse
FROM dbo.Employee AS ft
INNER JOIN dbo.Centers as cc ON ft.cc_id = cc.cc_id
INNER JOIN dbo.Clinic c ON c.id = ft.profit_center_id
GROUP BY
ft.profit_center_id,
c.region,
Use searched version of case expression(https://msdn.microsoft.com/en-us/library/ms181765.aspx):
SUM(CASE WHEN cc.is_nurse_cc = 1 AND is_contractor = 0
THEN ft.number_of_fte ELSE 0 END) AS sum_number_of_fte_nurse

SQL-Using JOINS to get the values from three tables

I have used four tables, namely VehicleMaster, OwnerMaster, CustomerMaster and CustomerVehicle in my project
As name explains, first three are master tables and last table (CustomerVehicle) contains CustomerID & VehicleID. The table hierarchy are as follows
OwnerMaster (OwnerID, OwnerName)
VehicleMaster (VehicleID, OwnerID, VehicleDetails blah blah...)
CustomerMaster (CustomerID, CustomerName..)
CustomerVehicle (CustomerID, VehicleID)
Now i would like to get how many vehicles are running under each owner. output should be something like this.
OwnerName, TotalVehicles, No of Running Vehicles, NonRunning Vehicles.
xxxx, 40, 34, 6
Any help will be much appreciated.
Thanks,
Something like this should work; left join the tables and (distinct) count the respective fields;
SELECT om.OwnerName,
COUNT(DISTINCT vm.VehicleID) TotalVehicles,
COUNT(DISTINCT cv.VehicleID) Running,
COUNT(DISTINCT vm.VehicleID)-COUNT(DISTINCT cv.VehicleID) NotRunning
FROM OwnerMaster om
LEFT JOIN VehicleMaster vm ON om.OwnerID = vm.OwnerID
LEFT JOIN CustomerVehicle cv ON vm.VehicleID = cv.VehicleID
GROUP BY om.OwnerID, om.OwnerName
An SQLfiddle to test with.
There can be owners without vehicles, we don't know... something like this should work as well:
SELECT
om.OwnerName,
count(vm.VehicleID) Total,
sum(case when vm.VehicleID is not null
and cv.VehicleID is not null then 1 else 0 end) Runnng,
sum(case when vm.VehicleID is not null
and cv.VehicleID is null then 1 else 0 end) NotRunnng
FROM OwnerMaster om
LEFT JOIN VehicleMaster vm ON om.OwnerID = vm.OwnerID
LEFT JOIN CustomerVehicle cv ON vm.VehicleID = cv.VehicleID
GROUP BY om.OwnerName
Thank you very much Elhana. I have updated my query to get the exact result what i need. Modified query as:
SELECT
om.OwnerName,
count(vm.VehicleID) Total,
sum(case when vm.VehicleID is not null
and cv.VehicleID is not null then 1 else 0 end) Runnng,
sum(case when vm.VehicleID is not null
and cv.VehicleID is null then 1 else 0 end) NotRunnng
FROM OwnerMaster om
LEFT JOIN VehicleMaster vm ON om.OwnerID = vm.OwnerID and vm.isactive = 1
LEFT JOIN (select distinct VehicleID from CustomerVehicle where isactive = 1) cv
ON vm.VehicleID = cv.VehicleID
where om.isactive = 1
GROUP BY om.OwnerName
SQL Fiddle