Count number of sale by order amount - sql-server

I'm using SQL Server 2008 R2 and doing a analysis on a table that contains CustomerID, OrderAmount, RegionID. I need to count number of orders in different categories according to the OrderAmount in each region. And if there is no sales in the category, returns 0.
Sample of data:
CustomerID | OrderAmount | RegionID
10001 | 50 | 801
10002 | 25 | 801
10003 | 200 | 802
10001 | 100 | 802
10002 | 20 | 802
...
And my expected result is:
RegionID | CategoryID | Num_of_Sales
801 | 1 | 2 -----Below 100
801 | 2 | 0 -----100-200
802 | 1 | 2 -----Below 100
802 | 2 | 1 -----100-200
...
My question is:
1. How to return 0 on the category that is empty?
2. Is there a better way to write the code?(Not using UNION)
WITH Category1 AS(
SELECT * FROM Sales_Table
WHERE NewAmount <= 100
)
, Category2 AS(
SELECT * FROM Sales_Table
WHERE NewAmount BETWEEN 101 AND 200
)
, [...]
SELECT Region_ID, CategoryID, Num_of_Sales
FROM (
SELECT Region_ID, COUNT(*) AS [Num_of_Sales], 1 AS CategoryID
FROM Category1
GROUP BY Region_ID
UNION
SELECT Region_ID, COUNT(*) AS [Num_of_Sales], 2 AS CategoryID
FROM Category2
GROUP BY Region_ID
UNION
[...]
)z
ORDER BY Region_ID, CategoryID
So, I use these code and get my result, but the count did not return 0 on the 100-200 Category at Region 801.

A table holding RegionID and CategoryID is needed for what you are trying to achieve. Then we can use that table to do a join as shown below.
With RegCatSales as
(
select RegionID,C,COUNT(*) AS [Num_of_Sales]
from
(
select RegionID,OrderAmount,
CASE
WHEN OrderAmount <= 100 THEN 1
WHEN OrderAmount BETWEEN 101 AND 200 THEN 2
END as C
from Sales_Table x
) xx
group by RegionID, C
),
Regions as
(
select distinct RegionID from RegCatSales
),
Categories as
(
select distinct C from RegCatSales
),
RegCat AS(
select distinct RegionID, C as CategoryID from Regions,Categories
)
select rc.RegionID,rc.CategoryID, ISNULL([Num_of_Sales],0) NUM_Of_Sales from
RegCatSales rcs
right join RegCat rc
on rc.RegionID= rcs.RegionID and rc.CategoryID = rcs.C
order by rc.RegionID, rc.CategoryID

Related

Multiple COUNT(DISTINCT) from CTE

This is for SQL Server 2012: a subset of the data in my CTE looks like this:
Employee | OrderID | OrderType
---------+---------+----------
Kala | 321111 | 953
Paul | 321222 | 1026
Don | 321333 | 1026
Don | 321333 | 953
Kala | 321444 | 953
I'd like the following result:
Employee | 953_Order_Count | 1026_Order_Count
---------+-----------------+-----------------
Kala | 2 | 0
Don | 1 | 1
Paul | 0 | 1
To validate that I want is possible in my mind, when I run:
SELECT
Employee,
OrderType,
COUNT(DISTINCT OrderID) AS 'Count'
FROM
CTE
GROUP BY
employee, ordertype
The following result is returned:
Employee | OrderType | Count
---------+-----------+------
Kala | 953 | 1
Paul | 1026 | 1
Don | 1026 | 1
Don | 953 | 1
Close, but not close enough. So I run:
SELECT
Employee,
COUNT(DISTINCT OrderID) AS 'Total_Orders',
COUNT(DISTINCT (CASE WHEN OrderType = 1026 THEN OrderID END)) AS '1026_Order_Count',
COUNT(DISTINCT(CASE WHEN OrderType = 953 THEN OrderID END)) AS '953_Order_Count'
FROM
CTE
GROUP BY
Employee
The result is an accurate first "count," but the rest return 0. If this were not a CTE, I'd use a recursive statement.
Any help is appreciated!
Just use conditional aggregation:
SELECT
Employee,
COUNT(CASE WHEN OrderType = 953 THEN 1 END) AS [953_Order_Count],
COUNT(CASE WHEN OrderType = 1026 THEN 1 END) AS [1026_Order_Count]
FROM CTE
GROUP BY
Employee;
Demo
The 953 count, for example, works above by counting 1 when the order type is 953 and NULL (the implicit ELSE value) when the order type is not 953. COUNT ignores NULL by default, so it only counts the 953 orders.
Tim's answer looks fine. You could also use a PIVOT:
; with cte (Employee, OrderID, OrderType)
as
(
select 'Kala', 321111, 953
union select 'Paul', 321222, 1026
union select 'Don', 321333, 1026
union select 'Don', 321333, 953
union select 'Kala', 321444, 953
)
select Employee, [953] as [953_Order_Count],[1026] as [1026_Order_Count]
from
(
select Employee, OrderType from cte ) as sourceData
pivot
(
count(OrderType)
for OrderType
in ([953],[1026])
) as myPivot
If you want to have dynamic columns based on the set of available values in the OrderType column, you can build the query dynamically. See #Taryn's answer to Understanding PIVOT function in T-SQL for an example.

