I Come across below problem and not sure how to get it sorted:
We have a dimensions of the boxes in database:
L,W,H
As the box may be measured in random position , for example the values for Lenght may be allocated in to Height column etc.
Now , we have a band for boxes which contains two conditions regarding the dimensions:
Band:
Every Box with Maximum of 24x34x6
OR
Every Box with Maximum of 21x21x21
To calculate this by AND , OR statements will be possible but we will end up with 100's of lines of code only for first condition as the number of combinations will be 27 and then this will need to by applied in sql statement for each of the columns H,L,W.
I managed to get the total number of combinations by cross join but how to apply that to CASE statement and get reasonable performance and amount of code ?
CREATE TABLE #XMS1
( XMS1 int)
INSERT #XMS1 select 6
INSERT #XMS1 select 24
INSERT #XMS1 select 34
select
a.XMS1 as H,
b.XMS1 as L,
c.XMS1 as W
from #XMS1 as a
cross join #XMS1 b
cross join #XMS1 c
The values of L,W,H can be everyting from 0 to 100 but I need to select only the lines which fits in to the band...
Oracle:
You can use this to order the dimensions and then just filter on smallest, middle and largest:
SELECT *
FROM (
SELECT LEAST( l, w, h ) AS small,
l + w + h - LEAST( l, w, h ) - GREATEST( l, w, h ) AS middle,
GREATEST( l, w, h ) AS large
FROM your_box_dimensions_table
)
WHERE ( small <= 6 AND middle <= 24 AND large <= 36 )
OR ( small <= 21 AND middle <= 21 AND large <= 21 ) -- or more simply: large <= 21
;
SQL Server:
Since SQL Server does not appear to have a LEAST or GREATEST function, you could just write a user-defined function for LEAST and GREATEST with three inputs and then just call that to mimic the Oracle solution.
Or:
SELECT *
FROM (
SELECT CASE
WHEN l <= w AND l <= h THEN l
WHEN w <= h THEN w
ELSE h
END AS small,
CASE
WHEN w <= l AND l <= h THEN l
WHEN h <= l AND l <= w THEN l
WHEN l <= w AND w <= h THEN w
WHEN h <= w AND w <= l THEN w
ELSE h
END AS middle,
CASE
WHEN l >= w AND l >= h THEN l
WHEN w >= h THEN w
ELSE h
END AS large
FROM your_box_dimensions_table
)
WHERE ( small <= 6 AND middle <= 24 AND large <= 36 )
OR ( small <= 21 AND middle <= 21 AND large <= 21 ); -- or more simply: large <= 21
Daniel, I think #MT0 has answered this so don't mark this as an answer I'm just simply expanding his answer into a complete example.
This first step is simply building a list of values between 1 and 100 to help create the table containing box dimensions.
DECLARE #n TABLE (n int)
INSERT INTO #n
SELECT ones.n + 10*tens.n +1
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n)
ORDER BY 1
Now creating a physical table Boxes to store each combination of box dimensions (1 million records)
CREATE TABLE Boxes (d1 int, d2 int, d3 int)
INSERT INTO Boxes
SELECT a.n, b.n, c.n
FROM #n a
cross join #n b
cross join #n c
Now create a view over this table with three additional columns, these will hold the dimensions in size order (small medium, large) (thanks to MT0 for saving me having to type all that out!)
You could also use a physical table instead of a view but this way offers a bit more flexibility if requirements change.
CREATE VIEW BoxesExtended AS
select
*
, CASE
WHEN d1 <= d2 AND d1 <= d3 THEN d1
WHEN d2 <= d3 THEN d2
ELSE d3
END AS small
, CASE
WHEN d2 <= d1 AND d1 <= d3 THEN d1
WHEN d3 <= d1 AND d1 <= d2 THEN d1
WHEN d1 <= d2 AND d2 <= d3 THEN d2
WHEN d3 <= d2 AND d2 <= d1 THEN d2
ELSE d3
END AS middle
, CASE
WHEN d1 >= d2 AND d1 >= d3 THEN d1
WHEN d2 >= d3 THEN d2
ELSE d3
END AS large
from Boxes
Now all we have to do is query the view to get the boxes that match your criteria
SELECT *
FROM BoxesExtended
WHERE (large <=34 and middle <=24 and small <=6)
OR (large <=21 and middle <=21 and small <=21)
You could extend this by having a 'Bands' table along the lines of
BandID BandName MaxSmall MaxMiddle MaxLarge
1 BandA 6 24 36
2 BandA 21 21 21
3 BandB 30 40 50
and then join this to the BoxesExtended view. This would allow you to define bands and then query against the view to get matching boxes within each BandName
Related
This question relates to SQL 2012 -
Lets say I have 3 rows generated as follows:
Start Position = 10
End Position = 13
Value = 100
Start position = 14
End Position = 14
Value = 250
Start Position = 15
End Position = 25
Value = 300
on 3 rows ..
Is there a way I can force SQL to write the output:
10 - 100
11 - 100
12 - 100
13 - 100
14 - 250
15 - 300
16 - 300
etc and so on and so forth
Been wracking the brains but cant work out an easy way to do it
Thanks a lot
J
You can do this with a recursive CTE or a numbers table. Assuming the gaps are no more than a few hundred or thousand:
with n as (
select row_number() over (order by (select null)) - 1 as n
from master.spt_values
)
select (t.startpos + n.n) as position, value
from t join
n
on t.startpos + n.n <= t.endpos;
No database is complete without its table of numbers! Instructions on how to create one are all over the net, here is an example:
Create a numbers table
I have a numbers table in my database, its called t_numbers, it has a single column "n" with a row for each number starting from 1. It goes up to 999,999 but it takes up very little space on disk. Once you have that, you can write something like this:
set up a bit of data to use first
declare #Rows table
(
StartPos int,
EndPos int,
Value int
)
insert into #Rows values (10, 13, 100), (14, 14, 250), (15, 25, 300)
If you want don't want gaps for nulls use an inner join
select n.n, Value
from t_numbers n
inner join #Rows r on n.n >= StartPos and n.n <= EndPos
If you want the gaps then left join, but limit the return with a where clause
select n.n, Value
from t_numbers n
left join #Rows r on n.n >= StartPos and n.n <= EndPos
where n <= (select MAX(EndPos) from #Rows)
Thankyou people! that did it
In the end I created a table of numbers (thankyou guys)
+ said
CROSS JOIN MyNumbers
WHERE T.Real_Position between T.triangle_start_position and T.triangle_end_position
This gave me the exact resultset I was looking for
Sample data below
id start end
a 1 3
a 5 6
a 8 9
b 2 4
b 6 7
b 9 10
c 2 4
c 6 7
c 9 10
I'm trying to come up with a query that will return all the overlap start-end inclusive between a, b, and c (but extendable to more). So the expected data will look like the following
start end
2 3
6 6
9 9
The only way I can picture this is with a custom aggregate function that tracks the current valid intervals then computes the new intervals during the iterate phase. However I can't see this approach being practical when working with large datasets. So if some bright mind out there have a query or some innate function that I'm not aware of I would greatly appreciate the help.
You can do this using aggregation and a join. Assuming no internal overlaps for "a" and "b":
select greatest(ta.start, tb.start) as start,
least(ta.end, tb.end) as end
from t ta join
t tb
on ta.start <= tb.end and ta.end >= tb.start and
ta.id = 'a' and tb.id = 'b';
This is a lot uglier and more complex than Gordon's solution, but I think it gives the expected answer better and should extend to work with more ids:
WITH NUMS(N) AS ( --GENERATE NUMBERS N FROM THE SMALLEST START VALUE TO THE LARGEST END VALUE
SELECT MIN("START") N FROM T
UNION ALL
SELECT N+1 FROM NUMS WHERE N < (SELECT MAX("END") FROM T)
),
SEQS(N,START_RANK,END_RANK) AS (
SELECT N,
CASE WHEN IS_START=1 THEN ROW_NUMBER() OVER (PARTITION BY IS_START ORDER BY N) ELSE 0 END START_RANK, --ASSIGN A RANK TO EACH RANGE START
CASE WHEN IS_END=1 THEN ROW_NUMBER() OVER (PARTITION BY IS_END ORDER BY N) ELSE 0 END END_RANK --ASSIGN A RANK TO EACH RANGE END
FROM (
SELECT N,
CASE WHEN NVL(LAG(N) OVER (ORDER BY N),N) + 1 <> N THEN 1 ELSE 0 END IS_START, --MARK N AS A RANGE START
CASE WHEN NVL(LEAD(N) OVER (ORDER BY N),N) -1 <> N THEN 1 ELSE 0 END IS_END /* MARK N AS A RANGE END */
FROM (
SELECT DISTINCT N FROM ( --GET THE SET OF NUMBERS N THAT ARE INCLUDED IN ALL ID RANGES
SELECT NUMS.*,T.*,COUNT(*) OVER (PARTITION BY N) N_CNT,COUNT(DISTINCT "ID") OVER () ID_CNT
FROM NUMS
JOIN T ON (NUMS.N >= T."START" AND NUMS.N <= T."END")
) WHERE N_CNT=ID_CNT
)
) WHERE IS_START + IS_END > 0
)
SELECT STARTS.N "START",ENDS.N "END" FROM SEQS STARTS
JOIN SEQS ENDS ON (STARTS.START_RANK=ENDS.END_RANK AND STARTS.N <= ENDS.N) ORDER BY "START"; --MATCH CORRESPONDING RANGE START/END VALUES
First we generate all the numbers between the smallest start value and the largest end value.
Then we find the numbers that are included in all the provided "id" ranges by joining our generated numbers to the ranges, and selecting each number "n" that appears once for each "id".
Then we determine whether each of these values "n" starts or ends a range. To determine that, for each N we say:
If the previous value of N does not exist or is not 1 less than current N, current N starts a range. If the next value of N does not exist or is not 1 greater than current N, current N ends a range.
Next, we assign a "rank" to each start and end value so we can match them up.
Finally, we self-join where the ranks match (and where the start <= the end) to get our result.
EDIT: After some searching, I came across this question which shows a better way to find the start/ends and refactored the query to:
WITH NUMS(N) AS ( --GENERATE NUMBERS N FROM THE SMALLEST START VALUE TO THE LARGEST END VALUE
SELECT MIN("START") N FROM T
UNION ALL
SELECT N+1 FROM NUMS WHERE N < (SELECT MAX("END") FROM T)
)
SELECT MIN(N) "START",MAX(N) "END" FROM (
SELECT N,ROW_NUMBER() OVER (ORDER BY N)-N GRP_ID
FROM (
SELECT DISTINCT N FROM ( --GET THE SET OF NUMBERS N THAT ARE INCLUDED IN ALL ID RANGES
SELECT NUMS.*,T.*,COUNT(*) OVER (PARTITION BY N) N_CNT,COUNT(DISTINCT "ID") OVER () ID_CNT
FROM NUMS
JOIN T ON (NUMS.N >= T."START" AND NUMS.N <= T."END")
) WHERE N_CNT=ID_CNT
)
)
GROUP BY GRP_ID ORDER BY "START";
In DB2, I have this query to list numbers 1-x:
select level from SYSIBM.SYSDUMMY1 connect by level <= "some number"
But this maxes out due to SQL20450N Recursion limit exceeded within a hierarchical query.
How can I generate a list of numbers between 1 and x using a select statement when x is not known at runtime?
I found an answer based on this post:
WITH d AS
(SELECT LEVEL - 1 AS dig FROM SYSIBM.SYSDUMMY1 CONNECT BY LEVEL <= 10)
SELECT t1.n
FROM (SELECT (d7.dig * 1000000) +
(d6.dig * 100000) +
(d5.dig * 10000) +
(d4.dig * 1000) +
(d3.dig * 100) +
(d2.dig * 10) +
d1.dig AS n
FROM d d1
CROSS JOIN d d2
CROSS JOIN d d3
CROSS JOIN d d4
CROSS JOIN d d5
CROSS JOIN d d6
CROSS JOIN d d7) t1
JOIN ("subselect that returns desired value as i") t2
ON t1.n <= t2.i
ORDER BY t1.n
That's how I usually create lists:
For your example
numberlist (num) as
(
select min(1) from anytable
union all
select num + 1 from numberlist
where num <= x
)
I did something like this when I wanted a list of values to correspond with months:
with t1 (mon) as (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)
select * from t1
It seems a bit kludgy, but for a small list like 1-12, or even 1-50, it did what I needed it to.
It's nice to see someone else tagging their questions with DB2.
If you have any table known to have more than x rows, you can always do:
select * from (
select row_number() over () num
from my_big_table
) where num <= x
or, per bhamby's suggestion:
select row_number() over () num
from my_big_table
fetch first X rows only
For DB2 you can use recursive common table expressions (cf. IBM documentation on recursive CTE):
with max(num) as (
select 1 from sysibm.sysdummy1
)
,result (num) as (
select num from max
union ALL
select result.num+1
from result
where result.num<=100
)
select * from result;
I have a row x with integers in range 0 < x <= maxX.
To create a histogram with five partitions of equal size I am using the following statement in sqlite
select case
when x > 0 and x <= 1*((maxX+4)/5) then 1
when x > 1*((maxX+4)/5) and x <= 2*((maxX+4)/5) then 2
when x > 2*((maxX+4)/5) and x <= 3*((maxX+4)/5) then 3
when x > 3*((maxX+4)/5) and x <= 4*((maxX+4)/5) then 4
else 5 end as category, count(*) as count
from A,B group by category
Is there a way to make a "dynamic" query for this in the way that I can create a histogram of n partitions without writing n conditions in the case-statement?
You can use arithmetic to divide the values. Here is one method. It essentially takes the ceiling value of maxX / 5 and uses that to define the partitions:
select (case when cast(maxX / params.n as int) = maxX / params.n
then (x - 1) / (maxX / param.n)
else (x - 1) / cast(1 + maxX / params.n as int)
end) as category, count(*)
from (select 5 as n) params cross join
A
group by category;
The -1 is because your numbers start at one rather than zero.
I have a TABLE "elements" with one COLUMN "number", type SMALLINT that contains numbers 1 thru 56. How can I generate unique sets of 5 numbers of every possible combination from 1 to 56, using an SQL statement?
In APL (programming language) a simple dyadic function 5!56 does the trick!
EDIT: In good ole MS-DOS QBASIC, I accomplished it like this:
10 OPEN "C:\5NUMBERS.OUT" FOR OUTPUT ACCESS READ WRITE AS #1
12 LET SER = 0
15 LET E = 56
30 FOR B5 = 5 TO E
40 FOR B4 = 4 TO E
50 FOR B3 = 3 TO E
60 FOR B2 = 2 TO E
70 FOR B1 = 1 TO E
80
88 IF B5 = B1 THEN 190
89 IF B5 = B2 THEN 190
90 IF B5 = B3 THEN 190
91 IF B5 = B4 THEN 190
92 IF B4 = B1 THEN 180
93 IF B4 = B2 THEN 180
94 IF B4 = B3 THEN 180
95 IF B3 = B1 THEN 170
96 IF B3 = B2 THEN 170
97 IF B2 = B1 THEN 160
98 LET SER = SER + 1
100 PRINT #1, SER; "|";
130 PRINT #1, B1; "|";
131 PRINT #1, B2; "|";
132 PRINT #1, B3; "|";
133 PRINT #1, B4; "|";
134 PRINT #1, B5; "|";
140 PRINT #1, B1 + B2 + B3 + B4 + B5; "|"
150 NEXT B1
160 NEXT B2
170 NEXT B3
180 NEXT B4
190 NEXT B5
205 CLOSE
210 END
220 SYSTEM
This, by the way, created my load file into an INFORMIX-SQL table
TABLE combos
(
seq_id SERIAL,
ball_1 SMALLINT,
ball_2 SMALLINT,
ball_3 SMALLINT,
ball_4 SMALLINT,
ball_5 SMALLINT,
sum SMALLINT
);
I used combos.sum to generate a bell curve graph, showing the count of combinations having the same sum of each element.
If by "unique sets" you mean what I think you do (sorry, I don't know APL!), you can write:
SELECT e1.number, e2.number, e3.number, e4.number, e.number
FROM elements e1, elements e2, elements e3, elements e4, elements e5
WHERE e1.number < e2.number
AND e2.number < e3.number
AND e3.number < e4.number
AND e4.number < e5.number
;
"could this be accomplished without actually having to store the
elements in a table?.. i.e. let the server do it without resorting to
table I/O? "
Yes, there is an Oracle trick to generate data on the fly, using the hierarchical query and the CTE syntax:
WITH elements AS
( select rownum as number
from dual
connect by level <= 56 )
SELECT e1.number, e2.number, e3.number, e4.number, e.number
FROM elements e1, elements e2, elements e3, elements e4, elements e5
WHERE e1.number < e2.number
AND e2.number < e3.number
AND e3.number < e4.number
AND e4.number < e5.number
;
If you want to include pairs of identical numbers, e.g. (5,5):
SELECT e1.number AS number1
,e2.number AS number2
FROM elements e1
,elements e2
WHERE e1.number <= e2.number;
If you want to only have different numbers in each pair:
SELECT e1.number AS number1
,e2.number AS number2
FROM elements e1
,elements e2
WHERE e1.number < e2.number;
Not that I would actually use a database for this type of task but, if you were forced to do this under threat of torture or dismemberment, I would look into something like (number shortened to num for formatting purposes):
select a.num, b.num, c.num, d.num, e.num
from elements a, elements b, elements c, elements d, elements e
where a.num <> b.num and a.num <> c.num and a.num <> d.num and a.num <> e.num
and b.num <> c.num and b.num <> d.num and b.num <> e.num
and c.num <> d.num and c.num <> e.num
and d.num <> e.num
It basically cross joins the table to itself to generate five columns and then filters out those where any of the numbers are identical.
Note that this gives you permutations: (1,2,3,4,5) is distinct from (1,2,3,5,4). If you want combinations (where the order doesn't matter), you would use slightly different clauses:
select a.num, b.num, c.num, d.num, e.num
from elements a, elements b, elements c, elements d, elements e
where a.num > b.num and b.num > c.num and c.num > d.num and d.num > e.num
My FIRST thought would be to do a Cartesian and just make sure that every record is higher than the last so you don't ever get numbers duplicated anywhere. Now this would create something like
1,2,3,4,5
1,2,3,4,6
1,2,3,4,7, etc...
but will NEVER have the reverse or mixed such as
6,4,3,2,1
6,2,4,3,1
4,6,1,2,3
as those would already be a "same" set of numbers (more along the lines of lottery style where no same number appears twice)
HOWEVER, if you also wanted duplicates, such as
1,1,1,1,1
1,2,1,2,1
1,2,3,1,1
Where a number COULD get repeated numbers just change the equality to <= instead of just <.
select
YT1.Number as Num1,
YT2.Number as Num2,
YT3.Number as Num3,
YT4.Number as Num4,
YT5.Number as Num5
from
YourTable YT1
JOIN YourTable YT2
ON YT1.Number < YT2.Number
JOIN YourTable YT3
ON YT2.Number < YT3.Number
JOIN YourTable YT4
ON YT3.Number < YT4.Number
JOIN YourTable YT5
ON YT4.Number < YT5.Number