Combining Multiple SQL Views ON Year & Month - sql

I have a SQL Server database (2012 express) with many tables.
I have produced three different VIEWS based on different combinations of the underlying tables.
Each of these views consists of three columns, Year, Month & Total
The Total column in each of the 3 Views is of a different measure.
What I want to be able to do is to combine the three Totals into a single View
I have attempted this with the following script -
SELECT b.[Year], b.[Month], b.Fees AS [Billing],
f.Estimate AS [Estimate],
w.Fees AS [WIP]
FROM MonthlyBillingTotals AS b
FULL JOIN MonthlyFeeEstimates AS f
ON (b.[Year] = f.[Year] AND b.[Month] = f.[Month])
FULL JOIN MonthlyInstructionsWIP AS w
ON (b.[Year] = w.[Year] AND b.[Month] = w.[Month])
ORDER BY b.[Year], b.[Month]
Originally I tried INNER JOINS but of course unless the Year / Month combo existed in the first view (MonthlyBillingTotals) then it did not appear in the combined query. I therefore tried FULL JOINS, but the problem here is that I get some NULLS in the Year and Month columns, when they do not exist in the first view (MonthlyBillingTotals).
If the data in the three Views is as follows -
Then what I want is -
And even better (if it is possible) -
with the missing months filled in

You could try building the full list of Months/Years from your tables using a UNION subquery, and then use that to drive your joins.. Something like this:
SELECT a.[Year], a.[Month], b.Fees AS [Billing],
f.Estimate AS [Estimate],
w.Fees AS [WIP]
FROM (SELECT a.[Year], a.[Month] FROM MonthlyBillingTotals AS a
UNION
SELECT b.[Year], b.[Month] FROM MonthlyFeeEstimates AS b
UNION
SELECT c.[Year], c.[Month] FROM MonthlyInstructionsWIP AS c) AS a
LEFT OUTER JOIN MonthlyBillingTotals AS b
ON (a.[Year] = b.[Year] AND a.[Month] = b.[Month])
LEFT OUTER JOIN MonthlyFeeEstimates AS f
ON (a.[Year] = f.[Year] AND a.[Month] = f.[Month])
LEFT OUTER JOIN MonthlyInstructionsWIP AS w
ON (a.[Year] = w.[Year] AND a.[Month] = w.[Month])
ORDER BY a.[Year], a.[Month]

This is completely untested, but see if this solves your problems:
SELECT b.[Year], b.[Month], Coalesce(b.Fees, '0') AS [Billing],
Coalesce(f.Estimate,'0') AS [Estimate],
Coalesce(w.Fees,'0') AS [WIP]
FROM MonthlyBillingTotals AS b
LEFT JOIN MonthlyFeeEstimates AS f
ON (b.[Year] = f.[Year] AND b.[Month] = f.[Month])
LEFT JOIN MonthlyInstructionsWIP AS w
ON (b.[Year] = w.[Year] AND b.[Month] = w.[Month])
ORDER BY b.[Year], b.[Month]
The Coalesce function puts in a '0' value if nothing is found, and left joins should only join parts of MonthlyFeeEstimates and MonthlyInstructionsWIP when the year and month match.

You could set up a small date table with year and month and left join the views with that, and use the ISNULL(variable,0) function to replace NULL with 0. Another option instead of a date table would be to use a common table expression to generate a date range to join with. In any case I suggest you look up the date table (or numbers table), it can be a really useful tool.
Edit: added an example on how a date table can be created (for reference):
declare #year_month table (y int, m int)
;with cte as (
select cast('2000-01-01' as datetime) date_value
union all
select date_value + 1
from cte
where date_value + 1 < '2010-12-31'
)
insert #year_month (y, m)
select distinct year(date_value), month(date_value)
from cte
order by 1, 2
option (maxrecursion 0)
select * from #year_month

Related

SQL - Pivoting where the rows of one table are the column titles of another

I'm looking to create a table (shown below) more in the form of a matrix, where the column titles are variables.
#ACTable AS ACT
#WCTable AS WCT
The temp table, derived from #ProductionTable AS PT
The output I'm looking for looks like this. Essentially I want the ACT.AC running as column titles, the WCT.WC running down, and counting how many were ActFin on Nov 6th. The colour shows the matching associations. I'll coalesce the rest after, not too concerned about NULLs or 0s.
The query so far (it fails at the FOR statement)
SELECT * FROM
(
SELECT
PT.ParentPart,
ACT.AC,
WCT.WC,
PT.ActFin
FROM #ProductionTable AS PT
INNER JOIN #WCTable AS WCT ON WCT.WC = PT.WC
INNER JOIN #ACTable AS ACT ON PT.AC = ACT.AC
) t
PIVOT(
COUNT(CASE
WHEN
PT.ActFin > '2019-11-06' --count
THEN
1
END)
FOR ACT.AC IN ( --this is where things fall apart
'54',
'53',
'52')
)
Is this possible?
The columns in the FOR clause need to be wrapped in []:
SELECT * FROM
(
SELECT
PT.ParentPart,
ACT.AC,
WCT.WC,
PT.ActFin
FROM #ProductionTable AS PT
INNER JOIN #WCTable AS WCT ON WCT.WC = PT.WC
INNER JOIN #ACTable AS ACT ON PT.AC = ACT.AC
) t
PIVOT(
COUNT(CASE
WHEN
PT.ActFin > '2019-11-06' --count
THEN
1
END)
FOR ACT.AC IN ( --this is where things fall apart
[54],
[53],
[52])
)

