Oracle Pivot/Decode - sql

Sample Table
EmployeeID | AssignmentID | WageCode | CompanyName | BillRate | BillTotal
1 | 1 | Regular | CompanyOne | 10 | 400
1 | 2 | Regular | CompanyTwo | 11 | 440
1 | 1 | Overtime | CompanyOne | 15 | 150
1 | 1 | Mileage | CompanyOne | 0 | 20
2 | 3 | Regular | CompanyThree| 20 | 800
2 | 3 | Regular | CompanyThree| 20 | 800
2 | 3 | Overtime | CompanyThree| 30 | 90
2 | 3 | Mileage | CompanyThree| 0 | 60
I want to only show rows with a WageCode of 'Regular', grouped by EmployeeID, WageCode, AssignmentID, CompanyName and BillRate, and pivot the other wage codes into columns.
The final result should look like this:
EmployeeID | AssignmentID | CompanyName | RegBillRate | RegBill | OTBillRate | OTBill | MileageBill
1 | 1 | CompanyOne | 10 | 400 | 15 | 150 | 20
1 | 2 | CompanyTwo | 11 | 440 | 0 | 0 | 0
2 | 3 | CompanyThree| 20 | 1600 | 30 | 90 | 60
What's a cleaner way to do this that's not a bunch of with statements like this:
with regular as
(select EmployeeID, AssignmentID, CompanyName, BillRate, sum(BillTotal) Total from SampleTable where wage code = 'Regular' group by EmployeeID, AssignmentID, CompanyName, BillRate
),
overtime as
(select EmployeeID, AssignmentID, CompanyName, BillRate, sum(BillTotal) Total from SampleTable where wage code = 'Overtime' group by EmployeeID, AssignmentID, CompanyName, BillRate
),
mileage as
(select EmployeeID, AssignmentID, CompanyName, BillRate, sum(BillTotal) Total from SampleTable where wage code = 'Mileage' group by EmployeeID, AssignmentID, CompanyName, BillRate
)
select r.*, o.BillRate, o.Total, m.Total
from regular r
left outer join overtime o
on r.EmployeeID = o.EmployeeID and r.AssignmentID= o.AssignmentID and r.CompanyName= o.CompanyName and r.BillRate= o.BillRateand
left outer join mileage m
on r.EmployeeID = m.EmployeeID and r.AssignmentID= m.AssignmentID and r.CompanyName= m.CompanyName and r.BillRate= m.BillRateand
The query above is paraphrased and probably doesn't work.
What's a better way to do this with some combination of decode and pivot? Is a single pivot table possible?

PIVOT: The Oracle PIVOT clause allows you to write a cross-tabulation query starting in Oracle 11g. This means that you can aggregate your results and rotate rows into columns.
DECODE: The Oracle/PLSQL DECODE function has the functionality of an IF-THEN-ELSE statement.
For your use case, you can use pivot and decode in the following way:
SELECT
EmployeeID, AssignmentID, CompanyName,
decode(REG_BILLRATE, NULL, 0, REG_BILLRATE) AS REG_BILLRATE,
decode(REG_FILL, NULL, 0, REG_FILL) AS REG_FILL,
decode(OT_BILLRATE, NULL, 0, OT_BILLRATE) AS OT_BILLRATE,
decode(OT_FILL, NULL, 0, OT_FILL) AS OT_FILL,
decode(MILEAGE_FILL, NULL, 0, MILEAGE_FILL) AS MILEAGE_FILL
FROM nbitra.tmp
pivot
(
max(BillRate) AS BillRate, sum(BillTotal) AS Fill
for WageCode IN ('Regular' Reg , 'Overtime' OT , 'Mileage' Mileage )
);
Note: The code replaces nulls with 0.

I think you just want conditional aggregation:
select EmployeeID, AssignmentID, CompanyName,
sum(case when WageCode = 'Regular' then billrate end) as regular_billrate,
sum(case when WageCode = 'Regular' then BillTotal end) as regular_billtotal,
sum(case when WageCode = 'Overtime' then billrate end) as ot_billrate,
sum(case when WageCode = 'Overtime' then BillTotal end) as ot_billtotal,
sum(case when WageCode = 'Mileage' then billrate end) as mileage_billrate,
sum(case when WageCode = 'Mileage' then BillTotal end) as mileage_billtotal
from SampleTable st
group by EmployeeID, AssignmentID, CompanyName;

Related

Calculate data in Pivot

