SQL sum of different status within 24 hrs group by hours - sql

I am trying to sum status within 24 hour groups by hours. I have an order, order status and status table.
Order Table:
+---------+-------------------------+
| orderid | orderdate |
+---------+-------------------------+
| 1 | 2015-09-16 00:04:19.100 |
| 2 | 2015-09-16 00:01:19.490 |
| 3 | 2015-09-16 00:02:33.733 |
| 4 | 2015-09-16 00:03:58.800 |
| 5 | 2015-09-16 00:01:16.020 |
| 6 | 2015-09-16 00:01:16.677 |
| 7 | 2015-09-16 00:02:06.920 |
+---------+-------------------------+
Order Status Table:
+---------+----------+
| orderid | statusid |
+---------+----------+
| 1 | 11 |
| 2 | 22 |
| 3 | 22 |
| 4 | 11 |
| 5 | 22 |
| 6 | 33 |
| 7 | 11 |
+---------+----------+
Status Table:
+----------+----------+
| statusid | status |
+----------+----------+
| 11 | PVC |
| 22 | CCC |
| 33 | WWW |
| | |
+----------+----------+
I am try to write SQL that display the count of the status within 24 hours for distinct orderids grouped by hour like below:
+------+-----+-----+-----+
| Hour | PVC | CCC | WWW |
+------+-----+-----+-----+
| 1 | 0 | 2 | 1 |
| 2 | 1 | 1 | 0 |
| 3 | 1 | 0 | 0 |
| 4 | 1 | 0 | 0 |
+------+-----+-----+-----+
This is my SQL so far. I am stuck trying to get the sum of each order status:
SELECT
DATEPART(hour, o.orderdate) AS Hour,
SUM(
CASE (
SELECT stat.status
FROM Status stat, orderstatus os
WHERE stat.status IN ('PVC') AND os.orderid = o.id AND os.statusid = stat.id
)
WHEN 'PVC' THEN 1
ELSE 0
END
) AS PVC,
SUM(
CASE (
SELECT stat.status
FROM Status stat, orderstatus os
WHERE stat.status IN ('WWW') AND os.orderid = o.id AND os.statusid = stat.id
)
WHEN 'CCC' THEN 1
ELSE 0
END
) AS CCC,
SUM(
CASE (
SELECT stat.status
FROM Status stat, orderstatus os
WHERE stat.status IN ('CCC') AND os.orderid = o.id AND os.statusid = stat.id)
WHEN 'WWW' THEN 1
ELSE 0
END
) AS WWW
FROM orders o
WHERE o.orderdate BETWEEN DATEADD(d,-1,CURRENT_TIMESTAMP) AND CURRENT_TIMESTAMP
GROUP BY DATEPART(hour, o.orderdate)
ORDER BY DATEPART(hour, o.orderdate);

Here you go -- I'm ignoring the errors in your data since this will fail if the status table really had duplicate ids like in your example data.
SELECT hour, sum(PVC) as PVC, sum(CCC) as CCC, sum(WWW) as WWW
from (
select datepart(hour,orderdate) as hour,
case when s.status = 'PVC' then 1 else 0 end as PVC,
case when s.status = 'CCC' then 1 else 0 end as CCC,
case when s.status = 'WWW' then 1 else 0 end as WWW
from order o
join orderstatus os on o.orderid = os.orderid
join status s on s.statusid = os.statusid
) sub
group by hour

this should get you closer, then you have to pivot:
SELECT
DATEPART(HOUR,o.orderdate) AS orderDate_hour,
s.status,
COUNT(DISTINCT o.orderid) AS count_orderID
FROM
orders o INNER JOIN
orderstatus os ON
o.orderid = os.orderid INNER JOIN
status s ON
os.statusid = s.statusid
WHERE
o.orderdate >= DATEADD(d,-1,CURRENT_TIMESTAMP)
GROUP BY
DATEPART(HOUR,o.orderdate) , s.status
ORDER BY
DATEPART(HOUR,o.orderdate)
try this for the pivot:
SELECT
*
FROM
(SELECT
DATEPART(HOUR,o.orderdate) AS orderDate_hour,
s.status,
COUNT(DISTINCT o.orderid) AS count_orderID
FROM
orders o INNER JOIN
orderstatus os ON
o.orderid = os.orderid INNER JOIN
status s ON
os.statusid = s.statusid
WHERE
o.orderdate >= DATEADD(d,-1,CURRENT_TIMESTAMP)
GROUP BY
DATEPART(HOUR,o.orderdate) , s.status) s
PIVOT ( MAX(count_orderID) FOR status IN ('pvc','ccc','www')) AS p
ORDER BY
orderDate_hour

