How to create a sequential range in SQL? - sql

I have a table where each row has a From and a To:
From|To
A | B
C | D
B | C
D | E
And I need to order them in a sequence where "To" in the first row is the "From" of the following and so on.
The result should be ordered like this:
From|To
A| B
B| C
C| D
D| E
The main problem is finding the From that follows the To in the previous record.

Not sure if I got your question right. Why don't you just sort?
;with cte as
(
select 'B' as "From", 'C' as "To"
union
select 'A', 'B'
union
select 'C', 'D'
)
select "From", "To"
from cte
order by "From";
(You don't need the cte/union stuff in your query, it's just for the sake of sample data.)
If you want to list only entries with successors:
;with cte as
(
select 'B' as "From", 'C' as "To"
union
select 'A', 'B'
union
select 'C', 'D'
union
select 'F', 'G'
)
select "From", "To"
from cte
where exists(select *
from cte cte2
where cte2."From" = cte."To")
or exists(select *
from cte cte3
where cte3."To" = cte."From")
order by "From";
Btw: Try using other column names than "From" and "To" as they are reserved statements that have to be used with "" (ANSI) or [] (T SQL).

declare #v table (ffrom varchar(10), fto varchar(10));
insert into #v values
('e', 'f'),
('a', 'b'),
('g', 'h'),
('c', 'd');
select *
from #v
order by ffrom;
GO
ffrom | fto
:---- | :--
a | b
c | d
e | f
g | h
dbfiddle here

You can use a recursive CTE:
with cte as (
select from, to, 1 as lev
from t
where not exists (select 1 from t t2 where t2.to = t.from)
union all
select t.from, t.to, cte.lev + 1
from cte join
t
on cte.to = t.from
)
select to, from
from cte
order by lev, to;
Note that this is affected by the maximum recursion depth, but it will work fine on four rows.

Related

Use CTE to generate test data in SQL

I am trying to use a CTE to generate a data table for a unit test in SQL (postgresql).
WITH temp1 AS (
SELECT
('A', 'A', 'B', 'B') AS grp,
(1, 2, NULL, 1) AS outcome
)
SELECT *
FROM temp1
The above query is generating a single row rather than a 4-row table that would be useful to my unit test. How can I generate the 4-row table in the form:
grp.....outcome
A.......1
A.......2
B.......NULL
B.......1
You could just use the values() syntax to create the rows, like so:
with temp1(grp, outcome) as (values ('A', 1), ('A', 2), ('B', null), ('B', 1))
select * from temp1
Demo on DB Fiddle:
grp | outcome
:-- | ------:
A | 1
A | 2
B | null
B | 1
You don't need a CTE. Use UNION ALL, as in:
select 'A' as grp, 1 as outcome
union all select 'A', 2
union all select 'B', null
union all select 'B', 1
WITH temp1 AS (
SELECT 'A' as grp ,1 as outcome
UNION
SELECT 'A', 2
UNION
SELECT 'B',NULL
UNION
SELECT 'B',1
) SELECT * FROM temp1

Return default value for some IN condition not match

I think this shouldn't be really hard. I am writing an Oracle-SQL code to extract data from SQL:
select ID, Qty from TableOne where ID in ('A', 'B', 'C')
I want the database to show the result of the query if there is match for some items in the IN condition, and return a default value if there is no match for those items in the IN condition.
For example, I want the result to be:
+----+-----------+
| ID | Qty |
+----+-----------+
| A | 3 |
| A | 5 |
| B | 4 |
| C | Not Found |
+----+-----------+
Where there is no ID = C in the table TableOne.
Is there any easy way to code the result?
Thank you very much!
Use COALESCE, NVL or CASE with a LEFT OUTER JOIN and specify the ids in a sub-query factoring clause:
WITH ids_to_match( id ) AS (
SELECT 'A' FROM DUAL UNION ALL
SELECT 'B' FROM DUAL UNION ALL
SELECT 'C' FROM DUAL
)
select i.ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from ids_to_match i
LEFT OUTER JOIN TableOne t
ON ( t.id = i.id )
or use a collection and a table collection expression:
select i.COLUMN_VALUE AS ID,
COALESCE( TO_CHAR(Qty), 'Not Found' ) AS Qty
from TABLE( SYS.ODCIVARCHAR2LIST( 'A', 'B', 'C' ) ) i
LEFT OUTER JOIN
TableOne t
ON ( t.id = i.COLUMN_VALUE )
You can use LEFT JOIN with UNION ALL :
WITH ALL_ID AS (
SELECT 'A' AS ID FROM DUAL UNION ALL
SELECT 'B' AS ID FROM DUAL UNION ALL
SELECT 'C' AS ID FROM DUAL
)
SELECT A.ID, t.Qty -- REPLACE NULL WITH NOT FOUND
FROM ALL_ID A ID LEFT JOIN
Table t
ON t.ID = A.ID;
if this works for you:
select t2.ID, case when t2.QTY is NULL then TO_CHAR('Not found') else t2.QTY end "QTY" from TableOnet1 t1 right join Tabletwo t2
on t1.ID = t2.ID where t2.ID in ('A', 'B', 'C')

