How to divide two values from the same column in SQL - sql

As the title states I would like to divide two values with each other that are in the same column.
E.g.
A B C D
Shirts 2011 85 0
Shirts 2012 92 percent change from 2011 to 2012
Shirts 2013 100 percent change from 2012 to 2013
Pants 2011 31 0
Pants 2012 42 percent change from 2011 to 2012
Pants 2013 55 percent change from 2012 to 2013
Jacket 2011 10 0
Jacket 2012 16 percent change from 2011 to 2012
Jacket 2013 18 percent change from 2012 to 2013
In this example column D would be a derived from column C, where the value of 2012 is subtracted from 2011, and then times by a 100 to get the percent.
I don't know how to set the query up I tried doing a bunch of sub-queries but didn't know how to link them together. Any help would be greatly appreciated.

Here's an option to get what you're after
SELECT t1.a, t1.b, t1.c,
CASE WHEN t2.c IS NULL THEN 0 ELSE t1.c - t2.c END AS d,
CASE WHEN t2.c IS NULL THEN 0 ELSE (1.0 * t1.c - t2.c) / t1.c * 100.0 END AS pct
FROM t t1
LEFT OUTER JOIN t t2 ON t1.a = t2.a
AND t1.b = t2.b + 1
SQL Fiddle Example

If I'm understanding your requirements correctly, you could use a recursive cte to accomplish your results. This uses ROW_NUMBER() and partitions by (groups by) column A.
This method would best work if you can't guarantee sequential years in column B. If you always have sequential years, then bobs provides the best alternative.
with cte as (
select A, B, C,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY B) rn
from yourtable
),
recursive_cte as (
select A, B, C, 0 prc
from cte
where rn = 1
union all
select y.A, y.B, y.C, y.C-c.C prc
from cte y
join cte c on y.a=c.a and y.rn=c.rn+1
)
select *
from recursive_cte
order by a, b
SQL Fiddle Demo
This will return the difference between each year and the previous grouped by column A. It uses a 2nd CTE just for simplicity. If you want the actual percent changed, update the formula above.
--EDIT
Since it sounds like you're looking for percentage growth, try replacing y.C-c.C prc with this formula instead:
cast(y.C as decimal(10,2))/cast(c.C as decimal(10,2))
Use CAST if your data types are integers.

You can accomplish this with a self-join:
SELECT curr.ClothingType, curr.SalesYear, curr.NumberSold
, COALESCE((CAST((curr.NumberSold - prev.NumberSold) AS NUMERIC(5,2)) / prev.NumberSold) * 100, 0) AS PercentChange
FROM #Clothes AS curr
LEFT JOIN #Clothes AS prev ON curr.ClothingType = prev.ClothingType
AND curr.SalesYear = prev.SalesYear + 1
SQL Fiddle Example

I agree with Shivan, but only because I don't know how to do it without PHP. With PHP it's just a matter of pulling the two totals, doing the math and sending a query back with the answers.
ALSO BE SURE TO READ THIS BECAUSE IT'S THE MORE IMPORTANT PART
Your math's wrong. (Shirts2011 - Shirts2012) * 100 doesn't give you the percent change. It just gives you 100 times the difference of the two (actually, the negative difference based on your wording). What you want, math-wise, is:
((Item2012-Item2011)/Item2011)*100
I know the outside parens aren't necessary but they make it easier to read. The percent change is the same as 100 times the ratio of the difference of the two to the initial.
Edited to add PHP code.
The PHP looks gross, but it gets the job done.
$conn = new PDO(HOST:DATABASE, USERNAME, PASSWORD);
$query = "SELECT A, B, C FROM yourTable";
$st = $conn->prepare($query);
$st->execute();
$list = array();
while($row = $st->fetch(PDO::FETCH_ASSOC))
$list[] = $row;
$fill = array();
for($i=1; $i<count($list); $i++)
if($list[$i]['A'] == $list[$i-1][A])
$fill[] = $array('A' => $list[$i]['A'],
'B' => $list[$i]['B'],
'D' => ($list[$i]['C']-$list[$i-1]['C'])/$list[$i-1]['C']*100);
$update = new PDO(HOST:DATABASE, USERNAME, PASSWORD);
for($i=0; $i<count($fill); $i++){
$query = "UPDATE yourTable SET D = " . $fill[$i]['D'] . " WHERE A = " . $fill[$i]['A'] . " AND B = " . $fill[$i]['B'];
$st = $update->prepare($query);
$st->execute();
}
Just initialize your table where the 2011 D columns = 0.
UPDATE yourTable SET D = 0 WHERE B = 2011;
And before the flames start, I know a foreach would have worked; I just think a standard for looks better (and allows you to skip the first 2011 value).

