Case Statement On Two Conditions - sql-server

Can i add these both together to work as one, the user can select valid for country 1 or 2 and BOTH so i need these to work together
if possible
and when user selects both i need it to show both measurements
I CANNOT CREATE A NEW TABLE so i need essentially column that will populate based on what country and unit price base will be sufficient
,CASE
WHEN bd.Validforcountry = 1 and BD.UnitpriceBase = 1
THEN '100 ml'
WHEN bd.Validforcountry = 1 and BD.UnitpriceBase = 2
THEN '1 l'
WHEN bd.Validforcountry = 1 and BD.UnitpriceBase = 3
THEN '100 g'
WHEN bd.Validforcountry = 1 and BD.UnitpriceBase = 4
THEN '1 Kg'
WHEN bd.Validforcountry = 1 and BD.UnitpriceBase = 5
THEN '750 ml'
ELSE BD.UnitpriceBase
END AS 'Unit price Declaration'
,CASE
WHEN bd.Validforcountry = 2 and BD.UnitpriceBase = 1
THEN '100 ml'
WHEN bd.Validforcountry = 2 and BD.UnitpriceBase = 2
THEN '1 l'
WHEN bd.Validforcountry = 2 and BD.UnitpriceBase = 3
THEN '100 g'
WHEN bd.Validforcountry = 2 and BD.UnitpriceBase = 4
THEN '1 Kg'
WHEN bd.Validforcountry = 2 and BD.UnitpriceBase = 5
THEN '750 ml'
ELSE BD.UnitpriceBase
END AS 'Unit price Declaration'

