Alternatives to Multiple Case Statements for Same Column - sql-server-2008

To match up some new schema to old, I'm having to do some ugly contortions that I figure could be done in a better way. For reference, I asked another question about this match-up process here: Creating View from Related Child Tables
I've placed a simplified example in SQLFiddle but the gist of it is, that the only way I can see reconciling these two different schemas is to do two case statements on the same value, something like this:
SELECT
CASE
WHEN n.FooBarStatusId = 1 OR n.FooBarStatusId = 2
THEN 1
ELSE 0
END as [IsFoo],
CASE
WHEN n.FooBarStatusId = 2
THEN 1
ELSE 0
END as [IsBar]
from Parent p
left join OldStuff o on p.ParentId = o.ParentId
left join NewStuff n on p.ParentId = n.ParentId
Is there a better and/or more efficient way of accomplishing the same thing? These case statements could be hit hundreds of times in a given query and I'm concerned about this specific logic.
I've thought about extracting this specific logic out (it is part of a larger query to build a view) into a temp table or perhaps even a table-valued function, but even still I can't come up with a way around using multiple case statements.

Just corrected grammar...
I found another solution:
select
p.Name,
ISNULL(o.IsFoo, CONVERT(BIT, n.FooBarStatusId)) as [IsFoo],
ISNULL(o.IsBar, CONVERT(BIT, n.FooBarStatusId * (n.FooBarStatusId - 1))) as [IsBar],
from Parent p
left join OldStuff o on p.ParentId = o.ParentId
left join NewStuff n on p.ParentId = n.ParentId
The only one arithmetic solution could be slow:
select
p.Name,
ISNULL(o.IsFoo, CAST( (n.FooBarStatusId % 0.35) * 4 AS int)) AS [IsFoo],
ISNULL(o.IsBar, n.FooBarStatusId/2) [IsBar]
from Parent p
left join OldStuff o on p.ParentId = o.ParentId
left join NewStuff n on p.ParentId = n.ParentId
Personally, I do not like to use the division, because of could be involved floating point operations, that way, it would be very slow.

As you have two columns, you will need two expressions... but they might not have to be CASE expressions. Reading your question, I get the impressions that the only possible values in the column are 0,1,2, and that this is an int type? If that's correct, you can use arithmetic rather than boolean logic to get what you need. Try this:
CAST( (n.FooBarStatusId % .35) * 4 AS int) AS [IsFoo],
n.FooBarStatusId/2 [IsBar]

Related

Using Case in WHERE condition MYSQL

Is it possible to use CASE statement in WHERE condition like below?
SELECT act.id_activity FROM activity act
LEFT JOIN work w ON w.id_work = act.id_work
WHERE
w.work_type=1
AND w.work_tender in (1,2)
AND act.id_activity_type IN
(CASE WHEN w.work_tender=1 THEN '2,3' WHEN w.work_tender=2 THEN '2,3,4,9' END)
it returns no error but the results always display act.id_activity_type = 2 instead of 2,3 or 2,3,4,9
In this case 1 work (table work) can have many activities (table activity). i want to display activities based on work.work_tender type. if work.work_tender=1 then need to choose activity.id_activity_type IN (2,3). if work.work_tender=2 then need to choose activity.id_activity_type IN (2,3,4,9)
You can try to write correct logic by OR & AND.
SELECT act.id_activity FROM activity act
LEFT JOIN work w ON w.id_work = act.id_work
WHERE
w.work_type=1
AND (
(act.id_activity_type IN ('2','3') AND w.work_tender=1) OR
(act.id_activity_type IN ('2','3','4','9') AND w.work_tender=2)
)
I think case is used for that case. I think it is possible to use in that case. And also for orderby phrase.
Maybe you can try with this:
SELECT *
FROM activity a
JOIN work w
ON w.work_tender=
CASE WHEN a.id_activity_type IN (2,3) THEN 1
WHEN a.id_activity_type IN (2,3,4,9) THEN 2 END;
I'm using CASE expression here to assign value matching w.work_tender if the a.id_activity_type fit the condition. So, if a.id_activity_type IN (2,3) THEN 1 will match with w.work_tender=1 similarly with a.id_activity_type IN (2,3,4,9) THEN 2 will match with w.work_tender=2.
Demo fiddle

How can I use group functions on a spatial JOIN to update a table in MySQL?