I have the following SQL table with 4 columns.
Table Name: tblTimeTransaction
Columns: EmployeeNumber, TransactionDate, CodeType, TimeShowninSeconds
CodeType has values : REG, OT1, OT2, OT3 respectively
I want it to show like this using pivot using 15 days incrementals starting from Jan 1 2020 onwards:
Employee Number | Effective Date | REG | OT1 | OT2 | OT3
E12345 | Between 10-1 till 10-15 | 200 | 100 | 50 | 45
E15000 | Between 10-1 till 10-15 | 400 | 600 | 903 | 49
E12345 | Between 10-15 till 10-31 | 200 | 100 | 50 | 45
E15000 | Between 10-15 till 10-31 | 400 | 600 | 903 | 49
E12346 | Between 11-1 till 11-15 | 4200 | 100 | 50 | 45
E15660 | Between 11-1 till 11-15 | 1200 | 600 | 6903 | 49
My SQL Code so far:
SELECT
Employee Number,
[TransactionDate] as [Effective Date],
[REG],
[OT1],
[OT2],
[OT3]
FROM
( SELECT Employee Number, TransactionDate, CodeType, TimeInSeconds
FROM [tblTimetransaction]
) ps
PIVOT
( SUM (TimeInSeconds)
FOR CodeType IN ( [REG], [OT1], [OT2], [OT3])
) AS pvt
where TransactionDate between '2020-01-01' and '2020-12-31'
If I follow you correctly, you can truncate the effective_date to either the 1st of 15th of the month depending on their day of the month, then use conditional aggregation to compute the total time_in_seconds for each code_type:
select employee_number,
datefromparts(year(effective_date), month(effective_date), case when day(effective_date) < 15 then 1 else 15 end) as dt,
sum(case when code_type = 'REG' then time_in_seconds else 0 end) as reg,
sum(case when code_type = 'OT1' then time_in_seconds else 0 end) as ot1,
sum(case when code_type = 'OT2' then time_in_seconds else 0 end) as ot2,
sum(case when code_type = 'OT3' then time_in_seconds else 0 end) as ot3
from tblTimetransaction
where effective_date >= '20200101' and effective_date < '20210101'
group by employee_number,
datefromparts(year(effective_date), month(effective_date), case when day(effective_date) < 15 then 1 else 15 end)

Sum all rows returned in query and use it in each row

I have a query (Oracle) that shows the sales of each customer by years:
SELECT cmp.company_key
, sum(CASE WHEN sd.date between TO_DATE('01-Jan-2010', 'dd-mm-yyyy') and TO_DATE('11-Jun-2010', 'dd-mm-yyyy') THEN sd.qty_ship * sd.unit_price END) AS year1sales
, sum(CASE WHEN sd.date between TO_DATE('01-Jan-2011', 'dd-mm-yyyy') and TO_DATE('11-Jun-2011', 'dd-mm-yyyy') THEN sd.qty_ship * sd.unit_price END) AS year2sales
FROM sales_detail sd
INNER JOIN sales_header sh on sd.sales_header_key = sh.sales_header_key
INNER JOIN companies cmp on sh.company_key = cmp.company_key
GROUP BY cmp.company_key
The query produces this:
company_key | year1sales | year2sales
------------|------------|------------
8687 | 21355.76 | 54326.45
25 | 9375.41 | 12401
34 | 6440.03 | 50349.27
247 | 47355.93 | 77432.67
83 | 15757.35 | 39999.12
But I also need it to return a value ("TBI") showing what percentage that company's sales are compared to the sum of all the other sales numbers.
So, for company #8687 it would be 21355.76 / sigma(year1 sales) which is 21355.76/100,284.48 = 21.3%.
So the result would be:
company_key | year1sales | year1 TBI | year2sales | year1 TBI
------------|------------|-----------|------------|----------
8687 | 21355.76 | 21.30 | 54326.45 | 23.17
25 | 9375.41 | 9.35 | 12401 | 5.29
34 | 6440.03 | 6.42 | 50349.27 | 21.47
247 | 47355.93 | 47.22 | 77432.67 | 33.02
83 | 15757.35 | 15.71 | 39999.12 | 17.06
And obviously the TBI columns would sum up to 100%.
How would you write this query? Also, what is the time complexity for a problem like this? I think it's O(n^2) best case.
You'd use SUM OVER to get the totals:
select
company_key,
year1sales,
year1sales / sum(year1sales) over() as year1tbi,
year2sales,
year2sales / sum(year2sales) over() as year2tbi
from
(
SELECT cmp.company_key
, sum(CASE WHEN sd.date between date '2010-01-01' and date '2010-06-11' THEN sd.qty_ship * sd.unit_price END) AS year1sales
, sum(CASE WHEN sd.date between date '2011-01-01' and date '2011-06-11' THEN sd.qty_ship * sd.unit_price END) AS year2sales
FROM sales_detail sd
INNER JOIN sales_header sh on sd.sales_header_key = sh.sales_header_key
INNER JOIN companies cmp on sh.company_key = cmp.company_key
GROUP BY cmp.company_key
)
order by company_key;
As to the complexity: I cannot answer this. The DBMS has to run through the result, build the totals and calculate the percentages per row then.

