T SQL to join record dynamically - sql

Can someone help me out with the following scenario. I am using SQL Server 2008r2. I need sql server query to get the expected result. Here is what I tried to do:
DECLARE #ProjectInvoice TABLE(InvoiceID INT, ProjectID INT, InvoiceAmount float)
DECLARE #ProjectBudget TABLE(ProjectBudgetID INT, ProjectID INT, BudgetAmount float)
INSERT INTO #ProjectBudget VALUES(11,1,100000),(12,1,50000)
INSERT INTO #ProjectInvoice VALUES (12345,1,25000)
,(12346,1, 30000)
,(12347,1, 40000)
,(12348,1, 30000)
,(12349,1, 10000)
select * from #ProjectBudget
select * from #ProjectInvoice
Expected result:
-- Expected Result
--InvoiceID ProjectID InvoiceAmount ProjectBudgetID BugetAmountLeft
-- 12345 1 25k 11 75k -- > 100k(original budget) - 25k
-- 12346 1 30k 11 45k --> 75k( budget amount left after first invoice) - 30k
-- 12347 1 40k 11 5k --> 45k - 40k
-- 12348 1 5k 11 0k --> now only 5k is left budget, whereas we have 30k to deduct, deduct 5k from projectBudget11
-- 12348 1 25k 12 25k --> and the rest 25k from project Budget 12
-- 12349 1 10k 12 15k

SELECT p.InvoiceID, p.ProjectID, p.InvoiceAmount, sm.ProjectBudgetID,
sm.BudgetAmount - sm.amt AS runningTot
FROM #ProjectInvoice AS p
INNER JOIN (SELECT i.InvoiceID, b.projectBudgetID, b.BudgetAmount,
SUM(i.InvoiceAmount) OVER (PARTITION BY b.ProjectBudgetID ORDER BY
i.InvoiceId) AS amt
FROM #ProjectBudget AS b
INNER JOIN #ProjectInvoice AS i ON b.ProjectID = i.ProjectID) as sm ON p.InvoiceID = sm.InvoiceId

Related

Aggregate payments per year per customer per type

Please consider the following payment data:
customerID paymentID pamentType paymentDate paymentAmount
---------------------------------------------------------------------
1 1 A 2015-11-28 500
1 2 A 2015-11-29 -150
1 3 B 2016-03-07 300
2 4 A 2015-03-03 200
2 5 B 2016-05-25 -100
2 6 C 2016-06-24 700
1 7 B 2015-09-22 110
2 8 B 2016-01-03 400
I need to tally per year, per customer, the sum of the diverse payment types (A = invoice, B = credit note, etc), as follows:
year customerID paymentType paymentSum
-----------------------------------------------
2015 1 A 350 : paymentID 1 + 2
2015 1 B 110 : paymentID 7
2015 1 C 0
2015 2 A 200 : paymentID 4
2015 2 B 0
2015 2 C 0
2016 1 A 0
2016 1 B 300 : paymentID 3
2016 1 C 0
2016 2 A 0
2016 2 B 300 : paymentID 5 + 8
2016 2 C 700 : paymentId 6
It is important that there are values for every category (so for 2015, customer 1 has 0 payment value for type C, but still it is good to see this).
In reality, there are over 10 payment types and about 30 customers. The total date range is 10 years.
Is this possible to do in only SQL, and if so could somebody show me how? If possible by using relatively easy queries so that I can learn from it, for instance by storing intermediary result into a #temptable.
Any help is greatly appreciated!
a simple GROUP BY with SUM() on the paymentAmount will gives you what you wanted
select year = datepart(year, paymentDate),
customerID,
paymentType,
paymentSum = sum(paymentAmount)
from payment_data
group by datepart(year, paymentDate), customerID, paymentType
This is a simple query that generates the required 0s. Note that it may not be the most efficient way to generate this result set. If you already have lookup tables for customers or payment types, it would be preferable to use those rather than the CTEs1 I use here:
declare #t table (customerID int,paymentID int,paymentType char(1),paymentDate date,
paymentAmount int)
insert into #t(customerID,paymentID,paymentType,paymentDate,paymentAmount) values
(1,1,'A','20151128', 500),
(1,2,'A','20151129',-150),
(1,3,'B','20160307', 300),
(2,4,'A','20150303', 200),
(2,5,'B','20160525',-100),
(2,6,'C','20160624', 700),
(1,7,'B','20150922', 110),
(2,8,'B','20160103', 400)
;With Customers as (
select DISTINCT customerID from #t
), PaymentTypes as (
select DISTINCT paymentType from #t
), Years as (
select DISTINCT DATEPART(year,paymentDate) as Yr from #t
), Matrix as (
select
customerID,
paymentType,
Yr
from
Customers
cross join
PaymentTypes
cross join
Years
)
select
m.customerID,
m.paymentType,
m.Yr,
COALESCE(SUM(paymentAmount),0) as Total
from
Matrix m
left join
#t t
on
m.customerID = t.customerID and
m.paymentType = t.paymentType and
m.Yr = DATEPART(year,t.paymentDate)
group by
m.customerID,
m.paymentType,
m.Yr
Result:
customerID paymentType Yr Total
----------- ----------- ----------- -----------
1 A 2015 350
1 A 2016 0
1 B 2015 110
1 B 2016 300
1 C 2015 0
1 C 2016 0
2 A 2015 200
2 A 2016 0
2 B 2015 0
2 B 2016 300
2 C 2015 0
2 C 2016 700
(We may also want to play games with a numbers table and/or generate actual start and end dates for years if the date processing above needs to be able to use an index)
Note also how similar the top of my script is to the sample data in your question - except it's actual code that generates the sample data. You may wish to consider presenting sample code in such a way in the future since it simplifies the process of actually being able to test scripts in answers.
1CTEs - Common Table Expressions. They may be thought of as conceptually similar to temp tables - except we don't actually (necessarily) materialize the results. They also are incorporated into the single query that follows them and the whole query is optimized as a whole.
Your suggestion to use temp tables means that you'd be breaking this into multiple separate queries that then necessarily force SQL to perform the task in an order that we have selected rather than letting the optimizer choose the best approach for the above single query.