I'm trying to update the total revenue for offices located in different geographies. The geographies are defined by circles and polygons which are both in the shapes.shape column.
When I run the query below, MySQL throws "R_INVALID_GROUP_FUNC_USE: Invalid use of group function"
I tried to adapt this answer, but I can't figure out the logic with the conditional join and geospatial data -- it's not as simple as adding a subquery with a WHERE clause. (Or is it?)
For context, I have about 350 geographies and 150,000 offices.
UPDATE
shapes s
LEFT JOIN offices ON (
CASE
WHEN s.type = 'circle' THEN ST_Distance_Sphere(o.coords, s.shape) < s.radius
ELSE ST_CONTAINS(s.shape, o.coords)
END
)
SET
s.totalRevenue = SUM(o.revenue);
UPDATE:
This works, but it's slow and confusing. Is there a faster/more concise way?
UPDATE
shapes s
LEFT JOIN (
SELECT
t.shape_id,
SUM(g.revenue) revenue
FROM
shapes t
LEFT JOIN offices o ON (
CASE
WHEN t.type = 'circle' THEN ST_Distance_Sphere(o.coords, t.shape) < t.radius
ELSE ST_CONTAINS(t.shape, o.coords)
END
)
GROUP BY
t.shape_id
) b ON s.shape_id = b.shape_id
SET
s.totalRevenue = b.revenue;
I think that speed can be helped by splitting into two UPDATEs:
... WHERE t.type = 'circle'
AND ST_Distance_Sphere ...
and
... WHERE t.type != 'circle'
AND ST_CONCAINS ...
And then see if the resulting SQLs can be simplified.
To further investigate the query, please isolate the subquery b and see if the bulk of the time is in doing that SELECT (as opposed to the time doing the UPDATE).
Please provide SHOW CREATE TABLE for each table and EXPLAIN for both the UPDATE(s) and the isolated SELECT(s). A number of clues might come from such.

How to Join to a table where the result can sometimes lead with a - sign?

Hopefully i can explain this well enough. I have a bit of a unique issue where the customer system we use can change a ID in the database in the background based on the products status.
What this means is when i want to report old products we don't use anymore along side active products there ID differs between the two key tables depending on there status. This means Active products in the product table match that of the stock item table with both showing as 647107376 but when the product is no long active the StockItem table will present as 647107376 but the table that holds the product information the id presents as -647107376
This is proving problematic for me when i comes to joining the tables together to get the information needed. Originally i had my query set up like this:
SELECT
Company_0.CoaCompanyName
,SopProduct_0.SopStiStockItemCode AS hbpref
,SopProduct_0.SopStiCustomerStockCode AS itemref
,SopProduct_0.SopDescription AS ldesc
,StockMovement_0.StmOriginatingEntityID AS Goodsin
FROM
SBS.PUB.StockItem StockItem_0
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON StockItem_0.StockItemID = SopProduct_0.StockItemID
LEFT JOIN SBS.PUB.Company Company_0 ON SopProduct_0.CompanyID = Company_0.CompanyID
LEFT JOIN SBS.PUB.StockMovement StockMovement_0 ON StockItem_0.StockItemID = StockMovement_0.StockItemID
WHERE
Company_0.CoaCompanyName = ?
AND StockMovement_0.MovementTypeID = '173355'
AND StockMovement_0.StmMovementDate >= ? AND StockMovement_0.StmMovementDate <= ?
AND StockMovement_0.StmQty <> 0
AND StockMovement_0.StockTypeID ='12049886'
Unfortunately though what this means is any of the old product will not show because there is no matching id due to the SopProduct table presenting the StockItemID with a leading -
So from this i thought best to use a case when statement with a nested concat and left in it to bring through the results but this doesn't appear to work either sample of the join below:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0 ON (CASE WHEN LEFT(SopProduct_0.StockItemID,1) = "-" THEN CONCAT("-",StockItem_0.StockItemID) ELSE StockItem_0.StockItemID END) = SopProduct_0.StockItemID
Can anyone else think of a way around this issue? I am working with a Progress OpenEdge ODBC.
Numbers look like numbers. If they are, you can use abs():
ON StockItem_0.StockItemID = ABS(SopProduct_0.StockItemID)
Otherwise a relatively simple method is:
ON StockItem_0.StockItemID IN (SopProduct_0.StockItemID, CONCAT('-', SopProduct_0.StockItemID))
Note that non-equality conditions often slow down JOIN operations.
Using an or in the join should work:
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = StockItem_0.StockItemID
OR
SopProduct_0.StockItemID = CONCAT("-", StockItem_0.StockItemID)
You might need to cast the result of the concat to a number (if the ids are stored as numbers).
Or you could use the abs function too (assuming the ids are numbers):
LEFT JOIN SBS.PUB.SopProduct SopProduct_0
ON SopProduct_0.StockItemID = abs(StockItem_0.StockItemID)

SQL - OutterApply and Left Join

