SQLITE - Using results from a subquery in same query - sqlite

I would like to know if, and if yes, how I could accomplsh the following:
Lets say I have two tables:
Table A has two Columns: id, name
Table B columns: owner, argument
Now I am trying to find in table A all rows with specific name (animal) and use their ids to find it's argument value in table b. Those argument values are different ids in table a. So as a result I would like to get two columns. first has the id of the items who has the specific name (animal) I am looking for and second column has the name of the item which has the id that is argument of the initial ids.
table a (example)
id || name
1 || animal
2 || animal
3 || animal
4 || animal
15 || cat
16 || dog
17 || horse
18 || bird
...
table b (example)
owner || argument
1 || 15
2 || 16
3 || 17
4 || 18
...
result (example)
id || name
1 || cat
2 || dog
3 || horse
4 || bird
Thanks in advance for any hints / help.
Andreas

You need a double join from tablea to tableb and again doublea:
select
a.name ownwename,
t.name name
from tablea a
inner join tableb b
on b.owner = a.id
inner join tablea t
on t.id = b.argument
where a.name = 'animal'
See the demo

I believe the following will do what you want
SELECT owner, name FROM tableb JOIN tablea ON argument = id;
However, as using a subquery you could use :-
SELECT owner, (SELECT name FROM tablea WHERE argument = id) AS name FROM tableb;
Working Example :-
DROP TABLE If EXISTS tablea;
CREATE TABLE IF NOT EXISTS tablea (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO tablea (name) VALUES ('animal'),('animal'),('animal'),('animal'),('cat'),('dog'),('horse'),('bird'),
('animal'),('cat'),('dog'),('horse'),('bird'),('animal'),
('cat'),('dog'),('horse'),('bird') -- id's 15-18 inclusive
;
DROP TABLE IF EXISTS tableb;
CREATE TABLE IF NOT EXISTS tableb (owner INTEGER PRIMARY KEY, argument INTEGER);
INSERT INTO tableb (argument) VALUES(15),(16),(17),(18);
SELECT owner, name FROM tableb JOIN tablea ON argument = id;
SELECT owner, (SELECT name FROM tablea WHERE argument = id) AS name FROM tableb;
Results :-
and the second

Related

Always return all row, even when query has no result

I have a "tt_Results" table which contains exactly three row / ID like:
ID | ResultType
---------------
1 | first
1 | second
1 | third
My query looks like:
select t.resultType
from tt_Results
where ID = 1;
Normally, this query should return all the three row. My problem is if one or more row not exist I must union all the three "type".
select res.* from
(
select resultType, '1' as exists
from tt_Results
where ID = 1
union all
select 'first' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'first' and ID = 1)
union all
select 'second' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'second' and ID = 1)
union all
select 'third' resulttype, '0' as exists
from dual
where not exists (
select resulttype
from tt_Results
where resulttype = 'third' and ID = 1)
) res
The final query result looks good but it is very slow. Anyone have a better PL/SQL solution for it? Thanks for the answers!
Left join to your table from a table of all types, using a case to calculate exists based on whether a join was made or not:
select
type,
case when resultType is null then '0' else '1' end as exists
from (select 'first' type from dual union
select 'second' from dual union
select 'third' from dual) t
left join tt_Results on resultType = type
and ID = 1
Note that the condition ID = 1 is part of the join condition, not in a where clause.
I recommend creating a 3-row table for the types, then the query would become simply:
select
type,
case when resultType is null then '0' else '1' end as exists
from types
left join tt_Results on resultType = type
and ID = 1
You can do this by creating a subquery to hold the result types you're interested in, and then using a partitioned outer join, like so:
with tt_results as (select 1 id, 'first' resulttype from dual union all
select 1 id, 'second' resulttype from dual union all
select 1 id, 'third' resulttype from dual union all
select 2 id, 'second' resulttype from dual union all
select 2 id, 'third' resulttype from dual union all
select 3 id, 'first' resulttype from dual),
dummy as (select 1 position, 'first' resulttype from dual union all
select 2 position, 'second' resulttype from dual union all
select 3 position, 'third' resulttype from dual)
select res.id,
d.resulttype,
case when res.resulttype is not null then 1 else 0 end res_exists
from dummy d
left outer join tt_results res partition by (res.id) on d.resulttype = res.resulttype
order by res.id,
d.position;
ID RESULTTYPE RES_EXISTS
---------- ---------- ----------
1 first 1
1 second 1
1 third 1
2 first 0
2 second 1
2 third 1
3 first 1
3 second 0
3 third 0
Adrian Billington has produced an excellent article on partititioned outer joins if you want to learn more about them.
If your tt_results table could contain more resulttypes than you're interested in, you might want/need to add a predicate to only grab the rows from the dummy subquery, otherwise you could get ids where all 3 resulttypes don't exist (although that may be what you want to see).
ETA: This will work if you need to select multiple ids in one go.