Related

SQL joining tables based off latest previous date

Let's say I have two tables for example:
Table 1 - customer order information
x---------x--------x-------------x
cust_id | item | order date |
x---------x--------x-------------x
1 | 100 | 01/01/2020 |
1 | 112 | 03/07/2022 |
2 | 100 | 01/02/2020 |
2 | 168 | 05/03/2022 |
3 | 200 | 15/06/2021 |
----------x--------x-------------x
and Table 2 - customer membership status
x---------x--------x-------------x
cust_id | Status | startdate |
x---------x--------x-------------x
1 | silver | 01/01/2019 |
1 | bronze | 05/12/2019 |
1 | gold | 05/06/2022 |
2 | silver | 24/12/2021 |
----------x--------x-------------x
I want to join the two tables so that I can see what their membership status was at the time of purchase, to produce something like this:
x---------x--------x-------------x----------x
cust_id | item | order date | status |
x---------x--------x-------------x----------x
1 | 100 | 01/01/2020 | bronze |
1 | 112 | 03/07/2022 | gold |
2 | 100 | 01/02/2020 | NULL |
2 | 168 | 05/03/2022 | silver |
3 | 200 | 15/06/2021 | NULL |
----------x--------x-------------x----------x
Tried multiple ways include min/max, >=, group by having etc with no luck. I feel like multiple joins are going to be needed here but I can't figure out - any help would be greatly appreciated.
(also note: dates are in European/au not American format.)
Try the following using LEAD function to define periods limits for each status:
SELECT T.cust_id, T.item, T.orderdate, D.status
FROM order_information T
LEFT JOIN
(
SELECT cust_id, Status, startdate,
LEAD(startdate, 1, GETDATE()) OVER (PARTITION BY cust_id ORDER BY startdate) AS enddate
FROM customer_membership
) D
ON T.cust_id = D.cust_id AND
T.orderdate BETWEEN D.startdate AND D.enddate
See a demo on SQL Server.
SELECT
[cust_id],
[item],
[order date],
[status]
FROM
(
SELECT
t1.[cust_id],
t1.[item],
t1.[order date],
t2.[status],
ROW_NUMBER() OVER (PARTITION BY t1.[cust_id], t1.[item] ORDER BY t2.[startdate] DESC) rn
FROM #t1 t1
LEFT JOIN #t2 t2
ON t1.[cust_id] = t2.[cust_id] AND t1.[order date] >= t2.[startdate]
) a
WHERE rn = 1
SELECT
o.cust_id,
o.item,
o.order_date,
m.status
FROM
customer_order o
LEFT JOIN
customer_membership m
ON o.cust_id = m.cust_id
AND o.order_date > m.start_date
GROUP BY
o.cust_id,
o.item,
o.order_date
HAVING
Count(m.status) = 0
OR m.start_date = Max(m.start_date);

Select highest value from joined table

