Compare version numbers in a SQL query - sql

I have a table which is used for storing the compatibility for a specific version of a software. For example whether a version of the client is compatible with the backend. There is a lower and an upper bound, both have major, minor and revision version numbers. Upper bound numbers can be null (there is a check constraint which ensures that either all or none of them is null).
I'd like to create a query which returns the rows for various majorVersion, minorVersion and revisionVersion numbers.
Example (clientId left out to make it more simple):
minMajorVersion | minMinorVersion | minRevisionVersion | maxMajorVersion | maxMinorVersion | maxRevisionVersion
1 0 0 NULL NULL NULL
1 2 5 NULL NULL NULL
1 3 0 NULL NULL NULL
2 0 1 5 1 0
Let's say I want to know which client version is compatible with a backend version 1.2.6. For this, the query should return the first two rows, because the min versions are smaller, and the max versions are NULL.
For another backend version 2.0.1 the query should return the last row, and for backend version 5.2.0 the query should return nothing.
What I was able to create is this:
SELECT c.* FROM COMPATIBILITYQUALIFIER q
join client c on (c.id = q.clientid)
WHERE (q.MINBACKENDMAJORVERSION < 2
OR (q.MINBACKENDMAJORVERSION = 2 AND q.MINBACKENDMINORVERSION < 3)
OR (q.MINBACKENDMAJORVERSION = 2 AND q.MINBACKENDMINORVERSION = 3 AND q.MINBACKENDREVISIONVERSION <=6))
AND ((q.MAXBACKENDMAJORVERSION IS NULL)
OR ((q.MAXBACKENDMAJORVERSION > 2)
OR (q.MAXBACKENDMAJORVERSION = 2 AND q.MAXBACKENDMINORVERSION > 3)
OR (q.MAXBACKENDMAJORVERSION = 2 AND q.MAXBACKENDMINORVERSION = 3 AND q.MAXBACKENDREVISIONVERSION >= 6)))
order by c.MAJORVERSION DESC, c.MINORVERSION DESC, c.REVISIONVERSION DESC;
I don't think it would be performant.
An easy way to do this would be to create a stored procedure, but I don't want to put code in the DB right now.
Is there a way to do it with sub-queries? Anything else which is fast?
UPDATED.

Sure, the query is not the prettiest. But just because you have multiple conditional clauses like that doesn't mean that your query will be any slower.
Only as a matter of readability and to avoid repeating hardcoded values, I would rewrite the query to something like this:
select c.*
from compatibilityqualifier q
join (select 2 as major,
3 as minor,
6 as revision
from dual) ver
on 1=1
join client c
on c.id = q.clientid
where ver.major >= q.minBackendMajorVersion
and (ver.major > q.minBackendMajorVersion or ver.minor >= q.minBackendMinorVersion)
and (ver.major > q.minBackendMajorVersion or ver.minor > q.minBackendMinorVersion or ver.revision >= q.minBackendRevisionVersion)
and (q.maxBackendMajorVersion is null
or (ver.major <= q.maxBackendMajorVersion
and (ver.major < q.maxBackendMajorVersion or ver.minor <= q.maxBackendMinorVersion)
and (ver.major < q.maxBackendMajorVersion or ver.minor < q.maxBackendMinorVersion or ver.revision <= q.maxBackendRevisionVersion)
)
)
order by c.majorversion desc,
c.minorversion desc,
c.revisionversion desc
But I expect the performance to be pretty much identical.

