Cross Tab or Pivot query in SQL 2005 - sql-server-2005

Can someone please help me with a cross tab/pivot query in SQL 2005
Given Data looks like
EmpId OrgId DayCt Cost
1 20 15 100
2 20 36 300
3 40 25 200
4 40 10 50
Result to be like:
EmpId OrgId 20 OrgId 40
DayCt Cost DayCt Cost
1 15 100
2 36 300
3 25 200
4 10 50
EmpId in 1st Col and then Org Ids in the next col. But under every OrgId, I want DayCt & Cost also to be included as sub columns. Not sure if this is doable. Please help.

There is no such thing as sub columns this seems like something that should be done in your application/reporting tool.
This is about the closest you can get in SQL
;WITH T(EmpId,OrgId,DayCt,Cost) AS
(
select 1, 20, 15, 100 UNION ALL
select 2, 20, 36, 300 UNION ALL
select 3, 40, 25, 200 UNION ALL
select 4, 40, 10, 50
)
SELECT EmpId,
MAX(CASE WHEN OrgId =20 THEN DayCt END) AS [OrgId 20 DayCt],
MAX(CASE WHEN OrgId =20 THEN Cost END) AS [OrgId 20 Cost],
MAX(CASE WHEN OrgId =40 THEN DayCt END) AS [OrgId 40 DayCt],
MAX(CASE WHEN OrgId =40 THEN Cost END) AS [OrgId 40 Cost]
FROM T
GROUP BY EmpId
Returns
EmpId OrgId 20 DayCt OrgId 20 Cost OrgId 40 DayCt OrgId 40 Cost
----------- -------------- ------------- -------------- -------------
1 15 100 NULL NULL
2 36 300 NULL NULL
3 NULL NULL 25 200
4 NULL NULL 10 50

Related

PosrgreSQL Pivot Table

I need to make a PIVOT table from Source like this table
FactID UserID QTY Product
1 10 100 A
2 10 200 B
3 10 300 C
4 12 50 A
5 12 60 B
6 12 70 C
7 15 500 A
8 15 550 B
9 15 600 C
Need Pivot Like this
UserID A B C
10 100 200 300
12 50 60 70
15 500 550 600
My try
Select UserID,
CASE WHEN product = 'A' then QTY end as A,
CASE WHEN product = 'B' then QTY end as B,
CASE WHEN product = 'C' then QTY end as C
from public.table
And Result
UserID A B C
10 100 100 100
10 200 200 200
10 300 300 300
12 50 50 50
12 60 60 60
12 70 70 70
15 500 500 500
15 550 550 550
15 600 600 600
Where's my mistake? Maybe there's another way to do it?
Very close. You just need aggregation:
Select UserID,
SUM(CASE WHEN product = 'A' then QTY end) as A,
SUM(CASE WHEN product = 'B' then QTY end) as B,
SUM(CASE WHEN product = 'C' then QTY end) as C
from public.table
group by UserId;
In Postgres, though, this would normally use the FILTER clause instead of CASE:
Select UserID,
SUM(qty) FILTER (WHERE product = 'A') as A,
SUM(qty) FILTER (WHERE product = 'B') as B,
SUM(qty) FILTER (WHERE product = 'C') as C
from public.table
group by UserId;
You need aggregate function as
Select UserID,
Max(CASE WHEN product = 'A' then QTY end) as A,
Max(CASE WHEN product = 'B' then QTY end) as B,
Max(CASE WHEN product = 'C' then QTY end) as C
from public.table
Group by userid

How to find missing Number along with the numbers present in table in Oracle