I need to select a distinct row based on a value from a joined table in SQL Server.
Table Orderlines:
| order_id | product_id|
|------------|------------|
| 1234 | 11 |
| 1234 | 22 |
| 1234 | 33 |
| 1234 | 44 |
| 1234 | 55 |
| 2222 | 66 |
| 2222 | 77 |
Table Products:
| product_id | deliverytime|
|------------|--------------|
| 11 | 2 |
| 22 | 3 |
| 33 | 5 |
| 44 | 2 |
| 55 | 1 |
| 66 | 4 |
| 77 | 1 |
Result I am looking for:
| order_id | product_id| deliverytime|
|------------|------------|--------------|
| 1234 | 33 | 5 |
| 2222 | 66 | 4 |
Thanks in advance
We can RANK by deliverytime DESC in a CTE and then only take RANK 1 which is the highest value.
WITH CTE AS
(SELECT
o.product_id,
o.order_id
p.deliverytime,
RANK() OVER (PARTITION BY order_id
ORDER BY deliverytime DESC) rn
FROM Orderline o
JOIN Products p
ON o.product_id = p.product_id )
SELECT
order_id,
product_id,
deliverytime
FROM CTE
WHERE rn = 1;
ORDER BY order_id
Maybe it should work for you, but if there are two or more products with the same highest value, you'd get more than 1 row per order:
select v.order_id
, p2.product_id
, p2.deliverytime
from (
select o.order_id
, max(p.deliverytime) as max_deliverytime
from Orderlines o
join Products p
on o.product_id = p.product_id
group by o.order_id
) v
join Products p2
on v.max_deliverytime = p2.deliverytime;
it is better to use row_number to get highest delivery_time row. we can also order it based on highest product_id if there is more than 1 highest delivery time
SELECT ol.order_id,
ol.product_id,
p.deliverytime
FROM (
SELECT ol.order_id,
ol.product_id,
p.deliverytime,
row_number() over(partition by ol.order_id
order by p.deliverytime desc, ol.product_id desc) rn
FROM orderline ol
JOIN products p
ON ol.product_id = p.product_id
)RPR
WHERE rn = 1

SQL Server - Find all customers who've placed two types of orders

I'm trying to pull a list of all open orders from a customer where the same customer has used both one of our special payment types as well as one of our standard options. Specifically, those that have open orders with either prepay or 10n30 and at least one normal order. So, in the example tables below I would want to return order_id 1, 3, and 4.
cust_orders order_info
+----------+-----------+ +----------+-------------+----------+
| cust_id | order_id | | order_id | pay_type | status |
+----------+-----------+ +----------+-------------+----------+
| 1 | 1 | | 1 | standard | open |
| 1 | 2 | | 2 | prepay | closed |
| 1 | 3 | | 3 | prepay | open |
| 1 | 4 | | 4 | 10n30 | open |
| 2 | 5 | | 5 | standard | deferred |
| 2 | 6 | | 6 | prepay | open |
| 3 | 7 | | 7 | N/A | deferred |
| 4 | 8 | | 8 | prepay | open |
| 4 | 9 | | 9 | standard | closed |
| 4 | 10 | | 10 | prepay | open |
+----------+-----------+ +----------+-------------+----------+
I have the following query
SELECT *
FROM cust_orders AS co
LEFT JOIN ( SELECT *
FROM order_info
WHERE pay_type IN('prepay', '10n30')
AND status = 'open' ) AS o1 on o1.order_id = co.order_id
LEFT JOIN ( SELECT *
FROM order_info
WHERE pay_type NOT IN('prepay', '10n30')
AND status = 'open' ) AS o2 on o2.order_id = co.order_id
WHERE o1.order_id IS NOT NULL
AND o2.order_id IS NOT NULL
ORDER BY co.order_id DESC;
but it runs very slowly and returns a bunch of duplicates.
I've looked at Search for orders that have two products, one with specific reference, other with specific description and SELECT all orders with more than one item and check all items status but neither seems to be what I need.
EDIT: Thanks to gjvdkamp for the basis to the code below; I modified their solution to use in a larger query and everything runs fine now.
SELECT co.*, [other fields]
FROM cust_order AS co
LEFT JOIN [other tables]
WHERE cust_id IN ( SELECT co.cust_id
FROM cust_order AS co
LEFT JOIN order_info o on o.order_id = co.order_id
WHERE o.status = 'open'
GROUP BY co.cust_id
HAVING SUM(CASE WHEN o.pay_type IN ('prepay', '10n30') THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN (o.pay_type NOT IN ('prepay', '10n30') OR o.pay_type IS NULL) THEN 1 ELSE 0 END) > 0)
A 'handrolled pivot' would work well here:
select cust_id,
sum(case when pay_type = 'normal' then 1 else 0 end) as NormalCount,
sum(case when pay_type in ('prepay', '10n30') then 1 else 0 end) as OtherCount
from cust_order co
inner join order o on co.order_id = o.order_id
where o.status = 'open'
and o.pay_type in ('normal','prepay','10n30')
group by cust_id
having NormalCount> 0 and
OtherCount > 0
This would only require a single join (merge if you have you indexes right) and then aggregegates that. Don't know the statistics on your orders table but added where statement on pay_type for good measure. This would be hard to beat speed wise..
Edit: removed the with statement as it's not even needed
I think some window functions do the trick:
select o.*
from (select o.*,
sum(case when o.pay_type in ('prepay', '10n30') then 1 else 0 end) over (partition by co.cust_id) as num_special,
sum(case when o.pay_type in ('standard') then 1 else 0 end) over (partition by co.cust_id) as num_standard
from cust_orders co join
order_info o
on co.orderid = o.order_id
where o.status = 'open'
) o
where num_standard > 0 and
num_special > 0;