Instead of creating complex CASE WHEN, just create a new table, so you can select the correct value with a simple JOIN. Something like this:
CREATE TABLE UnitPriceDeclaration
(
Country int,
UnitPriceBase int,
UnitPriceDeclaration varchar(20),
PRIMARY KEY (Country, UnitPriceBase)
);
This way, you can write your query like this (dummy joins, I don't know anything about your other tables):
SELECT t1.someField,
t2.someOtherField,
t1.UnitPriceBase,
t2.Country,
upd.UnitPriceDeclaration
FROM oneTable t1,
INNER JOIN anotherTable t2 ON ...
INNER JOIN UnitPriceDeclaration upd ON upd.Country = t2.Country AND upd.UnitPriceBase = t1.UnitPriceBase -- maybe LEFT JOIN
WHERE...
This allows you to keep flexibility and add/change as many countries, UPB or UPD as you want, without anything to change other than data. Using CASE WHEN as you do, you would have to change your code each time something changes (potentially in more than one place).
Your edit advocates for this solution. See all those repeated values ?
So let's say you want to display two UPD, one for UK and one for IRE. You just have to do something like this:
SELECT t1.someField,
t2.someOtherField,
t1.UnitPriceBase,
t2.Country,
upduk.UnitPriceDeclaration as UPD_UK,
updire.UnitPriceDeclaration as UPD_IRE
FROM oneTable t1,
INNER JOIN UnitPriceDeclaration upduk ON upd.UnitPriceBase = t1.UnitPriceBase AND upd.Country = 1
INNER JOIN UnitPriceDeclaration updire ON upd.UnitPriceBase = t1.UnitPriceBase AND upd.Country = 2
WHERE...

Related

alias in case when sqlserver

I wrote a trigger to update status to Order from OrderDetails status:
BEGIN
/*
Order Status:
Pending = 0,
Processing = 1,
Proceeded = 2,
Completed = 3,
Cancelled = 4,
Order Detail Status:
Pending = 0,
Processing = 1,
Proceeded = 2,
Emailed = 3,
Ordered = 4,
Cancelled = 5
*/
IF (UPDATE([Status]))
BEGIN
UPDATE [Order]
SET [Status] =
CASE (SELECT MIN(od.[Status]) FROM OrderDetail od WHERE od.OrderId = i.OrderId)
WHEN 5 THEN 4
WHEN 4 THEN 3
WHEN 3 THEN 2
WHEN 2 THEN 2
WHEN 1 THEN 1
WHEN 0 THEN 0
END
FROM INSERTED i
WHERE [Order].Id = i.OrderId
END
END
You can see, WHEN OrderDetailStatus = 2 OR 3 THEN OrderStatus = 2, otherwise OrderStatus = OrderDetailStatus. For now, I have to list the values of status.
So, is it possible to create a alias for value statement, like this:
SET [Status] =
CASE (SELECT MIN(od.[Status]) FROM OrderDetail od WHERE od.OrderId = i.OrderId) AS val
WHEN 3 THEN 2
ELSE val
END
FROM INSERTED i
WHERE [Order].Id = i.OrderId
The only way I can think to "shorten" this would be:
UPDATE O
SET [Status] = CASE WHEN od.[Status] BETWEEN 3 AND 5 THEN od.[Status] - 1 ELSE od.[Status] END
FROM [Order] O
JOIN INSERTED i ON O.Id = i.OrderId
CROSS APPLY (SELECT MIN(od.[Status]) AS [Status]
FROM OrderDetail ca
WHERE ca.OrderId = i.OrderId) od;
On a different note, you should really avoid using reserved (or even key) words for object names. ORDER is a reserved word in SQL Server, so should really not be used. Status is a key word, so can be, but should also be avoided.

SQL query to bring back all orders only if all order details are in a list

I have an orders table that contains a lot of order specific info that is irrelevant to the question. However, I then have an orderDetails table that has a foreign key (orders.id == orderDetails.orderId). In this orderDetails, a customer can select to order lots of flavors of a product, each flavor gets a new entry in this table linked back to the main order.
What I want to do is select all the orders where ALL the flavors are present in the order. So, if an order has apple, peach and orange and I query for apple and peach, it wouldn't return that order because orange wasn't in my query.
I have tried subqueries and so on, but I feel like the only way to solve it is with looping each order and looking at the details, but that is horribly inefficient. Any thoughts?
SELECT *
FROM orders
WHERE id IN (SELECT orderId
FROM orderdetails
WHERE flavor IN('apple', 'peach', 'orange'))
AND isInvoiced = 1
AND isShipped = 0
AND isOnHold = 0
So, if I don't have any peach in stock, I want to see orders that do not contain any peach:
SELECT *
FROM orders
WHERE id IN (SELECT orderId
FROM orderdetails
WHERE flavor IN ('apple', 'orange'))
AND isInvoiced = 1
AND isShipped = 0
AND isOnHold = 0
The problem with the existing query here is that it returns everything because it just says, sure, you asked for apple... sure you asked for orange and this order contains those so I will return it. I need it to be ALL or nothing.
In the real database, the flavors are ID's, I just simplified it for this example.
Database tables were requested... I'll go ahead and list them as they really exist.
orders
-------
id
isInvoiced
isShipped
isOnHold
orderDetails
------------
id
orderId
flavorId
One more edit, this is my original failed attempt:
select * from orders WHERE id in
(
select orderId from orderdetails where flavorId in
(
'616a6d8e-be2e-4740-820b-1cad2a3d89b5',
'5d02f25b-f717-4079-97af-8aa444fe26b1',
'3504be8b-bebe-4b69-a22f-724d90003f99',
'c0a5a036-6dbe-417d-afcf-644f5520f2a8',
'29bfdea5-f270-44f0-9f48-245992af8401',
'29e53a21-4fdc-40e7-8bd9-733058a48097',
'60a90505-b9f5-4a60-8444-a35c2477d4a5',
'c9b93e89-98b0-4765-aedf-3a5f9d182c77',
'651ea709-a885-4f12-ad53-3290e8f0b18f',
'c5962375-d4d5-4ec7-82c0-0293475e6204',
'7faeffc0-fa88-4904-a6a9-7201949b23fd',
'24979b0d-7200-4a7d-9271-d26912d1b16d',
'5efeb81a-7642-4484-b8fc-62544bc8bff7'
)
)
and isInvoiced = 1 and isShipped = 0 and isOnHold = 0
That list of ID's would change based on what flavors are actually in stock.
basically you can just GROUP BY flavor with condition HAVING COUNT(*) = 3. So orders with those 3 flavor will be listed
select *
from orders o
where exists
(
select x.flavor
from orderdetails x
where x.orderId = o.id
and x.flavor in ('apple', 'peach', 'orange')
group by x.flavor
having count(*) = 3
)
and isInvoiced = 1
and isShipped = 0
and isOnHold = 0
You can use count function to make sure all flavors are represented.
select o.*
from orders o
inner join
(
select orderId, count(*) as flavorCount
from orderdetails
where flavor in ('apple', 'peach', 'orange')
group by orderId
) as t1
on o.orderId = t1.orderId
and isInvoiced = 1
and isShipped = 0
and isOnHold = 0
and t1.falvourCount = 3;
It would be simpler if you have a list of out-of-stock and in-stock flavors.
So if for example 'peach' is out of stock , and 'apple' and 'orange' are in stock, the following query will produce Orders that have only 'apple' OR 'orange' :
SELECT * FROM orders
WHERE
id IN (SELECT id FROM orderdetails WHERE flavor IN ('apple','orange') ) -- in stock
AND
id NOT IN (SELECT id FROM orderdetails WHERE flavor IN ('peach') ) --out of stock
What do you think ?
The question completely changed via this comment "I realize now I left out one very important part in that each order may have 1 or any number of flavors, not all have to be present."
The following query meets the original requirements: "What I want to do is select all the orders where ALL the flavors are present in the order" & "I need it to be ALL or nothing."
An order might have more than one item referring to a flavor ("apple pie", and "apple cake" for example), so I recommend you use 3 case expressions in a having clause to guard against this, whilst still achieving your objective:
select o.*
from orders as o
inner join (
select orderId
from orderdetails
group by orderId
having sum(case when flavor = 'apple' then 1 else 0 end) > 0
and sum(case when flavor = 'peach' then 1 else 0 end) > 0
and sum(case when flavor = 'orange' then 1 else 0 end) > 0
) as od on o.id = od.orderid
where o.isInvoiced = 1
and o.isShipped = 0
and o.isOnHold = 0
Note that the use of an inner join limits the orders listed to only those that refer to all 3 flavors.
This query is demonstrated here: http://rextester.com/AKMM54555

Get TOP 8 PERCENT then insert into another table

I have a scenario where I need to calculate the top 8 percent of records based on scores attributed to each artist/record as determined by 5 different judges over 3 categories.
To do this I use the following in a stored procedure
SELECT TOP 8 PERCENT
*
FROM
(SELECT
MEM.Id,
EN.artistName, EN.dateAdded, EN.voteStatus,
ES.enterNextRound, ES.notified, ES.voted,
GR.genre,
ES.entrantId AS bandID,
ES.rnd2Feedback AS feedback, ES.compositionVote,
ES.vocalsVote, ES.originalityVote,
(SELECT COUNT(Voted)
FROM recEntrantStatus
WHERE voted = 1
AND roundId = 2
AND entrantId = ES.entrantId) CountVoted,
(SELECT (COUNT(Voted)/*-1*/)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId) CountTotalVotes,
(SELECT COUNT(Id)
FROM recMembers) TotalJudges,
(SELECT coalesce(SUM(compositionVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalComposition,
(SELECT coalesce(SUM(vocalsVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalVocals,
(SELECT coalesce(SUM(originalityVote),0)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId
AND voted = 1) SumTotalOrig,
(SELECT SUM(compositionVote + vocalsVote + originalityVote)
FROM recEntrantStatus
WHERE roundId = 2
AND entrantId = ES.entrantId) TotalVoteScore
FROM
recMembers AS MEM
LEFT JOIN
recEntrantStatus AS ES ON MEM.Id = ES.judgeId
LEFT JOIN
recEntrants AS EN ON ES.entrantId = EN.Id
LEFT JOIN
recGenre AS GR ON EN.genreId = GR.Id
WHERE
MEM.Id = 4
AND ES.roundId = #input) q
ORDER BY
TotalVoteScore DESC, compositionVote DESC,
originalityVote DESC, vocalsVote DESC
Now, to prepare the competition for the next round I need to take this result set and create a new record for each row, once for each of the full set of judges(currently approximately 20 in total). So that would be 20 records for each of this top 8% recordset (approximately 12 entrants / records). This therefore should result in approximately 12*20 records being inserted.
In previous rounds I simply selected 'ALL' entrants who had a bit field used as a marker to indicate that they were to progress to the next round. I did this with the following code:
INSERT INTO recEntrantStatus (entrantId, roundId, judgeId, notified, voted, enterNextRound)
SELECT
r.entrantId, (#input + 1), j.judgeId /*Now getting tblJudges Id*/, 0, 0, 0
FROM
recEntrantStatus r
-- Get all of the judges
CROSS JOIN
(SELECT DISTINCT Id AS judgeId
FROM recMembers
WHERE Privilege >= 2) AS j
WHERE
r.roundId = #input
AND r.voted = 1
AND r.enterNextround = 1
So, my problem is essentially how do I combine these two queries so as to take the top 8% from the current round, then add a new record for each of 20 judges for each of this top 8% of entrants?
Unfortunately it's not a simple case of replacing the CROSS JOIN SELECT with the one that retrieves the TOP 8%.
Any suggestions??
Compose your query using CTEs that identify your TOP 8 PERCENT members and all of your judges, then cross join the two so you have the multiple records to insert.
Here is the skeleton of the query, which you can fill in with all of your properties and necessary conditions.
WITH Members AS
(
--note, for a CTE, all columns in the select must have a name (or you must specify the column names above next to the CTE name)
SELECT TOP 8 PERCENT mem.id, entrantId, ... FROM recMembers [mem] ... ORDER BY ...
),
Judges AS
(
SELECT Id [judgeId] FROM recMembers WHERE Privilege >= 2
)
INSERT INTO recEntrantStatus (entrantId, roundId, judgeId, notified, voted, enterNextRound)
SELECT m.entrantId, ...
FROM Members m, Judges j --cross join of your top 8% members and all of your judges
WHERE ...
You can do this in other ways such as with a derived table expression in your example, but I think the CTE approach is more readable.

Sql query to create teams

I need a query to assign teams to a series of users. Data looks like this:
UserId Category Team
1 A null
2 A null
3 B null
4 B null
5 A null
6 B null
8 A null
9 B null
11 B null
Teams should be created by sorting by userid and the first userid becomes the team number and the consecutive A's are part of that team as are the B's that follow. The first A after the Bs starts a new team. There will always be at least one A and one B. So after the update, that data should look like this:
UserId Category Team
1 A 1
2 A 1
3 B 1
4 B 1
5 A 5
6 B 5
8 A 8
9 B 8
11 B 8
EDIT:
Need to add that the user id's will not always increment by 1. I edited the example data to show what I mean. Also, the team ID doesn't strictly have to be the id of the first user, as long as they end up grouped properly. For example, users 1 - 4 could all be on team '1', users 5 and 6 on team '2' and users 8,9 and 11 on team '3'
First you could label each row with an increasing number. Then you can use a left join to find the previous user. If the previous user has category 'B', and the current one category 'A', that means the start of a new team. The team number is then the last UserId that started a new team before the current UserId.
Using SQL Server 2008 syntax:
; with numbered as
(
select row_number() over (order by UserId) rn
, *
from Table1
)
, changes as
(
select cur.UserId
, case
when prev.Category = 'B' and cur.Category = 'A' then cur.UserId
when prev.Category is null then cur.UserId
end as Team
from numbered cur
left join
numbered prev
on cur.rn = prev.rn + 1
)
update t1
set Team = team.Team
from Table1 t1
outer apply
(
select top 1 c.Team
from changes c
where c.UserId <= t1.UserId
and c.Team is not null
order by
c.UserId desc
) as team;
Example at SQL Fiddle.
You can do this with a recursive CTE:
with userCTE as
(
select UserId
, Category
, Team = UserId
from users where UserId = 1
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
from userCTE
inner join users on users.UserId = userCTE.UserId + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
You can update the CTE to get this to go:
with userOrder as
(
select *
, userRank = row_number() over (order by userId)
from users
)
, userCTE as
(
select UserId
, Category
, Team = UserId
, userRank
from userOrder where UserId = (select min(UserId) from users)
union all
select users.UserId
, users.Category
, Team = case when users.Category = 'A' and userCTE.Category = 'B' then users.UserId else userCTE.Team end
, users.userRank
from userCTE
inner join userOrder users on users.userRank = userCTE.userRank + 1
)
update users
set Team = userCTE.Team
from users
inner join userCTE on users.UserId = userCTE.UserId
option (maxrecursion 0)
SQL Fiddle demo.
Edit:
For larger datasets you'll need to add the maxrecursion query hint; I've edited the previous queries to show this. From Books Online:
Specifies the maximum number of recursions allowed for this query.
number is a nonnegative integer between 0 and 32767. When 0 is
specified, no limit is applied.
In this case I've set it to 0, i.e. not limit on recursion.
Query Hints.
I actually ended up going with the following. It finished on all 3 million+ rows in a half an hour.
declare #userid int
declare #team int
declare #category char(1)
declare #lastcategory char(1)
set #userid = 1
set #lastcategory='B'
set #team=0
while #userid is not null
begin
select #category = category from users where userid = #userid
if #category = 'A' and #lastcategory = 'B'
begin
set #team = #userid
end
update users set team = #team where userid = #userid
set #lastcategory = #category
select #userid = MIN(userid) from users where userid > #userid
End

TSQL while loop

UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(code) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
The above code gets all users for every house (Houses table). SUMs the code column (Users table) for all users. And finally updates the result in lstatus column of the houses table.
My question is:
I need to rewrite the query which is NOT to sum the code column. Rather I want to create case statements. for example:
tempvar = 0 //local variable might be required
For each user {
If code == 1 then tempvar += 5
else if code == 2 then tempvar += 10
etc
tempvar = 0;
}
Once we have looped through all the users for each house we can now set lStatus = tempvar.
The tempvar should then be reset to 0 for the next house.
You should try to avoid loops and other procedural constructs when coding SQL. A relational database can't easily optimize such things and they often perform orders of magnitude slower than their declarative counterparts. In this case, it seems simple enough to replace your SUM(code) with the CASE statement that you describe:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(CASE code WHEN 1 THEN 5 WHEN 2 THEN 10 ELSE 0 END) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
In this way, SUM can still handle the duty that you imagine your temp variable would be doing.
Also, if you have many cases, you might think about putting those in a table and simply joining on that to get your sum. This might be better to maintain. I'm using a table variable here, but it could look like the following:
DECLARE #codes TABLE (
code INT NOT NULL PRIMARY KEY,
value INT NOT NULL
)
INSERT INTO #codes SELECT 1, 5
INSERT INTO #codes SELECT 2, 10
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT a.ref, SUM(b.value) AS codesum
FROM Users a
JOIN #codes b on a.code = b.code -- Here we get the values dynamically
GROUP BY a.ref) AS U ON U.ref = H.ref
Try this:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
END
) AS codesum
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref
Try this :
UPDATE Houses
SET lStatus = U.code
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
ELSE 0
END
) AS code
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref

Resources