What Microsoft tSql code will create "Group Identifiers" for groups of rows delimited by a 1

--drop table GroupIdentifierTest
go
Create table GroupIdentifierTest
(
RowId varchar(10),
[Delimiter] int
)
insert into GroupIdentifierTest
select 'a', 0 union all
select 'b', 0 union all
select 'c', 0 union all
select 'd', 1 union all
select 'e', 0 union all
select 'f', 1
select * from GroupIdentifierTest
delete GroupIdentifierTest
insert into GroupIdentifierTest
select 'a', 1 union all
select 'b', 1 union all
select 'c', 1 union all
select 'd', 2 union all
select 'e', 2 union all
select 'f', 2
select r.RowId, r.Delimiter As GroupId from GroupIdentifierTest r
If you have a rowset, and the data is in groupings, indicated by a column value of 1, how can you generate GroupId numbers?
In this example, the first rowset needs to be transformed into the second rowset.
How many groups do you need? Perhaps this will help:
WITH prep AS
(
SELECT RowId, delimiter, GroupId = NTILE(2) OVER (ORDER BY RowID)
FROM GroupIdentifierTest
)
UPDATE prep
SET delimiter = GroupId;
SELECT RowId, Delimiter AS groupID
FROM GroupIdentifierTest;
Results:
RowId groupID
---------- -----------
a 1
b 1
c 1
d 2
e 2
f 2

SQL Query with group by clause, but counting two distinct values as if they were the same

I have a simple table with two columns, like the one below:
Id | Name
0 | A
1 | A
2 | B
3 | B
4 | C
5 | D
6 | E
7 | E
I want to make a SQL query which will count how many times each "Name" appears on the table. However, I need a few of these values to count as if they were the same. For example, a normal group by query would be:
select Name, count(*)
from table
group by Name
The above query would produce the result:
Name | Count
A | 2
B | 2
C | 1
D | 1
E | 2
but I need the query to count "A" and "B" as if they were only "A", and to count "D" and "E" as if they were only "D", so that the result would be like:
Name | Count
A | 4 // (2 "A"s + 2 "B"s)
C | 1
D | 3 // (1 "D" + 2 "E"s)
How can I make this kind of query?
You can make translation with case. Also, you can use subquery or CTE so you don't have to repeat yourself:
with cte as (
select
case Name
when 'B' then 'A'
when 'E' then 'D'
else Name
end as Name
from table
)
select Name, count(*)
from cte
group by Name
or with with online translation table:
select
isnull(R.B, t.Name), count(*)
from table as t
left outer join (
select 'A', 'B' union all
select 'E', 'D'
) as R(A, B) on R.A = t.Name
group by isnull(R.B, t.Name)
If you need A and B, D and E, to count the same, you can build a query like this:
SELECT
CASE Name WHEN 'B' THEN 'A' WHEN 'E' THEN 'D' ELSE Name END as Name
, COUNT(*)
FROM table
GROUP BY CASE Name WHEN 'B' THEN 'A' WHEN 'E' THEN 'D' ELSE Name END
Demo on sqlfiddle.
With a layer of abstraction and a CASE (SQL Fiddle example):
;WITH x AS
(
SELECT CASE Name WHEN 'B' THEN 'A'
WHEN 'E' THEN 'D'
ELSE Name
END AS Name
FROM Table1
)
SELECT Name, COUNT(1)
FROM x
GROUP BY Name
With a translation table (SQL Fiddle):
CREATE TABLE Translate(FromName char(1), ToName char(1));
INSERT INTO Translate VALUES ('B', 'A'), ('E', 'D');
SELECT COALESCE(t.ToName, a.Name) Name, COUNT(1)
FROM Table1 a
LEFT OUTER JOIN Translate t ON a.Name = t.FromName
GROUP BY COALESCE(t.ToName, a.Name)
FWIW, you can also do this with a VALUES derived table instead of a real table (SQL Fiddle):
SELECT COALESCE(t.ToName, a.Name) Name, COUNT(1)
FROM Table1 a
LEFT OUTER JOIN
(
VALUES ('B', 'A'),
('E', 'D')
) t(FromName, ToName) ON a.Name = t.FromName
GROUP BY COALESCE(t.ToName, a.Name)
this works
select t.a,count(t.id) from (
select case name when 'A' then 'A' when 'B' then 'A'
when 'C' then 'C' when 'D' then 'C'
when 'D' then 'D' when 'E' then 'D' end as A,id
from test) as t
group by A;