I am trying to fetch data for one of my clients, but there are missing tokens in his data. I tried the below query for fetching the missing data and inserting into one dump table, but would like to find a more optimized way where I find the missing tokens along with the token's present in the table.
SELECT MinToken + 1 Level
FROM (SELECT Min(Token) AS MINTOKEN, MAX(Token) AS MAXTOKEN
FROM dmp_SellerOrders
Where OrderNo = 1
AND TradeDate = '27-Oct-20')
CONNECT BY LEVEL < MAXTOKEN - MINTOKEN
MINUS
SELECT TOKEN
FROM dmp_SellerOrders
Where (OrderNo, TranserialNo) IN (SELECT OrderNo, MAX(TranserialNo)
FROM dmp_SellerOrders
GROUP BY OrderNo)
AND TradeDate = '27-Oct-20';
Actual Data in Table looks like this,
Original Order OrderNo TranserialNo Token Qty Price
1 1 25 100 100
1 1 26 100 100
1 1 27 100 100
1 1 28 100 100
1 1 30 100 100
1 1 31 100 100
Order Price Modified OrderNo TranserialNo Token Qty Price
1 2 25 100 200
1 2 26 100 200
1 2 27 100 200
1 2 28 100 200
1 2 30 100 200
1 2 31 100 200
I need data to show by grouping Qty, as show below,
OrderNo MinToken MaxToken Qty Price
1 25 28 100 200
1 29 29 0 0
1 30 31 100 200
But if I group qty then I get below out,
OrderNo MinToken MaxToken Qty Price
1 25 31 100 200
1 29 29 0 0
Can any one please help me, how can I get the output as expected.
Regards,
Mehul
Here a simplified solution without orderNo and TradeDate to demonstrate the concept.
You perform following steps in subsequent subqueries
get all tokens and outer join to the order table to get a complete sequence
find the LAG of the qty column
set grp_id to 1 if there is a break. i.e. if the previos qty is null and the current not null or vice versa - else to 0
cummulate the grp_id using analytic form of SUM
here the result with the sample data
TOKEN QTY PRICE QTY_LAG GRP_ID CUM_GRP_ID
---------- ---------- ---------- ---------- ---------- ----------
25 100 100 1 1
26 100 100 100 0 1
27 100 100 100 0 1
28 100 100 100 0 1
29 100 1 2
30 100 100 1 3
The last step is a simple GROUP BY on qty, price with added cummulated group id cum_grp_id which splits the non adjacent groups.
Query
with orders as (
select 25 token, 100 qty, 100 price from dual union all
select 26 token, 100 qty, 100 price from dual union all
select 27 token, 100 qty, 100 price from dual union all
select 28 token, 100 qty, 100 price from dual union all
select 30 token, 100 qty, 100 price from dual union all
select 31 token, 100 qty, 100 price from dual),
tokens as (
select min(token) min_token, max(token) max_token from orders),
all_tokens as (
select min_token - 1 + level token from tokens
connect by level <= max_token - min_token),
grp as (
select
t.token,
o.qty, o.price,
lag(o.qty) over (order by t.token) as qty_lag
from all_tokens t
left outer join orders o
on t.token = o.token),
grp2 as (
select
token, qty, price, qty_lag,
case when qty is null and qty_lag is null or qty is not null and qty_lag is not null then 0 else 1 end as grp_id
from grp),
grp3 as (
select
token, qty, price, qty_lag, grp_id,
sum(grp_id) over (order by token) cum_grp_id
from grp2)
select min(token), max(token), qty, price
from grp3
group by cum_grp_id, qty, price
order by 1
result
MIN(TOKEN) MAX(TOKEN) QTY PRICE
---------- ---------- ---------- ----------
25 28 100 100
29 29
30 30 100 100
Adapt if required by partitioning the analytic function by orderNo and/or tradeDate.

SQL: Difference in amounts between rows and columns