SQL Server : display distinct list of orders, and indicate which orders contain specific products

I have a table that contains a lot of data, but the relevant data in the table looks something like this:
Orders table:
+----------+-----------+---------------+
| OrderID | Product | Date |
+----------+-----------+---------------+
| 1 | Apple | 01/01/2001 |
| 1 | Pear | 01/01/2001 |
| 1 | Pear | 01/01/2001 |
| 1 | Orange | 01/01/2001 |
| 1 | Pineapple | 01/01/2001 |
| 2 | Cherry | 02/02/2002 |
| 2 | Cherry | 02/02/2002 |
| 3 | Orange | 03/03/2003 |
| 3 | Apple | 03/03/2003 |
| 3 | Cherry | 03/03/2003 |
+----------+-----------+---------------+
I'd like a query to return a distinct list of orders, and if the order contains certain products, to indicate as such:
+----------+-----------+--------+-------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+--------+-------+
| 1 |01/01/2001 | X | X |
| 2 |02/02/2002 | | |
| 3 |03/03/2003 | X | |
+----------+-----------+--------+-------+
Here's where I've left off and decided to seek out help:
WITH CTEOrder AS
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS OrderRN
FROM
Orders
)
CTEApple as
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS AppleRN
FROM
Orders
WHERE
Product = 'Apple'
),
CTEPear
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS PearRN
FROM
Orders
WHERE
Product = 'Pear'
)
SELECT
o.OrderID, o.Product, o.Date,
co.OrderRN, a.AppleRN, p.PearRN
FROM
Orders AS o
OUTER JOIN
CTEOrder AS co ON o.OrderID = co.Orderid
OUTER JOIN
CTEApple AS a ON o.OrderID = a.OrderID
OUTER JOIN
CTEPear AS p ON o.OrderID = p.OrderID
WHERE
(co.OrderRN IS NULL AND a.AppleRN IS NULL AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN IS NULL AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN = 1 AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN = 1 AND p.PearRN = 1
OR co.OrderRN = 1 AND a.AppleRN IS NULL AND p.PearRN = 1
OR co.OrderRN IS NULL AND a.AppleRN = 1 AND p.PearRN IS NULL
OR co.OrderRN IS NULL AND a.AppleRN = 1 AND p.PearRN = 1
OR co.OrderRN IS NULL AND a.AppleRN IS NULL AND p.PearRN = 1)
Currently my result set is unwieldy with a significant amount of duplication.
I'm thinking that I am heading in the wrong direction, but I don't know what other tools are available to me within SQL Server to cut up this data the way I need.
Thanks for any guidance!
Here's my result set after Nik Shenoy's guidance:
+----------+-----------+----------------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+----------------+
| 1 | 01/01/2001| x | NULL |
| 1 | 01/01/2001| NULL | x |
| 1 | 01/01/2001| NULL | x |
| 1 | 01/01/2001| NULL | NULL |
| 1 | 01/01/2001| NULL | NULL |
| 2 | 02/02/2002| NULL | NULL |
| 2 | 02/02/2002| NULL | NULL |
| 3 | 03/03/2003| NULL | NULL |
| 3 | 03/03/2003| x | NULL |
| 3 | 03/03/2003| NULL | NULL |
+----------+-----------+----------------+
What is my next step to have only 1 row per Order:
+----------+-----------+--------+-------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+--------+-------+
| 1 |01/01/2001 | X | X |
| 2 |02/02/2002 | | |
| 3 |03/03/2003 | X | |
+----------+-----------+--------+-------+
You can just use conditional aggregation:
select o.orderid, date,
max(case when product = 'Apple' then 'X' end) as IsApple,
max(case when product = 'Pear' then 'X' end) as IsPear
from orders o
group by o.orderid, date;
If you know all the products in advance, you can use the Transact-SQL PIVOT relational operator to cross-tabulate the data by product. If you use MAX or COUNT, you can just transform non-NULL or non-ZERO output to an 'x'
SELECT
PivotData.OrderID
, PivotData.OrderDate
, CASE WHEN PivotData.Apple IS NULL THEN '' ELSE 'X' END AS [Apple?]
, CASE WHEN PivotData.Pear IS NULL THEN '' ELSE 'X' END AS [Pear?]
, CASE WHEN PivotData.Orange IS NULL THEN '' ELSE 'X' END AS [Orange?]
, CASE WHEN PivotData.Pineapple IS NULL THEN '' ELSE 'X' END AS [Pineapple?]
, CASE WHEN PivotData.Cherry IS NULL THEN '' ELSE 'X' END AS [Cherry?]
FROM
(SELECT OrderID, Product, OrderDate) AS [Order]
PIVOT (MAX(Product) FOR Product IN ( [Apple], [Pear], [Orange], [Pineapple], [Cherry] )) AS PivotData