For any given version number expressed as a tuple of (Major, Minor, Revision) you can use the following query to retrieve rows from your CompatibilityQualifier table. For example Version 1,2,6 below:
select q.*
from (select 1 major
, 2 minor
, 6 revision from dual) v
join CompatibilityQualifier q
on ( q.minMajorVersion < v.major or
( q.minMajorVersion = v.major and
( q.minMinorVersion < v.minor or
( q.minMinorVersion = v.minor and
q.minRevisionVersion <= v.revision))))
and ( q.maxMajorVersion is null or
q.maxMajorVersion > v.major or
( q.maxMajorVersion = v.major and
( q.MaxMinorVersion is null or
q.MaxMinorVersion > v.minor or
( q.MaxMinorVersion = v.minor and
( maxRevisionVersion is null or
q.maxRevisionVersion >= v.revision)))));
Which yields the following results:
| MINMAJORVERSION | MINMINORVERSION | MINREVISIONVERSION | MAXMAJORVERSION | MAXMINORVERSION | MAXREVISIONVERSION |
|-----------------|-----------------|--------------------|-----------------|-----------------|--------------------|
| 1 | 0 | 0 | (null) | (null) | (null) |
| 1 | 2 | 5 | (null) | (null) | (null) |
With revision 2,0,1 every row from CompatibilityQualifier would be returned since there are no upper bounds on any of the 1,x,x records.
If you really want records with NULL values of maxMajorVersion excluded from the result set when the queried major version number differs from the minMajorVersion then you can use this revised version:
select q.*
from (select 2 major
, 0 minor
, 1 revision from dual) v
join CompatibilityQualifier q
on ( q.minMajorVersion < v.major or
( q.minMajorVersion = v.major and
( q.minMinorVersion < v.minor or
( q.minMinorVersion = v.minor and
q.minRevisionVersion <= v.revision))))
and ( --q.maxMajorVersion is null or
q.maxMajorVersion > v.major or
( coalesce(q.maxMajorVersion -- When Null compare to minMajorVersion
,q.minMajorVersion) = v.major and
( q.MaxMinorVersion is null or
q.MaxMinorVersion > v.minor or
( q.MaxMinorVersion = v.minor and
( maxRevisionVersion is null or
q.maxRevisionVersion >= v.revision)))));
which just returns the one row:
| MINMAJORVERSION | MINMINORVERSION | MINREVISIONVERSION | MAXMAJORVERSION | MAXMINORVERSION | MAXREVISIONVERSION |
|-----------------|-----------------|--------------------|-----------------|-----------------|--------------------|
| 2 | 0 | 1 | 5 | 1 | 0 |

I know I am bit late to the party, but I found a very easy solution to compare versions in Oracle. Just need to make compare versions as varchar and compare.
Check the following simple query and tested it for different versions and it worked!!!
select case when '1.0.30' < '1.1.22' then 'true' else 'false' end as isVersionHigher
from dual;
--Result => 'true'
You can even compare versions just having major and minor digits. Check below query.
select case when '1.0' < '1.1.22' then 'true' else 'false' end as isVersionHigher
from dual;
--Result => 'true'

Related

How to integrate over segments using SQL

I have a table with columns t_b; t_e; x were [t_b, t_e) denotes a period during which x resources where used. I want to compute a table were for each hour h I have amount of resources that where used during [h, h+1) period.
So far my only idea was to generate multiple rows from each input row for each hour (I use an extension of SQL with UDFs) and then simply group by by hour, but I'm afraid this may be too slow considering large amount of data at hand.
Say for example I have a table with two rows:
+-----+-----+---+
| t_b | t_e | x |
+-----+-----+---+
| 1 | 3.5 | a |
| 0.5 | 4 | b |
+-----+-----+---+
Then resulting table should be:
+---+-------------+
| h | x |
+---+-------------+
| 0 | 0*a + 0.5*b |
| 1 | 1*a + 1*b |
| 2 | 1*a + 1*b |
| 3 | 0.5*a + 1*b |
+---+-------------+
You can have a trigger on insert into the stats table that also adds to the aggregate table (the per-hour sums).
If you also need to convert the existing data, you need to run over every row of your current table, split it into amounts/hours and add to the aggregate table.
This is an sql-server example for all number columns
with h as (
-- your hours tally here
select top(24) row_number() over(order by (select null)) eoh from sys.all_objects
), myTable as (
select 1 t_b, 3.5 t_e, 20 v union all
select 0.5, 4, 40
)
select eoh-1 h_starth
, sum(v * (case when t_e < eoh then t_e else eoh end - case when t_b > eoh-1 then t_b else eoh-1 end)) usage
from h
left join myTable t on t_e > eoh - 1 and eoh > t_b -- [..) intresection with [..)
group by eoh;
Fiddle

MS-Access Query to PostgreSQL View