I have a Table
With the below data
Table X
Seq_no A Claim Payment Balance (to be calculated)
1 abc 100 10 90 (100-10)
2 abc 50 40 (90-50)
3 abc 20 20 (40-20)
1 xyz 150 10 140 (150-10)
1 qwe 200 10 190 (200-10)
I need to calculate the column Balance.
I am trying with the below Query
SQL >
Select
Seq_no, a, Claim, Payment,
CASE
When Seq_no =1
then (claim-Payment)
Else ( lag(Balance)- Payment over (order by Balance))
END as Balance
from table X
However i am getting a error
ORA-00904: "Balance": invalid identifier
00904. 00000 - "%s: invalid identifier"
I believe this is because Balance is not an existing column name.
Is there a correct way to achieve the results.?
Update:
*I missed an important part. *
The data i have is in the below format:
Table X
Seq_no A Claim Payment
1 abc 100 10
2 abc 100 50
3 abc 100 20
1 xyz 150 10
1 qwe 200 10
I need the results in the below format.
Table X
Seq_no A Claim Payment Balance (to be calculated)
1 abc 100 10 90 (100-10)
2 abc 50 40 (90-50)
3 abc 20 20 (40-20)
1 xyz 150 10 140 (150-10)
1 qwe 200 10 190 (200-10)
The Seq_no calculation has been done to make the claim column null for the cases of duplicate claims, which i had figured out already.
You cannot reference balance before create it.
You can use a "running sum" for what you want to achieve.
Notice that I partitioned by A because you want another balance for every A.
Select
Seq_no, a, Claim, Payment,
sum(nvl(claim,0) - payment) over (partition by A order by seq_no) as Balance
from X;
Result:
SEQ_NO A CLAIM PAYMENT BALANCE
1 abc 100 10 90
2 abc 50 40
3 abc 20 20
1 qwe 200 10 190
1 xyz 150 10 140
EDIT: With newer dataset you just need to replace nvl function with a case when seq=1:
Select
Seq_no,
a,
case when seq_no=1 then claim else 0 end as Claim,
Payment,
sum(case when seq_no=1 then claim else 0 end - payment)
over (partition by A order by seq_no) as Balance
from X;
In order to use balance as a column identifier, you must go one level deep, i.e. make your existing query as a sub-query.
Working demo:
SQL> WITH sample_data AS(
2 SELECT 1 Seq_no, 'abc' A, 100 Claim, 10 Payment FROM dual UNION ALL
3 SELECT 2 Seq_no, 'abc' A, NULL Claim, 50 FROM dual UNION ALL
4 SELECT 3 Seq_no, 'abc' A, NULL Claim, 20 FROM dual UNION ALL
5 SELECT 1 Seq_no, 'xyz' A, 150 Claim, 10 FROM dual UNION ALL
6 SELECT 1 Seq_no, 'qwe' A, 200 Claim, 10 FROM dual
7 )
8 -- end of sample_data mimicking real table
9 SELECT seq_no, A, claim, payment,
10 CASE
11 WHEN lag(balance) OVER(PARTITION BY A ORDER BY seq_no) IS NULL
12 THEN balance
13 ELSE lag(balance) OVER(PARTITION BY A ORDER BY seq_no) - payment
14 END balance
15 FROM
16 (SELECT seq_no, A, claim, payment,
17 CASE
18 WHEN seq_no = 1
19 THEN claim - payment
20 ELSE lag(claim - payment) OVER(PARTITION BY A ORDER BY seq_no) - payment
21 END balance
22 FROM sample_data
23 );
SEQ_NO A CLAIM PAYMENT BALANCE
---------- --- ---------- ---------- ----------
1 abc 100 10 90
2 abc 50 40
3 abc 20 20
1 qwe 200 10 190
1 xyz 150 10 140
SQL>
UPDATE : If you not-null values always for claim, then you could do a running diff:
SQL> WITH sample_data AS(
2 SELECT 1 Seq_no, 'abc' A, 100 Claim, 10 Payment FROM dual UNION ALL
3 SELECT 2 Seq_no, 'abc' A, 100 Claim, 50 FROM dual UNION ALL
4 SELECT 3 Seq_no, 'abc' A, 100 Claim, 20 FROM dual UNION ALL
5 SELECT 4 Seq_no, 'abc' A, 100 Claim, 10 FROM dual UNION ALL
6 SELECT 5 Seq_no, 'abc' A, 100 Claim, 10 FROM dual UNION ALL
7 SELECT 1 Seq_no, 'xyz' A, 150 Claim, 10 FROM dual UNION ALL
8 SELECT 1 Seq_no, 'qwe' A, 200 Claim, 10 FROM dual
9 )
10 -- end of sample_data mimicking real table
11 SELECT Seq_no,
12 a,
13 Claim,
14 Payment,
15 CASE
16 WHEN seq_no = 1
17 THEN claim - payment
18 ELSE SUM(claim - payment) over (partition BY A order by seq_no) - claim*(seq_no-1)
19 END balance
20 FROM sample_data;
SEQ_NO A CLAIM PAYMENT BALANCE
---------- --- ---------- ---------- ----------
1 abc 100 10 90
2 abc 100 50 40
3 abc 100 20 20
4 abc 100 10 10
5 abc 100 10 0
1 qwe 200 10 190
1 xyz 150 10 140
7 rows selected.
SQL>
You are correct that the error occurs because Balance is not a column in your table. You need to add that column.
The bigger problem is that unfortunately it's not possible to compute a running total using a single SQL statement the way you are trying to do it. SQL is not well suited to an operation like this. It can be done in a stored procedure using a cursor but the end result is procedural and not set-based, thus performance would be poor. Here's how to do it in SQL Server (as an example, not tested):
DECLARE PaymentCursor CURSOR FOR SELECT A, Claim, Payment FROM X ORDER BY A, Seq_no
OPEN PaymentCursor
DECLARE #A VARCHAR(16)
DECLARE #Claim MONEY
DECLARE #Payment MONEY
DECLARE #Balance MONEY
DECLARE #PreviousA VARCHAR(16)
SET #PreviousA = ''
FETCH NEXT FROM PaymentCursor INTO #A, #Claim, #Payment
WHILE ##FETCH_STATUS = 0
BEGIN
IF #A <> #PreviousA
BEGIN
SET #Balance = 0
SET #PreviousA = #A
END
SET #Balance = #Balance + #Claim - #Payment
UPDATE X SET Balance = #Balance WHERE CURRENT OF PaymentCursor
FETCH NEXT FROM PaymentCursor INTO #A, #Claim, #Payment
END
CLOSE PaymentCursor
DEALLOCATE PaymentCursor
If your application really needs to know the account balance associated with each transaction, the better approach is to compute the balance each time a row is inserted. Wrap the insertion logic in a stored procedure:
CREATE PROCEDURE InsertAndComputeBalance
#A VARCHAR(16),
#Claim MONEY,
#Payment MONEY
AS
DECLARE #MAX_Seq_no INTEGER = (SELECT MAX(Seq_no) FROM X WHERE A = #A)
INSERT INTO X
SELECT #MAX_Seq_no + 1, #A, #Claim, #Payment, Balance + #Claim - #Payment FROM X
WHERE A = #A AND Seq_no = #MAX_Seq_no
In theory you could call similar logic from a trigger so that it is automatically executed each time a row is inserted, but triggers can be evil so proceed very carefully with that approach.

