SQL: How do dynamically parse ranges from min to next min? - sql

I am trying to figure out how (or see if possible) I can grab the minimum value of 'TEST' and get just the next minimum value of 'TEST', then join again and go after the 2nd minimum value, etc, etc.
Table1
ID TEST
A12 1
A12 2
A12 3
A12 5
A12 8
B35 1
B35 3
Results I'm after:
ID RANGE1 RANGE2
A12 1 2
A12 2 3
A12 3 5
A12 5 8
B35 1 3
table code I'm using:
WITH FRED AS
(
SELECT 'A12' AS ID
, 1 AS TEST
UNION
SELECT 'A12' AS ID
, 2 AS TEST
UNION
SELECT 'A12' AS ID
, 3 AS TEST
UNION
SELECT 'B35' AS ID
, 1 AS TEST
UNION
SELECT 'B35' AS ID
, 2 AS TEST
)
SELECT *
FROM FRED F

This works for me:
--Construct sample data.
WITH FRED AS
(
SELECT 'A12' AS ID
, 1 AS TEST
UNION
SELECT 'A12' AS ID
, 2 AS TEST
UNION
SELECT 'A12' AS ID
, 3 AS TEST
UNION
SELECT 'A12' AS ID
, 5 AS TEST
UNION
SELECT 'A12' AS ID
, 8 AS TEST
UNION
SELECT 'B35' AS ID
, 1 AS TEST
UNION
SELECT 'B35' AS ID
, 3 AS TEST
),
--This is the sample data
test_data AS
(
SELECT *
FROM FRED F
ORDER BY ID, TEST
)
SELECT td.ID, td.TEST as RANGE1, MIN(td2.TEST) as RANGE2
FROM test_data td
cross join test_data td2
WHERE td.ID = td2.ID
AND
td.TEST < td2.TEST
GROUP BY td.ID, td.TEST
ORDER BY td.ID, td.TEST
test_data gives the sample you provided. I can try to handle repeated value cases once you let us all know how you want those to be handled. The current code above doesn't do that.

Assuming this is mysql (but most other DBMS will do the same) you can join the table with itself and use the limit in the ON clause.
Minima and grouping helps you from there.
e.g.:
mysql> SELECT m.id, m.test AS RANGE1 ,min(mm.test) AS RANGE2
FROM table1 AS m
INNER JOIN table1 AS mm ON m.id=mm.id AND mm.test > m.test
GROUP BY m.id,m.test;
+------+--------+--------+
| id | RANGE1 | RANGE2 |
+------+--------+--------+
| a12 | 1 | 2 |
| a12 | 2 | 3 |
| a12 | 3 | 5 |
| a12 | 5 | 8 |
| b35 | 1 | 3 |
+------+--------+--------+
5 rows in set (0.00 sec)
Take care with the efficiency of these things.
You might need to look into how to deal with edge cases where no second value is present in the table.
See also here: https://dba.stackexchange.com/questions/24014/how-do-i-get-the-current-and-next-greater-value-in-one-select

Related

Is there a version of 'CONTAINS' function in SQLITE other than 'LIKE'?

I'm trying to find totals for each number in the range of 1 to 7. But the data contains different combinations of these numbers. For e.g. 1; 2; 3,7; 1,2,3 and so on. I want to find the total number of times each number pops up. What I essentially want is a code for SQLite that's goes like:
select <fields>, count(*)
from tablexyz
where <field> contains '2' (and '3','4',... individually)
When I input "where like '2%'" and such, it only gives me all series that start with 2 but negates series that starts with 1 but contains 2.
Any help would be appreciated!
I want to find the total number of times each number pops up
Your sample code and the solution you say you want don't exactly align. The closest I can think of is
with t (txt) as -- a sample record from your table
(select '1; 2; 3,7; 1,2,3'),
t2 (num) as -- a lookup table we can create for range of numbers 1-7
(select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7)
select t2.num, length(t.txt) - length(replace(t.txt,t2.num,'')) as num_occurence
from t2
left join t on t.txt like '%' || t2.num || '%'
Outputs
+-----+---------------+
| num | num_occurence |
+-----+---------------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | NULL |
| 5 | NULL |
| 6 | NULL |
| 7 | 1 |
+-----+---------------+
Demo
Using the solution below, you can build a "table" of the numbers 1 to 7, then join it to your source data table to count if the number occurs in that row, then sum it together.
Query
WITH
sample_data (nums)
AS
(SELECT '1,2,3,4,5,6'
UNION ALL
SELECT '3,4,5,6'
UNION ALL
SELECT '1,2,7,6'
UNION ALL
SELECT '6' ),
search_nums (search_num)
AS
(VALUES(1)
UNION ALL
SELECT search_num+1 FROM search_nums WHERE search_num<7)
select search_num, sum(count_of_num) from (
SELECT s.nums,
n.search_num,
case
instr(s.nums, n.search_num)
when 0 then 0
else 1
end as count_of_num
FROM sample_data s, search_nums n
) group by search_num;
Result
search_num sum(count_of_num)
1 2
2 2
3 2
4 2
5 2
6 4
7 1

