How to create a case statement which groups fields? - sql

I am trying to understand how to group values together to add an indicator. I want to 'fix' the values and based on this, attribute an indicator.
The values I am trying to group are date, customer name and product type to create an indicator which captures what kind of order was placed (fruit only, fruit and vegetable, vegetable only). The goal is to calculate the total volume of each kind of order placed. The data is set out like this, and the column I am trying to create is the 'Order Type.
What I have done so far:
I originally completed this analysis in Tableau ]where I was able to use the 'Fixed' function and sum the value of indicators (for fruit or veggie) to determine each order type individually.
I have written case statements to identify the product type, with the idea that I could sum this to determine order type (code below) however this did not work as I only need one instance of the indicator for each order. To solve this, I have written a case statement which partitions the fields and orders by date to get one instance of an indicator for each order.
Case Statements
CASE WHEN Product_Type = 'Fruit' THEN 1 ELSE 0 END AS Fruit_Indicator
, CASE WHEN Product_Type = 'Vegetable' THEN 1 ELSE 0 END AS Veg_Indicator
Case Statement with partition by and order by
, CASE WHEN ROW_NUMBER() OVER (PARTITION BY Order_Date, Customer ORDER BY Order_Date ASC) = 1 AND Product_Type = 'Fruit' THEN 1 ELSE NULL END AS Fruit_Ind
, CASE WHEN ROW_NUMBER() OVER (PARTITION BY Order_Date, Customer ORDER BY Order_Date ASC) = 1 AND Product_Type = 'Vegetable' THEN 1 ELSE NULL END AS Veg_Ind
I would appreciate any guidance on the right direction.
Thanks!

It APPEARS you are trying to get data grouped by date such as Mar 21, Mar 22, etc... So, you may want to have a secondary query to join the primary data from. The second query will be an aggregate by customer and date. If the date field is date/time oriented, you will have to adjust the group by to get proper formatted context such as date-format using month/day/year and ignoring any time component. This might also be handled by a function to just get the date-part and ignoring the time. Then, your original data to the aggregate should get you what you need. Maybe something like.
select
yt.date,
yt.customer,
yt.product,
yt.productType,
case when PreQuery.IsFruit > 0 and PreQuery.IsVegetable > 0
then 'Fruit & Vegetable'
when PreQuery.IsFruit > 0 and PreQuery.IsVegetable = 0
then 'Fruit Only'
when PreQuery.IsFruit = 0 and PreQuery.IsVegetable > 0
then 'Vegetable Only' end OrderType
from
YourTable yt
JOIN
( select
yt2.customer,
yt2.date,
max( case when yt2.ProductType = 'Fruit'
then 1 else 0 end ) IsFruit,
max( case when yt2.ProductType = 'Vegetable'
then 1 else 0 end ) IsVegetable
from
YourTable yt2
-- if you want to restrict time period, add a where
-- clause here on the date range as to not query entire table
group by
yt2.customer,
yt2.date ) PreQuery
ON yt.customer = PreQuery.customer
AND yt.date = PreQuery.date
-- same here for your outer query to limit just date range in question.
-- if you want to restrict time period, add a where
-- clause here on the date range as to not query entire table
order by
yt.date,
yt.customer,
yt.product

Related

How do I take the values of one record in a table and split it into multiple columns?

I have two tables which contain the following fields I need to use:
Master Data: Master_ID (PK)
Item Data: Crate_ID, Master_ID (FK), Item_Type_ID, Item_Type_Description, Item_Date
The Item_Type_ID has several different numerical values, i.e. 10, 20, 30, 40, 50 ... 100 ... etc.
Each numerical value represents a type, i.e. Veggie, Fruit, Grains, Meat, etc.
The Item_Type_Description are things like: Fruit, Veggies, Grains, Meat, etc.
The Item_Date is a single date that identifies when that particular item (based upon Item_ID) was added to the Crate.
Note that there can only ever be one unique Item_Type_ID per Master_ID. Meaning, Item_Type_ID '10' can only ever be related to Master_ID '1234' once. An Item_Type_ID can be related to many different Master_IDs, but each of those Master_IDs, it can only be related once.
The issue I am having is that I can get the combined results, but for each Item_Type_ID, a distinct record/row is being created.
Here is the code I have generated thus far, which is giving me the incorrect Results:
USE Shipping
GO
BEGIN
SELECT
vmi.master_id
,CASE
WHEN vid.item_type_id = 10 THEN vid_item_date
ELSE NULL
END as 'Fruit_Item_Date'
,CASE
WHEN vid.item_type_id = 20 THEN vid_item_date
ELSE NULL
END as 'Veggie_Item_Date'
,CASE
WHEN vid.item_type_id = 30 THEN vid_item_date
ELSE NULL
END as 'Grains_Item_Date'
,CASE
WHEN vid.item_type_id = 40 THEN vid_item_date
ELSE NULL
END as 'Meat_Item_Date'
FROM v_master_data vmi
LEFT JOIN v_item_data vid ON vmi.master_id = vid.master_id
WHERE vid.item_type_id IN (10,20,30,40)
END
GO
Any input, pointers, assistance, direction, advice, is greatly appreciated.
Running SQL Server 2016, accessed via SQL Server Management Studio v18.
Perhaps this will give you a little nudge
PIVOT
Select Master_ID
,Fruit_Date = [10]
,Veggie_Date = [20]
,Grains_Date = [30]
,Meat_Date = [40]
From (
Select Master_ID
,Item_Type_ID
,Item_Date
From YourTable
) src
Pivot ( max(Item_Date) for Item_Type_ID in ( [10],[20],[30],[40] ) ) pvt
Conditional Aggregation
Select Master_ID
,Fruit_Date = max( case when Iten_Type_ID =10 then Item_Date end)
,Veggie_Date = max( case when Iten_Type_ID =20 then Item_Date end)
,Grains_Date = max( case when Iten_Type_ID =30 then Item_Date end)
,Meat_Date = max( case when Iten_Type_ID =40 then Item_Date end)
From YourTable
Group By Master_ID
A conditional aggregation offers a bit more flexibility and is often more performant.