Sum one column and subtract over second column

I want to display the subtraction of two columns. From the first column I need to get sum all value and substract with each value from the second column.
This is the table structure:
id | name | col1 | col2 | date
------------------------------------
432| xxx | 0 | 15 |2015-11-17
432| yyy | 10 | 30 |2015-11-19
432| zzz | 60 | 40 |2015-11-20
433| aaa | 0 | 60 |2015-11-17
433| bbb | 80 | 20 |2015-11-19
433| ccc | 60 | 10 |2015-11-20
Formula should go:
sum(col1) = 70 =>>> WHERE ID = 432
70 - col2 col3
-------------------------
=> 70 - 15 = 55
=> 70 - (30 + 15) = 25
=> 70 - (40 + 45) = -15
---------------------------
sum(col1) = 140 ===>> WHERE ID = 433
140 - col2 col3
-------------------------
=> 140 - 60 = 80
=> 140 - (60 + 20) = 60
=> 140 - (10 + 80) = 50
result is col3 and Output should be like as
id | name | col1 | col2 | col3 | date
-------------------------------------------
432| xxx | 0 | 15 | 55 | 2015-11-17
432| yyy | 10 | 30 | 25 | 2015-11-19
432| zzz | 60 | 40 | -15 | 2015-11-20
433| aaa | 0 | 60 | 80 | 2015-11-17
433| bbb | 80 | 20 | 60 | 2015-11-19
433| ccc | 60 | 10 | 50 | 2015-11-20
EDIT: What if I need the values ​​vary depending on the group as a 432 and 433 id column.
Schema Info
DECLARE #TEST TABLE
(
id INT,
name VARCHAR(10),
col1 INT,
col2 int
)
INSERT INTO #TEST VALUES
(432,'xxx',0, 15 ),
(432,'yyy',10, 30 ),
(432,'zzz',60, 40 ),
(433,'aaa',0, 60 ),
(433,'bbb',80, 20 ),
(433,'ccc',60, 10 )
Query
SELECT T.id ,
T.name ,
T.col1 ,
T.col2 ,
SUM(T.col1) OVER( PARTITION BY T.id ORDER BY T.id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
- SUM(T.col2) OVER ( PARTITION BY T.id ORDER BY T.id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS col3
FROM #TEST T;
Results
id | name | col1 | col2 | col3 |
---------------------------------
432 | xxx | 0 | 15 | 55 |
432 | yyy | 10 | 30 | 25 |
432 | zzz | 60 | 40 | -15 |
433 | aaa | 0 | 60 | 80 |
433 | bbb | 80 | 20 | 60 |
433 | ccc | 60 | 10 | 50 |
SQL Fiddle
This should work:
declare #total int = (select sum(col1) from Table)
select id, name, col1, col2, #total - (select sum(col2) from Table where date <= T.date) as col3, date from Table T
I was assuming you want to substract every time the previous total (based on the date). I hope this is OK.
You can use simple select query with cross apply
SELECT ID
,NAME
,COL1
,COL2
,A.C1 - (
SUM(COL2) OVER (
ORDER BY ID
)
) AS COL3
FROM TABLE1 T1
CROSS APPLY (
SELECT SUM(COL1) AS C1
FROM TABLE1 T2
) A
You can use two subqueries in SELECT fields list.
With the first you'll get a sum of all rows of your table named yourtable, in the second you'll get a sum of all rows before the current. So you can subtract two values.
Try this:
SELECT T.id, T.name, T.col1, T.col2,
ISNULL(
(SELECT SUM(T2.col1) FROM yourtable T2)
,0) -
ISNULL(
(SELECT SUM(T3.col2) FROM yourtable T3
WHERE T3.id <= T.id)
,0) as col3,
t.date
FROM yourtable T
Go on Sql fiddle example
EDIT
SELECT T.id, T.name, T.col1, T.col2,
ISNULL(
(SELECT SUM(T2.col1) FROM yourtable T2 where T2.id = T.id)
,0) -
ISNULL(
(SELECT SUM(T3.col2) FROM yourtable T3
WHERE T3.id = T.id AND T3.date <= T.date)
,0) as col3,
t.date
FROM yourtable T
Go on Sql Fiddle edited example
Pay attention: A deep edit can be a different question. Two queries are differents
Pay attention: it's no good a field named ID with repeated values

Most recent date and price from multiple tables in SQL Server

I have 5 tables:
contracts, contracts_data, contracts_anexes, anexes, anexes_data
Table contracts columns :
id_contract | date_sign
------------+-----------
1 | 2013-01-03
2 | 2013-06-05
3 | 2014-10-12
Table contracts_data columns :
id_contract | price
------------+------
1 | 100
2 | 200
3 | 300
Table uontracts_anexes columns :
id_contract | id_anex
------------+--------
1 | 1
1 | 2
2 | 3
Table anexes columns :
id_anex | date_of_sign
--------+--------------
1 | 2014-01-03
2 | 2014-06-05
3 | 2015-01-12
Table anexes_Data columns :
id_anex | price
--------+------
1 | 200
2 | 300
3 | 400
Now I need to select price (from contracts_data or anexes_data) where the date of sign is most recent (max date_sign from contracts and anexes), but not all id_contract are in table contracts_anexes (not all contracts have a annex), and one contract (id_contract) may have multiple anexes (multiple rows in contracts_anexes table)
For example
for id_contract = 1 I need to return price 300 and date 2014-06-05,
for id_contract = 2 I need to return price 400 and date 2015-01-12
for id_contract = 3 I need to return price 300 and date 2014-10-12
You could use UNION ALL together with ROW_NUMBER:
;WITH CteUnion AS(
SELECT
id_contract = c.id_contract,
price = cd.price,
date_sign = c.date_sign
FROM contracts c
LEFT JOIN contracts_data cd
ON cd.id_contract = c.id_contract
UNION ALL
SELECT
id_contract = c.id_contract,
price = ad.price,
date_sign = a.date_sign
FROM contracts c
LEFT JOIN contracts_anexes ca
ON ca.id_contract = c.id_contract
LEFT JOIN anexes a
ON a.id_anex = ca.id_anex
LEFT JOIN anexes_data ad
ON ad.id_anex = a.id_anex
)
SELECT
id_contract,
price,
date_sign
FROM(
SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY id_contract ORDER BY date_sign DESC)
FROM CteUnion
)c
WHERE RN = 1
See SQL Fiddle.

The highest value from list-distinct

Can anyone help me with query, I have table
vendorid, agreementid, sales
12001 1004 700
5291 1004 20576
7596 1004 1908
45 103 345
41 103 9087
what is the goal ?
when agreemtneid >1 then show me data when sales is the highest
vendorid agreementid sales
5291 1004 20576
41 103 9087
Any ideas ?
Thx
Well you could try using a CTE and ROW_NUMBER something like
;WITH Vals AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY AgreementID ORDER BY Sales DESC) RowID
FROM MyTable
WHERE AgreementID > 1
)
SELECT *
FROM Vals
WHERE RowID = 1
This will avoid you returning multiple records with the same sale.
If that was OK you could try something like
SELECT *
FROM MyTable mt INNER JOIN
(
SELECT AgreementID, MAX(Sales) MaxSales
FROM MyTable
WHERE AgreementID > 1
) MaxVals ON mt.AgreementID = MaxVals.AgreementID AND mt.Sales = MaxVals.MaxSales
SELECT TOP 1 WITH TIES *
FROM MyTable
ORDER BY DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC)
Explanation
We break table MyTable into partitions by agreementid.
For each partition we construct a ranking or its rows.
If agreementid is greater than 1 ranking will be equal to ORDER BY sales DESC.
Otherwise ranking for every single row in partition will be the same: ORDER BY 0 DESC.
See how it looks like:
SELECT *
, SIGN (SIGN (agreementid - 2) + 1) * sales AS x
, DENSE_RANK() OVER(PARTITION BY agreementid ORDER BY SIGN (SIGN (agreementid - 2) + 1) * sales DESC) AS rnk
FROM MyTable
+----------+-------------+-------+-------+-----+
| vendorid | agreementid | sales | x | rnk |
+----------|-------------|-------+-------+-----+
| 0 | 0 | 3 | 0 | 1 |
| -1 | 0 | 7 | 0 | 1 |
| 0 | 1 | 3 | 0 | 1 |
| -1 | 1 | 7 | 0 | 1 |
| 41 | 103 | 9087 | 9087 | 1 |
| 45 | 103 | 345 | 345 | 2 |
| 5291 | 1004 | 20576 | 20576 | 1 |
| 7596 | 1004 | 1908 | 1908 | 2 |
| 12001 | 1004 | 700 | 700 | 3 |
+----------+-------------+-------+-------+-----+
Then using TOP 1 WITH TIES construction we leave only rows where rnk equals 1.
you can try like this.
SELECT TOP 1 sales FROM MyTable WHERE agreemtneid > 1 ORDER BY sales DESC
I really do not know the business logic behind agreement_id > 1. It looks to me you want the max sales (with ties) by agreement id regardless of vendor_id.
First, lets create a simple sample database.
-- Sample table
create table #sales
(
vendor_id int,
agreement_id int,
sales_amt money
);
-- Sample data
insert into #sales values
(12001, 1004, 700),
(5291, 1004, 20576),
(7596, 1004, 1908),
(45, 103, 345),
(41, 103, 9087);
Second, let's solve this problem using a common table expression to get a result set that has each row paired with the max sales by agreement id.
The select statement just applies the business logic to filter the data to get your answer.
-- CTE = max sales for each agreement id
;
with cte_sales as
(
select
vendor_id,
agreement_id,
sales_amt,
max(sales_amt) OVER(PARTITION BY agreement_id) AS max_sales
from
#sales
)
-- Filter by your business logic
select * from cte_sales where sales_amt = max_sales and agreement_id > 1;
The screen shot below shows the exact result you wanted.

