Each order may include a hot or cold drink or both.
As in the table below.
orderID
ProductID
type
am2
212
cold drink
am2
51004
hot drink
am2
10032
hot drink
I want it to be labeled "both" if an order contains both types of drinks, otherwise, it should be labeled as it is. As in the table below.
How should I write this query in SQL?
orderID
label
am2
both
As already suggested you can do a group by with a case expression, and you could do this in an outer apply, like this DBFiddle example
select o.orderID,
o.ProductID,
case when oo.coldcount > 0 and oo.hotcount > 0 then 'both' else o.type end
from orders o
outer apply ( select o2.orderID,
sum (case when o2.type = 'cold drink' then 1 else 0 end) coldcount,
sum (case when o2.type = 'hot drink' then 1 else 0 end) hotcount
from orders o2
where o2.orderID = o.orderID
group by o2.orderID
) oo
which results in this
orderID
ProductID
(No column name)
am2
212
both
am2
51004
both
am2
10032
both
am3
51004
hot drink
am3
10032
hot drink
I think you can do this with a little bit of conditional aggregation, assuming the type descriptions are as described:
select orderID, case when Min(both) != Max(both) then 'both' else Min(type) end [Label]
from t
cross apply (values(case when type in('hot drink', 'cold drink') then type end))b(both)
group by orderID
order by orderID;
Test Fiddle
Related
I have a table of data which contains user actions such as 'buy' and 'sell' in a column and a separate column which contains the quantity traded for the movement. Is there a way to connect the two columns that would allow me to filter results where, for example, the quantity of the brought good is greater than the quantity of the sold goods.
Example of table
Action
Quantity
Product
Buy
10
abc
Sell
9
abc
short
11
xyz
cover
11
xyz
Thanks in advance.
There might be way more efficient ways to do this, but you can pretty easily join them together as subqueries.
SELECT
product
FROM
(
SELECT * FROM table WHERE action = 'Buy'
) as bought
JOIN
(
SELECT * FROM table WHERE action = 'Sell'
) as sold
ON
bought.quantity > sold.quantity
AND
bought.product = sold.product
Try something like this:
select product.name as product ,
coalesce( bought.quantity , 0 ) as bought ,
coalesce( sold.quantity , 0 ) as sold ,
coalesce( short.quantity , 0 ) as shorted ,
coalesce( covered.quantity , 0 ) as covered
from ( select distinct
t.Product as name
from my_table t
) as product
left join ( select t.Quantity as quantity
from my_table t
where action = 'Buy'
) as bought on bought.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'Sell'
) sold on sold.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'short'
) shorted on shorted.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'cover'
) covered on covered.product = product.name
That should give you a flat result set with 1 row per product, transforming this:
Action
Quantity
Product
Buy
10
abc
Sell
9
abc
short
11
xyz
cover
11
xyz
Into this:
product
bought
sold
shorted
covered
abc
10
9
0
0
xyz
0
0
11
11
Once you have flattened your table, you can filter it however you like with a simple where clause, for instance:
select product.name as product ,
coalesce( bought.quantity , 0 ) as bought ,
coalesce( sold.quantity , 0 ) as sold ,
coalesce( short.quantity , 0 ) as shorted ,
coalesce( covered.quantity , 0 ) as covered
from ( select distinct
t.Product as name
from my_table t
) as product
left join ( select t.Quantity as quantity
from my_table t
where action = 'Buy'
) as bought on bought.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'Sell'
) sold on sold.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'short'
) shorted on shorted.product = product.name
left join ( select t.Quantity as quantity
from my_table t
where action = 'cover'
) covered on covered.product = product.name
where bought.quantity > sold.quantity
OR short.quantity > cover.quantity
Another way to get to [arguably] the same result is via group by (but much depends on the cardinality of the data):
select t.product as product,
sum(case t.action when 'Buy' then t.Quantity else 0 end) as bought,
sum(case t.action when 'Sold' then t.Quantity else 0 end) as sold,
sum(case t.action when 'short' then t.Quantity else 0 end) as shorted,
sum(case t.action when 'cover' then t.Quantity else 0 end) as covered
from my_table t
group by t.product
having sum(case t.action when 'Buy' then t.Quantity else 0 end)
> sum(case t.action when 'Sold' then t.Quantity else 0 end)
OR sum(case t.action when 'short' then t.Quantity else 0 end)
> sum(case t.action when 'cover' then t.Quantity else 0 end)
You'll note that with the group by, the filter moves from the where clause to the having clause. That's because the conceptual order of operations for a select statement is
Compute the full, cartesian join of all tables in the from clause.
Apply the join criteria to filter the results set.
Filter that by the criteria in the where clause.
If a group by clause exists, reduce the results set to to one row for each distinct combination of values in the grouping columns and compute the value of any aggregate functions.
Filter that summary result set by the criteria in the having clause.
Finally, if an order by clause exists, order the results set accordingly [without an order by clause, order is not guaranteed, even between 2 subsequent executions of the same exact select statement.]
Some assumptions about your table structure: each product has exactly 4 rows, one for each action, never more or less. You have no time or actionId.
This would get you a view of the data that will be more intuitive for you to work from. If you're still in the design stages, I'd consider changing this table to be more like this anyway, OR update the table to have one row per action (per sale of that product, rather than for all sales of that product).
SELECT
product, action,
buy.quantity AS bought, sell.quantity AS sold,
short.quantity AS shorts, cover.quantity AS covers
FROM
table_name AS buy
JOIN
table_name AS sell ON buy.product = sell.product
JOIN
table_name AS short ON buy.product = short.product
JOIN
table_name AS cover ON buy.product = cover.product
WHERE
buy.action = 'Buy'
AND sell.action = 'Sell'
AND short.action = 'short'
AND cover.action = 'cover';
If you restructure your table this way, or use this as a base query (or encode it as a VIEW), it should be intuitive for you to do the things you ask:
SELECT product
FROM (subquery) AS summary
WHERE bought > sold;
If your table is actually one row per action (as in, one row every time you sell) then you will need to do more work.
I have a table as follows
OrderId Carrier Truck Trailer
10001 ABC TruckA TrailerA
10001 ABC TruckA TrailerA
10001 ABC TruckB TrailerA
10001 ABC TruckC TrailerB
10001 ABC TruckC TrailerD
The Output of my query should be single row
OrderId Carrier Truck Trailer
10001 ABC NULL NULL
The logic have to be applied as if the each column contains the same value then that value have to be in that particular column otherwise it would be NULL
I tried the below queries, but not getting correct output
SELECT
s.OrderId
,LEAD(s.OrderId,1) OVER (ORDER BY t.Code) AS PreviousOrderId
,c.LegalName AS CarrierName
,LEAD(c.LegalName,1) OVER (ORDER BY t.Code) AS PreviousCarrierName
,t.TruckLicensePlate
,LEAD(t.TruckLicensePlate,1) OVER (ORDER BY t.Code) AS PreviousTruckLicensePlate
,t.TrailerLicensePlate
,LEAD(t.TrailerLicensePlate,1) OVER (ORDER BY t.Code) AS PreviousTrailerLicensePlate
INTO #tempResult
FROM
OrderDetails
SELECT
CASE WHEN t.OrderId = t.PreviousOrderId
THEN t.OrderId ELSE NULL END AS OrderId
,CASE WHEN t.CarrierName = t.PreviousCarrierName
THEN t.CarrierName ELSE NULL END AS CarrierName
,CASE WHEN t.TruckLicensePlate = t.PreviousTruckLicensePlate
THEN t.TruckLicensePlate ELSE NULL END AS TruckLicensePlate
,CASE WHEN t.TrailerLicensePlate = t.PreviousTrailerLicensePlate
THEN t.TrailerLicensePlate ELSE NULL END AS TrailerLicensePlate
from #tempResult t
order by orderid desc
The logic have to be applied as if the each column contains the same value then that value have to be in that particular column otherwise it would be NULL
Use aggregation and conditional logic. I find that comparing min() and max() is a straight-forward approach:
select
orderid,
case when min(carrier) = max(carrier) then min(carrier) end carrier,
case when min(truck) = max(truck) then min(carrier) end truck,
case when min(trailer) = max(trailer) then min(carrier) end trailer
from orderdetails
group by orderid
Seems like you could just use a CASE with COUNT and MAX:
SELECT CASE COUNT(DISTINCT OrderID) WHEN 1 THEN MAX(OrderID) END AS OrderID,
CASE COUNT(DISTINCT Carrier) WHEN 1 THEN MAX(Carrier) END AS Carrier,
CASE COUNT(DISTINCT Truck) WHEN 1 THEN MAX(Truck) END AS Truck,
CASE COUNT(DISTINCT Trailer) WHEN 1 THEN MAX(Trailer) END AS Trailer
FROM dbo.YourTable;
I am looking to get a summary of multiple states from the same column.
select c.brand
sum amount as total
from charges as c
where c.invoive_id is not null
and c.paid = true
group by c.brand
gets me the sum of all completed purchases grouped by brand.
I want to have a separate column in the same query, summed by brand for "c.paid = false"
so I will have:
Brand Total(true) Total(false)
b_one 25 12
b_two 38 16
You seems to have a simple conditional aggregation statement -
SELECT c.brand
,SUM(CASE WHEN c.paid = 'true' THEN amount END) as Total(true)
,SUM(CASE WHEN c.paid = 'false' THEN amount END) as Total(false)
from charges as c
where c.invoive_id is not null
group by c.brand
You don't say which database you are using so I'll assume PostgreSQL. You can usually use a CASE clause to do this. For example:
select
c.brand,
sum(case when c.paid then 1 else 0 end) as total_true,
sum(case when c.paid then 0 else 1 end) as total_false
from charges as c
where c.invoive_id is not null
group by c.brand
In databases that support boolean types, you can often do:
select c.brand,
sum(c.paid) as num_true,
sum(not c.paid) as num_falst
from charges as c
where c.invoive_id is not null
group by c.brand
I have been building up a query today and I have got stuck. I have two unique Ids that identify if and order is Internal or Web. I have been able to split this out so it does the count of how many times they appear but unfortunately it is not providing me with the intended result. From research I have tried creating a Count Distinct Case When statement to provide me with the results.
Please see below where I have broken down what it is doing and how I expect it to be.
Original data looks like:
Company Name Order Date Order Items Orders Value REF
-------------------------------------------------------------------------------
CompanyA 03/01/2019 Item1 Order1 170 INT1
CompanyA 03/01/2019 Item2 Order1 0 INT1
CompanyA 03/01/2019 Item3 Order2 160 WEB2
CompanyA 03/01/2019 Item4 Order2 0 WEB2
How I expect it to be:
Company Name Order Date Order Items Orders Value WEB INT
-----------------------------------------------------------------------------------------
CompanyA 03/01/2019 4 2 330 1 1
What currently comes out
Company Name Order Date Order Items Orders Value WEB INT
-----------------------------------------------------------------------------------------
CompanyA 03/01/2019 4 2 330 2 2
As you can see from my current result it is counting every line even though it is the same reference. Now it is not a hard and fast rule that it is always doubled up. This is why I think I need a Count Distinct Case When. Below is my query I am currently using. This pull from a Progress V10 ODBC that I connect through Excel. Unfortunately I do not have SSMS and Microsoft Query is just useless.
My Current SQL:
SELECT
Company_0.CoaCompanyName
, SopOrder_0.SooOrderDate
, Count(DISTINCT SopOrder_0.SooOrderNumber) AS 'Orders'
, SUM(CASE WHEN SopOrder_0.SooOrderNumber IS NOT NULL THEN 1 ELSE 0 END) AS 'Order Items'
, SUM(SopOrderItem_0.SoiValue) AS 'Order Value'
, SUM(CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN 1 ELSE 0 END) AS 'INT'
, SUM(CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'WEB%' THEN 1 ELSE 0 END) AS 'WEB'
FROM
SBS.PUB.Company Company_0
, SBS.PUB.SopOrder SopOrder_0
, SBS.PUB.SopOrderItem SopOrderItem_0
WHERE
SopOrder_0.SopOrderID = SopOrderItem_0.SopOrderID
AND Company_0.CompanyID = SopOrder_0.CompanyID
AND SopOrder_0.SooOrderDate > '2019-01-01'
GROUP BY
Company_0.CoaCompanyName
, SopOrder_0.SooOrderDate
I have tried using the following line but it errors on me when importing:
, Count(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference ELSE 0 END) AS 'INT'
Just so know the error I get when importing at the moment is syntax error at or about "CASE WHEN sopOrder_0.SooParentOrderRefer" (10713)
Try removing the ELSE:
COUNT(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference END) AS num_int
You don't specify the error, but the problem is probably that the THEN is returning a string and the ELSE a number -- so there is an attempt to convert the string values to a number.
Also, learn to use proper, explicit, standard JOIN syntax. Simple rule: Never use commas in the FROM clause.
count distinct on the SooOrderNumber or the SooParentOrderReference, whichever makes more sense for you.
If you are COUNTing, you need to make NULL the thing that your are not counting. I prefer to include an else in the case because it is more consistent and complete.
, Count(DISTINCT CASE WHEN SopOrder_0.SooParentOrderReference LIKE 'INT%' THEN SopOrder_0.SooParentOrderReference ELSE null END) AS 'INT'
Gordon Linoff is correct regarding the source of your error, i.e. datatype mismatch between the case then value else value end. null removes (should remove) this ambiguity - I'd need to double check.
Editing my earlier answer...
Even though it looks, as you say, like count distinct is not supported in Pervasive PSQL, CTEs are supported. So you can do something like...
This is what you are trying to do but it is not supported...
with
dups as
(
select 1 as id, 'A' as col1 union all select 1, 'A' union all select 1, 'B' union all select 2, 'B'
)
select id
,count(distinct col1) as col_count
from dups
group by id;
Stick another CTE in the query to de-duplicate the data first. Then count as normal. That should work...
with
dups as
(
select 1 as id, 'A' as col1 union all select 1, 'A' union all select 1, 'B' union all select 2, 'B'
)
,de_dup as
(
select id
,col1
from dups
group by id
,col1
)
select id
,count(col1) as col_count
from de_dup
group by id;
These 2 versions should give the same result set.
There is always a way!!
I cannot explain the error you are getting. You are mistakenly using single quotes for alias names, but I don't actually think this is causing the error.
Anyway, I suggest you aggregate your order items per order first and only join then:
SELECT
c.coacompanyname
, so.sooorderdate
, COUNT(*) AS orders
, SUM(soi.itemcount) AS order_items
, SUM(soi.ordervalue) AS order_value
, COUNT(CASE WHEN so.sooparentorderreference LIKE 'INT%' THEN 1 END) AS int
, COUNT(CASE WHEN so.sooparentorderreference LIKE 'WEB%' THEN 1 END) AS web
FROM sbs.pub.company c
JOIN sbs.pub.soporder so ON so.companyid = c.companyid
JOIN
(
SELECT soporderid, COUNT(*) AS itemcount, SUM(soivalue) AS ordervalue
FROM sbs.pub.soporderitem
GROUP BY soporderid
) soi ON soi.soporderid = so.soporderid
GROUP BY c.coacompanyname, so.sooorderdate
ORDER BY c.coacompanyname, so.sooorderdate;
I've read dozens of answers regarding CASE and am not sure that is what i need to be using here, it seems like it should work but its not:
Data:
OrderNum OrderLine PartNum
200011 1 ABC-1
200011 2 DEF-1
200012 1 XYZ-1
What I would like to return:
OrderNum Item#
200011 MIXED
200012 XYZ-1
What I am returning instead:
OrderNum Item#
200011 ABC-1
200011 MIXED
200012 XYZ-1
My query:
SELECT OrderHed.OrderNum,
(CASE WHEN ShipDtl.OrderLine > '1' then 'MIXED' else ShipDtl.PartNum end) as [Item#]
FROM dbo.OrderHed, dbo.ShipDtl
WHERE ShipDtl.Company = OrderHed.Company
AND ShipDtl.OrderNum = OrderHed.OrderNum
GROUP BY OrderHed.OrderNum, ShipDtl.OrderLine, ShipDtl.Part
Try with grouping like
SELECT OrderHed.OrderNum,
(CASE WHEN SUM(ShipDtl.OrderLine) > 1 then 'MIXED' else MAX(ShipDtl.PartNum) end) as [Item#]
FROM dbo.OrderHed, dbo.ShipDtl
WHERE ShipDtl.Company = OrderHed.Company
AND ShipDtl.OrderNum = OrderHed.OrderNum
GROUP BY OrderHed.OrderNum
SQLFiddle Demo : http://sqlfiddle.com/#!3/209d8/1
You didn't write which database engine you use, but if it is SQL 2005 and above, I'd think using the window function for COUNT will make things easier as you then do not need to group.
SELECT DISTINCT
OrderHed.OrderNum ,
CASE
WHEN COUNT(ShipDtl.OrderLine) OVER (PARTITION BY ShipDtl.OrderNum) > 1 THEN 'MIXED'
ELSE PartNum
END AS [Item#]
FROM dbo.OrderHed ,
dbo.ShipDtl
WHERE ShipDtl.Company = OrderHed.Company
AND ShipDtl.OrderNum = OrderHed.OrderNum
You'll need to DISTINCT though, because it'll select a row per line, but each order with multiple lines will be MIXED, so you can easily distinct.
This will simply select the OrderNum and if multiple orderlines exists per ordernum Count(xxx) OVER (partition by yyy) it'll select 'MIXED', otherwise the partnum.
And then distinct the result.
There is no compulsion to use CASE (is there?).
In your example, CASE is being used to perform logical OR. There are other ways of performing logical OR in SQL e.g. UNION:
WITH T
AS
(
SELECT OrderHed.OrderNum, ShipDtl.PartNum, ShipDtl.OrderLine
FROM dbo.OrderHed, dbo.ShipDtl
WHERE ShipDtl.Company = OrderHed.Company
AND ShipDtl.OrderNum = OrderHed.OrderNum
)
SELECT OrderNum, PartNum
FROM T
WHERE OrderLine = 1
AND OrderNum NOT IN (
SELECT OrderNum
FROM T T2
WHERE OrderLine > 1
)
UNION
SELECT OrderNum, 'MIXED' AS PartNum
FROM T
WHERE OrderLine > 1;