Joining two tables and producing results only on one table when there are null values on joining column

I have table Quota with columns 'Month Number', 'year','Goal' as below
I have other table Sales with columns 'id', 'Sale Date' as below.
I am joining both the tables on Month and year of 'sales date' from Sales table to 'Month Number' and 'year' from Quota Table to get the results and i am able to get results if i have sales for that particular month and year.Now for the month of may i have no sales so when i join on those columns i am not getting any results from quota table. how can i just display Quota table values if there are no corresponding sales in Sales table? I tried left joining but its not displaying any results.
Like already said, we don't know your desired output. But by using a left join you should retrieve the records from the quota table.
declare #quota table (monthnumber int, qyear int, goal int)
insert into #quota values
(2,2017,5),
(3,2017,10),
(4,2017,8),
(5,2017,8),
(6,2017,10)
declare #sales table (id int, salesdate date)
insert into #sales values
(101,'20170321'),
(102,'20170427'),
(103,'20170223'),
(105,'20170427'),
(108,'20170321'),
(109,null),
(111,null)
select q.*
from #quota as q
left outer join #sales as s
on year(s.salesdate) = q.qyear and
month(s.salesdate) = q.monthnumber
Returns
monthnumber qyear goal
--------------------------
2 2017 5
3 2017 10
3 2017 10
4 2017 8
4 2017 8
5 2017 8
6 2017 10
You can use month and year of function and do left join as below
select * from Quota q left join sales s
on q.year = year(s.saledate) and q.MonthNumber = month(s.saledate)
Despite you didn't specify what output you need or your effort so far, I made my attempt anyway and I guess this is what you want:
SELECT
Q.*,
SalesCount = (SELECT COUNT(*) FROM Sales S
WHERE YEAR(S."Sale Date") = Q.Year AND MONTH(S."Sale Date") = Q.MonthNumber)
FROM Quota Q
And this is the result:
MonthNumber Year Goal SalesCount
2 2017 5 1
3 2017 10 2
4 2017 8 2
5 2017 8 0
6 2017 10 0

Loop through rows and count based on table name