Oracle SQL -- Combining two tables, but taking duplicates from one?

I have these tables:
Table A
Num Letter
1 A
2 B
3 C
Table B
Num Letter
2 C
3 D
4 E
I want to union these two tables, but I only want each number to appear once. If the same number appears in both tables, I want it from Table B instead of table A.
Result
Num Letter
1 A
2 C
3 D
4 E
How could I accomplish this? A union will keep duplicates and an intersect would only catch the same rows -- I consider a row a duplicate when it has the same number, regardless of the letter.
Try this: http://www.sqlfiddle.com/#!4/0b796/1
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select
Num
,case when count(*) > 1 then
min(case when src = 'B' then Letter end)
else
min(Letter)
end as Letter
from a
group by Num
order by Num;
Output:
| NUM | LETTER |
----------------
| 1 | A |
| 2 | C |
| 3 | D |
| 4 | E |
And another one:
SELECT COALESCE(b.num, a.num) num, COALESCE(b.letter, a.letter) letter
FROM a FULL JOIN b ON a.num = b.num
ORDER BY 1;
With your data:
WITH a AS
(SELECT 1 num, 'A' letter FROM dual
UNION ALL SELECT 2, 'B' FROM dual
UNION ALL SELECT 3, 'C' FROM dual),
b AS
(SELECT 2 num, 'C' letter FROM dual
UNION ALL SELECT 3, 'D' FROM dual
UNION ALL SELECT 4, 'E' FROM dual)
SELECT COALESCE(b.num, a.num) num, COALESCE(b.letter, a.letter) letter
FROM a FULL JOIN b ON a.num = b.num
ORDER BY 1;
NUM L
---------- -
1 A
2 C
3 D
4 E
The efficiency might be lacking, but it produces the correct answer.
select nums.num, coalesce(b.letter, a.letter)
from
(select num from b
union
select num from a) nums
left outer join b
on (b.num = nums.num)
left outer join a
on (a.num = nums.num);
Or you can use Oracle-specific technique to make the code shorter: http://www.sqlfiddle.com/#!4/0b796/11
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select Num, min(Letter) keep(dense_rank first order by src desc) as Letter
from a
group by Num
order by Num;
Output:
| NUM | LETTER |
----------------
| 1 | A |
| 2 | C |
| 3 | D |
| 4 | E |
The code works regardless of min(letter) or max(letter), it has the same output, it gives the same output. Important is you use keep dense_rank. Another important thing is, the order matter, we use order by src desc to give priority to source table B when keeping a row.
And to really make it shorter, use keep dense_rank last, and omit the desc on order by, asc is the default anyway http://www.sqlfiddle.com/#!4/0b796/12
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select Num, min(Letter) keep(dense_rank last order by src) as Letter
from a
group by Num
order by Num;
Again, using min or max on Letter doesn't matter, as long as your keep dense_rank get the prioritized/preferred row
Another option is to combine the UNION and MINUS commands as follows:
SELECT
NUM, LETTER
FROM
TABLE B
UNION
( SELECT
NUM, LETTER
FROM
TABLE A
WHERE
NUM IN (SELECT
NUM
FROM
TABLE A
MINUS
SELECT
NUM
FROM
TABLE B ))
SELECT A.*
FROM A
WHERE A.NUM NOT IN
(SELECT A.NUM
FROM B
WHERE A.NUM=B.NUM
AND B.NUM IS NOT NULL
AND A.NUM IS NOT NULL
)
UNION
SELECT * FROM B;