quickly take all correlated rows from the table

I have a large table with multiple columns representing linked events. This includes columns id and nextId, where id means id of some event1, and nextId suggest in which another event this event1 was used. However, there is no column 'prev_id' which would say which event0 contributed to event1. Is it possible to build a query which will generate for me such a table without taking a very long running time?
Here is an example of what I mean:
id | nextId
10 | 34
5 | 67
22 | 23
2 | 10
16 | 22
4 | 5
What I want to have is the following:
prev_id | id | next_id
2 | 10 | 34
4 | 5 | 67
16 | 22 | 23
You can use a join:
select t.id as prev_id, t.nextid as id, tnext.nextid as next_id
from t join
t tnext
on tnext.id = t.nextid;
You only need a self join.
But for the sake of readability, I would recommend using a CTE:
with prevs as (
select nextid as id, id as previd from ids
)
select previd, id, nextid
from ids
join prevs using(id)
;
previd | id | nextid
--------+----+--------
4 | 5 | 67
2 | 10 | 34
16 | 22 | 23
(3 rows)
In addition to what others have said, you can also accomplish this using hierarchical queries.
WITH test_data AS (
SELECT 10 AS ID,34 AS nextID FROM DUAL
UNION SELECT 5,67 FROM DUAL
UNION SELECT 22,23 FROM DUAL
UNION SELECT 2,10 FROM DUAL
UNION SELECT 16,22 FROM DUAL
UNION SELECT 4,5 FROM DUAL
)
SELECT h.*
FROM (
SELECT PRIOR t.ID AS prevID,
t.ID,
t.nextID
FROM test_data t
CONNECT BY t.ID = PRIOR t.nextID
) h
WHERE h.prevID IS NOT NULL
ORDER BY h.prevID

To count a column based on another column's repeating(same) entry

I want to create a report of calls last made based on weeks from last call and call-Group
Actual Data is like below with call id, date of call and call grouping
callid | Date | Group
----------------------------
1 | 6-1-18 | a1
2 | 6-1-18 | a2
3 | 7-1-18 | a3
4 | 8-1-18 | a1
5 | 9-1-18 | a2
6 | 9-1-18 | a4
Expected data is to display the number of calls for each call group corresponding to the number of week from last call
week | |
from | |
last |Group|Group
call | a1 | a2
--------------------
1 | 2 | 2 ->number of calls
2 | - | -
3 | 1 | -
4 | 2 | -
5 | - | 3
6 | - | -
Can anyone please tell me some solution for this
Although you data provided was a very small set and not rich enough to cover all cases, here is an sql that will calculate the number of weeks difference between each call and last call within a group and count the number of calls for each group for the particular week difference.
with your_table as (
select 1 as "callid", to_date('6-1-18','dd-mm-rr') as "date", 'a1' as "group" from dual
union select 2, to_date('6-1-18','mm-dd-rr'), 'a2' from dual
union select 3, to_date('7-1-18','mm-dd-rr'), 'a3' from dual
union select 4, to_date('8-1-18','mm-dd-rr'), 'a1' from dual
union select 5, to_date('9-1-18','mm-dd-rr'), 'a2' from dual
union select 6, to_date('6-1-18','mm-dd-rr'), 'a4' from dual
),
data1 as (
select t.*, max(t."date") over (partition by t."group") last_call_dt from your_table t
),
data2 as (select t.*, round((last_call_dt-t."date")/7,0) as weeks_diff from data1 t)
select * from (
select t.weeks_diff, t."callid", t."group" from data2 t
)
PIVOT
(
COUNT("callid")
FOR "group" IN ('a1', 'a2', 'a3','a4')
)
order by weeks_diff
to try it out with your table just make the following change:
with your_table as (select * from my_table), ....
let me know how it goes :)

