SQL Sum of differences getting different results - sql

I have the following query:
WITH
FOO as(
SELECT
IF (EXTRACT(MONTH FROM date_key) > 9,EXTRACT(YEAR FROM date_key) || EXTRACT(MONTH FROM date_key),
EXTRACT(YEAR FROM date_key) || '0' || EXTRACT(MONTH FROM date_key)) AS YEAR_MONTH,
cast(ITEM_KEY AS INT) AS ITEM_KEY,
cast(STORE_KEY AS INT) AS STORE_KEY,
MAIN_SUPPLIER_KEY AS SUPPLIER_KEY,
SUM(SAL_QTY_CMP_NPCE_XTAX) AS SAL_QTY_CMP_NPCE_XTAX, -- 3º KPI CUSTO_FINANCEIRO CR4
SUM(SAL_AMT_XTAX) - SUM(SAL_QTY_CMP_NPCE_XTAX) AS MARGEM_BRUTA, -- 8º KPI MARGEM_BRUTA CR4
SUM(SAL_AMT_XTAX) AS SAL_AMT_XTAX, -- 14º KPI VENDA_LIQUIDA CR4
'CR4' AS ORIGEM
FROM `foo`
GROUP BY YEAR_MONTH,ITEM_KEY,STORE_KEY,SUPPLIER_KEY
),
BAR AS (
SELECT YEAR_MONTH,
ITEM_KEY,
STORE_KEY,
SUPPLIER_KEY,
SUM(SAL_AMT_XTAX) as SAL_AMT_XTAX,
sum(SAL_QTY_CMP_NPCE_XTAX) as SAL_QTY_CMP_NPCE_XTAX,
sum(MARGEM_BRUTA) as MARGEM_BRUTA,
ORIGEM
FROM
FOO
GROUP BY YEAR_MONTH,ITEM_KEY,STORE_KEY,SUPPLIER_KEY,ORIGEM
)
select SUM(SAL_AMT_XTAX) - SUM(SAL_QTY_CMP_NPCE_XTAX) as MARGEM1, --213465264
sum(MARGEM_BRUTA) AS MARGEM2 --213461382
from BAR
The calculation of MARGEM1 and MARGEM2 are conceptually the same. However, I'm getting slightly different numbers in the end:
MARGEM1: 213465264
MARGEM2: 213461382
I'm kinda confused. How it gave me different result as it is supposed to be the same formula but calculated at different times? Feel free to as me any further information you'd like. I appreciate any help in advance.

Related

COUNT from DISTINCT values in multiple columns