How to join two tables having two common column values and unioning the rest

I'd like to combine Table A and Table B at the link below and end up with Table C. What is the best way to do this in SQL? I've thought about creating a composite key between the tables for LedgerID + Year doing an inner join and then unioning the left and right only data. I'm also curious how to avoid duplicating values across rows like Balance = 50.00 ending up in rows for Tires and Windshield.
Try a full outer join, joining on LedgerID and Year, using coalesce to show Table B's LedgerID/Year when Table A's is NULL:
SELECT
COALESCE(A.LedgerID, B.LedgerID) as LedgerID,
COALESCE(A.Year, B.Year) as Year,
A.Title,
A.Payment,
B.Balance
FROM "Table A" AS A
FULL OUTER JOIN "Table B" AS B ON (A.LedgerID=B.LedgerID AND A.Year=B.Year)
--Please try this Query. Since you have only reference LedgerId and Year, the balance will show 50 for both Tires & Windshield
; with cte_Ledger (LedgerId, [year])
AS
(
Select DISTINCT LedgerId, [year]
From tableA
UNION
Select DISTINCT LedgerId, [year]
From tableB
)
select t.LedgerId
, t.[year]
, t1.Title
, T1.Payments
, t2.Balance
FROM cte_Ledger t
left join tableA t1 on t.LedgerId = t1.LedgerId and t.[year] = t1.[year]
left join tableB t2 on t2.LedgerId = t.LedgerId and t2.[year] = t.[year]
I think so, Above Queries will not help to get expected result.
some misunderstanding is with requirement.
For ledgerid = 22 and Year = 2017, have 2 records in table-A and 1 with Table-B. But in expecting result, Balance 50(Record of table-B) is exists with matched first row of Table-A only. As per above all logic it will be with 2 Records where ledgerid = 22, Year = 2017 and Title with "Tires" & "Windshield".
If required same result as mentioned then need to use recursive CTE or ranking function with order of ID column.
Here is my solution after I loaded the tables, another nested case statement may be need to format out the zero on Ledger 24.
Select
[LedgerID],
[Year],
Case when PayRank = 1 then Title else '' end as Title,
Case when PayRank = 1 then convert(varchar(20),Payments) else '' end as
Payments,
Case when BalRank = 1 then convert(varchar(20),Balance) else '' end as
Balance
from(
SELECT
B.[LedgerID]
,B.[Year]
,Rank()Over(Partition by B.LedgerID,Payments order by
B.LedgerID,B.Year,Title) as PayRank
,isnull([Title],'') as Title
,isnull([Payments],0) as Payments
,Rank()Over(Partition by B.LedgerID,B.Year order by
B.LedgerID,B.Year,Payments) as BalRank
,Balance
FROM [TableB] B
left outer join [TableA] A
on A.LedgerID = B.LedgerID
) Query
order by LedgerID,Year

SQL Inner Join and nearest row to date