Select data from three table in sql

I have three table Like
Student : Sid, SName, SEmail
Fees_Type : Fid, FName, FPrice
StudentFees : Sid(FK from Student),Fid(FK from Fees_Type), FDate
Data of Each Table :
Student :
SID |SName | SEmail
1 | ABC | ABC#www.com
2 | XYZ | xyz#www.com
Fees_Type:
Fid | FName | FPrice
1 | Chess | 100
2 | Cricket | 200
StudentFees:
Sid | Fid| FDate
1 | 1 | 5/2
1 | 2 | 6/2
2 | 1 | 7/2
2 | 2 | 8/2
1 | 1 | 6/2
Now I want to Get data Like
SID|SName|SEmail | Total_Chess_Played|Total_Cricket_Played | ToTal_Fees
1 | ABC |ABC#www.com | 2 | 1 | 400
2 | XYZ |xyz#www.com | 1 | 1 | 300
I have tried these following query but can not get Group by or perfect result
SELECT s.sId, SEmail, SName, FName ,FPrice
FROM Student s
INNER JOIN StudentFees sf ON s.sId = sf.EId
INNER JOIN Fees_Type f ON f.fId = sf.fId
WHERE MONTH(pr.TDDate) = MONTH(dateadd(dd, 1, GetDate())) AND
YEAR(pr.TDDate) = YEAR(dateadd(dd, -1, GetDate()))
I am new in SQL. So Please Help Me.
Thank You.
You could do something like this:
SELECT
Student.SID,
Student.SName,
Student.SEmail,
SUM(CASE WHEN Fees_Type.FName='Chess' THEN 1 ELSE 0 END) AS Total_Chess_Played,
SUM(CASE WHEN Fees_Type.FName='Cricket' THEN 1 ELSE 0 END) AS Total_Cricket_Played,
SUM(Fees_Type.FPrice) AS ToTal_Fees
FROM
Student
JOIN StudentFees ON Student.sId = StudentFees.EId
JOIN Fees_Type ON Fees_Type.fId = StudentFees.fId
WHERE
MONTH(StudentFees.TDDate) = MONTH(dateadd(dd, 1, GetDate())) AND
YEAR(StudentFees.TDDate) = YEAR(dateadd(dd, -1, GetDate()))
GROUP BY
Student.SID,
Student.SName,
Student.SEmail
try this
SELECT
s.SID,
s.SName,
s.SEmail,
SUM(CASE WHEN ft.FName='Chess' THEN 1 ELSE 0 END) AS Total_Chess_Played,
SUM(CASE WHEN ft.FName='Cricket' THEN 1 ELSE 0 END) AS Total_Cricket_Played,
SUM(ft.FPrice) AS ToTal_Fees
FROM
Student s
JOIN StudentFees sf ON s.sId = sf.Sid
JOIN Fees_Type ft ON ft.fId = sf.fId
GROUP BY
s.SID,
s.SName,
s.SEmail

SQL Group By Issue with same item ID