If this has been asked before, I apologize, I wasn't able to find a question/solution like it before breaking down and posting. I have the below query (using Oracle SQL) that works fine in a sense, but not fully what I'm looking for.
SELECT
order_date,
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
srt AS srt_level,
COUNT(*) AS total_orders
FROM
database.t_con
WHERE
order_date IN (
'&Enter_Date_YYYYMM'
)
GROUP BY
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
srt,
order_date
ORDER BY
p_category,
issue_group,
srt_level,
order_date
Current Return (12 rows):
Needed Return (8 rows without the tan rows being shown):
Here is the logic of total_order column that I'm expecting:
count of order_date where (srt_level = 80 + 100 + Late) ... 'Late' counts needed to be added to the total, just not be displayed
I'm eventually adding a filled_orders column that will go before the total_orders column, but I'm just not there yet.
Sorry I wasn't as descriptive earlier. Thanks again!
You don't appear to need a subquery; if you want the count for each combination of values then group by those, and aggregate at that level; something like:
SELECT
t1.order_date,
t1.p_category,
CASE
WHEN ( t1.issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
t1.srt AS srt_level,
COUNT(*) AS total_orders
FROM
database.t_con t1
WHERE
t1.order_date = TO_DATE ( '&Enter_Date_YYYYMM', 'YYYYMM' )
GROUP BY
t1.p_category,
CASE
WHEN ( t1.issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
t1.srt,
t1.order_date
ORDER BY
p_category,
issue_group,
srt_level,
order_date;
You shouldn't be relying on implicit conversion and NLS settings for your date argument (assuming order_date is actually a date column, not a string), so I've used an explicit TO_DATE() call, using the format suggested by your substitution variable name and prompt.
However, that will give you the first day of the supplied month, since a day number isn't being supplied. It's more likely that you either want to prompt for a full date, or (possibly) just the year/month but want to include all days in that month - which IN() will not do, if that was your intention. It also implies that stored dates all have their time portions set to midnight, as that is all it will match on. If those values have non-midnight times then you need a range to pick those up too.
I got it working to the extent of what my question was. Just needed to nest each column where counts/calculations were happening.
SELECT
order_date,
p_category,
issue_group,
srt_level,
order_count,
SUM(order_count) OVER(
PARTITION BY order_date, issue_group, p_category
) AS total_orders
FROM
(
SELECT
order_date,
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END AS issue_group,
srt AS srt_level,
COUNT(*) AS order_count
FROM
database.t_con
WHERE
order_date IN (
'&Enter_Date_YYYYMM'
)
GROUP BY
p_category,
CASE
WHEN ( issue_grp = 1 ) THEN '1'
ELSE '2/3 '
END,
srt,
order_date
)
ORDER BY
order_date,
p_category,
issue_group

Invalid identifier in pivot table

Here is my query:
SELECT NVL(REVENUE,0), OUTNO, MONTH_NAME FROM
(
SELECT ROUND((RETURNDATE-STARTDATE)*DAILYRATE) AS REVENUE,
OUTNO,
EXTRACT(MONTH FROM RETURNDATE)AS MONTH_NAME
FROM RAGREEMENT LEFT JOIN VEHICLE ON
RAGREEMENT.LICENSENO=VEHICLE.LICENSENO
AND EXTRACT(YEAR FROM RETURNDATE)=EXTRACT(YEAR FROM SYSDATE)-1
)
PIVOT (
SUM(REVENUE)
FOR OUTNO IN (1,2,3,4,5,6,-1 AS TOTAL)
)
ORDER BY MONTH_NAME;
and here is the error
SELECT NVL(REVENUE,0), OUTNO, MONTH_NAME FROM
*
ERROR at line 1:
ORA-00904: "OUTNO": invalid identifier
I fail to understand why this happens when SELECT * works perfectly
What comes out from a pivot is a whole new set of columns names, and for each one of those new columns, you will need an NVL() or COALESCE(). This is because a completely new "matrix" is formed and many positions in this can be null. You cannot overcome this by using NVL() in the inner subquery.
Assuming you want months as columns your query might look more like this:
SELECT
OUTNO
, NVL('M1', 0)
, NVL('M2', 0)
, NVL('M3', 0)
, NVL('M4', 0)
, NVL('M5', 0)
, NVL('M6', 0)
, NVL('M7', 0)
, NVL('M8', 0)
, NVL('M9', 0)
, NVL('M10', 0)
, NVL('M11', 0)
, NVL('M12', 0)
FROM (
SELECT
ROUND((RETURNDATE - STARTDATE) * DAILYRATE) AS REVENUE
, OUTNO
, 'M' || EXTRACT(MONTH FROM RETURNDATE) AS MONTH_NAME
FROM RAGREEMENT
LEFT JOIN VEHICLE ON RAGREEMENT.LICENSENO = VEHICLE.LICENSENO
AND EXTRACT(YEAR FROM RETURNDATE) = EXTRACT(YEAR FROM SYSDATE) - 1
)
PIVOT(
SUM(REVENUE)
FOR MONTH_NAME IN ('M1','M2','M3','M4','M5','M6','M7','M8','M9','M10','M11','M12')
)
ORDER BY OUTNO;
This line produces the new columns:
FOR MONTH_NAME IN ('M1','M2','M3','M4','M5','M6','M7','M8','M9','M10','M11','M12')
and it is each one of these you will need to "fix" for nulls in the select clause.
To put OUTNO values into columns requires a similar pattern but you need to know what the distinct set of values will be from that originating column. This MIGHT be 1,2,3,4,5,6,-1 but I wasn't certain.
nb: I prefixed 'M' in columns headings as many systems object to numbers as heading names.

SQL - values from two rows into new two rows

I have a query that gives a sum of quantity of items on working days. on weekend and holidays that quantity value and item value is empty.
I would like that on empty days is last known quantity and item.
My query is like this:
`select a.dt,b.zaliha as quantity,b.artikal as item
from
(select to_date('01-01-2017', 'DD-MM-YYYY') + rownum -1 dt
from dual
connect by level <= to_date(sysdate) - to_date('01-01-2017', 'DD-MM-YYYY') + 1
order by 1)a
LEFT OUTER JOIN
(select kolicina,sum(kolicina)over(partition by artikal order by datum_do) as zaliha,datum_do,artikal
from
(select sum(vv.kolicinaulaz-vv.kolicinaizlaz)kolicina,vz.datum as datum_do,vv.artikal
from vlpzaglavlja vz, vlpvarijante vv
where vz.id=vv.vlpzaglavlje
and vz.orgjed='01006'
and vv.skladiste='01006'
and vv.artikal in (3069,6402)
group by vz.datum,vv.artikal
order by vv.artikal,vz.datum asc)
order by artikal,datum_do asc)b
on a.dt=b.datum_do
where a.dt between to_date('12102017','ddmmyyyy') and to_date('16102017','ddmmyyyy')
order by a.dt`
and my output is like this:
and I want this:
In short, if quantity is null use lag(... ignore nulls) and coalesce or nvl:
select dt, item,
nvl(quantity, lag(quantity ignore nulls) over (partition by item order by dt))
from t
order by dt, item
Here is the full query, I cannot test it, but it is something like:
with t as (
select a.dt, b.zaliha as quantity, b.artikal as item
from (
select date '2017-10-10' + rownum - 1 dt
from dual
connect by date '2017-10-10' + rownum - 1 <= date '2017-10-16' ) a
left join (
select kolicina, datum_do, artikal,
sum(kolicina) over(partition by artikal order by datum_do) as zaliha
from (
select sum(vv.kolicinaulaz-vv.kolicinaizlaz) kolicina,
vz.datum as datum_do, vv.artikal
from vlpzaglavlja vz
join vlpvarijante vv on vz.id = vv.vlpzaglavlje
where vz.orgjed = '01006' and vv.skladiste='01006'
and vv.artikal in (3069,6402)
group by vz.datum, vv.artikal)) b
on a.dt = b.datum_do)
select *
from (
select dt, item,
nvl(quantity, lag(quantity ignore nulls)
over (partition by item order by dt)) qty
from t)
where dt >= date '2017-10-12'
order by dt, item
There are several issues in your query, major and minor:
in date generator (subquery a) you are selecting dates from long period, january to september, then joining with main tables and summing data and then selecting only small part. Why not filter dates at first?,
to_date(sysdate). sysdate is already date,
use ansi joins,
do not use order by in subqueries, it has no impact, only last ordering is important,
use date literals when defining dates, it is more readable.

Not Exists query

I'm trying to find the clients, those who didn't order in the last 2 years and they ordered this year more than 500.. I wrote this query and I used the "NOT EXISTS" condition, but it is still showing me the wrong results.
Some suggestions would be appreciated.
My code:
SELECT
"Sales"."Kundennummer" as 'Neuer Kunde',
year("Sales"."Datum"),
sum("Sales"."Umsatz mit Steuer") as "Umsatz"
FROM "Sales"
WHERE year("Sales"."Datum") = '2017'
AND NOT EXISTS
(
SELECT "Sales"."Kundennummer"
FROM "Sales"
WHERE year("Sales"."Datum") = '2015'
AND year("Sales"."Datum") = '2016'
)
GROUP BY
"Sales"."Kundennummer",
"Sales"."Datum"
HAVING sum("Sales"."Umsatz mit Steuer") > 500
The query in the NOT EXISTS clause will probably yield 0 rows, since a row can't have Datum both 2015 and 2016. So it should probably be OR instead of AND.
Also, if you fix this, there is no link between the subquery and the superquery, which means that it will return rows for any customer (given that there exists a row with Datum either 2015 or 2016 in your table which I guess it does).
So, something like:
SELECT
"Sales"."Kundennummer" as 'Neuer Kunde',
year("Sales"."Datum"),
sum("Sales"."Umsatz mit Steuer") as "Umsatz"
FROM "Sales" sales
WHERE year("Sales"."Datum") = '2017'
AND NOT EXISTS
(
SELECT "Sales"."Kundennummer"
FROM "Sales" salesI
WHERE salesI."Kundennummer" = sales."Kundennummer"
AND (year("Sales"."Datum") = '2015'
OR year("Sales"."Datum") = '2016')
)
GROUP BY
"Sales"."Kundennummer",
"Sales"."Datum"
HAVING sum("Sales"."Umsatz mit Steuer") > 500
Your EXISTS query is not correlated to the main query, i.e. it doesn't look for data for the Kundennummer in question, but whether there are any records in 2015 and 2016.
(You also have the condition for the years wrong by using AND where it must be OR and you should not use quotes on numbers like 2015', and you should not use single quotes on names like 'Neuer Kunde'.)
It should be
AND NOT EXISTS
(
SELECT *
FROM Sales other_years
WHERE other_years.Kundennummer = Sales.Kundennummer
AND year(other_years.Datum) in (2015, 2016)
)
or uncorrelated with NOT IN
AND Kundennummer NOT IN
(
SELECT Kundennummer
FROM Sales
WHERE year(Datum) in (2015, 2016)
)
Be aware though, that when using NOT IN the subquery must return no nulls. E.g. where 3 not in (1, 2, null) does not result in true, as one might expect, because the DBMS argues that the unknown value (null) might very well be a 3 :-)
I propose you here below 3 different ways to do it:
Joining 2 tables
select this_year_sales.kundenummer, this_year_sales.tot_umsatz
from (select sum(umsatz) tot_umsatz, kundenummer from sales where extract(year from (datum)) = extract(year from sysdate) group by kundenummer) this_year_sales
, (select kundenummer, max(datum) max_datum from sales where datum < trunc(sysdate, 'year') group by kundenummer) previous_sales
where this_year_sales.kundenummer = previous_sales.kundenummer
and extract(year from previous_sales.max_datum) < (extract(year from sysdate)-2)
and this_year_sales.tot_umsatz > 500;
Using NOT INT
select kundenummer, sum(umsatz)
from sales s
where extract(year from datum) = extract(year from sysdate)
and kundenummer not in (select kundenummer from sales where extract(year from datum) > (extract(year from sysdate) - 2) and extract(year from datum) < (extract(year from sysdate)-1))
group by kundenummer
having sum(umsatz) > 500;
Using NOT EXISTS
select kundenummer, sum(umsatz)
from sales s
where extract(year from datum) = extract(year from sysdate)
and not exists(
select s1.kundenummer, s1.datum from sales s1 where extract (year from s1.datum) >= (extract(year from sysdate)-2) and extract(year from s1.datum) < extract (year from sysdate) and s1.kundenummer = s.kundenummer
)
group by kundenummer
having sum(umsatz) > 500;

Refer to a Derived Table in a SELECT Subquery

In the code below, the resulting error is "object t does not exist". T is the derived table defined in FROM. Rather than refiltering in the SELECT subquery, I would like to use the derived table to save on processing. Since FROM is processed first in the order of operations, I feel that there should be a way for me to refer to "t" in the SELECT.
(I am in Teradata in case that matters)
SELECT (100000/(SELECT COUNT(DISTINCT EXTRACT(DAY FROM t.saledate))
FROM t
WHERE EXTRACT(MONTH FROM t.saledate) = 11)) as "NOVEMBER"
FROM (SELECT sprice, quantity, sku, store, saledate
FROM trnsact
WHERE (saledate BETWEEN '2004-11-01' AND '2004-12-31')
) as t
We can achieve the result (it seems like you are going for) with an inline view, or a common table expression. As an example of a using an inline view:
SELECT ( 100000
/ COUNT(DISTINCT
CASE WHEN EXTRACT(MONTH FROM t.saledate) = 11
THEN EXTRACT(DAY FROM t.saledate)
ELSE NULL
END
)
) AS "NOVEMBER"
FROM ( SELECT r.sprice
, r.quantity
, r.sku
, r.store
, r.saledate
FROM trnsact r
WHERE r.saledate BETWEEN '2004-11-01' AND '2004-12-31'
) t
I'd also want to avoid any potential "divide by zero" error, so I'd wrap that COUNT() expression in a NULLIFZERO function.
We will note that for that resultset, we wouldn't actually need an inline view or CTE. It could be achieved with a much simpler query:
SELECT ( 100000
/ NULLIFZERO(COUNT(DISTINCT EXTRACT(DAY FROM t.saledate)))
) AS "NOVEMBER"
FROM trnsact t
WHERE t.saledate >= '2004-11-01'
AND t.saledate < '2004-12-01'
If you're using SQL Server you'd do:
;WITH t as
SELECT sprice, quantity, sku, store, saledate
FROM trnsact
WHERE (saledate BETWEEN '2004-11-01' AND '2004-12-31')
SELECT (100000/(SELECT COUNT(DISTINCT EXTRACT(DAY FROM t.saledate))
FROM t
WHERE EXTRACT(MONTH FROM t.saledate) = 11)) as "NOVEMBER"
I think there are a lot more improvements that can be made to the query, but this is a good start.