Related
I have the following table and I need to flatten the data.
From here
table 1
id val1 val2 ....valn
-----------------------------------
1 a z
1 b x
1 c v
2 a w
2 b q
..n
To here
id val1_1 val2_1 ....valn_1 val1_2 val2_2 ... valn_2 .... val1_n...valn_n
-----------------------------------
1 a z b x
2 a w b q
..n
Any ideas?
I have done something using cursors, however it is a little nasty and It has some bugs. Pivot?
Yes you need to a pivot, but you also need to calculate a row-number to pivot against. However, given this is a multi-column pivot, it's probably easier to just use conditional aggregation.
I suggest you pick a proper ordering for the row-number
SELECT
id,
val1_1 = MAX(CASE WHEN t.rn = 1 THEN t.val1 END),
val2_1 = MAX(CASE WHEN t.rn = 1 THEN t.val2 END),
val3_1 = MAX(CASE WHEN t.rn = 1 THEN t.val3 END),
-- ...........
val1_2 = MAX(CASE WHEN t.rn = 2 THEN t.val1 END),
val2_2 = MAX(CASE WHEN t.rn = 2 THEN t.val2 END),
val3_2 = MAX(CASE WHEN t.rn = 2 THEN t.val3 END),
-- ...........
FROM (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY t.rn ORDER BY (SELECT 1))
FROM table1 t
) t
GROUP BY t.id;
I have data similar to below. I need to group the status column based on the occurrence of data.
Id Status Value
1 K 1
2 K 3
3 K 2
4 B 2
5 B 3
6 K 6
7 J 5
8 J 2
I want data as below
Status Value
K 6
B 5
K 6
J 7
I need the cumulative sum of the value column.In the status column if the data is same consecutively, then I need to add the value columns. I cannot apply group by. In the example given K is repeated twice, because they are not consecutive.
I have tried below query, but it doesn't work as required.
select Status,
(select sum(value)
from table t2
where
t2.Status = t.Status and
t2.SNO <= t.SNO
) as total
from table t;
This is a Gaps and Islands Question
I tackle these by using the incrementing Id and combing this with ROW_NUMBER window function
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
This gives this result set, note the Grp column
Grp Id Status Value
------- ------- ------- -------
0 1 K 1
0 2 K 3
0 3 K 2
3 4 B 2
3 5 B 3
2 6 K 6
6 7 J 5
6 8 J 2
Then combine with a CTE or derived table you can get your expected output
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT Y.Status
, Value = SUM(Y.Value)
FROM
(
SELECT TOP 100 PERCENT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.Status ORDER BY X.Id)
, X.Id
, X.Status
, X.Value
FROM
cteX X
ORDER BY
X.Id
) Y
GROUP BY
Y.Grp, Y.Status
Output
Status Value
------- -------
B 5
J 7
K 6
K 6
Update Question include "Preserve order" solution
Just include an Order by MIN(Id)
--Using a CTE just to replicate the sample data
;WITH cteX (Id,Status,Value)
AS(
SELECT 1,'K', 1 UNION ALL
SELECT 2,'K', 3 UNION ALL
SELECT 3,'K', 2 UNION ALL
SELECT 4,'B', 2 UNION ALL
SELECT 5,'B', 3 UNION ALL
SELECT 6,'K', 6 UNION ALL
SELECT 7,'J', 5 UNION ALL
SELECT 8,'J', 2
)
SELECT
Y.[Status]
,[Value] = SUM(Y.[Value])
FROM
(
SELECT
Grp = Id - ROW_NUMBER()OVER( PARTITION BY X.[Status] ORDER BY X.Id)
, X.Id
, X.[Status]
, X.[Value]
FROM
cteX X
) Y
GROUP BY
Y.Grp, Y.[Status]
ORDER BY
MIN(Y.Id) --preserve the Status Order
Output
Status Value
------- -------
K 6
B 5
K 6
J 7
select all the departments having students with even roll number
Dept No Roll No Student Name
1 1 lee
1 2 scott
2 2 scott
2 4 smith
1 4 smith
This should result in DEpt no 2 as it has only students with roll number divisible by 2
Another(imo easy and lightweight) way is using NOT EXISTS and DISTINCT:
SELECT DISTINCT [Dept No]
FROM dbo.TableName t
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TableName t2
WHERE t.[Dept No] = t2.[Dept No]
AND t2.[Roll No] % 2 = 1
)
Demo
If there is no odd number all must be even.
You can use GROUP BY with HAVING like this.
Query
SELECT [Dept No]
FROM departments
GROUP BY [Dept No]
HAVING SUM(CASE WHEN [Roll No] % 2 = 0 THEN 1 ELSE 0 END) > 1
AND SUM(CASE WHEN [Roll No] % 2 = 1 THEN 1 ELSE 0 END) = 0
Explanation
The query returns the departments if there a rollno which is even using SUM(CASE WHEN [Roll No] % 2 = 0 THEN 1 ELSE 0 END) > 1. If there is any rollno with odd roll no, SUM(CASE WHEN [Roll No] % 2 = 1 THEN 1 ELSE 0 END) will return non zero sum and that department will be excluded.
declare #t table (Dept int,Rno int,Student varchar(10))
insert into #t (Dept,Rno,Student)values (1,1,'lee'),(1,2,'scott'),(2,2,'scott'),(2,4,'smith'),(1,4,'smith')
SELECT Dept,Rno,Student
FROM (SELECT ROW_NUMBER () OVER (ORDER BY Rno DESC) row_number, Dept,Rno,Student
FROM #t) a WHERE (row_number%2) = 0
I don't think this is going to be too rough, though I am not seeing it in the asked questions and answered questions.
I am looking for a way to group by sales person for two different types of sales.
So something like
Name PendingORders CompletedOrders TotalOrders
Grouped by the Name first, Count Type = P, Count = C and then sum Type= P and count = C.
Though there are only two types of orders in this report so technically the criteria is just a count of all orders by the name.
My table, would look like this
Name|Order#|Type
Jane| 1 | C
Jane| 2 | P
Jane| 3 | P
Fred| 4 | P
Fred| 5 | P
Hopefully the query would produce
Name|TotalOrder|Pending|Completed
Jane|3 |2 |1
Fred|2 |2 |0
Many thanks in advance
So you're basically just grouping by salesperson and computing three different aggregates for each. That's completely straightforward:
SELECT
Name,
count(*) AS TotalOrder,
SUM(CASE Type WHEN 'P' THEN 1 ELSE 0 END) AS Pending,
SUM(CASE Type WHEN 'C' THEN 1 ELSE 0 END) AS Completed,
FROM orders
GROUP BY name;
Use Conditional Aggregate
select Name
TotalOrder = count(Name),
Completed = count(case when type = 'C' then 1 end),
PENDING = count(case when type = 'P' then 1 end)
From yourtable
group by Name
or use Pivot
SELECT Name,
TotalOrder,
Completed = C,
PENDING = P
FROM (SELECT *,
TotalOrder=Count(1)OVER(partition BY name)
FROM Yourtable) a
PIVOT (Max(order#)
FOR type IN ([C],
[P]))pv
I worked out your problem using temp tables. You have to use conditional (CASE WHEN) statements to work out the math then group by name.
CREATE TABLE #Orders (
Name VARCHAR(25) NULL ,
OrderNumber INT NULL ,
OrderType CHAR(1) NULL )
INSERT INTO #Orders (Name, OrderNumber , OrderType)
VALUES ( 'Jane' , 1 , 'C') ,
( 'Jane' , 2 , 'P') ,
( 'Jane' , 3 , 'P') ,
( 'Fred' , 4 , 'P') ,
( 'Fred' , 5 , 'P')
SELECT Name ,
COUNT(*) AS TotalOrder ,
SUM(CASE OrderType WHEN 'P' THEN 1 ELSE 0 END) AS Pending ,
SUM(CASE OrderType WHEN 'C' THEN 1 ELSE 0 END) AS Completed
FROM #Orders
GROUP BY Name
DROP TABLE #Orders
I'm trying to aggregate some customer order data into one table for analysis. The data is the number of products a customer orders, then trying to determine whether the order is a small, medium, or large order, then determine the total products they bought and the cost of the order by OrderSize.
Small order - 1 - 2 products
Medium order - 3 - 4 products
Large Order - >=5 products
Here is the data:
CustomerID OrderID OrderSize OrderTotal
1 800 1 $20
2 801 1 $10
3 802 4 $85
1 803 1 $30
2 804 8 $120
3 805 1 $40
Here is the table I am attempting to build (easier to just post an image):
I could run an update query like this to populate the table:
-- Build the table --
CREATE TABLE Customers (
CustomerID varchar(10) PRIMARY KEY,
SmallOrderCount int,
SmallOrderProducts int,
SmallOrderTotal money,
MedOrderCount int,
MedOrderProducts int,
MedOrderTotal money,
LargeOrderCount int,
LargeOrderProducts int,
LargeOrderTotal money
);
-- Insert the unique customers --
INSERT INTO Customers
SELECT CustomerID FROM Orders GROUP BY CustomerID;
-- Update to populate the order information --
UPDATE Customers
SET SmallOrderCount = (SELECT count(*) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
UPDATE Customers
SET SmallOrderProducts = (SELECT sum(OrderSize) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
UPDATE Customers
SET SmallOrderTotal = (SELECT sum(OrderTotal) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = Customers.CustomerID);
Then I could repeat this for the remaining 6 columns.
However, this seems like a lot of work. Is there a way to populate my Customer table using a subquery that may be less work? Or is my approach above the most direct?
You can do all of it in one INSERT command with CASE statements:
INSERT INTO Customers
SELECT CustomerID
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN 1 ELSE 0 END) AS SmallOrderCount
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderSize ELSE 0 END) AS SmallOrderProducts
,sum(CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderTotal ELSE 0 END) AS SmallOrderTotal
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN 1 ELSE 0 END) AS MedOrderCount
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderSize ELSE 0 END) AS MedOrderProducts
,sum(CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderTotal ELSE 0 END) AS MedOrderTotal
,sum(CASE WHEN OrderSize > 4 THEN 1 ELSE 0 END) AS LargeOrderCount
,sum(CASE WHEN OrderSize > 4 THEN OrderSize ELSE 0 END) AS LargeOrderProducts
,sum(CASE WHEN OrderSize > 4 THEN OrderTotal ELSE 0 END) AS LargeOrderTotal
FROM Orders
GROUP BY CustomerID;
See this working demo for the full query on data.SE.
One insert should be enough:
INSERT INTO Customers(CustomerID,SmallOrderCount,SmallOrderProducts,
SmallOrderTotal)
SELECT a.CustomerID, COUNT(a.*) as cnt, sum(a.OrderSize) as OrderSize,
sum(a.OrderTotal) as OrderTotal
FROM Orders a
WHERE a.OrderSize BETWEEN 1 AND 2
GROUP BY a.CustomerID
The query above will insert only customers whose order size is between 1 and 2. If you need to insert others as well, you can use :
INSERT INTO Customers(CustomerID,SmallOrderCount,SmallOrderProducts,
SmallOrderTotal)
SELECT a.CustomerID,
COUNT(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN 1 END) as cnt,
sum(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN a.OrderSize ELSE 0 END) as OrderSize,
sum(CASE WHEN a.OrderSize BETWEEN 1 AND 2 THEN a.OrderTotal ELSE 0 END ) as OrderTotal
FROM Orders a
GROUP BY a.CustomerID
I'd only create a single query (which can be used as view) for that and not a whole new persisted table.
WITH cteOrders AS (
SELECT CustomerID,
CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderSize END SmallOrderProducts,
CASE WHEN OrderSize BETWEEN 1 AND 2 THEN OrderTotal END SmallOrderTotal,
CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderSize END MediumOrderProducts,
CASE WHEN OrderSize BETWEEN 3 AND 4 THEN OrderTotal END MediumOrderTotal,
CASE WHEN OrderSize > 4 THEN OrderSize END LargeOrderProducts,
CASE WHEN OrderSize > 4 THEN OrderTotal END LargeOrderTotal
FROM Orders
)
SELECT CustomerID,
COUNT(SmallOrderProducts) SmallOrderCount,
COALESCE(SUM(SmallOrderProducts), 0) SmallOrderProducts,
COALESCE(SUM(SmallOrderTotal), 0) SmallOrderTotal,
COUNT(MediumOrderProducts) MediumOrderCount,
COALESCE(SUM(MediumOrderProducts), 0) MediumOrderProducts,
COALESCE(SUM(MediumOrderTotal), 0) MediumOrderTotal,
COUNT(LargeOrderProducts) LargeOrderCount,
COALESCE(SUM(LargeOrderProducts), 0) LargeOrderProducts,
COALESCE(SUM(LargeOrderTotal), 0) LargeOrderTotal
FROM cteOrders
GROUP BY CustomerID
You can do this by using single INSERT INTO SELECT statement
INSERT INTO Customers
SELECT * ,
(SELECT count(*) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = CustomerID),
(SELECT sum(OrderSize) FROM Orders WHERE OrderSize BETWEEN 1 AND 2 AND Orders.CustomerID = CustomerID)
FROM CUSTOMERS