Related
Here is my query, I have different account values (the accValByProd table) that I want to add to my table by their product type. If I just select the accValByProd table by itself the numbers are accurate, but when I join it with the billing table the numbers become much larger than they should be.
As you can see since I need to take the MAX of values in the billing table and then SUM them in the final calculation, I can't just do that in the final SELECT (unless there's a way to do that?). Any help is appreciated I'm not quite sure what I'm doing wrong.
WITH billing AS
( SELECT
bh.debit_account_id,
bh.period_start_date,
bh.period_end_date,
bh.a_id,
bh.account_value,
bh.platform_fee
FROM
billing_history bh
WHERE
-- LESS than same day following year includes any
-- possible time portion if so part of.
bh.period_start_date >= '2020-07-01'
and bh.period_end_date < '2021-07-01'
and bh.a_fee != 0
),
acc_prods AS
(
SELECT
a.account_id,
a.product_id,
CASE WHEN p.product_type = 3 THEN 'FSP'
WHEN p.product_type = 6 THEN 'APM'
WHEN p.product_type = 13 THEN 'UMA'
ELSE 'Unknown' END product_type,
a.a_id
FROM
account a
LEFT JOIN product p
ON a.product_id = p.product_id
WHERE
-- which table alias, should never leave blind
-- assumption here is "Account" table (alias a)
a.close_date IS NULL
OR a.close_date >= GETDATE()
),
accounts AS (
SELECT
a.account_id,
a.a_id,
a.customer_id,
a.close_date
FROM
account a
),
accValByProd as
(
SELECT
bh.debit_account_id AS deb_id,
bh.period_end_date,
MAX(bh.account_value) AS acc_val,
bh.a_id,
ISNULL(ap.product_type, 'Unknown') AS prod_type
FROM
billing_history bh
LEFT JOIN acc_prods ap
ON bh.debit_account_id = ap.account_id
WHERE
bh.period_end_date = '2021-06-30'
GROUP BY
bh.debit_account_id,
bh.period_end_date,
bh.a_id,
ap.product_type
)
SELECT DISTINCT
ad.ad_id,
ad.fname + ' ' + ad.middle + ' ' + ad.lname 'full name',
accValByProd.prod_type AS 'product type',
COUNT(DISTINCT billing.debit_account_id) AS 'number of accounts',
SUM(accValByProd.acc_val) AS 'product aum',
SUM(CASE WHEN DATEDIFF(DAY, billing.period_start_date, billing.period_end_date) > 31
THEN billing.platform_fee ELSE 0 END) AS 'fees'
FROM
advisor ad
JOIN billing
ON ad.a_id = billing.a_id
JOIN accounts
ON billing.debit_account_id = accounts.account_id
JOIN accValByProd
ON billing.debit_account_id = accValByProd.deb_id
WHERE
1=1
and ( ad.termination_date IS NULL
OR ad.termination_date >= GETDATE())
AND ( accounts.close_date IS NULL
OR accounts.close_date >= '2021-06-30')
GROUP BY
ad.a_id,
ad.fname,
ad.middle,
ad.lname,
accValByProd.prod_type
Here are the expected results for a_id 6835 (plus the other information)
a_id product aum product type
---- ------------ -------------
6835 7861895.23 APM
6835 47059722.64 FSP
6835 9816992.32 UMA
6835 528930.47 Unknown
And here are the actual results:
a_id product aum product type
---- ------------ -------------
6835 45447953.20 APM
6835 203942000.07 FSP
6835 77678383.30 UMA
6835 1706276.86 Unknown
I am writing SQL to report data similar to the below format. The row's beginning with 'P' are summary rows for a specific vendor for the corresponding remittance 'R' rows below it associated to the payment or that vendor. For example the PAID_AMT amount on the P rows is a summed value (1000.50 = sum of corresponding R rows 110 + 800 + 100.50 = 1000.50) for 'NW Forest Supplies'. I started doing this as a UNION query however the challenge is that the 'P' rows represent different fields from what the 'R' rows represent, so combining the data together is what I am struggling with.
The final row beginning with 'C' is a overall summary of all rows and I am also struggling with and is not correct. Per the example, it should sum the total PAID_AMT values from the 'P' rows which would be 2056.00 (1000.50 + 55 + 1000.50), then output the total count of the number of payments (3), and then final the total count of remittances - 7 in this example.
Perhaps it's better to make use of a CTE and union in the final result, or make use of temp tables to insert the data for the 'P' and 'R' data into their own tables, and then query from?
Example of how report output needs to be:
SELECT
'P'
, 'CRD'
, 'PayerName.Payables'
, A.ADDRESS4
, A.PYMNT_ID_REF
, SUM(B.PAID_AMT)
, A.REMIT_VENDOR
, CAST(B.REMIT_ADDR_SEQ_NUM AS VARCHAR)
, A.NAME1
, 'USD'
FROM
PS_PAYMENT_TBL A
INNER JOIN PS_PYMNT_VCHR_XREF B
ON B.PYMNT_ID = A.PYMNT_ID
AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE A.PYMNT_DT > '01/12/2021'
GROUP BY
A.REMIT_VENDOR
, A.ADDRESS4
, A.PYMNT_ID_REF
, A.REMIT_VENDOR
, CAST(B.REMIT_ADDR_SEQ_NUM AS VARCHAR)
, A.NAME1
UNION
SELECT
'R'
, A.PYMNT_ID_REF
, C.INVOICE_ID
, C.INVOICE_DT
, C.GROSS_AMT
, C.DSCNT_AMT
, B.VOUCHER_ID
, C.PO_ID
, ''
, ''
FROM
PS_PYMNT_VCHR_XREF B
INNER JOIN PS_PAYMENT_TBL A
ON B.PYMNT_ID = A.PYMNT_ID
AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
INNER JOIN PS_VOUCHER C
ON B.VOUCHER_ID = C.VOUCHER_ID
AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT > '01/12/2021'
UNION
SELECT
'C'
, SUM(A.PAID_AMT)
, COUNT(A.PAID_AMT) , COUNT(??)
FROM
PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B
ON B.PYMNT_ID = A.PYMNT_ID
AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE A.PYMNT_DT > '01/12/2021'
GROUP BY B.NAME1
ORDER BY 7, 1
Edit 1/17/21:
I modified the SQL slightly, and believe I have the individual parts of the code working, but I am struggling as to how I can combine these the same way the example has it illustrated, with having the Remittances displayed below each Payment.
The UNION was the only function I could think of to combine the data vertically, but I need to be able to group the remittances below each corresponding payment row and I don't know what ways I can do this. Also it's worth mentioning that the columns for the Payment and Remittances queries are different (number of columns and type) and do not always relate to each other.
--Payment:
SELECT B.ADDRESS4, B.PYMNT_ID_REF, SUM(A.PAID_AMT), B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT > '2021-01-03'
--AND A.PYMNT_ID = '0000263556'
GROUP BY B.ADDRESS4, B.PYMNT_ID_REF, B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1
--Remittances:
SELECT 2, A.PYMNT_ID_REF, C.INVOICE_ID, C.INVOICE_DT, C.GROSS_AMT, C.DSCNT_AMT, B.VOUCHER_ID,
C.PO_ID
FROM PS_PYMNT_VCHR_XREF B
INNER JOIN PS_PAYMENT_TBL A ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
INNER JOIN PS_VOUCHER C ON B.VOUCHER_ID = C.VOUCHER_ID AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT > '2021-01-03'
--AND A.PYMNT_ID = '0000263556'
--Control data:
WITH CONTROLTOTALS AS (
SELECT SUM(A.PAID_AMT) AS TOT_PAID_AMT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT > '2021-01-03' )
, CONTROLCOUNT1 AS (
SELECT COUNT(PYMNT_ID_REF) AS PYMNT_COUNT
FROM PS_PAYMENT_TBL B
WHERE B.PYMNT_DT > '2021-01-03'
AND PYMNT_ID <> '' ) ,
CONTROLCOUNT2 AS (
SELECT COUNT(*) AS REMIT_COUNT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE PYMNT_DT > '2021-01-03' )
SELECT 'C', A.TOT_PAID_AMT, B.PYMNT_COUNT, C.REMIT_COUNT
FROM CONTROLTOTALS A, CONTROLCOUNT1 B, CONTROLCOUNT2 C
EDIT 1/18/21:
Here is the table structure for the 3 tables being used in these queries:
Here is some sample data (2 payment rows for two vendors (Radiometer America - payment ID 023946, and Highmark Inc. - Payment ID 023943), along with corresponding remittances rows associated with each vendor:
Here is sample Remittance data that is associate to each payment for a vendor (Highmark is highlighted in Red with 2 Remittances, Radiometer America is in Green with 6 remittances, for a total of 8 for the example output):
Here is the final Control (Grand) totals row at the bottom. The TOT_PAID_AMT represents the total 2 payments from above for each vendor (8901.94 + 997,343.44), the total payment count (2), and the total remittances in the report (2 from Highmark, 6 from Radiometer America:
So now I need to bring all this together and output it the same way the original example displays it as, with the remittances displayed below each vendor payment.
This is how the output would look like for this particular example, notice the remittances are displayed with the corresponding payment:
So I have all the individual pieces, but I now need to tie them together in this format, for which I am struggling with. Hope this provides more clarity.
Table structure / sample files here:
https://pastebin.com/Ym9PuKXf
https://pastebin.com/F62467zE
https://pastebin.com/4EBrdyjN
https://pastebin.com/iU20L8Jf
https://pastebin.com/k9P75DAJ
https://pastebin.com/f4Ci6qXd
If I understood you correctly you have three query results with variety of columns and you want to combine those in a single column result separated by Pipe("|") delimiter.
To achieve that I have add an additional column in all three of your query output name "ConcatedValue" in which i have combined values from all the columns into one.
Then to have the value from controls at the end i have introduced a column named sortid, and to maintain order of payment and remittance for each pymnt_id_ref i have introduced a column named internalsortid. Since controls table doesn't have pymnt_id_ref column I have added that with null value.
I have created three tables named payment, remittance and control with your results and get your desired result with below query:
with cte as(
SELECT 1 SorTID,1 internalsortid, pymnt_id_ref,('P|CRD|Hosp.Payables'+'|'+ ADDRESS4+'|'+ PYMNT_ID_REF+'|'+ colc+'|'+REMIT_VENDOR+'|'+ REMIT_ADDR_SEQ_NUM+'|'+NAME1+'|USD')concatedvalue from payment b
union all
SELECT 1 SortID,2 internalsortid, pymnt_id_ref,('R|'+PYMNT_ID_REF +'|'+ INVOICE_ID +'|'+ CONVERT(VARCHAR(10),INVOICE_DT ,120)+'|'+ GROSS_AMT +'|'+ DSCNT_AMT +'|'+ VOUCHER_ID +'|'+ PO_ID )
from Remittance
union all
SELECT 3 SortID,3 internalsortid, NULL pymnt_id_ref, ('C|'+ TOT_PAID_AMT+'|'+ PYMNT_COUNT+'|'+REMIT_COUNT)concatedvalue from control)
select concatedvalue from cte order by sortid,pymnt_id_ref,internalsortid
Desired Result:
Since I don't have your actual tables I couldn't be able to executer your query after changing it. I am sharing it here. But you if you have any column with data type int, float etc. you need to convert while combining those in a single column. For example if pymnt_id_ref is a int column you need to use below line for payment table's concatedvalue column:
Revised Query:
With Payment as(
SELECT 'P', 'CRD', 'Hosp.Payables', B.ADDRESS4, B.PYMNT_ID_REF, SUM(A.PAID_AMT), B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1, 'USD',1 SorTID,1 internalsortid,
('P|CRD|Hosp.Payables'+'|'+ b.ADDRESS4+'|'+ b.PYMNT_ID_REF+'|'+ SUM(A.PAID_AMT)+'|'+b.REMIT_VENDOR+'|'+ a.REMIT_ADDR_SEQ_NUM+'|'+b.NAME1+'|USD')concatedvalue
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT = '2021-01-04'
--AND A.PYMNT_ID = '0000263556'
AND B.REMIT_VENDOR IN ('51503A', '71520A')
--AND B.REMIT_VENDOR = '71520A'
AND B.PYMNT_STATUS NOT IN ('S','V')
AND (( A.BANK_SETID = 'SHARE'
AND A.BANK_CD = 'MT'
AND A.BANK_ACCT_KEY = 'TGC')
OR ( A.BANK_SETID = 'SHARE'
AND A.BANK_CD = 'PNC'
AND A.BANK_ACCT_KEY = '1'))
--AND D.VENDOR_CLASS <> 'E'
GROUP BY B.ADDRESS4, B.PYMNT_ID_REF, B.REMIT_VENDOR, A.REMIT_ADDR_SEQ_NUM,
B.NAME1
--ORDER BY B.NAME1, PYMNT_ID_REF
) ,
Remittance as(
--Remittances:
SELECT 2, A.PYMNT_ID_REF, C.INVOICE_ID, C.INVOICE_DT, C.GROSS_AMT, C.DSCNT_AMT, B.VOUCHER_ID,C.PO_ID,
1 SortID,2 internalsortid, ('R|'+A.PYMNT_ID_REF +'|'+ C.INVOICE_ID +'|'+ CONVERT(VARCHAR(10),c.INVOICE_DT ,120) +'|'+ C.GROSS_AMT +'|'+ C.DSCNT_AMT +'|'+ VOUCHER_ID +'|'+ PO_ID )
FROM PS_PYMNT_VCHR_XREF B
INNER JOIN PS_PAYMENT_TBL A ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
INNER JOIN PS_VOUCHER C ON B.VOUCHER_ID = C.VOUCHER_ID AND C.BUSINESS_UNIT = B.BUSINESS_UNIT
WHERE A.PYMNT_DT = '2021-01-04'
--AND A.PYMNT_ID = '0000263556'
--AND B.REMIT_VENDOR = '51503A'
AND A.PYMNT_ID_REF IN ('023946', '023943')
--OR A.PYMNT_ID_REF = '023943'
--ORDER BY PYMNT_ID_REF
),
--Control data:
CONTROLTOTALS AS (
SELECT SUM(A.PAID_AMT) AS TOT_PAID_AMT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE B.PYMNT_DT = '2021-01-04'
AND B.PYMNT_ID_REF IN ('023946','023943'))
, CONTROLCOUNT1 AS (
SELECT COUNT(PYMNT_ID_REF) AS PYMNT_COUNT
FROM PS_PAYMENT_TBL B
WHERE B.PYMNT_DT = '2021-01-04'
AND PYMNT_ID <> ''
AND B.PYMNT_ID_REF IN ('023946','023943')) ,
CONTROLCOUNT2 AS (
SELECT COUNT(*) AS REMIT_COUNT
FROM PS_PYMNT_VCHR_XREF A
INNER JOIN PS_PAYMENT_TBL B ON B.PYMNT_ID = A.PYMNT_ID AND A.BANK_SETID = B.BANK_SETID
AND A.BANK_CD = B.BANK_CD
AND A.BANK_ACCT_KEY = B.BANK_ACCT_KEY
WHERE PYMNT_DT = '2021-01-04'
AND B.PYMNT_ID_REF IN ('023946','023943') ),
finalCTE as(
SELECT SortID, internalsortid,pymnt_id_ref,concatedvalue FROM Payment
union all
SELECT SortID, internalsortid,pymnt_id_ref,concatedvalue FROM Remittance
union all
SELECT 3 SortID,3 internalsortid, NULL pymnt_id_ref, ('C|'+ A.TOT_PAID_AMT+'|'+ B.PYMNT_COUNT+'|'+C.REMIT_COUNT)concatedvalue
FROM CONTROLTOTALS A, CONTROLCOUNT1 B, CONTROLCOUNT2 C)
select concatedvalue from finalcte order by sortid,pymnt_id_ref,internalsortid
You can't make a query in SQL Server that would return the data in exactly the format that you want. In SQL Server (and in any other relational DBMS) the schema is static. Schema is the definition of a table, it is a list of columns in a table and their types. A query in SQL Server returns a table with a specific static list of columns and their types.
In SQL Server you can't have a query that returns 4 columns in one row and 7 columns in another row.
In SQL Server you can't have a query that returns money type values in a column in one row and varchar type values in another row in the same column. All values in a given column must have the same type.
If you need to show the user such mixed data you'd better run three separate queries and use some reporting tool that would show the data neatly in a way that you want.
If you insist on putting everything in one single query you need to decide on what values to put in what column.
In your example the P rows have 10 columns, the R rows have 7 columns, the C rows have 4 columns. So, overall, your query will produce (at least) 10 columns. Some of these columns will have NULL values for R and C rows.
If you want to put values of different type in the same column, like dollar amounts (TOT_PAID_AMT) and some text (CRD), then you can convert numbers into text using the built-in Convert or Format functions.
In the end you use UNION ALL (not UNION) to put data from three independent tables / queries together.
Putting together
In your example you didn't give names to all columns in your query results, so I'll put my names here.
The Payment query returns these columns:
QueryType Col1 Col2 ADDRESS4 PYMNT_ID_REF Col3 REMIT_VENDOR EMIT_ADDR_SEQ_NUM NAME1 Col4
The Remittance query returns these columns:
QueryType PYMNT_ID_REF INVOICE_ID INVOICE_DT GROSS_AMT DSCNT_AMT VOUCHER_ID PO_ID
The Control query return these columns:
QueryType TOT_PAID_AMT PYMNT_COUNT REMIT_COUNT
You can put them into temp tables, or wrap them in CTEs. I'll refer to them as TableP, TableR, TableC below.
Now we can write
SELECT
QueryType
,Col1
,Col2
,ADDRESS4
,PYMNT_ID_REF
,Col3
,REMIT_VENDOR
,EMIT_ADDR_SEQ_NUM
,NAME1
,Col4
FROM TableP
UNION ALL
SELECT
QueryType
,PYMNT_ID_REF
,INVOICE_ID
,INVOICE_DT
,GROSS_AMT
,DSCNT_AMT
,VOUCHER_ID
,PO_ID
,NULL
,NULL
FROM TableR
UNION ALL
SELECT
QueryType
,TOT_PAID_AMT
,PYMNT_COUNT
,REMIT_COUNT
,NULL
,NULL
,NULL
,NULL
,NULL
,NULL
FROM TableC
Add necessary conversions to text, such as CONVERT(nvarchar(255), TOT_PAID_AMT). You don't have to put NULL values at the end, you just need to make sure that each SELECT in the UNION ALL returns 10 columns. And that these columns have the same type.
Sorting
Once you get to the point when you see results from all three queries together you can move on to the last bit - sorting them.
You want to put C values last and R values below corresponding P values. By "corresponding" I'm guessing that we can use the PYMNT_ID_REF to match them.
So, I'm adding one helper column here:
SELECT
'P' AS QueryType
,Col1
,Col2
,ADDRESS4
,PYMNT_ID_REF
,Col3
,REMIT_VENDOR
,EMIT_ADDR_SEQ_NUM
,NAME1
,Col4
,NULL
,0 AS SortOrder
FROM TableP
UNION ALL
SELECT
'R' AS QueryType
,NULL
,NULL
,NULL
,PYMNT_ID_REF
,INVOICE_ID
,INVOICE_DT
,GROSS_AMT
,DSCNT_AMT
,VOUCHER_ID
,PO_ID
,0 AS SortOrder
FROM TableR
UNION ALL
SELECT
'C' AS QueryType
,TOT_PAID_AMT
,PYMNT_COUNT
,REMIT_COUNT
,NULL
,NULL
,NULL
,NULL
,NULL
,NULL
,NULL
,1 AS SortOrder
FROM TableC
ORDER BY
SortOrder -- C values last
,PYMNT_ID_REF -- group by PYMNT_ID_REF
,QueryType -- P before R
;
For this to work I had to move PYMNT_ID_REF values into the same column (the 5th column) and move NULL values around. I ended up with 12 columns altogether.
I have an SQL query that's calculating numbers inside it's SELECT statement. It's slowing the querytime down tremendously, and I can't help but figure there should be an easier way. I only have a very basic understanding for SQL and am tasked to alter an existing query containing the snippet below.
SELECT
**Relevant Columns I want to see, i.e.:
TableActual.Price AS [Final Pricing],
**More columns
FROM
(
SELECT
a0.DocNum AS Ordernumber,
a0.DocStatus AS OrderStatus,
(
SELECT
ISNULL(ROUND(SUM(b.Credit - b.Debit),2), 0)
FROM
db.[dbo].ORDR a
LEFT JOIN
db.[dbo].JDT1 b ON CAST(a.DocNum AS nvarchar(15)) = b.Project
WHERE
a.DocNum = a0.DocNum
AND
(
b.Account > '7999'
AND
b.Account < '8701'
)
) AS Revenue,
(
SELECT
ISNULL(ROUND((SUM(b.Credit - b.Debit) * - 1 ),2), 0)
FROM
db.[dbo].ORDR a
LEFT JOIN
db.[dbo].JDT1 b ON CAST(a.DocNum AS nvarchar(15)) = b.Project
WHERE
a.DocNum = a0.DocNum
AND
(
b.Account = '7000'
OR
b.Account = '7010'
OR
b.Account = '7020'
)
) AS Price,
(
SELECT
ISNULL(ROUND((SUM(b.Credit - b.Debit) * - 1 ),2), 0)
FROM
db.[dbo].ORDR a
LEFT JOIN
db.[dbo].JDT1 b ON CAST(a.DocNum AS nvarchar(15)) = b.Project
WHERE
a.Docnum = a0.DocNum
AND
b.Account > 4000
AND
(
(
b.Account < 8000
OR
b.Account > 8700
)
AND
b.Account != 7000
AND
b.Account != 7010
AND
b.Account != 7020
AND
b.Account != 7250
)
) AS InstallationCosts
FROM
db.[dbo].ORDR a0
GROUP BY
a0.DocNum,
a0.DocStatus
) AS TableActual
LEFT JOIN
** some more tables
I've already managed to pinpoint the issue, which is in the snippet above. In particular, I believe it's the lines a.DocNum = a0.DocNum. It appears to be iterating over all the database rows for every row it wants to add to the output. However, when I remove this WHERE statement, it will add all ISNULL(ROUND(SUM(b.Credit - b.Debit),2), 0) values together for each row.
So for example, where I would want my rows for the "Revenue" Column to list '5, 10, 25', it will list '40, 40, 40' instead.
When running this snippet in isolation, it'll take upwards of 30 seconds to complete. However, when I include it in the main query, it'll increase the query time from 15 seconds (main query without the snippet), to upwards of 15 minutes (main query with the snippet).
I've been playing around with the a.DocNum = a0.DocNum statement for a good half a day now, but I'm afraid my familiarity with SQL lacks severely to find a solution. The snippet has been written by a colleague for a different application, where it runs in isolation and the querytime is managable.
My next attempt to improve quertimes would be to combine the three select statements into one and attempt to retain the three output columns, hoping to cut runtime by two thirds at the very least.
here is one way :
SELECT
ISNULL(ROUND(SUM(CASE WHEN b.Account BETWEEN 7999 AND 870 THEN (b.Credit - b.Debit) ELSE NULL END),2), 0) Revenue
, ISNULL(ROUND(SUM(CASE WHEN b.Account IN (7000,7010, 7020) THEN (b.Credit - b.Debit) ELSE NULL END),2), 0) Price
, ISNULL(ROUND(SUM(CASE WHEN b.Account > 4000 AND ((b.Account < 8000 OR b.Account > 8700) AND b.Account NOT IN (7000 ,7010,7020 ,7250) THEN (b.Credit - b.Debit) ELSE NULL END),2), 0) InstallationCosts
FROM
db.[dbo].ORDR a
LEFT JOIN
db.[dbo].JDT1 b
ON CAST(a.DocNum AS nvarchar(15)) = b.Project
GROUP BY
a.DocNum,
a.DocStatus
I have a table with data like this
Road Item Response added_on
1 82 Yes 7/11/16
1 83 Yes 7/11/16
1 84 Yes 7/11/16
2 82 Yes 8/11/16
2 83 No 8/11/16
2 85 Yes 8/11/16
This reflects an assessment of a road where 'item' is things being assessed.
Some items will always be done during an assessment (82, 83) where others are optional (84, 85).
I want to return something that combines all of the assessment results for a road/date, returning null if that item was not assessed. And also only returning last month's results. For example
Road 82 83 84 85 added_on
1 Yes Yes Yes 7/11/16
2 Yes No Yes 8/11/16
I have tried a multiple self joins like this but it's returning nothing.
FROM assess AS A
JOIN assess AS B
ON A.road = B.road AND a.added_on = B.added on
JOIN assess AS C
ON A.road = C.road AND a.added_on = C.added on
JOIN assess AS D
ON A.road = D.road AND a.added_on = D.added on
WHERE A.item = '81'
AND B.item = '82'
AND (C.item = '83' OR C.item IS NULL)
AND (D.item = '84' OR D.item IS NULL)
AND datepart(month,A.added_on) = datepart(month,getdate()) -1
To clarify,
-no road is assessed more than once a day
-each item is only assessed once, and sometimes is NULL i.e. not applicable
-multiple roads are assessed each day
-this table has other assessments but we aren't worried about those.
Any ideas? Using SQL server 2008. Thanks.
Assuming you need to go Dynamic
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Item) From YourTable Order By 1 For XML Path('')),1,1,'')
Select #SQL = 'Select [Road],' + #SQL + ',[added_on]
From YourTable
Pivot (max(Response) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - The SQL Generated is as follows. (just in case you can't go
dynamic)
Select [Road],[82],[83],[84],[85],[added_on]
From YourTable
Pivot (max(Response) For Item in ([82],[83],[84],[85]) ) p
Another way of achieving this is less elegant, but uses basic operations if you don't want to use pivot.
Load up test data
create table #assess ( road int, item varchar(10), response varchar(3), added_on date )
insert #assess( road, item, response, added_on )
values
(1, '82', 'Yes', '2016-07-11' )
, (1, '83', 'Yes', '2016-07-11' )
, (1, '84', 'Yes', '2016-07-11' )
, (2, '82', 'Yes', '2016-08-11' )
, (2, '83', 'No', '2016-08-11' )
, (2, '85', 'Yes', '2016-08-11' )
Process the data
-- Get every possible `item`
select distinct item into #items from #assess
-- Ensure every road/added_on combination has all possible values of `item`
-- If the combination does not exist in original data, leave `response` as blank
select road, added_on, i.item, cast('' as varchar(3)) as response into #assess2
from #items as i cross join #assess AS A
group by road, added_on, i.item
update a set response = b.response
from #assess2 a inner join #assess b on A.road = B.road AND a.added_on = B.added_on AND a.item = b.item
-- Join table to itself 4 times - inner join if `item` must exist or left join if `item` is optional
select a.road, a.added_on, a.response as '82', b.response as '83', c.response as '84', d.response as '85'
FROM #assess2 AS A
INNER JOIN #assess2 AS B ON A.road = B.road AND a.added_on = B.added_on
LEFT JOIN #assess2 AS C ON A.road = C.road AND a.added_on = C.added_on
LEFT JOIN #assess2 AS D ON A.road = D.road AND a.added_on = D.added_on
WHERE A.item = '82'
AND B.item = '83'
AND (C.item = '84' OR C.item IS NULL)
AND (D.item = '85' OR D.item IS NULL)
--AND datepart(month,A.added_on) = datepart(month,getdate()) -1
The resultset is:
road added_on 82 83 84 85
1 2016-07-11 Yes Yes Yes
2 2016-08-11 Yes No Yes
I would do this using conditional aggregation:
select road,
max(case when item = 82 then response end) as response_82,
max(case when item = 83 then response end) as response_83,
max(case when item = 84 then response end) as response_84,
max(case when item = 85 then response end) as response_85,
added_on
from t
group by road, added_on
order by road;
For the month component, you can add a where clause. One method is:
where year(date_added) * 12 + month(date_added) = year(getdate())*12 + month(getdate()) - 1
Or, you can use logic like this:
where date_added < dateadd(day, 1 - day(getdate()), cast(getdate() as date)) and
date_added >= dateadd(month, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
The second looks more complicated but it is sargable, meaning that an index on date_added can be used (if one is available).
I have a list of places where people have been discharged and I need to stratify them by age. The query that I have currently below works, but it lists every discharge type count, by every age.
You can see how for age 0-1-2-3 there are all separate counts of discharge type based on what age. How can I make it so that it's only the dischargeType and the counts that are listed (as long as they are 18 and under)
SELECT DATEDIFF(yyyy, tblVisits.dob, tblVisits.admitdate) AS Age, tblDischarge.dischargeType, COUNT(tblDischarge.dischargeType) AS COUNTS
FROM tblVisits INNER JOIN
tblDischarge ON tblVisitsDischargeStatus = tblDischarge.dis_statID
GROUP BY DATEDIFF(yyyy,tblVisits.dob, tblVisits.Admitdate), tblDischarge.dischargeType
HAVING (DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate) <=18)
If you just want people aged under 18, but only one row per discharge-type...
SELECT
tblDischarge.dischargeType,
COUNT(tblDischarge.dischargeType) AS COUNTS
FROM
tblVisits
INNER JOIN
tblDischarge
ON tblVisitsDischargeStatus = tblDischarge.dis_statID
WHERE
(DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate) <=18)
GROUP BY
tblDischarge.dischargeType
If you want to group the ages into strata...
SELECT
tblDischarge.dischargeType,
CASE DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate)
WHEN <= 3 THEN '0..3'
WHEN <= 8 THEN '4..8'
WHEN <= 12 THEN '9..12'
ELSE '13..18'
END AS ageBand,
COUNT(tblDischarge.dischargeType) AS COUNTS
FROM
tblVisits
INNER JOIN
tblDischarge
ON tblVisitsDischargeStatus = tblDischarge.dis_statID
WHERE
(DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate) <=18)
GROUP BY
tblDischarge.dischargeType,
CASE DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate)
WHEN <= 3 THEN '0..3'
WHEN <= 8 THEN '4..8'
WHEN <= 12 THEN '9..12'
ELSE '13..18'
END
Remove the AGE in the selection and in the Group by part:
SELECT tblDischarge.dischargeType, COUNT(tblDischarge.dischargeType) AS COUNTS
FROM tblVisits INNER JOIN
tblDischarge ON tblVisitsDischargeStatus = tblDischarge.dis_statID
GROUP BY tblDischarge.dischargeType
HAVING (DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate) <=18)
I am not sure of your problem, but if you just want to count by dischargeType, for all people under 18, the code follows:
SELECT dis.dischargeType, COUNT(dis.dischargeType) AS COUNTS
FROM tblDischarge dis
JOIN tblVisits vst ON vst.DischargeStatus = dis.dis_statID
WHERE (DATEDIFF(yyyy,vst.DOB, vst.AdmitDate) <= 18)
GROUP BY dis.dischargeType;
Or you can use sub-query, like:
SELECT dischargeType, COUNT(dischargeType) AS COUNTS
FROM tblDischarge
WHERE dis_statID IN (
SELECT vst.DischargeStatus
FROM tblVisits vst
WHERE (DATEDIFF(yyyy, vst.DOB, vst.AdmitDate) <= 18)
)
GROUP BY dis.dischargeType;
Simply remove the calculation for age from the SELECT and the GROUP BY, and change the HAVING to a WHERE:
SELECT tblDischarge.dischargeType,
COUNT(tblDischarge.dischargeType) as COUNTS
FROM tblVisits
INNER JOIN tblDischarge
ON tblVisitsDischargeStatus = tblDischarge.dis_statID
WHERE (DATEDIFF(yyyy,tblVisits.DOB, tblVisits.AdmitDate) <=18)
GROUP BY tblDischarge.dischargeType