Try this:
SELECT t1.type A, t1.year B, t1.value C,
IF(
EXISTS(
SELECT 1 FROM table t2 WHERE t1.type = t2.type AND t1.year = (t2.year - 1)
),
((SELECT t3.year FROM table t3 WHERE t1.type = t3.type AND t1.year = (t3.year - 1)) - t1.year)/t1.year,
0
) D
FROM table t1
Not super efficient, but it can be done.

You could try this too:
UPDATE mytable t1
INNER JOIN mytable t2
ON t1.A = t2.A AND t2.B = t1.B - 1
SET t1.D = ((t1.C - t2.C) / t2.C) * 100;
see fiddle.
Assuming you already have 0 as a default for D. If not, just set it to 0 for all rows before running this query.

Try this:
select Y2013.*, Y2012.C, Y2013.C, Y2013.C/Y2011.C
from (
select * from data where B = year(getdate())
) Y2013
left join (
select * from data where B = year(getdate()) - 1
) Y2012 on Y2012.A = Y2013.A
left join (
select * from data where B = year(getdate()) - 2
) Y2011 on Y2011.A = Y2012.A
and Y2011.A = Y2013.A

Related

proc sql join in SAS that is closest to a date

How can I do a one-to-many join between two datasets with proc sql in SAS to obtain the record in Dataset B is closest to a value in Dataset A?
Dataset A
#Patient #Date of Dose
001 2020-02-01
Dataset B
# Patient # Lab Test #Date of Test # Value
001 Test 1 2020-01-17 6
001 Test 1 2020-01-29 10
I want to do the join to select the second record in Dataset B, the record with a "Date of Test" that is the closest (less than or equal to) to the "Date of Dose" in the first dataset.
I want to do the join to select the [..] record in Dataset B [...] with a "Date of Test" that is the closest (less than or equal to) to the "Date of Dose" in the first dataset.
You could use outer appy - if sas supports that:
select a.*, b.*
from a
outer apply(
select top 1 b.*
from b
where b.patient = a.patient and b.date_of_test <= a.date_of_dose
order by b.date_of_test desc
) b
Another solution is to join with a not exists condition:
select a.*, b.*
from a
left join b
on b.patient = a.patient
and b.date_of_test <= a.date_of_dose
and not exists (
select 1
from b b1
where
b1.patient = a.patient
and b1.date_of_test <= a.date_of_dose
and b1.date_of_test > b.date_of_test
)
Calculate the absolute difference between both dates and select the minimum date with a having clause. You'll need to do additional logic, such as distinct, to remove any duplicates.
proc sql noprint;
select t1.patient
, t1.date_of_dose
, abs(t1.date - t2.date) as date_dif
from dataset_A as t1
LEFT JOIN
dataset_B as t2
ON t1.patient = t2.patient
where t1.date <= t2.date
group by t1.patient
having calculated date_dif = min(calculated date_dif)
;
quit;
try this:
SELECT TOP 1 *
FROM (
SELECT DSA.Patient
,DSA.Date_Of_Dose
,DSB.Date_Of_Test
,DATEDIFF(Day, DSA.Date_Of_Dose, DSA.Date_Of_Dose) Diff
FROM DataSetA DSA
JOIN DataSetB DSB ON DSA.Patient = DSB.Patient
) Data
WHERE ABS(Diff) = MIN(ABS(Diff));
Sorry, I got no way to know if this is working 'cause I'm not at home. I hope it helps you.
I want to do the join to select the second record in Dataset B, the record with a "Date of Test" that is the closest (less than or equal to) to the "Date of Dose" in the first dataset.
I would recommend cross-joining Date of Test and Date of Dose, and calculate absolute difference between the dates using intck() function, and leave the minimal value.

