PostgreSQL where clause not pushed down when using grouping sets - sql

SELECT *
FROM (
SELECT SUM(quantity) AS quantity,
product_location_id,
location_bin_id,
product_lot_id,
product_serial_id,
CASE
WHEN GROUPING (product_location_id, location_bin_id, product_lot_id, product_serial_id) = 0 AND product_serial_id IS NOT NULL THEN
'Serial'
WHEN GROUPING (product_location_id, location_bin_id, product_lot_id, product_serial_id) = 0 THEN
'Lot'
ELSE
'Quantity'
END AS pick_by
FROM product_location_bins
WHERE status != 'Void'
AND has_quantity = 'Yes'
GROUP BY GROUPING SETS (
(product_location_id, location_bin_id, product_lot_id, product_serial_id),
(product_location_id, location_bin_id)
)
HAVING SUM(quantity) > 0
) x
WHERE x.product_serial_id = 5643
I have the above query. Using a normal GROUP BY postgres is able to "push down" the outer where clause and use the index on product_serial_id. When I use grouping sets it's unable to do so. It resolves the entire inner query and then filters the results. I'm wondering why this is. Is it a limitation with grouping sets?

Your query is odd. Your outer where clause eliminates the second set of results from grouping sets, because product_serial_id would be NULL for the second set. This gets filtered out in the outer where.
I think you want something like this for the outer query:
WHERE x.product_serial_id = 5643 OR x.product_serial_id IS NULL
I suppose that Postgres could add optimizations for poorly written code -- that is, eliminate the work for the second grouping sets set because it is filtered out by the outer where. However, that is not usually the focus of optimizations.

Related

Alter a existing SQL statement, to give an additional column of data, but to not affect performance, so best approach

In this query, I want to add a new column, which gives the SUM of a.VolumetricCharge, but only where PremiseProviderBillings.BillingCategory = 'Water'. But i don't want to add it in the obvious place since that would limit the rows returned, I only want it to get the new column value
SELECT b.customerbillid,
-- Here i need SUM(a.VolumetricCharge) but where a.BillingCategory is equal to 'Water'
Sum(a.volumetriccharge) AS Volumetric,
Sum(a.fixedcharge) AS Fixed,
Sum(a.vat) AS VAT,
Sum(a.discount) + Sum(deferral) AS Discount,
Sum(Isnull(a.estimatedconsumption, 0)) AS Consumption,
Count_big(*) AS Records
FROM dbo.premiseproviderbillings AS a WITH (nolock)
LEFT JOIN dbo.premiseproviderbills AS b WITH (nolock)
ON a.premiseproviderbillid = b.premiseproviderbillid
-- Cannot add a where here since that would limit the results and change the output
GROUP BY b.customerbillid;
Bit of a tricky one, as what you're asking for will definitely affect performance (your asking SQL Server to do more work after all!).
However, we can add a column to your results which performs a conditional sum so that it does not affect the result of the other columns.
The answer lies in using a CASE expression!
Sum(
CASE
WHEN PremiseProviderBillings.BillingCategory = 'Water' THEN
a.volumetriccharge
ELSE
0
END
) AS WaterVolumetric

ORA-00909: invalid number of arguments when using NVL

I have a column that gets the specific amount which has a condition like below.
I tried this one to get his specific value but I getting a multiple rows which it should be single row.
select distinct
(case
when aila.line_type_lookup_code = 'ITEM' and aila.tax_classification_code = 'VAT12 SERVICES' then to_char(aila.assessable_value) else '0'
end) as taxable_lines
from
ap_invoice_lines_all aila
where
aila.invoice_id = '31004'
then I tried this one to replace a null values.
select distinct
(case
when aila.line_type_lookup_code = 'ITEM' and aila.tax_classification_code = 'VAT12 SERVICES' then nvl(to_char(aila.assessable_value,0)) end) as taxable_lines
from
ap_invoice_lines_all aila
where
aila.invoice_id = '31004'
For example I have a table named ap_invoice_lines_all that has a columns name
line_type_lookup_code string
tax_classification_code string
assessable_value double
then the expected output I want based on the query I tried above is
taxable lines
1300
but the one I get is
taxable lines
0
1300
How do I remove the 0 in the result?
Thanks!
First off, you are using to_char wrong. This function should only have a single argument in this context:
nvl(to_char(aila.assessable_value,0))
should be
nvl(to_char(aila.assessable_value),0)
I would recommend against using a case expression like this in such a simple query to improve readability. Case expressions are great tools, but typically decrease the query readability. And in this particular instance, you don't really need an IF..THEN..ELSE function (which is the reason to use case expressions).
Secondly, are you sure there is only record where aila.invoice_id = '31004'? The query will only return two rows if there are actually two rows in the table where that clause is true. In this case it finds one row where assessable_value is null and one where it isn't.
In any case, to remove the zero (or in this case a null value) from the resultset, you can simply do this:
select distinct aila.assessable_value as taxable_lines
from ap_invoice_lines_all aila
where aila.invoice_id = '31004'
and aila.line_type_lookup_code = 'ITEM' -- Originally a condition in the case statement
and aila.tax_classification_code = 'VAT12 SERVICES' -- Originally a condition in the case statement
and aila.assessable_value is not null; -- Removes any null values from the result
Or if you want null values to be replaced by a zero;
select distinct nvl(aila.assessable_value, 0) as taxable_lines
from ap_invoice_lines_all aila
where aila.invoice_id = '31004'
and aila.line_type_lookup_code = 'ITEM' -- Originally a condition in the case statement
and aila.tax_classification_code = 'VAT12 SERVICES' -- Originally a condition in the case statement
Note that the distinct clause will cause all rows where assessable_value to be grouped into a single row if multiple rows are a possibility. Remove the distinct clause if you want all rows where assessable_value is null to show in the result as 0.
Lastly, you might want to think about implementing a primary key (if the table doesn't have one) or unique index on invoice_id, if there shouldn't ever be duplicate ids. And it's simply good practice to do so for ID columns which are used often in queries and should be unique.

Convert null values to zero in crosstab query

The code below is a subset of a larger code that results in a cross tab query. The issue is that the total column to the right which sums all the number columns in the cross tab query does not calculate accurately as it results a 0 for any rows that have null value in one of the twelve columns it is attempting to sum.
I believe that the i need to add a condition to the following line in the code to result in a zero if the value is null. I just need someone to take a second look at it. If there is a better alternate solution, I am happy to entertain that also.
SUM(entry) for MX in (M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11,M12)
SELECT
tblCenters.prop
,tblAccounts.Category_EXCO
,tblCenters.CenterNum
,tblAccounts.AccountNum
,tblAccounts.Account_Description
,tblAccounts.Is_Revenue
,tblAccounts.Is_Above_EBITDA
,tblCenters.Division_Description
,tblCenters.[Is_F&B1]
,tblCenters.Group_Description
,Entry
,MX
FROM GA_Financial.dbo.tblSAP
left join tblMX on tblSAP.MDY = tblMX.MDY
left join tblAccounts on tblSAP.AccountNum = tblAccounts.AccountNum
left join tblCenters on tblSAP.CenterNum = tblCenters.CenterNum and tblSAP.Prop_SAP = tblCenters.PROP_SAP
WHERE tblAccounts.Is_Above_EBITDA = 1
AND tblSAP.Type = 'A'
)
AS Tab1
--The code below breaks down column "Entry" into twelve individual monthly columns and fills columns M1 through M12
PIVOT
(
SUM(entry) for MX in (M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11,M12)
) as TAb2
where Prop = 'RWNY'
I think that you can use the COALESCE or IFNULL functions to replace the null value with a 0 for the sum calculation. Check out this response: COALESCE, IFNULL, or NZ() function that can be used in SQL Server and MS Access
To avoid a null result use ISNULL(Entry, 0) instead of Entry in your original query. This will substitute any returned null's with 0's.