I am trying to track the total number of sales a rep has along with the amount of time he was clocked into work.
I have the following two tables:
table1:
employeeID | item | price | timeID
----------------------------------------
1 | 1 | 12.92 | 123
1 | 2 | 10.00 | 123
1 | 2 | 10.00 | 456
table2:
ID | minutes_in_shift
--------------------------
123 | 45
456 | 15
I would join these two queries with the following SQL:
SELECT
t1.employeeID, t1.item, t1.price, t1.shiftID, t2.minutes_in_shift
FROM table1 t1
JOIN table 2 t2 ON (t2.ID = t1.timeID)
Which would return the following table:
employeeID | item | price | timeID | minutes_in_shift
---------------------------------------------------
1 | 1 | 12.92 | 123 | 45
1 | 2 | 10.00 | 123 | 45
1 | 2 | 10.00 | 456 | 15
I would like for the consolidate results, however, to have this outcome:
employeeID | itemsSold | priceTotals | totaltimeworked
-----------------------------------------------------------------
1 | 3 | 32.92 | 60
I could use COUNT and SUM for the items and price but I cannot figure out how to properly show the total time worked in the manner it appears above.
Note: I am only having trouble with calculating the time worked. In shift 123 - employee 1 was working 45 minutes, regardless of how many items he sold.
Any suggestions?
If you wish to use the sample data as they are you will need to extract the shifts and sum the minutes, like this:
with a as (
select employeeID, count(*) itemsSold, sum(price) priceTotals
from Sampletable1
group by employeeID),
b as (
select employeeID, shiftID, max(minutes_in_shift) minutes_in_shift
from Sampletable1
group by employeeID, shiftID),
c as (
select employeeID, sum(minutes_in_shift) totaltimeworked
from b
group by employeeID)
select a.employeeID, a.itemsSold, a.priceTotals, c.totaltimeworked
from a inner join c on a.employeeID = c.employeeID
However, with your existing tables the select statement will be much easier:
with a as (
select employeeID, timeID, count(*) itemsSold, sum(price) priceTotals
from table1
group by employeeID, timeID)
select a.employeeID, sum(a.itemsSold), sum(a.priceTotals), sum(table2.minutes_in_shift) totaltimeworked
from a inner join table2 on a.timeID = table2.ID
group by a.employeeID
I think this query should do what you want:
SELECT t1.employeeID,
count(t1.item) AS itemsSold,
sum(t1.price) AS priceTotals,
sum(DISTINCT t2.minutes_in_shift) AS totaltimeworked
FROM table1 t1
JOIN table2 t2 ON (t2.ID = t1.timeID)
GROUP BY t1.employeeID;
Check on SQL Fiddle

Select distinct records with Min Date from two tables with Left Join

I'm trying to retrieve all distinct AccountId’s as well as the earliest InsertDate for each. Occasionally the AccountId is not known and although the transactions may be distinct I want to bucket all of the ‘-1’s into their own group.
This is what I have attempted so far along with the schemas.
CREATE TABLE #tmpResults (
Trans Varchar(12),
AccountId Varchar(50),
EarlyDate DateTime DEFAULT getdate(), CardType Varchar(16))
insert #tmpResults
select [Trans] = convert(varchar(12),'CashSale')
, [AccountId] = b.AccountId
, [EarlyDate] = min(b.InsertDate)
, case when c.name LIKE '%VISA%' then 'VISA'
when c.name LIKE '%MasterCard%' then 'MasterCard'
when c.name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end as [CardType]
from TransBatch b
left join CardVer_3 c WITH (NOLOCK) ON c.Id = B.BatchId
left join TransBatch b2
on (b.accountid = b2.accountid and (b.InsertDate > b2.InsertDate or b.InsertDate = b2.InsertDate))
and b2.accountid is NULL
group by b.accountid, b.InsertDate,c.name
order by b.accountid DESC
select * from #tmpResults
The table schemas are like so:
**TransBatch**
RecordId |BatchId |InsertDate | AccountId | AccNameHolder
6676 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6677 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6678 | 11 | 2012-11-01 05:19:04.000 | 55555 | Account2
6679 | 11 | 2012-11-01 05:19:04.000 | -1 | NULL
6680 | 12 | 2012-11-02 05:20:04.000 | 12345 | Account1
6681 | 12 | 2012-11-02 05:20:04.000 | 55555 | Account2
6682 | 13 | 2012-11-04 06:20:04.000 | 44444 | Account3
6683 | 14 | 2012-11-05 05:30:04.000 | 44444 | Account3
6684 | 14 | 2012-11-05 05:31:04.000 | -1 | NULL
**CardVer_3**
BatchId |Name
11 |MasterCard
12 |Visa
13 |AMEX
14 |GoCard
This will be an intermediate table, the output is planned to look like the attached.
Gordon, I made some very minor changes to your suggestion and believe I have the correct output: http://www.sqlfiddle.com/#!3/cfbc3/7/0 . Thank you very much. I'm not at all familiar with the windows functions so I'm going to brush up on these.
The code is here:
select 'CashSale' as [Trans],
AccountId,
min(InsertDateTime),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDateTime, c.name,
row_number() over (partition by AccountId order by insertDateTime asc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK) ON c.batchId = B.BatchId
) t
where seqnum = 1
group by t.accountid, t.name
The next steps are to dump this into a temp table and try and get the output looking like the attached excel screen.
It sounds like you are trying to get the full record for the minimum insert date time. For this, you want to use windows functions:
select 'CashSale' as Trans,
AccountId,
min(InsertDate),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDate, c.name,
row_number() over (partition by AccountId order by insertDate desc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK)
ON c.Id = B.BatchId
) t
where seqnum = 1
I'm taking a guess that "CashSale" means that the credit card did not match. The TransId is then either the recordId or "CashSale".