I am converting a microsoft access query into a postgresql view. The query has obvious components that I have found reasonable answers to. However, I am still stuck on getting the final result:
SELECT All_Claim_Data.Sec_ID,
Sum(IIf([Type]="LODE",IIf([Status]="Active",1,0),0)) AS LD_Actv,
Sum(IIf([Type]="LODE",IIf([Loc_Date]>#8/31/2017#,IIf([Loc_Date]<#9/1/2018#,1,0),0),0)) AS LD_stkd_17_18,
Sum(IIf([Type]="LODE",IIf([Loc_Date]>#8/31/2016#,IIf([Loc_Date]<#9/1/2017#,1,0),0),0)) AS LD_stkd_16_17,
Sum(IIf([Type]="LODE",IIf([Loc_Date]<#1/1/1910#,IIf(IsNull([Clsd_Date]),1,(IIf([Clsd_Date]>#1/1/1900#,1,0))),0),0)) AS Actv_1900s,
Sum(IIf([Type]="LODE",IIf([Loc_Date]<#1/1/1920#,IIf(IsNull([Clsd_Date]),1,(IIf([Clsd_Date]>#1/1/1910#,1,0))),0),0)) AS Actv_1910s,
FROM All_Claim_Data.Sec_ID,
GROUP BY All_Claim_Data.Sec_ID,
HAVING (((Sum(IIf([casetype_txt]="LODE",1,0)))>0));
Realizing I need to use CASE SUM WHEN, here is what I have worked out so far:
CREATE OR REPLACE VIEW hgeditor.vw_test AS
SELECT All_Claim_Data.Sec_ID,
SUM (CASE WHEN(Type='LODE' AND WHEN(Status='Active',1,0),0)) AS LD_Actv,
SUM (CASE WHEN(Type='LODE' AND WHEN(Loc_Date>'8/31/2017' AND Loc_Date<'9/1/2018',1,0),0),0)) AS LD_stkd_17_18,
SUM (CASE WHEN(Type='LODE' AND WHEN(Loc_Date<'1/1/1910' AND (IsNull(Clsd_Date),1,(WHEN([Clsd_Date]>'1/1/1900',1,0))),0),0)) AS Actv_1900s
FROM All_Claim_Data.Sec_ID,
GROUP BY All_Claim_Data.Sec_ID,
HAVING (((SUM(IIf(Type='LODE',1,0)))>0));
The goal is to count the number of instances in which the Sec_ID has the following:
has (Type = LODE and Status = Active) = SUM integer
has (Type = LODE and Loc_Date between 8/31/2017 and 9/1/2018) = SUM Integer
My primary issue is getting a SUM integer to populate in the new columns
Case expressions are the equivalent to the Access IIF() functions, but WHEN isn't a function so it isn't used by passing a set of parameters. Think of it as being a tiny where clause instead, it evaluates one or more predicates to determine what to do, and the action taken is established by what you specify after THEN
CREATE OR REPLACE VIEW hgeditor.vw_test AS
SELECT
All_Claim_Data.Sec_ID
, SUM( CASE
WHEN TYPE = 'LODE' AND
STATUS = 'Active' THEN 1
ELSE 0
END ) AS LD_Actv
, SUM( CASE
WHEN TYPE = 'LODE' AND
Loc_Date > to_date('08/31/2017','mm/dd/yyyy') AND
Loc_Date < to_date('09/1/2018','mm/dd/yyyy') THEN 1
ELSE 0
END ) AS LD_stkd_17_18
, SUM( CASE
WHEN TYPE = 'LODE' AND
Loc_Date < to_date('1/1/1910','mm/dd/yyyy') AND
[Clsd_Date] > to_date('1/1/1900','mm/dd/yyyy') THEN 1
ELSE 0
END ) AS Actv_1900s
FROM All_Claim_Data.Sec_ID
GROUP BY
All_Claim_Data.Sec_ID
HAVING COUNT( CASE
WHEN Type = 'LODE' THEN 1
END ) > 0
;
By the way, you should NOT be relying on MM/DD/YYYY as dates in Postgres
nb: Aggregate functions ignore NULL, take this example:
+----------+
| id value |
+----------+
| 1 x |
| 2 NULL |
| 3 x |
| 4 NULL |
| 5 x |
+----------+
select
count(*) c_all
, count(value) c_value
from t
+-------+----------+
| c_all | c_value |
+-------+----------+
| 5 | 3 |
+-------+----------+
select
sum(case when value IS NOT NULL then 1 else 0 end) sum_case
, count(case when value IS NOT NULL then 1 end) count_case
from t
+----------+-------------+
| sum_case | count_case |
+----------+-------------+
| 3 | 3 |
+----------+-------------+

Fetch rows that correspond to the state of the application at some point in history

I'm not certain how to describe my problem in words so I've created an illustration to help.
|-------------------------------------------|
| version_table (many-to-many) |
|-------------------------------------------|
| version_id | a_id | b_id | operation_type |
|------------|------|------|----------------|
| 1 | 1 | 1 | INSERT |
| 1 | 1 | 2 | INSERT |
| 2 | 1 | 1 | DELETE |
| 3 | 1 | 2 | DELETE |
|------------|------|------|----------------|
In this table querying for each version would yield these results:
Version 1 should return two rows (obvious because of the inserts).
Version 2 should return one row (less obvious but the row exists until a DELETE operation has been called).
Version 3 should return zero rows (all rows cleared by the previous DELETE operations).
Its obvious is that we need to fetch all of the rows that were inserted before or on the supplied version.
WHERE table.version_id <= :VERSION
But whats not obvious is how we exclude rows that have been "DELETED".
AND table.version_id > alias.version_id AND alias.operation_type = "DELETE"
This is the query I ended up writing:
SELECT tag.id AS tag_id, tag.name AS tag_name
FROM tag
JOIN article_tag_version ON article_tag_version.tag_id = tag.id
LEFT OUTER JOIN article_tag_version AS article_tag_version_1 ON
article_tag_version_1.tag_id = tag.id AND
article_tag_version_1.operation_type = "DELETE"
WHERE article_tag_version.version_id <= ? AND article_tag_version.version_id > article_tag_version_1.version_id
...but it doesn't return the results in the way I expect (no results).
You can check with a NOT EXISTS if the "thing" (whatever it is) has been deleted in a version between the version it was inserted and the target version.
SELECT *
FROM version_table v1
WHERE v1.operation_type = 'INSERT'
AND NOT EXISTS (SELECT *
FROM version_table v2
WHERE v2.version_id >= v1.version_id
AND v2.version_id <= :VERSION
AND (v2.a_id,
v2.b_id) = (v1.a_id,
v1.b_id)
AND v2.operation_type = 'DELETE')
AND v1.version_id <= :VERSION;
SQL Fiddle
I would just select the most recent record for each a_id/b_id pair, then filter out the ones that are deleted:
select atv.*
from (select distinct on (a_id, b_id) atv.*
from article_tag_version atv
where version <= ? -- the version you care about
order by a_id, b_id, version desc
) atv
where operation_type <> 'DELETE';

select max value in a group of consecutive values

How do you do to retrieve only the max value of a group with only consecutive values?
I have a telephone database with only unique values and I want to get only the highest number of each telephone number group TelNr and I am struggling.
id | TeNr | Position
1 | 100 | SLMO2.1.3
2 | 101 | SLMO2.3.4
3 | 103 | SLMO2.4.1
4 | 104 | SLMO2.3.2
5 | 200 | SLMO2.5.1
6 | 201 | SLMO2.5.2
7 | 204 | SLMO2.5.5
8 | 300 | SLMO2.3.5
9 | 301 | SLMO2.6.2
10 | 401 | SLMO2.4.8
Result should be:
TelNr
101
104
201
204
301
401
I have tried almost every tip I could find so far and whether I get all TelNr or no number at all which is useless in my case.
Any brilliant idea to run this with SQLITE?
So you're searching for gaps and want to get the first value of those gaps.
This is probably the best way to get them, try to check for a row with the current TeNr plus 1 and if there's none you found it:
select t1.TeNr, t1.TeNr + 1 as unused_TeNr
from tab as t1
left join Tab as t2
on t2.TeNr = t1.TeNr + 1
where t2.TeNr is null
Edit:
To get the range of missing values you need to use some old-style SQL as SQLite doesn't seem to support ROW_NUMBER, etc.
select
TeNr + 1 as RangeStart,
nextTeNr - 1 as RangeEnd,
nextTeNr - TeNr - 1 as cnt
from
(
select TeNr,
( select min(TeNr) from tab as t2
where t2.TeNr > t1.TeNr ) as nextTeNr
from tab as t1
) as dt
where nextTeNr > TeNr + 1
It's probably not very efficient, but might be ok if the number of rows is small and/or there's a index on TeNr.
Getting each value in the gap as a row in your result set is very hard, if your version of SQLite supports recursive queries:
with recursive cte (TeNr, missing, maxTeNr) as
(
select
min(TeNr) as TeNr, -- start of range of existing numbers
0 as missing, -- 0 = TeNr exists, 1 = TeNr is missing
max(TeNr) as maxTeNr -- end of range of existing numbers
from tab
union all
select
cte.TeNr + 1, -- next TeNr, if it doesn't exists tab.TeNr will be NULL
case when tab.TeNr is not null then 0 else 1 end,
maxTeNr
from cte left join tab
on tab.TeNr = cte.TeNr + 1
where cte.TeNr + 1 < maxTeNr
)
select TeNr
from cte
where missing = 1
Depending on your data this might return a huge amount of rows.
You might also use the result of the previous RangeStart/RangeEnd query as input to this recursion.

Selecting with two different WHERE statements

I'm struggling to work out how to run a select query where I'm checking for two different values at the same time, and wanting them in seperate columns.
My table example:
ID | foreignID | value | accepted
----------------------------------
1 | 1 | 5 | Y
2 | 1 | 2 | Y
3 | 1 | 4 | N
4 | 2 | 8 | Y
And what I'm trying to do is along the lines of this:
SELECT
foreignID,
SUM(value WHERE (accepted='Y')) AS sum1,
SUM(value WHERE (accepted='N')) AS sum2
FROM example
WHERE foreignID='1'
My expected results would be:
foreignID | sum1 | sum2
------------------------
1 | 7 | 4
Obviously the above code wouldn't work, it's just a half-sudo code to show what I want. Essentially I only want to check with one foreignID but then get results from several SUMs that each require their own argument.
Does anyone know of any ways in which this could be achieved, or something similar. I've tried UNION which puts it into...
foreignID | sum
1 | 7
1 | 4
... but that's not really what I'm after.
I've also seen multiple select in one sql statement which seems to be on the right track but again, that's using UNION which I don't think is ideal for my example.
I could be wrong there so please do prove me wrong if I am. I might just be overlooking something! Thanks for any help you can provide.
Try this:
SELECT
foreignID,
SUM(CASE WHEN accepted = 'Y' THEN value ELSE 0 END) AS sum1,
SUM(CASE WHEN accepted = 'N' THEN value ELSE 0 END) AS sum2
FROM example
WHERE foreignID='1'
SQL fiddle
create table test(
id int ,
foreignid int,
value int,
accepted char(1)
)
INSERT INTO test values (1,1,5,'Y');
INSERT INTO test values (2,1,2,'Y');
INSERT INTO test values (3,1,4,'N');
INSERT INTO test values (4,2,8,'Y');
select
foreignid,
sum( case when accepted='Y' then value else 0 end) as sumY
,sum( case when accepted='N' then value else 0 end) as sumN
from test
group by foreignid;
This is more readable than the case:
select
foreignID,
sum("value" * (accepted = 'Y')::int) sum1,
sum("value" * (accepted = 'N')::int) sum2
from example
where foreignID = '1'
The cast of the boolean to integer results in 0 or 1. Many, if not most, of the languages cast the same way. I tested in four:
C#
Console.WriteLine("{0} {1}",
7 * Convert.ToInt32(true) - 2 * Convert.ToInt32(false),
// Or shorter:
7 * (true ? 1 : 0) - 2 * (false ? 1 : 0)
);
Python
>>> 7 * True - 2 * False
7
Javascript
<script type="text/javascript">
document.write(7 * true - 2 * false);
</script>
PHP
<?php
echo 7 * true - 2 * false;
?>