CTE with blank expressions

I am building a fairly large set of tabulated data. In this data I have employees and their receptive totals against company overall totals. I am having a problem in that I am using CTEs and one of my expressions counts for the number of a particular item. The count returns nothing and by including this expression in the final query makes the whole result set blank. I'm sure I'm missing something simple here:
...
COMPANY_TOTAL_A(A) AS
(
SELECT NVL(COUNT(ITEM),0)
FROM COMPANY_TOTALS_FINAL
WHERE ITEM = 'A'
GROUP BY ITEM
),
...
This query returns nothing and when I use it in my final query the whole result set is blank. If I exclude it then I get all the rows I expect back. It looks like this?
SELECT DISTINCT C.ID,
C.NAME,
P.LOCATION,
...
NVL(T.A, 0)
...
FROM COMPANY C
INNER JOIN PLACE P
ON P.P_ID = C.P_ID,
...
COMPANY_TOTAL_A T;
As tthis value is relevant to all employees I expected it to just return the company total for when item is in class A. Even if that is 0 I thought 0 would be returned?
Grouping by the same column you are counting on is useless. You want a simple:
SELECT count(*)
FROM company_totals_final
WHERE item = 'A'
count(item) counts all rows where item is not null, but the condition will ITEM = 'A' will already remove those rows anyway. So it's also not needed.

Use of the HAVING clause when using muliple sums

I was having a problem getting mulitple sums from multiple tables. Short story, my answer was solved in the "sql sum data from multiple tables" thread on this site. But where it came up short, is that now I'd like to only show sums that are greater than a certain amount. So while I have sub-selects in my select, I think I need to use a HAVING clause to filter the summed amounts that are too low.
Example, using the code specified in the link above (more specifically the answer that the owner has chosen as correct), I would only like to see a query result if SUM(AP2.Value) > 1500. Any thoughts?
If you need to filter on the results of ANY aggregate function, you MUST use a HAVING clause. WHERE is applied at the row level as the DB scans the tables for matching things. HAVING is applied basically immediately before the result set is sent out to the client. At the time WHERE operates, the aggregate function results are not (and cannot) be available, so you have to use a HAVING clause, which is applied after the main query is complete and all aggregate results are available.
So... long story short, yes, you'll need to do
SELECT ...
FROM ...
WHERE ...
HAVING (SUM_AP > 1500)
Note that you can use column aliases in the having clause. In technical terms, having on a query as above works basically exactly the same as wrapping the initial query in another query and applying another WHERE clause on the wrapper:
SELECT *
FROM (
SELECT ...
) AS child
WHERE (SUM_AP > 1500)
You could wrap that query as a subselect and then specify your criteria in the WHERE clause:
SELECT
PROJECT,
SUM_AP,
SUM_INV
FROM (
SELECT
AP1.[PROJECT],
(SELECT SUM(AP2.Value) FROM AP AS AP2 WHERE AP2.PROJECT = AP1.PROJECT) AS SUM_AP,
(SELECT SUM(INV2.Value) FROM INV AS INV2 WHERE INV2.PROJECT = AP1.PROJECT) AS SUM_INV
FROM AP AS AP1
INNER JOIN INV AS INV1 ON
AP1.[PROJECT] = INV1.[PROJECT]
WHERE
AP1.[PROJECT] = 'XXXXX'
GROUP BY
AP1.[PROJECT]
) SQ
WHERE
SQ.SUM_AP > 1500