I dont't get it. I changed some of the code. In the WPLEVENT Table are a lot of Events per person. In the Persab-Table are the Persons with their History. Now I need the from the Persab Table just that row wich matches the persab.gltab Date nearest to the WPLEVENT.vdat Date. So all rows from the WPLEVENT, but just the one matching row from the PERSAB-Table.
SELECT
persab.name,
persab.vorname,
vdat,
eventstart,
persab.rc1,
persab.rc2
FROM wplevent
INNER JOIN
persab ON WPLEVENT.PersID = persab.PRIMKEY
INNER JOIN
(SELECT TOP 1 persab.rc1
FROM PERSAB
WHERE persab.gltab <= getdate() --/ Should be wplevent.vdat instead of getdate()
) NewTable ON wplevent.persid = persab.primkey
WHERE
persid ='100458'
ORDER BY vdat DESC
Need to use the MAX() function with the proper syntax by supplying an expression like MAX(persab.rc1). Also need to use GROUP BY for the second column rc2 in the subquery (although it looks like you do not need it). Finally you are missing the ON clause for the final INNER JOIN. I can update the answer to fix the query if you provide that information.
SELECT
Z1PERS.NAME
, Z1PERS.VORNAME
, WPLEVENT.VDat
, WPLEVENT.EventStart
, WPLEVENT.EventStop
, WPLEVENT.PEPGROUP
, Z1SGRP.TXXT
, PERSAB.GLTAB
, Z1PERS.PRIMKEY AS Expr1
, PERSAB.PRIMKEY
FROM
Z1PERS
INNER JOIN
WPLEVENT ON Z1PERS.PRIMKEY = WPLEVENT.PersID
INNER JOIN
Z1SGRP ON WPLEVENT.PEPGROUP = Z1SGRP.GRUPPE
INNER JOIN
(
SELECT MAX(Persab.rc1) --Fixed MAX expression
, persab.rc2
FROM
persab
GROUP BY
persab.rc2 --Need to group on rc2 if you want that column in the query otherwise remove this AND the rc2 column from select list
WHERE
WPLEVENT.PersID = PERSAB.PRIMKEY
AND WPLEVENT.VDat <= PERSAB.GLTAB
) --Missing ON clause for the INNER JOIN here
WHERE z1pers.vorname = 'henning'

SQL Statement Issue combining 4 tables to get the correct information to display

I am having trouble figuring this one out. I'm using MS SQL 2008 and trying to add the total tech hours from the unbilled table and the billed tables and group them together. I also need to exclude rows for the billed hours based on the report header on a third table if the billed report is voided. I then need to grab the tech names from a 4th table. The unbilled table is SCQReportLabors which lists all the labor records. SCReportLabors table lists all the billed labor records. SCReports is the billed report header which I need to figure out if the VoidID column is null. ShAgents table holds the tech names. Below is what I currently have and I know it doesn't work but it's what I've got so far. For my output data I woul like a TotalHours column and a TechName column. If I end up with a billed total, unbilled total and techname column I can live with that as well. I've been staring at this for a little while and need some perspective and advice. Thanks in advance for any help.
SELECT a.TotalHours, c.PrefFullName AS TechName
FROM (SELECT SUM(LaborHours)+SUM(OvertimeHours)+SUM(TravelHours) AS TotalHours, TechnicianID
FROM SCReportLabors
LEFT OUTER JOIN SCReports d ON a.ReportID = d.ReportID
WHERE d.VoidID IS NULL
GROUP BY TechnicianID) a
JOIN (SELECT SUM(LaborHours)+SUM(OvertimeHours)+SUM(TravelHours) AS TotalHours, TechnicianID
FROM SCQReportLabors
GROUP BY TechnicianID) b
ON a.TechnicianID = b.TechnicianID
LEFT OUTER JOIN ShAgents c ON a.TechnicianID = c.AgentID
ORDER BY c.PrefFullName
This query should capture all data and handles null hours.
SELECT
A.PrefFullName AS 'TechName',
(ISNULL(B.TotalHours, 0) + ISNULL(C.TotalHours, 0)) AS 'Total Hours'
FROM
ShAgents AS A
LEFT OUTER JOIN
(
SELECT
(
SUM(ISNULL(LaborHours, 0)) +
SUM(ISNULL(OvertimeHours, 0)) +
SUM(ISNULL(TravelHours, 0))
) AS 'TotalHours',
TechnicianID
FROM
SCReportLabors AS tA
LEFT OUTER JOIN SCReports AS tB
ON tA.ReportID = tB.ReportID
WHERE
tB.VoidID IS NULL
GROUP BY
TechnicianID
) AS B
ON A.AgentID = B.TechnicianID
LEFT OUTER JOIN
(
SELECT
(
SUM(ISNULL(LaborHours, 0)) +
SUM(ISNULL(OvertimeHours, 0)) +
SUM(ISNULL(TravelHours, 0))
) AS 'TotalHours',
TechnicianID
FROM
SCQReportLabors
GROUP BY
TechnicianID
) AS C
ON A.AgentID = C.TechnicianID
ORDER BY
A.PrefFullName
I made the two main queries a UNION query and also touched up on JOIN for the VOID column.
SELECT SQ1.TotalHours, SHA.PrefFullName AS TechName
FROM
(
SELECT SUM(TOTALHOURS) AS TotalHours, TechnicianID
(SELECT SUM(LaborHours)+SUM(OvertimeHours)+SUM(TravelHours) AS TotalHours, TechnicianID
FROM SCReportLabors L
JOIN SCReports RPTS
ON L.ReportID = RPTS.ReportID
WHERE RPTS.VoidID IS NULL
GROUP BY TechnicianID)
UNION ALL
(SELECT SUM(LaborHours)+SUM(OvertimeHours)+SUM(TravelHours) AS TotalHours, TechnicianID
FROM SCQReportLabors
GROUP BY TechnicianID
)
) SQ
GROUP BY TechnicianID
JOIN ShAgents SHA
ON SQ.TechnicianID = SHA.AgentID
ORDER BY SHA.PrefFullName