I'm working with a large stored procedure, I'm having trouble with a small portion of it.
When I execute a query on the table im joining, there can be 0, 1 or 2 results. If there are 0 results, I don't really care, my code returns null values, no big deal. If there is 1 result, my code returns the correct values, however, if there are 2 results, I am having trouble selecting the second result.
My code below works until the second OutterApply(the AHM2 stuff). Does anyone see what I am doing wrong?
The animal ID is identical for both OuterApplys. I just need to return the second result, if there is one, and if it is not the same as the first one.
SELECT TOP 1
AHM.AnimalHerdManagementId,
AHM.HerdManagementId,
AHM2.AnimalHerdManagementId,
AHM2.HerdManagementId,
HM.Code AS HerdManagementCode,
HM2.Code AS HerdManagementCode2
OUTER APPLY
(
SELECT TOP 1 AHM.AnimalHerdManagementId, AHM.HerdManagementId
FROM dbo.AnimalHerdManagement AHM
WHERE AHM.AnimalId = A.AnimalId AND ISNULL(AHM.EffectiveFrom, #EffectiveFrom) <= #EffectiveFrom
ORDER BY AHM.EffectiveFrom DESC
) AHM
LEFT JOIN dbo.HerdManagement HM ON AHM.HerdManagementId = HM.HerdManagementId
OUTER APPLY
(
SELECT TOP 1 AHM2.AnimalHerdManagementId, AHM2.HerdManagementId
FROM dbo.AnimalHerdManagement AHM2
WHERE AHM2.AnimalId = A.AnimalId AND AHM2.AnimalHerdManagementId != AHM.AnimalHerdManagementId AND ISNULL(AHM2.EffectiveFrom, #EffectiveFrom) <= #EffectiveFrom
ORDER BY AHM2.EffectiveFrom DESC
) AHM2
LEFT JOIN dbo.HerdManagement HM2 ON AHM2.HerdManagementId = HM2.HerdManagementId
I think I can help you with the OUTER APPLY but the method of getting the two different values is going to need some help as my solution is a total hack.
First, you don't need to join on the outer apply. The join is implied. So you can completely eliminate the join syntax from your query.
Second, AnimalHerdManagement looks/seems like a special table called a Junction Table. All the data contained in it is contained elsewhere (That it contains completely redundant data is why it's called a special table). But that is minor.
Finally, here is some example code I threw together that accomplishes what you are after. The method I am using to retrieve different results on the two outer apply's is a hack, but if you are sure that will always be true, it might work. I am not able to get a multi-level outer apply to work.
select * from AH_Animal A
outer apply
(
select max (HerdManagementID) as HerdMgmtID1 from AH_AnimalHerdManagement HM1 where HM1.AnimalID = A.AnimalID
) as z
outer apply
(
select min (HerdManagementID) as HerdMgmtID2 from AH_AnimalHerdManagement HM2 where HM2.AnimalID = A.AnimalID
) as zz
I hope that helped. There has to be another solution to this, as this would not work at all if you ever expected 3 results.
Query Results:

The left joins making query slow,is there any method to increase the speed of this query

select
b.entry_id,
b.assign_id,
a.profile_type,
a.profile_id,
a.profile_name,
a.profile_status,
b.entry_type,
b.assign_id,
c.chapter_name,
d.section_name,
h.group_name,
i.programme_name,
k.subjectprogramme_name,
j.masterprogramme_name,
l.developmentprogramme_name
from profile_master a
left join profile_assign b on (a.profile_id = b.profile_id)
left join chapter_master c
on (b.entry_id = c.chapter_id and b.entry_type='chapter')
left join section_master d
on (b.entry_id = d.section_id and b.entry_type='section')
left join group_master h
on (b.entry_id = h.group_id and b.entry_type='Group'
and h.year_id='".$this->year."')
left join programme_master i
on (b.entry_id = i.programme_id and b.entry_type='Programme'
and i.year_id='".$this->year."')
left join subjectprogramme_master k
on (b.entry_id = k.subjectprogramme_id and b.entry_type='subjectProgramme'
and k.year_id='".$this->year."')
left join masterprogramme_master j
on (b.entry_id = j.masterprogramme_id and b.entry_type='masterProgramme'
and j.year_id='".$this->year."')
left join developmentprogramme_master l
on (b.entry_id = l.developmentprogramme_id
and b.entry_type='developmentProgramme')
1) Get rid of where coditions from left join. Use WHERE clause for filtering
2) I guess UNION or 7 queries (by each entity separetely) will be much better in your case
This is a hard question to answer without having direct access to the database, so I'll try a general answer!
Use "explain" on this query to see if MySQL suggests some indexes. No doubt it'll suggest a few, because you're accessing a few columns several times, and oftentimes indexes will improve even the slowest OUTER JOIN
You're using lots of checks against $this->year, so that would suggest some composite indexes where e.g. the programme_id and the year_id are both in the same index
Of course, there are solutions that might depend on how you're using the output, e.g.:
If this query is run frequently enough to be a problem for users waiting for it, but infrequently enough for latency not to be an issue (e.g. it's ok to run it based on last night's data), you could run it overnight and cache the results.
You really only do a join when a condition is passed, I suggest doing subselects like so:
SELECT
b.entry_id,
b.assign_id,
a.profile_type,
a.profile_id,
a.profile_name,
a.profile_status,
b.entry_type,
b.assign_id,
CASE b.entry_type
WHEN 'chapter' THEN SELECT(c.chapter_name FROM c WHERE b.entry_id = c.chapter_id)
WHEN 'section' THEN SELECT(d.section_name FROM d WHERE b.entry_id = d.section_id)
WHEN ....
END as name
from profile_master a
left join profile_assign b on (a.profile_id = b.profile_id)
If you insist on having the output be the same, then you need to wrap this select in a outer select like so:
SELECT
entry_id, assign_id, ......
, CASE entry_type WHEN 'chapter' THEN name ELSE null END as chapter_name
, CASE entry_type WHEN 'section' THEN name ELSE null END as section_name
FROM
(select statement like above) sub