Subtract two prices - sql

I want to subtract two prices in SQL Server. Both prices are in the same table, for example list_id 5 - list_id6 I want to get results.
SELECT MIN(fiyat) - MAX(fiyat)
FROM table
WHERE liste_id in (5,6)
I want to subtract list_id 6 from list_id 5 here.

I understand that you want to substract the price of liste_id 5 from the price of liste_id 6.
One option uses two subqueries:
select
(select fiyat from mytable where list_id = 5)
- (select fiyat from mytable where list_id = 6) as res
You can also use conditional aggregation:
select max(case when liste_id = 5 then fiyat end) - max(case when liste_id = 6 then fiyat end)
from mytable
where liste_id in (5, 6)
Note that this assumes that liste_id is a unique key in the table, in other words there is at most one row for a given value.

SQL is concerned with set-operations, not arithmetic. So if you have two rows that you want to get a single scalar value from then use you should use separate T-SQL variables, like so:
DECLARE #fiyat_5 int = ( SELECT fiyat FROM [table] WHERE liste_id = 5 );
DECLARE #fiyat_6 int = ( SELECT fiyat FROM [table] WHERE liste_id = 6 );
IF #fiyat_5 IS NULL OR #fiyat_6 IS NULL
BEGIN
THROW 51000, 'Either value does not exist.', 1;
END;
SELECT ( #fiyat_5 - #fiyat_6 ) AS diff;

Related

Conditional SQL logic

I have a simple table of voting frequencies of registered voters
create table public.campaign_202206 (
registrant_id INTEGER not null references votecal.voter_registration (registrant_id),
voting_frequency smallint
);
I want to insert values into this table with the count of elections that the voter has participated in among the past four elections:
insert into campaign_202206 (
select registrant_id, count(*)
from votecal.voter_participation_history
where election_date in ('2021-09-14', '2020-11-03', '2020-03-03', '2018-11-06')
group by registrant_id
);
However, if the count is 1, then I want to look at the participation from five elections ago on '2018-06-05' and if there is no participation in that election, I want to store the voting_frequency as 0 instead of 1.
insert into campaign_202206 (
select
registrant_id,
case
when count(*) = 1 then --- what goes here?
else count(*)
end as voting_frequency
from votecal.voter_participation_history
where election_date in ('2021-09-14', '2020-11-03', '2020-03-03', '2018-11-06')
group by registrant_id
);
What would go in this case-when-then to get the value for this special case?
Use a correlated subquery as foloows:
insert into campaign_202206 (
select
registrant_id,
case when count(*) = 1 then
(
select count(*)
from votecal.voter_participation_history sqvph
where sqvph.election_date = '2018-06-05'
and sqvph.registrant_id = vph.registrant_id
)
else count(*)
end as voting_frequency
from votecal.voter_participation_history vph
where election_date in ('2021-09-14', '2020-11-03', '2020-03-03', '2018-11-06')
group by registrant_id
);
The resultset providers in the query need aliases for this to work.
User nested case:
insert into campaign_202206 (
select
registrant_id,
case
when count(*) = 1 then
case
when (select count(*) from voter_participation_history
where election_date in ('2018-06-05') and registrant_id
= v1.registrant_id) > 0
then 1
else 0
end
else count(*)
end as voting_frequency from voter_participation_history v1 where
election_date in ('2021-09-14', '2020-11-03', '2020-03-03', '2018-11-06')
group by v1.registrant_id);

Subtract value to multiple rows

Well I am stuck at a point where I need to distribute a value across multiple rows. Since I do not know the specific term, I would put it in the form of example below for better understanding:
Assuming the value of x to be 20, I need to distribute/subtract it to rows in descending order.
TABLE:
ID Value1
1 6
2 5
3 4
4 3
5 9
Result should look like: (x=20)
ID Value1 Answer
1 6 14
2 5 9
3 4 5
4 3 2
5 9 0
Can anyone just give me an idea how I could go with this?
Untested for syntax, but the idea should work in SQL Server 2005 and newer.
SQL Server 2012 has SUM OVER clause which makes this even handier.
SELECT ID, Value1, CASE WHEN 20-SumA < 0 THEN 0 ELSE 20-SumA END AS Answer
FROM TABLE A
CROSS APPLY (SELECT SUM(B.Answer) SumA FROM TABLE B
WHERE B.ID <= A.ID) CA
It is perhaps easier to think of this problem in a different way. You want to calculate the cumulative sum of value1 and then subtract that value from #X. If the difference is negative, then put in 0.
If you are using SQL Server 2012, then you have cumulative sum built-in. You can do this as:
select id, value1,
(case when #X - cumvalue1 < 0 then 0 else #X - cumvalue1 end) as answer
from (select id, value1,
sum(value1) over (order by id) as cumvalue1
from table t
) t;
If you don't have cumulative sum, you can do this with a subquery instead:
select id, value1,
(case when #X - cumvalue1 < 0 then 0 else #X - cumvalue1 end) as answer
from (select id, value1,
(select sum(value1)
from table t2
where t2.id <= t.id
) as cumvalue1
from table t
) t;
I don't understand your question. I know what I think you're trying to do. But your example doesn't make sense.
You say you want to distribute 20 over the 5 rows, yet the sum of the difference between Value1 and Answer is only 3 (8+4+1+-1+-9).
And how do you want to distribute the values? Using a spread/split based on the value in Value1?
Edit: I made an example which splits 20 over the values you've specified above:
DECLARE #x FLOAT = 20.0
DECLARE #values TABLE (
ID INT,
VALUE FLOAT,
NEWVAL FLOAT)
INSERT INTO #values (ID, VALUE) VALUES (1,6), (2,5),(3,4),(4,3),(5,9)
UPDATE f
SET [NEWVAL] = [newValue]
FROM #values f
INNER JOIN (
SELECT
ID,
value + ((VALUE / [maxValue]) * #x) [newValue]
FROM
#values
CROSS APPLY (
SELECT
SUM(value) [maxValue]
FROM
#values
) m
) a ON a.ID = f.ID
SELECT * FROM #values
Unfortunately I had to change your values to floats for this to work. If you require them as integers, you'll need to use rounding and then calculate the difference of the sum of new value - #x and then spread the difference over the rows (if > 1 then add to lowest number, if < 1 subtract from largest value). Your rounding should be usually just 1 or 2.
I don't even know if I this is what you're trying to do yet.

Can I get the minimum of 2 columns which is greater than a given value using only one scan of a table

This is my example data (there are no indexes and I do not want to create any):
CREATE TABLE tblTest ( a INT , b INT );
INSERT INTO tblTest ( a, b ) VALUES
( 1 , 2 ),
( 5 , 1 ),
( 1 , 4 ),
( 3 , 2 )
I want the minimum value in of both column a and column b which is greater then a given value. E.g. if the given value is 3 then I want 4 to be returned.
This is my current solution:
SELECT MIN (subMin) FROM
(
SELECT MIN (a) as subMin FROM tblTest
WHERE a > 3 -- Returns 5
UNION
SELECT MIN (b) as subMin FROM tblTest
WHERE b > 3 -- Returns 4
)
This searches the table twice - once to get min(a) once to get min(b).
I believe it should be faster to do this with just one pass. Is this possible?
You want to use conditional aggregatino for this:
select min(case when a > 3 then a end) as minA,
min(case when b > 3 then b end) as minB
from tblTest;
To get the minimum of both values, you can use a SQLite extension, which handles multiple values for min():
select min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
The only issue is that the min will return NULL if either argument is NULL. You can fix this by doing:
select coalesce(min(min(case when a > 3 then a end),
min(case when b > 3 then b end)
),
min(case when a > 3 then a end),
min(case when b > 3 then b end)
)
from tblTest
This version will return the minimum value, subject to your conditions. If one of the conditions has no rows, it will still return the minimum of the other value.
From the top of my head, you could modify the table and add a min value column to store the minimum value of the two columns. then query that column.
Or you can do this:
select min(val)
from
(
select min(col1, col2) as val
from table1
)
where
val > 3
The outer SELECT, queries the memory, not the table itself.
Check SQL Fiddle

SQL - Combining incomplete

I'm using Oracle 10g. I have a table with a number of fields of varying types. The fields contain observations that have been made by made about a particular thing on a particular date by a particular site.
So:
ItemID, Date, Observation1, Observation2, Observation3...
There are about 40 Observations in each record. The table structure cannot be changed at this point in time.
Unfortunately not all the Observations have been populated (either accidentally or because the site is incapable of making that recording). I need to combine all the records about a particular item into a single record in a query, making it as complete as possible.
A simple way to do this would be something like
SELECT
ItemID,
MAX(Date),
MAX(Observation1),
MAX(Observation2)
etc.
FROM
Table
GROUP BY
ItemID
But ideally I would like it to pick the most recent observation available, not the max/min value. I could do this by writing sub queries in the form
SELECT
ItemID,
ObservationX,
ROW_NUMBER() OVER (PARTITION BY ItemID ORDER BY Date DESC) ROWNUMBER
FROM
Table
WHERE
ObservationX IS NOT NULL
And joining all the ROWNUMBER 1s together for an ItemID but because of the number of fields this would require 40 subqueries.
My question is whether there's a more concise way of doing this that I'm missing.
Create the table and the sample date
SQL> create table observation(
2 item_id number,
3 dt date,
4 val1 number,
5 val2 number );
Table created.
SQL> insert into observation values( 1, date '2011-12-01', 1, null );
1 row created.
SQL> insert into observation values( 1, date '2011-12-02', null, 2 );
1 row created.
SQL> insert into observation values( 1, date '2011-12-03', 3, null );
1 row created.
SQL> insert into observation values( 2, date '2011-12-01', 4, null );
1 row created.
SQL> insert into observation values( 2, date '2011-12-02', 5, 6 );
1 row created.
And then use the KEEP clause on the MAX aggregate function with an ORDER BY that puts the rows with NULL observations at the end. whatever date you use in the ORDER BY needs to be earlier than the earliest real observation in the table.
SQL> ed
Wrote file afiedt.buf
1 select item_id,
2 max(val1) keep( dense_rank last
3 order by (case when val1 is not null
4 then dt
5 else date '1900-01-01'
6 end) ) val1,
7 max(val2) keep( dense_rank last
8 order by (case when val2 is not null
9 then dt
10 else date '1900-01-01'
11 end) ) val2
12 from observation
13* group by item_id
SQL> /
ITEM_ID VAL1 VAL2
---------- ---------- ----------
1 3 2
2 5 6
I suspect that there is a more elegant solution to ignore the NULL values than adding the CASE statement to the ORDER BY but the CASE gets the job done.
i dont know about commands in oracle but in sql you could use some how that
first use pivot table is contains consecutives numbers 0,1,2...
i'm not sure but in oracle the function "isnull" is "NVL"
select items.ItemId,
case p.i = 0 then observation1 else '' end as observation1,
case p.i = 0 then observation1 else '' end as observation2,
case p.i = 0 then observation1 else '' end as observation3,
...
case p.i = 39 then observation4 else '' as observation40
from (
select items.ItemId
from table as items
where items.item = _paramerter_for_retrive_only_one_item /* select one item o more item where you filter items here*/
group by items.ItemId) itemgroup
left join
(
select
items.ItemId,
p.i,
isnull( max ( case p.i = 0 then observation1 else '' end ), '' ) as observation1,
isnull( max ( case p.i = 1 then observation2 else '' end ), '' ) as observation2,
isnull( max ( case p.i = 2 then observation3 else '' end), '' ) as observation3,
...
isnull( max ( case p.i = 39 then observation4), '' ) as observation40,
from
(select i from pivot where id < 40 /*you number of columns of observations, that attach one index*/
)
as p
cross join table as items
lef join table as itemcombinations
on item.itemid = itemcombinations.itemid
where items.item = _paramerter_for_retrive_only_one_item /* select one item o more item where you filter items here*/
and (p.i = 0 and not itemcombinations.observation1 is null) /* column 1 */
and (p.i = 1 and not itemcombinations.observation2 is null) /* column 2 */
and (p.i = 2 and not itemcombinations.observation3 is null) /* column 3 */
....
and (p.i = 39 and not itemcombinations.observation3 is null) /* column 39 */
group by p.i, items.ItemId
) as itemsimplified
on itemsimplified.ItemId = itemgroup.itemId
group by itemgroup.itemId
About pivot table
create an pivot table, Take a look at that
pivot table schema
name: pivot columns: {i : datatype int}
How populate
create foo table
schema foo
name: foo column: value datatype varchar
insert into foo
values('0'),
values('1'),
values('2'),
values('3'),
values('4'),
values('5'),
values('6'),
values('7'),
values('8'),
values('9');
/* insert 100 values */
insert into pivot
select concat(a.value, a.value) /* mysql */
a.value + a.value /* sql server */
a.value | a.value /* Oracle im not sure about that sintax */
from foo a, foo b
/* insert 1000 values */
insert into pivot
select concat(a.value, b.value, c.value) /* mysql */
a.value + b.value + c.value /* sql server */
a.value | b.value | c.value /* Oracle im not sure about that sintax */
from foo a, foo b, foo c
the idea about pivot table can consult in "Transact-SQL Cookbook By Jonathan Gennick, Ales Spetic"
I have to admit that the above solution (by Justin Cave) is simpler and easier to understand but this is another good option
at the end like you said you solved

SQL statement for maximum common element in a set

I have a table like
id contact value
1 A 2
2 A 3
3 B 2
4 B 3
5 B 4
6 C 2
Now I would like to get the common maximum value for a given set of contacts.
For example:
if my contact set was {A,B} it would return 3;
for the set {A,C} it would return 2
for the set {B} it would return 4
What SQL statement(s) can do this?
Try this:
SELECT value, count(distinct contact) as cnt
FROM my_table
WHERE contact IN ('A', 'C')
GROUP BY value
HAVING cnt = 2
ORDER BY value DESC
LIMIT 1
This is MySQL syntax, may differ for your database. The number (2) in HAVING clause is the number of elements in set.
SELECT max(value) FROM table WHERE contact IN ('A', 'C')
Edit: max common
declare #contacts table ( contact nchar(10) )
insert into #contacts values ('a')
insert into #contacts values ('b')
select MAX(value)
from MyTable
where (select COUNT(*) from #contacts) =
(select COUNT(*)
from MyTable t
join #contacts c on c.contact = t.contact
where t.value = MyTable.value)
Most will tell you to use:
SELECT MAX(t.value)
FROM TABLE t
WHERE t.contact IN ('A', 'C')
GROUP BY t.value
HAVING COUNT(DISTINCT t.*) = 2
Couple of caveats:
The DISTINCT is key, otherwise you could have two rows of t.contact = 'A'.
The number of COUNT(DISTINCT t.*) has to equal the number of values specified in the IN clause
My preference is to use JOINs:
SELECT MAX(t.value)
FROM TABLE t
JOIN TABLE t2 ON t2.value = t.value AND t2.contact = 'C'
WHERE t.contact = 'A'
The downside to this is that you have to do a self join (join to the same table) for every criteria (contact value in this case).