Conditional sum in Group By query MSSQL

I have a table OrderDetails with the following schema:
----------------------------------------------------------------
| OrderId | CopyCost | FullPrice | Price | PriceType |
----------------------------------------------------------------
| 16 | 50 | 100 | 50 | CopyCost |
----------------------------------------------------------------
| 16 | 50 | 100 | 100 | FullPrice |
----------------------------------------------------------------
| 16 | 50 | 100 | 50 | CopyCost |
----------------------------------------------------------------
| 16 | 50 | 100 | 50 | CopyCost |
----------------------------------------------------------------
I need a query that will surmise the above table into a new table with the following schema:
----------------------------------------------------------------
| OrderId | ItemCount | TotalCopyCost | TotalFullPrice |
----------------------------------------------------------------
| 16 | 4 | 150 | 100 |
----------------------------------------------------------------
Currently I am using a Group By on the Order.Id to the the item count. But I do not know how to conditionally surmise the CopyCost and FullPrice values.
Any help would be much appreciated.
Regards
Freddie
Try
SELECT OrderId,
COUNT(*) ItemCount,
SUM(CASE WHEN PriceType = 'CopyCost' THEN Price ELSE 0 END) TotalCopyCost,
SUM(CASE WHEN PriceType = 'FullPrice' THEN Price ELSE 0 END) TotalFullPrice
FROM OrderDetails
GROUP BY OrderId
SQLFiddle
Try this query
select
orderId,
count(*) as cnt,
sum(if(pricetype='CopyCost', CopyCost, 0)) as totalCopyCost,
sum(if(pricetype='FullPrice', FullPrice, 0)) as totalFullPrice
from
tbl
group by
orderId
SQL FIDDLE:
| ORDERID | CNT | TOTALCOPYCOST | TOTALFULLPRICE |
--------------------------------------------------
| 16 | 4 | 150 | 100 |
Could you use:
SELECT
OrderId,
Count(1) as ItemCount,
SUM(CASE WHEN PriceType = 'CopyCost'
THEN CopyCost ELSE 0 END) AS TotalCopyCost,
SUM(CASE WHEN PriceType = 'FullPrice'
THEN FullPrice ELSE 0 END) AS TotalFullPrice
FROM OrderDetails
GROUP BY OrderId
You could also try...
select A.OrderID, A.ItemCount,B.TotalCopyCost, C.TotalFullPrice
from (select OrderID, count(*) as ItemCount from orderdetails) as A,
(select OrderID, sum(CopyCost) as TotalCopyCost from orderdetails where PriceType = 'CopyCost') as B,
(select OrderID, sum(FullPrice) as TotalFullPrice from orderdetails where PriceType = 'FullPrice') as C
where A.OrderID = B.OrderID
SQLFiddle: http://sqlfiddle.com/#!2/946af/6