Recursing through a SQLite table to find a matching subset of records

I have a table with 3 text fields column (A, B & C) imported from a flat file comprising many thousands of lines. None of these fields have a UNIQUE constraint and there is no primary key combination. As a result one or more records may have the same values and there will even be records with the same values across all fields. In many records, columns A, B and C should be the same but due to data quality issues, column C has many variant where column A and B are the same. Where column A and B are the same the corresponding value in column C may be subsets of the value of column C in another record having the same values as other records for column A and B.
To illustrate a subset arrived at by using GROUP BY gives:
enter image description here
I now need to narrow down that subset further to find all records where the value in column C is INSTR the values of the other grouped results i.e. i'd like to return:
enter image description here
because "Buckingham" and "Lindsey" are both INSTR the records that contain "Lindsey Buckingham" in column C
With EXISTS and INSTR():
select t.* from tablename t
where exists (
select 1 from tablename
where a = t.a and b = t.b and c <> t.c and instr(c, t.c) > 0
)
or with LIKE:
select t.* from tablename t
where exists (
select 1 from tablename
where a = t.a and b = t.b and c <> t.c and c like '%' || t.c || '%'
)
or with a self join:
select distinct t.*
from tablename t inner join tablename tt
on tt.a = t.a and tt.b = t.b and tt.c <> t.c and tt.c like '%' || t.c || '%'

Count strings ocurrences in one field result - Oracle 11

What I want is to count strings ocurrences in this way:
ID | NAME | ITEMS
1 | JFK | 100/100/100/200/300/300
I want to turn it into
ID | NAME | ITEMS
1 | JFK | 100(3),200(1),300(2)
A function will be fine, but if you can do within a SELECT will be awesome.
I am using Oracle 11, but if you can provide generic may help more people.
Thanks :)
This is a bit long-winded, but you can tokenize your strings into separate rows:
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null;
and then group by item to get the counts:
select id, name, item, count(*) as item_count
from (
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null
)
group by id, name, item;
and then group again by just ID and name to aggregate back to a single string:
select id, name,
listagg(item || '(' || item_count || ')', '/') within group (order by item) as items
from (
select id, name, item, count(*) as item_count
from (
select id, name, regexp_substr(items, '(.*?)(/|$)', 1, level, null, 1) as item
from your_table
connect by level < regexp_count(items, '(.*?)(/|$)')
and prior id = id
and prior dbms_random.value is not null
)
group by id, name, item
)
group by id, name
order by id;
With your example and another one in a different order provided via a CTE:
with your_table (id, name, items) as (
select 1, 'JFK', '100/100/100/200/300/300' from dual
union all select 2, 'LBJ', '100/300/100/200/100' from dual
)
select ...
that query gets
ID NAM ITEMS
---------- --- --------------------------------------------------
1 JFK 100(3)/200(1)/300(2)
2 LBJ 100(3)/200(1)/300(1)

Zipping rows with the same "key" while joining tables