Limit join to one row

I have the following query:
SELECT sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount, 'rma' as
"creditType", "Clients"."company" as "client", "Clients".id as "ClientId", "Rmas".*
FROM "Rmas" JOIN "EsnsRmas" on("EsnsRmas"."RmaId" = "Rmas"."id")
JOIN "Esns" on ("Esns".id = "EsnsRmas"."EsnId")
JOIN "EsnsSalesOrderItems" on("EsnsSalesOrderItems"."EsnId" = "Esns"."id" )
JOIN "SalesOrderItems" on("SalesOrderItems"."id" = "EsnsSalesOrderItems"."SalesOrderItemId")
JOIN "Clients" on("Clients"."id" = "Rmas"."ClientId" )
WHERE "Rmas"."credited"=false AND "Rmas"."verifyStatus" IS NOT null
GROUP BY "Clients".id, "Rmas".id;
The problem is that the table "EsnsSalesOrderItems" can have the same EsnId in different entries. I want to restrict the query to only pull the last entry in "EsnsSalesOrderItems" that has the same "EsnId".
By "last" entry I mean the following:
The one that appears last in the table "EsnsSalesOrderItems". So for example if "EsnsSalesOrderItems" has two entries with "EsnId" = 6 and "createdAt" = '2012-06-19' and '2012-07-19' respectively it should only give me the entry from '2012-07-19'.
SELECT (count(*) * sum(s."price")) AS amount
, 'rma' AS "creditType"
, c."company" AS "client"
, c.id AS "ClientId"
, r.*
FROM "Rmas" r
JOIN "EsnsRmas" er ON er."RmaId" = r."id"
JOIN "Esns" e ON e.id = er."EsnId"
JOIN (
SELECT DISTINCT ON ("EsnId") *
FROM "EsnsSalesOrderItems"
ORDER BY "EsnId", "createdAt" DESC
) es ON es."EsnId" = e."id"
JOIN "SalesOrderItems" s ON s."id" = es."SalesOrderItemId"
JOIN "Clients" c ON c."id" = r."ClientId"
WHERE r."credited" = FALSE
AND r."verifyStatus" IS NOT NULL
GROUP BY c.id, r.id;
Your query in the question has an illegal aggregate over another aggregate:
sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount
Simplified and converted to legal syntax:
(count(*) * sum(s."price")) AS amount
But do you really want to multiply with the count per group?
I retrieve the the single row per group in "EsnsSalesOrderItems" with DISTINCT ON. Detailed explanation:
Select first row in each GROUP BY group?
I also added table aliases and formatting to make the query easier to parse for human eyes. If you could avoid camel case you could get rid of all the double quotes clouding the view.
Something like:
join (
select "EsnId",
row_number() over (partition by "EsnId" order by "createdAt" desc) as rn
from "EsnsSalesOrderItems"
) t ON t."EsnId" = "Esns"."id" and rn = 1
this will select the latest "EsnId" from "EsnsSalesOrderItems" based on the column creation_date. As you didn't post the structure of your tables, I had to "invent" a column name. You can use any column that allows you to define an order on the rows that suits you.
But remember the concept of the "last row" is only valid if you specifiy an order or the rows. A table as such is not ordered, nor is the result of a query unless you specify an order by
Necromancing because the answers are outdated.
Take advantage of the LATERAL keyword introduced in PG 9.3
left | right | inner JOIN LATERAL
I'll explain with an example:
Assuming you have a table "Contacts".
Now contacts have organisational units.
They can have one OU at a point in time, but N OUs at N points in time.
Now, if you have to query contacts and OU in a time period (not a reporting date, but a date range), you could N-fold increase the record count if you just did a left join.
So, to display the OU, you need to just join the first OU for each contact (where what shall be first is an arbitrary criterion - when taking the last value, for example, that is just another way of saying the first value when sorted by descending date order).
In SQL-server, you would use cross-apply (or rather OUTER APPLY since we need a left join), which will invoke a table-valued function on each row it has to join.
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
-- CROSS APPLY -- = INNER JOIN
OUTER APPLY -- = LEFT JOIN
(
SELECT TOP 1
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(#in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(#in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
) AS FirstOE
In PostgreSQL, starting from version 9.3, you can do that, too - just use the LATERAL keyword to achieve the same:
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
LEFT JOIN LATERAL
(
SELECT
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
LIMIT 1
) AS FirstOE
Try using a subquery in your ON clause. An abstract example:
SELECT
*
FROM table1
JOIN table2 ON table2.id = (
SELECT id FROM table2 WHERE table2.table1_id = table1.id LIMIT 1
)
WHERE
...