SQL - Number of entries needed to reach given value

I need to find how many records it took to reach a given value. I have a table in the below format:
ID Name Time Time 2
1 Campaign 1 7 100
2 Campaign 3 5 165
3 Campaign 1 3 321
4 Campaign 2 610 952
5 Campaign 2 15 13
6 Campaign 2 310 5
7 Campaign 3 0 3
8 Campaign 1 0 610
9 Campaign 1 1 15
10 Campaign 1 54 310
11 Campaign 3 4 0
12 Campaign 2 23 0
13 Campaign 2 8 1
14 Campaign 3 23 1
15 Campaign 3 7 0
16 Campaign 3 5 5
17 Campaign 3 2 66
18 Campaign 3 100 7
19 Campaign 1 165 3
20 Campaign 1 321 13
21 Campaign 1 952 5
22 Campaign 1 13 3
23 Campaign 2 15 610
24 Campaign 2 0 15
25 Campaign 1 100 310
26 Campaign 2 165 0
27 Campaign 3 321 0
28 Campaign 3 952 1
29 Campaign 3 0 1
30 Campaign 3 5 0
I'd like to find out how many entries of 'Campaign 1' there were before the total of Time1 + Time2 was equal to or greater than a given number.
As an example, the result for Campaign 1 to reach 1400 should be 5.
Apologies if I haven't explained this clearly enough - the concept is still a little muddy at the moment.
Thanks
In SQL Server 2012, you can get the row using:
select t.*
from (select t.*, sum(time1 + time2) over (partition by name order by id) as cumsum
from table t
) t
where cumsum >= #VALUE and (cumsum - (time1 + time2)) < #VALUE;
You can get the count using:
select name, count(*)
from (select t.*, sum(time1 + time2) over (partition by name order by id) as cumsum
from table t
) t
where (cumsum - (time1 + time2)) < #VALUE
group by name;
If you are not using SQL Server 2012, you can do the cumulative sum with a correlated subquery:
select name, count(*)
from (select t.*,
(select sum(time1 + time2)
from table t2
where t2.name = t.name and
t2.id <= t.id
) as cumsum
from table t
) t
where (cumsum - (time1 + time2)) < #VALUE
group by name;
A recursive CTE computing a running total should work:
;WITH CTE AS
(
SELECT id,
name,
SUM([time]+[time 2])
OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
AS RunningTotal
FROM Table1
WHERE name = 'Campaign 1'
)
SELECT count(*)+1 AS [Count]
FROM CTE
WHERE RunningTotal < 1400
Note that I added 1 to the count as the query counts the number of rows needed to reach up to, but not including, 1400. Logic dictates that the next row will push the value above 1400.

Grouping Hierarchical data (parentID+ID) and running sum?