Select statement that duplicates rows based on N value of column

I have a Power table that stores building circuit details. A circuit can be 1 phase or 3 phase but is always represented as 1 row in the circuit table.
I want to insert the details of the circuits into a join table which joins panels to circuits
My current circuit table has the following details
CircuitID | Voltage | Phase | PanelID | Cct |
1 | 120 | 1 | 1 | 1 |
2 | 208 | 3 | 1 | 3 |
3 | 208 | 2 | 1 | 8 |
Is it possible to create a select where by when it sees a 3 phase row it selects 3 rows (or 2 select 2 rows) and increments the Cct column by 1 each time or do I have to create a loop?
CircuitID | PanelID | Cct |
1 | 1 | 1 |
2 | 1 | 3 |
2 | 1 | 4 |
2 | 1 | 5 |
3 | 1 | 8 |
3 | 1 | 9 |
Here is one way to do it
First generate numbers using tally table(best possible way). Here is one excellent article about generating number without loops. Generate a set or sequence without loops
Then join the numbers table with yourtable where phase value of each record should be greater than sequence number in number's table
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2), -- 10*100
numbers as ( SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e3 )
SELECT CircuitID,
PanelID,
Cct = Cct + ( n - 1 )
FROM Yourtable a
JOIN numbers b
ON a.Phase >= b.n
You can do this with a one recursive cte.
WITH cte AS
(
SELECT [CircuitID], [Voltage], [Phase], [PanelID], [Cct], [Cct] AS [Ref]
FROM [Power]
UNION ALL
SELECT [CircuitID], [Voltage], [Phase], [PanelID], [Cct] + 1, [Ref]
FROM cte
WHERE [Cct] + 1 < [Phase] + [Ref]
)
SELECT [CircuitID], [PanelID], [Cct]
FROM cte
ORDER BY [CircuitID]
Simplest way,
Select y.* from (
Select 1 CircuitID,120 Voltage,1 Phase,1 PanelID, 1 Cct
union
Select 2,208,3,1,3
union
Select 3,208,2,1,8)y,
(Select 1 x
union
Select 2 x
union
Select 3 x)x
Where x.x <= y.Phase
Directly copy paste this and try, it will run 100%. After that, just replace my 'y' table with your real table.

Tables for recursive hierarchial data in SQL [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Simplest way to do a recursive self-join in SQL Server?
I have to create a table in SQL that will comprise of groups of items/products. Each new group made will be made under one of the pre-defined groups or the groups previously formed. I want to keep all this data in a SQL Table. So far, I have though of creating a table like this:
Group ID
Group Name
Group Under (This will store the ID of the group under which this group is from
But this can only refer to just the next level, how will I get to know who is the super-parent of this group.
For example:
I have groups A, B, C.
A has further subgroups A1, A2, A3.
A1 has further subgroups, A11, A12, A13.
I will I have the information about super-parent group i.e A from A11 or A22 or A33?
Let me know if the problem is not clear..
Assuming T-SQL and MSSQLServer (you didn't specify), and given that your Group table should look something like this:
Id | Name | ParentId
---+------+---------
1 | A | NULL
2 | B | NULL
3 | C | NULL
4 | A1 | 1
5 | A2 | 1
6 | A3 | 1
7 | A11 | 4
8 | A12 | 4
9 | A13 | 4
You can use the following recursive CTE to find the top level a given group, say 'A12':
WITH [Group](Id, Name, ParentId) AS
(
SELECT 1, 'A' , NULL UNION
SELECT 2, 'B' , NULL UNION
SELECT 3, 'C' , NULL UNION
SELECT 4, 'A1' , 1 UNION
SELECT 5, 'A2' , 1 UNION
SELECT 6, 'A3' , 1 UNION
SELECT 7, 'A11', 4 UNION
SELECT 8, 'A12', 4 UNION
SELECT 9, 'A13', 4
), q AS
(
SELECT
*
FROM
[Group]
WHERE
[Name] = 'A12' -- Given 'A12' as the child
UNION ALL
SELECT
g.*
FROM
[Group] g
JOIN
q
ON
q.ParentId = g.Id
)
SELECT
*
FROM
q
WHERE
ParentId IS NULL
This query returns:
Id | Name | ParentId
---+------+---------
1 | A | NULL