how to set expression variable in query select oracle sql

I have Oracle SQL like these :
SELECT
z."date", z.id_outlet as idOutlet, z.name as outletName, z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,rownumber = tartot + rownumber as targetTotal
FROM (SELECT
b.visit_date as "date", a.id_outlet, max(o.name) as name, max(a.target_sales) as targetBulanan, a.id_material_class as matClass,
max(x.totalVisit) as totalVisit, NVL(SUM(d.billing_value),0) as actual
FROM (
select * from target_bulanan
where deleted = 0 and enabled = 1 and id_salesman = :id_salesman AND id_material_class like :id_material_class AND id_outlet like :id_outlet AND month = TO_NUMBER(TO_CHAR(current_date,'mm')) and year = to_number(TO_CHAR(current_date,'YYYY'))
) a
INNER JOIN outlet o ON o.id_outlet = a.id_outlet
LEFT JOIN visit_plan b ON b.deleted = 0 and a.id_salesman = b.id_salesman AND a.month = TO_NUMBER(TO_CHAR(b.visit_date,'mm')) AND a.year = to_number(TO_CHAR(b.visit_date,'yyyy')) AND a.id_outlet = b.id_outlet
LEFT JOIN so_header c ON SUBSTR(c.id_to,'0',1) = 'TO' AND a.id_salesman = c.id_salesman AND a.id_outlet = c.id_outlet
LEFT JOIN assign_billing d ON c.no_so_sap = d.no_so_sap AND d.billing_date = b.visit_date AND a.id_material_class = (SELECT id_material_class FROM material WHERE id = d.id_material)
LEFt JOIN (SELECT id_salesman, to_char(visit_date,'mm') as month, to_char(visit_date,'yyyy') as year, id_outlet, COUNT(*) as totalVisit FROM visit_plan
WHERE deleted = 0
group by id_salesman, id_outlet,to_char(visit_date,'mm'), to_char(visit_date,'yyyy')) x on
x.id_salesman = a.id_salesman AND x.month = a.month AND x.year = a.year AND x.id_outlet = a.id_outlet
GROUP BY b.visit_date, a.id_outlet, a.id_material_class) z
CROSS JOIN (SELECT 0 as rownumber FROM DUAL ) r
CROSS JOIN (SELECT 0 as tartot FROM DUAL ) t
CROSS JOIN (SELECT '' as mat FROM DUAL ) m
CROSS JOIN (SELECT '' as outlet FROM DUAL ) o
ORDER by outletName, z.matClass, z."date"
I want value of rownumber is formula in my select query but the result is error with this message
ORA-00923: FROM keyword not found where expected
00923. 00000 - "FROM keyword not found where expected"
Anyone can help me ? thanks
Just for enumeration -
replace the line
rownumber = rownumber + 1 AS row_number
with this
rownum AS row_number
rownum is an Oracle inbuilt function that enumerates each record of the result set and with auto increments
As mentioned by Gordon Linoff in his answer, there are further problems in your query.
At the first look (without executing it), I could list the problematic lines -
AND month = TO_NUMBER(TO_CHAR(current_date,'mm'))
AND year = to_number(TO_CHAR(current_date,'YYYY'))
Instead of current_date use sysdate
LEFT JOIN so_header c ON SUBSTR(c.id_to,'0',1) = 'TO'
I guess, you meant to do this -
LEFT JOIN so_header c ON SUBSTR(c.id_to,0,2) = 'TO'
i.e. substring from index 0 upto 2 characters
Plus, no need of those cross joins
THIS ADDRESSES THE ORIGINAL QUESTION.
You may have multiple problems in your query. After all, the best way to debug and to write queries is to start simple and gradually add complexity.
But, you do have an obvious error. In your outermost select:
SELECT z."date", z.id_outlet as idOutlet, z.name as outletName,
z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,
rownumber = rownumber + 1 as row_number
The = is not Oracle syntax -- it looks like a SQL Server extension for naming a column or a MySQL use of variables.
I suspect that you want to enumerate the rows. If so, one syntax is row_number():
SELECT z."date", z.id_outlet as idOutlet, z.name as outletName,
z.matClass, z.targetBulanan, z.targetBulanan/totalVisit as targetAwal,
z.actual,
row_number() over (order by outletName, z.matClass, z."date") as row_number
In Oracle, you could also do:
rownum as row_number