I have two tables, one with objects, one with properties of the objects. Both tables have a personal ID and a date as "key", but since multiple orders of objects can be done by one person on a single day, it doesn't match well. I do know however, that the entries are entered in the same order in both tables, so it is possible to join on the order, if the personID and date are the same.
This is what I want to accomplish:
Table 1:
PersonID Date Object
1 20-08-2013 A
2 13-11-2013 B
2 13-11-2013 C
2 13-11-2013 D
3 21-11-2013 E
Table 2:
PersonID Date Property
4 05-05-2013 $
1 20-08-2013 ^
2 13-11-2013 /
2 13-11-2013 *
2 13-11-2013 +
3 21-11-2013 &
Result:
PersonID Date Object Property
4 05-05-2013 $
1 20-08-2013 A ^
2 13-11-2013 B /
2 13-11-2013 C *
2 13-11-2013 D +
3 21-11-2013 E &
So what I want to do, is join the two tables and "zip" the group of entries that have the same (PersonID,Date) "key".
Something called "Slick" seems to have this (see here), but I'd like to do it in SQLite.
Any advice would be amazing!
You are on the right track. Why not just do a LEFT JOIN between the tables like
select t2.PersonID,
t2.Date,
t1.Object,
t2.Property
from table2 t2
left join table1 t1 on t2.PersonID = t1.PersonID
order by t2.PersonID
Use a additional column to make every key unique in both tables. For example in SQLite you could use RowIDs to keep track of the order of insertion. To store this additional column in the database itself might be useful for other queries as well, but you do not have to store this.
First add the column ID to both tables, the DDL queries should now look like this: (make sure you do not add the primary key constraint until both tables are filled.
CREATE TABLE table1 (
ID,
PersonID,
Date,
Object
);
CREATE TABLE table2 (
ID,
PersonID,
Date,
Property
);
Now populate the ID column. You can adjust the ID to your liking. Make sure you do this for table2 as well:
UPDATE table1
SET ID =(
SELECT table1.PersonID || '-' || table1.Date || '-' || count( * )
FROM table1 tB
WHERE table1.RowID >= tB.RowID
AND
table1.PersonID == tB.PersonID
AND
table1.Date == tB.Date
);
Now you can join them:
SELECT t2.PersonID,
t2.Date,
t1.Object,
t2.Property
FROM table2 t2
LEFT JOIN table1 t1
ON t2.ID = t1.ID;

SQL Concatenate multiple rows

I'm using Teradata, I have a table like this
ID String
123 Jim
123 John
123 Jane
321 Jill
321 Janine
321 Johan
I want to query the table so I get
ID String
123 Jim, John, Jane
321 Jill, Janine, Johan
I tried partition but there can be many names.
How do I get this result. Even, to point me in the right direction would be great.
Unfortunately there's no PIVOT in Teradata (only a TD_UNPIVOT in 14.10).
If you got luck there's an aggregate UDF at your site to do a group concat (probably low possibility).
Otherwise there are two options: recursion or aggregation.
If the maximum number of rows per id is known aggregation is normally faster. It's a lot of code, but most of it is based on cut&paste.
SELECT
id,
MAX(CASE WHEN rn = 1 THEN string END)
|| MAX(CASE WHEN rn = 2 THEN ',' || string ELSE '' END)
|| MAX(CASE WHEN rn = 3 THEN ',' || string ELSE '' END)
|| MAX(CASE WHEN rn = 4 THEN ',' || string ELSE '' END)
|| ... -- repeat up to the known maximum
FROM
(
SELECT
id, string,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY string) AS rn
FROM t
) AS dt
GROUP BY 1;
For large tables it's much more efficient when you materialize the result of the Derived Table in a Volatile Table first using the GROUP BY column as PI.
For recursion you should use a Volatile Table, too, as OLAP functions are not allowed in the recursive part. Using a view instead will repeatedly calculate the OLAP function and thus result in bad performance.
CREATE VOLATILE TABLE vt AS
(
SELECT
id
,string
,ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY string DESC) AS rn -- reverse order!
,COUNT(*)
OVER (PARTITION BY id) AS cnt
FROM t
) WITH DATA
UNIQUE PRIMARY INDEX(id, rn)
ON COMMIT PRESERVE ROWS;
WITH RECURSIVE cte
(id, list, rn) AS
(
SELECT
id
,CAST(string AS VARCHAR(1000)) -- define maximum size based on maximum number of rows
,rn
FROM vt
WHERE rn = cnt
UNION ALL
SELECT
vt.id
,cte.list || ',' || vt.string
,vt.rn
FROM vt
JOIN cte
ON vt.id = cte.id
AND vt.rn = cte.rn - 1
)
SELECT id, list
FROM cte
WHERE rn = 1;
There's one problem with this approach, it might need a lot of spool which is easy to see when you omit theWHERE rn = 1.
SELECT ID,
TRIM(TRAILING ',' FROM (XMLAGG(TRIM(String)|| ',' ORDER BY String) (VARCHAR(10000)))) as Strings
FROM db.table
GROUP BY 1
SQL Server 2017+ and SQL Azure: STRING_AGG
Starting with the next version of SQL Server, we can finally concatenate across rows without having to resort to any variable or XML witchery.
STRING_AGG (Transact-SQL)
SELECT ID, STRING_AGG(String, ', ') AS Strings
FROM TableName
GROUP BY ID

Resources