Updating a value in a Select based on a Greater Than clause

I'm attempting to make a SQL report that would identify the current on hand quantity. The value listed in the select would display the given value in the table unless a rule is applied.
Essentially, I need assistance on how to write the IF statement.
SELECT itemno
,QOHQTY
,SAFETYSTOCKQTY
FROM EDI846OutboundDetail WITH (NOLOCK)
WHERE PARTNERID = '987654321'
AND itemno = '123465'`
This select would return
itemno QOHQTY SAFETYSTOCKQTY
123465 3.00000000 12.00000000
However, since SAFETYSTOCKQTY is greater than 3, then the select should return a ZERO in QOHQTY. If it is not, then it should continue to display 3 in QOHQTY.
I've been looking to use both an IF statement in a temp table, but I was trying to avoid that. Likewise, I discovered a WHERE CASE statement that may work, such as this example below.
SELECT '1'
FROM EDI846OutboundDetail WITH (NOLOCK)
WHERE CASE WHEN SAFETYSTOCKQTY > 1 THEN '1'
END
and PARTNERID = '987654321'
AND itemno = '123465'
However, I was having troubles with how to format that code.
since SAFETYSTOCKQTY is greater than 3, then the select should return a ZERO in QOHQTY. If it is not, then it should continue to display 3 in QOHQTY.
Use CASE:
SELECT
itemno,
CASE WHEN safetystockqty > 3 THEN 0 ELSE qohqty END qohqty,
safetystockqty
FROM EDI846OutboundDetail WITH (NOLOCK)
WHERE partnerid = '987654321' AND itemno = '123465'`
Or maybe, as commented by averey_larry, you want to compare safetystockqty against the value of qohqty? Then:
SELECT
itemno,
CASE WHEN safetystockqty > qohqty THEN 0 ELSE qohqty END qohqty,
safetystockqty
FROM EDI846OutboundDetail WITH (NOLOCK)
WHERE partnerid = '987654321' AND itemno = '123465'`

reward points and allocate those points in the original table's field

I have a table here :
I want to reward a gold and a silver(i.e a value of 1 wherever applicable,else 0 )to top 2 persons by looking at pointsRewarded field.
I already have the first table created .I have modified it with the extra fields that I want i.e rank,gold,silver fields.It has null values now.
i want the output to be something like this:
I have tried some query :
update dbo.original
set rnk = dense_rank() over (partition by WeekNumber order by pointsrewarded desc)
set gold =
case when rnk = '1' then 1 else 0 end
set silver =
case when rnk = '2' then 1 else 0 end
I already have modified the table design by adding the rnk,gold and silver fields.I want the values generated by the query to go and sit in those fields.
Please help me with the query or give me some suggestions on how to proceed.Please.
Thanks a lot.
This seems like a follow-up to your earlier question.
In SQL Server you can use an updatable CTE:
with toupdate as (
select o.*,
dense_rank() over (partition by WeekNumber order by pointsrewarded desc) as new_rnk
from dbo.original
)
update toupdate
set rnk = new_rank,
gold = (case when rnk = 1 then 1 else 0 end),
silver = (case when rnk = 2 then 1 else 0 end);
Note: Only use single quotes for string and date constants. Do not use single quotes for numeric constants.

Constructing A Query In BigQuery With CASE Statements

So I'm trying to construct a query in BigQuery that I'm struggling with for a final part.
As of now I have:
SELECT
UNIQUE(Name) as SubscriptionName,
ID,
Interval,
COUNT(mantaSubscriptionIdmetadata) AS SubsPurchased,
SUM(RevenueGenerated) as RevenueGenerated
FROM (
SELECT
mantaSubscriptionIdmetadata,
planIdmetadata,
INTEGER(Amount) as RevenueGenerated
FROM
[sample_internal_data.charge0209]
WHERE
revenueSourcemetadata = 'new'
AND
Status = 'Paid'
GROUP BY
mantaSubscriptionIdmetadata,
planIdmetadata,
RevenueGenerated
)a
JOIN (
SELECT
id,
Name,
Interval
FROM
[sample_internal_data.subplans]
WHERE
id in ('150017','150030','150033','150019')
GROUP BY
id,
Name,
Interval )b
ON
a.planIdmetadata = b.id
GROUP BY
ID,
Interval,
Name
ORDER BY
Interval ASC
The resulting query looks like this
Which is exactly what I'm looking for up to that point.
Now what I'm stuck on this. There is another column I need to add called SalesRepName. The resulting field will either be null or not null. If its null it means it was sold online. If its not null, it means it was sold via telephone. What I want to do is create two additional columns where it says how many were sold via telesales and via online. The sum total of the two columns will always equal the SubsPurchased total.
Can anyone help?
You can include case statements within aggregate functions. Here you could choose sum(case when SalesRepName is null then 1 else 0 end) as online and sum(case when SalesRepName is not null then 1 else 0 end) as telesales.
count(case when SalesRepName is null then 1 end) as online would give the same result. Using sum in these situations is simply my personal preference.
Note that omitting the else clause is equivalent to setting else null, and null isn't counted by count. This can be very useful in combination with exact_count_distinct, which has no equivalent in terms of sum.
Try below:
it assumes your SalesRepName field is in [sample_internal_data.charge0209] table
and then it uses "tiny version" of SUM(CASE ... WHEN ...) which works when you need 0 or 1 as a result to be SUM'ed
SUM(SalesRepName IS NULL) AS onlinesales,
SUM(NOT SalesRepName IS NULL) AS telsales
SELECT
UNIQUE(Name) AS SubscriptionName,
ID,
Interval,
COUNT(mantaSubscriptionIdmetadata) AS SubsPurchased,
SUM(RevenueGenerated) AS RevenueGenerated,
SUM(SalesRepName IS NULL) AS onlinesales,
SUM(NOT SalesRepName IS NULL) AS telesales
FROM (
SELECT SalesRepName, mantaSubscriptionIdmetadata, planIdmetadata, INTEGER(Amount) AS RevenueGenerated
FROM [sample_internal_data.charge0209]
WHERE revenueSourcemetadata = 'new'
AND Status = 'Paid'
GROUP BY mantaSubscriptionIdmetadata, planIdmetadata, RevenueGenerated
)a
JOIN (
SELECT id, Name, Interval
FROM [sample_internal_data.subplans]
WHERE id IN ('150017','150030','150033','150019')
GROUP BY id, Name, Interval
)b
ON a.planIdmetadata = b.id
GROUP BY ID, Interval, Name
ORDER BY Interval ASC

sql query: subtract from results the corresponding USD, YEN value which has Type='r'

I need help with a query. Consider the following table:
I need to select first the sum of each Code from table. I am doing it with simple sum and group by statement. Then I have to subtract the results from each code sum where type='r'
1) Say for first part of query, we will get 2 rows from SUM (one with total USD and one with total YEN)
2) Now I need to subtract from these results the corresponding USD, YEN value which has Type='r'
I have to do it inside SQL and not a stored procedure.
Why not use a WHERE statement to say WHERE Type != 'r' so that those values never even get added to sum in the first place...
SELECT `Code`, SUM(`Amount`) AS `Total`
FROM `Table`
WHERE `Type` != 'r'
GROUP
BY `Code`;
Something like that.
select code, l.amount - r.amount
from
(select code, sum(amount) as amount from my_table group by code) l
left join (select code, sum(amount) as amount from my_table where type = 'r' group by code) r
on l.code = r.code
You can do this in a single, simple query:
select
code,
sum(case when type = 'r' then (-1 * amount) else amount end) as sum
from
yourtable
group by
code
Basically, you're changing the sign of the rows that have type = 'r', so when you sum all rows for a particular code you'll get the correct answer.
Does it have to be a single query?
I'd say SUM the total, then SUM the subcategory where Type='r', then subtract one from the other.
You could do this in one line of SQL, but I'm pretty sure it would be either joining the table with itself or using a subquery. Either way, it's doing the same amount of work as the above.
Try:
select code,
sum(amount) gross_total,
sum(case when type = 'r' then amount else 0 end) type_r_total,
sum(case when type != 'r' then amount else 0 end) net_total
from yourtable
group by code;
to see the overall totals, type R only totals and non-type R totals for each currency on one row per currency, in a single pass.