sql function case returns more than one row

Going to use this query as a subquery, the problem is it returns many rows of duplicates. Tried to use COUNT() instead of exists, but it still returns a multiple answer.
Every table can only contain one record of superRef.
The below query I`ll use in SELECT col_a, [the CASE] From MyTable
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = myTable.sysno AND A_specAttr = 'value')
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo WHERE C_superRef = myTable.sysno AND b_type = 2)
THEN 2
ELSE (SELECT C_intType FROM C
WHERE C_superRef = myTable.sysno)
END
FROM A, B, C
result:
3
3
3
3
3
3...
What if you did this? Because Im guessing you are getting an implicit full outer join A X B X C then running the case statement for each row in that result set.
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM ( SELECT COUNT(*) FROM A ) --This is a hack but should work in ANSI sql.
--Your milage my vary with different RDBMS flavors.
DUAL is what I needed, thanks to Thorsten Kettner
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM DUAL

Is there a way to make this query more efficient performance wise?

This query takes a long time to run on MS Sql 2008 DB with 70GB of data.
If i run the 2 where clauses seperately it takes a lot less time.
EDIT - I need to change the 'select *' to 'delete' afterwards, please keep it in mind when answering. thanks :)
select *
From computers
Where Name in
(
select T2.Name
from
(
select Name
from computers
group by Name
having COUNT(*) > 1
) T3
join computers T2 on T3.Name = T2.Name
left join policyassociations PA on T2.PK = PA.EntityId
where (T2.EncryptionStatus = 0 or T2.EncryptionStatus is NULL) and
(PA.EntityType <> 1 or PA.EntityType is NULL)
)
OR
ClientId in
(
select substring(ClientID,11,100)
from computers
)
Swapping IN for EXISTS will help.
Also, as per Gordon's answer: UNION can out-perform OR.
SELECT computers.*
FROM computers
LEFT
JOIN policyassociations
ON policyassociations.entityid = computers.pk
WHERE (
computers.encryptionstatus = 0
OR computers.encryptionstatus IS NULL
)
AND (
policyassociations.entitytype <> 1
OR policyassociations.entitytype IS NULL
)
AND EXISTS (
SELECT name
FROM (
SELECT name
FROM computers
GROUP
BY name
HAVING Count(*) > 1
) As duplicate_computers
WHERE name = computers.name
)
UNION
SELECT *
FROM computers As c
WHERE EXISTS (
SELECT SubString(clientid, 11, 100)
FROM computers
WHERE SubString(clientid, 11, 100) = c.clientid
)
You've now updated your question asking to make this a delete.
Well the good news is that instead of the "OR" you just make two DELETE statements:
DELETE
FROM computers
LEFT
JOIN policyassociations
ON policyassociations.entityid = computers.pk
WHERE (
computers.encryptionstatus = 0
OR computers.encryptionstatus IS NULL
)
AND (
policyassociations.entitytype <> 1
OR policyassociations.entitytype IS NULL
)
AND EXISTS (
SELECT name
FROM (
SELECT name
FROM computers
GROUP
BY name
HAVING Count(*) > 1
) As duplicate_computers
WHERE name = computers.name
)
;
DELETE
FROM computers As c
WHERE EXISTS (
SELECT SubString(clientid, 11, 100)
FROM computers
WHERE SubString(clientid, 11, 100) = c.clientid
)
;
Some things I would look at are
1. are indexes in place?
2. 'IN' will slow your query, try replacing it with joins,
3. you should use column name, I guess 'Name' in this case, while using count(*),
4. try selecting required data only, by selecting particular columns.
Hope this helps!
or can be poorly optimized sometimes. In this case, you can just split the query into two subqueries, and combine them using union:
select *
From computers
Where Name in
(
select T2.Name
from
(
select Name
from computers
group by Name
having COUNT(*) > 1
) T3
join computers T2 on T3.Name = T2.Name
left join policyassociations PA on T2.PK = PA.EntityId
where (T2.EncryptionStatus = 0 or T2.EncryptionStatus is NULL) and
(PA.EntityType <> 1 or PA.EntityType is NULL)
)
UNION
select *
From computers
WHERE ClientId in
(
select substring(ClientID,11,100)
from computers
);
You might also be able to improve performance by replacing the subqueries with explicit joins. However, this seems like the shortest route to better performance.
EDIT:
I think the version with join's is:
select c.*
From computers c left outer join
(select c.Name
from (select c.*, count(*) over (partition by Name) as cnt
from computers c
) c left join
policyassociations PA
on T2.PK = PA.EntityId and PA.EntityType <> 1
where (c.EncryptionStatus = 0 or c.EncryptionStatus is NULL) and
c.cnt > 1
) cpa
on c.Name = cpa.Name left outer join
(select substring(ClientID, 11, 100) as name
from computers
) csub
on c.Name = csub.name
Where cpa.Name is not null or csub.Name is not null;

SQL SERVER SELECT sum values

Problem is when I try get sum values from 2 different tables, but using condition from table 3 result are corrupted by wrong sum result . So I tried Select sum() as t1 (select sum()...) as t2 and I want to sum t1 and t2, in this way t1 and t2 result are correct
so there are code
SELECT
SUM(daa.[price]) AS t1,
(
SELECT SUM(dap.[price]) AS suma
FROM fydtr.dbo.[sales] AS dap,
[fydtr].[dbo].[work info] AS di
WHERE YEAR(di.[end of work datetime]) = 2013
AND MONTH(di.[end of work datetime]) = 12
AND di.[state] = 'e'
AND di.[reg. nr.] = dap.[reg. nr.]
) AS t2
FROM [fydtr].[dbo].[work sale] AS daa,
fydtr.dbo.[work info] AS dbi
WHERE YEAR(dbi.[end of work datetime]) = 2013
AND MONTH(dbi.[end of work datetime]) = 12
AND dbi.[state] = 'e'
AND dbi.[reg. nr.] = daa.[reg. nr.]
It gives result
t1 340
t2 509
And I need sum these and get 849 as t3.
What about something like this.
select t1, t2, t1 + t2 t3
from (
the query from your question
) temp
Not completely clear, due to missing input data. But I assume you're searching for something like this:
select sum(sale.pice) as t1
, sum(sales.price) as t2
, sum(sales.price) + sum(sale.price) as t3
from [work info] as info
left outer join [work sale] as sale on (info.state = 'e' and info.[reg. nr.] = sale.[reg. nr.])
left outer join [work sales] as sales on (info.state = 'e' and info.[reg. nr.] = sales.[reg. nr.])
where year(info.[end of work datetime]) = 2013
and month(info.[end of work datetime]) = 12
This depends on the relations between your tables, e.g. in my example, I'm assuming that there's only one entry per [reg. nr.] in all tables. Otherwise you could use "window functions", or UNIONS or CTE's (http://msdn.microsoft.com/en-us/library/ms175972.aspx). You might need to supply more context to get the answer you're searching for.
My given query is probably a little bit cleaner than your query. If that's not an issue, or if my assumption is wrong, then Dan Bracuk's answer helps you out.
And you should probably look at the column names, too. They're a little bit too complex in my opinion :)