SQL Server : select distinct by one column and by another column value

This is a SQL Server table's data
id user_id start_date status_id payment_id
======================================================
2 4 20-nov-11 1 5
3 5 23-nov-11 1 245
4 5 25-nov-11 1 128
5 6 20-nov-11 1 223
6 6 25-nov-11 2 542
7 4 29-nov-11 2 123
8 4 05-jan-12 2 875
I need to get distinct values by user_id also order by id asc, but only one user_id with highest start_date
I need the following output:
id user_id start_date status_id payment_id
======================================================
8 4 05-jan-12 2 875
4 5 25-nov-11 1 128
6 6 25-nov-11 2 542
Please help!
What is SQL query for this?
You can use row_number() in either a sub-query or using CTE.
Subquery Version:
select id, user_id, start_date, status_id, payment_id
from
(
select id, user_id, start_date, status_id, payment_id,
row_number() over(partition by user_id order by start_date desc) rn
from yourtable
) src
where rn = 1
See SQL Fiddle with Demo
CTE Version:
;with cte as
(
select id, user_id, start_date, status_id, payment_id,
row_number() over(partition by user_id order by start_date desc) rn
from yourtable
)
select id, user_id, start_date, status_id, payment_id
from cte
where rn = 1
See SQL Fiddle with Demo
Or you can join the table to itself:
select t1.id,
t1.user_id,
t1.start_date,
t1.status_id,
t1.payment_id
from yourtable t1
inner join
(
select user_id, max(start_date) start_date
from yourtable
group by user_id
) t2
on t1.user_id = t2.user_id
and t1.start_date = t2.start_date
See SQL Fiddle with Demo
All of the queries will produce the same result:
| ID | USER_ID | START_DATE | STATUS_ID | PAYMENT_ID |
---------------------------------------------------------------------------
| 8 | 4 | January, 05 2012 00:00:00+0000 | 2 | 875 |
| 4 | 5 | November, 25 2011 00:00:00+0000 | 1 | 128 |
| 6 | 6 | November, 25 2011 00:00:00+0000 | 2 | 542 |
Not the best and untested:
select *
from ServersTable
join (
select User_Id, max(Id) as ID
from ServersTable x
where x.start_date = (
select max(start_date)
from ServersTable y
where y.UserID = x.UserId
)
group by User_Id) s on ServersTable.Id = s.Id

Resources