I have the following data:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
I am trying to group this data with Reporting Services 2008 and have a sum of the price per group of level 1 (Flowers/Electro) and for level 0 (Root).
I have a table grouped on [ID] with a recursive parent of [parendID] and I am able to calculate the sum for the level 0 (just one more row in the table outside the group), but somehow I am not able to create sum's per group as SRSS does "create" groups per level. My desired result looks like so:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
|-5 Violet 5
| |-6 Red Rose 12
| Group Sum-->27
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
Group Sum-->260
----------------------
Total 287
(indentation of ID just added for level clarification)
With my current approach I cannot get the group sums, so I figured out I would need the following data structure:
ID parentID Text Price level0 level1 level2 level3
1 Root 1
2 1 Flowers 1 1
3 1 Electro 1 2
4 2 Rose 10 1 1 1
5 2 Violet 5 1 1 2
6 4 Red Rose 12 1 1 1 1
7 3 Television 100 1 2 1
8 3 Radio 70 1 2 2
9 8 Webradio 90 1 2 2 1
When having the above structure I can create an outer grouping of level0, with child groupings level1, level2, level3 accordingly . When now having a "group sum" on level1, and the total sum outside the group I have EXACTLY what I want.
My question is the following:
How do I either achieve my desired result with my current data structure, or how do I convert my current data structure (outer left joins?) into the "new data structure" temporarily - so I can run my report off of the temp table?
Thanks for taking your time,
Dennis
WITH q AS
(
SELECT id, parentId, price
FROM mytable
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN mytable p
ON p.id = q.parentID
)
SELECT id, SUM(price)
FROM q
GROUP BY
id
Update:
A test script to check:
DECLARE #table TABLE (id INT NOT NULL PRIMARY KEY, parentID INT, txt VARCHAR(200) NOT NULL, price MONEY)
INSERT
INTO #table
SELECT 1, NULL, 'Root', NULL
UNION ALL
SELECT 2, 1, 'Flowers', NULL
UNION ALL
SELECT 3, 1, 'Electro', NULL
UNION ALL
SELECT 4, 2, 'Rose', 10
UNION ALL
SELECT 5, 2, 'Violet', 5
UNION ALL
SELECT 6, 4, 'Red Rose', 12
UNION ALL
SELECT 7, 3, 'Television', 100
UNION ALL
SELECT 8, 3, 'Radio', 70
UNION ALL
SELECT 9, 8, 'Webradio', 90;
WITH q AS
(
SELECT id, parentId, price
FROM #table
UNION ALL
SELECT p.id, p.parentID, q.price
FROM q
JOIN #table p
ON p.id = q.parentID
)
SELECT t.*, psum
FROM (
SELECT id, SUM(price) AS psum
FROM q
GROUP BY
id
) qo
JOIN #table t
ON t.id = qo.id
Here's the result:
1 NULL Root NULL 287,00
2 1 Flowers NULL 27,00
3 1 Electro NULL 260,00
4 2 Rose 10,00 22,00
5 2 Violet 5,00 5,00
6 4 Red Rose 12,00 12,00
7 3 Television 100,00 100,00
8 3 Radio 70,00 160,00
9 8 Webradio 90,00 90,00
I found a really ugly way to do what I want - maybe there is something better?
SELECT A.Text, A.Price,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
A.ID
ELSE B.ID
END
ELSE C.ID
END
ELSE D.ID
END
AS LEV0,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
CASE
WHEN B.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
ELSE C.ID
END
AS LEV1,
CASE
WHEN D.Text IS NULL
THEN
CASE
WHEN C.Text IS NULL
THEN
NULL
ELSE A.ID
END
ELSE B.ID
END
AS LEV2,
CASE
WHEN D.Text IS NULL
THEN NULL
ELSE A.ID
END
AS LEV3
FROM dbo.testOld AS A LEFT OUTER JOIN
dbo.testOld AS B ON A.parentID = B.ID LEFT OUTER JOIN
dbo.testOld AS C ON B.parentID = C.ID LEFT OUTER JOIN
dbo.testOld AS D ON C.parentID = D.ID
Output of this is:
Text Price LEV0 LEV1 LEV2 LEV3
---------- ----------- ----------- ----------- ----------- -----------
Root NULL 1 NULL NULL NULL
Flowers NULL 1 3 NULL NULL
Electro NULL 1 4 NULL NULL
Television 100 1 4 5 NULL
Radio 70 1 4 6 NULL
Rose 10 1 3 7 NULL
Violet 5 1 3 8 NULL
Webradio 90 1 4 5 14
Red Rose 12 1 3 7 15
With this structure I can go ahead and create 4 nested groups on the LEV0-3 columns including subtotals per group (as shown above in my desired result).