This is my database table:
Status
------
Active
Active
Inactive
Removed
Removed
My desired output:
Status | Total | Percent
-------------------------------
Active | 2 | 33.33
Inactive | 1 | 16.66
Removed | 3 | 50
Total | 6 | 100
What i've attempted:
SELECT
OrderStatus AS Status,
COUNT(OrderStatus) AS Total,
ROUND(((COUNT(OrderStatus ) * 100)/COUNT(*)),2) AS Percent
FROM
myTable
For obvious reasons my query is not working, any help appreciated!
You're missing the group by clause, and you need to divide by the total number of records, which you can get with a subquery:
SELECT
OrderStatus AS Status,
COUNT(OrderStatus) AS Total,
ROUND((COUNT(OrderStatus ) * 100)/(select COUNT(*) from myTable),2) AS [Percent]
FROM
myTable
Group by OrderStatus
Assign the total records in the table to a variable. This will prevent it from needing to be recalculated for each record. A small issue with only 5 records, but if you ever applied this logic to a larger table with more unique values, the difference should become noticable.
SQL:
declare #TempTable table([OrderStatus] varchar(50))
insert into #TempTable
values
('Active'),
('Active'),
('Inactive'),
('Removed'),
('Removed')
DECLARE #TotalRecords int = (Select Count(*) from #TempTable)
SELECT
OrderStatus AS Status,
COUNT(OrderStatus) AS Total,
ROUND((COUNT(OrderStatus ) * 100)/#TotalRecords,2) AS [Percent]
FROM
#TempTable
Group by OrderStatus
Results:
(5 row(s) affected)
Status Total Percent
-------------------------------------------------- ----------- -----------
Active 2 40
Inactive 1 20
Removed 2 40
(3 row(s) affected)

Calculate MonthlyVolume - PIVOT SQL

I am using SQL server 2008 and I have following Table with millions of rows...Here are few sample records
Serial_Num ReadingDate M_Counter Dyn_Counter
XYZ 3/15/2014 100 190
XYZ 4/18/2014 140 240
XYZ 5/18/2014 200 380
ABC 3/12/2014 45 40
ABC 4/19/2014 120 110
ABC 5/21/2014 130 155
This table will always have only one reading for each month and no missing months....
and I would like calculate M_Counter and Dyn_Counter values for each month, For an example XYZ -> May month calculated counter value should be 60 = 200 (05/18/2014 value) - 140 (04/18/2014 value). I would like to insert data into another table in following way.
CalculatedYear CalculatedMonth Serial_Num M_Counter_Calc Dyn_Counter_Calc
2014 4 XYZ 40 50
2014 5 XYZ 60 140
2014 4 ABC 75 70
2014 5 ABC 10 45
Any help really appreciated!
If you're using MS SQL, something like this should work. The concept is to sort the dataset based on Serial_Num and ReadingDate. Add a sequential Row ID and store into a temp table. Join the table onto itself such that you match up the current row with the previous row where the serial numbers still match. If there wasn't a prior month's reading, the value will be null. We use Isnull( x, 0) to account for this when doing the calculations.
declare #Temp1 table
(
RowID int,
Serial_Num varchar(3),
ReadingDate datetime,
M_Counter int,
Dyn_Counter int
)
insert into #Temp1
select ROW_NUMBER() over (order by Serial_Num, ReadingDate), *
from MyTable T
select
Year(T1.ReadingDate) As CalculatedYear,
Month(T1.ReadingDate) as CalculatedMonth,
T1.Serial_Num,
T1.M_Counter - ISNULL(T2.M_Counter,0) as Calculated_M_Counter,
T1.Dyn_Counter - isnull(T2.Dyn_Counter,0) as Calculated_Dyn_Counter
from #Temp1 T1
left outer join #Temp1 T2 on T1.RowID = T2.RowID + 1 and T1.Serial_Num = T2.Serial_Num
order by T1.Serial_Num, Year(T1.ReadingDate), Month(T1.ReadingDate)

Split a Table into 2 or more Tables based on Column value

I have a Table called "MIVTable" which has the following records,
MIVID Quantity Value
------ ---------- --------
14 10 3000
14 20 3500
14 15 2000
15 20 3000
15 50 7500
16 25 2000
Here, I need to store the above Table into two tables such as "HeaderTbl" and "DetailTbl" based on the MIVID as follows:
HeaderTbl:
HID MIVID TotalQuantity TotalValue
----- ------- ------------- -----------
1 14 45 8500
2 15 70 10500
3 16 25 2000
Here HID is the Primary Key with Identity Column.
DetailTbl:
HID MIVID Quantity Value
----- ------- ------------ -------
1 14 10 3000
1 14 20 3500
1 14 15 2000
2 15 20 3000
2 15 50 7500
3 16 25 2000
Suppose, if the MIVTable contains 4 different MIVID means, then 4 row should be created based on the MIVID on the HeaderTbl. How to do this?
To insert records in HeaderTbl from MIVTable use this: (HID should be auto increment)
INSERT INTO HeaderTbl
([MIVID], [TotalQuantity], [TotalValue])
SELECT MIVID, SUM(Quantity), SUM(Value) FROM MIVTable GROUP BY MIVID;
To insert records in DetailTbl from HeaderTbl and MIVTable use this:
INSERT INTO DetailTbl
([HID], [MIVID], [Quantity], [Value])
SELECT H.HID, M.*
FROM HeaderTbl H
INNER JOIN MIVTable M
ON H.MIVID = M.MIVID;
Look at this SQLFiddle
Here you need to use INSERT INTO SELECT statement to insert data from one table to another. You can also use JOIN in such statement as I did it for DetailTbl.
You would generate the HeaderTbl using RANK() SQL Server function, as follows:
SELECT RANK() OVER (ORDER BY MIVID) as HID, MIVID, TotalQuantity, TotalValue
FROM
(
SELECT
MIVID,
SUM(Quantity) as TotalQuantity,
SUM(Value) as TotalValue
FROM MIVTable GROUP BY MIVID
) AS A
and the Detail table using the ROW_NUMBER() SQL Server function, as follows:
SELECT
ROW_NUMBER() OVER (ORDER BY MIVID) AS HID,
MIVID,
Quantity,
Value
FROM MIVTable