How to join a subquery to itself? - sql

How do can you join a subquery onto itself? I'd like to do something like the following.
SELECT
four.src AS start, four.dest AS layover, f.dest AS destination
FROM
( SELECT 1 AS src, 2 as dest union all select 2, 3 ) AS four
JOIN
four AS f
ON f.src = four.dest
However the query above gives me the error
Msg 208, Level 16, State 1, Line 1
Invalid object name 'four'.
I'd rather not have to store it as a variable or view etc first since this is part of a monolithic query (this is itself a subquery and its part of a series of UNIONS) and I do not want to make sure that there are no impacting joins elsewhere that relate.
The force behind this change is that fourused to be a simple lookup but now for this query the values have to be calculated.
PS - this is a simplified example, in my case the subquery for four is a hundred lines long

You can make use of CTE (Common Table Expression in this scenario. Here, you need not to store this result in any temporary objects.
;WITH four AS (
SELECT 1 AS src, 2 as dest
union all
select 2, 3
)
SELECT F1.src AS start, F1.dest AS layover, f2.dest AS destination
FROM four F1
INNER JOIN four F2 ON F1.src = F2.dest

Use a temp table.
Declare #Temp(src int, desc int);
INSERT INTO #Temp(src,desc)
VALUES
(SELECT 1 AS src, 2 as dest union all select 2, 3)
SELECT * FROM #Temp t1
INNER JOIN #Temp t2 ON t1.src = t2.dest

You need to write it again. You alias 'four' can only be called in 'Select','Where', 'have', 'On'etc. conditions only and NOT as table in joins until and unless it's a table name in itself
SELECT
four.src AS start, four.dest AS layover, f.dest AS destination
FROM
(SELECT 1 AS src, 2 as dest union all select 2, 3 ) AS four
JOIN
(SELECT 1 AS src, 2 as dest union all select 2, 3 ) AS f
ON f.src = four.dest

Related

Where clause between union all in sql?

I have a query that vertically expands data by using Union condition. Below are the 2 sample tables:
create table #temp1(_row_ord int,CID int,_data varchar(10))
insert #temp1
values
(1,1001,'text1'),
(2,1001,'text2'),
(4,1002,'text1'),
(5,1002,'text2')
create table #temp2(_row_ord int,CID int,_data varchar(10))
insert #temp2
values
(1,1001,'sample1'),
(2,1001,'sample2'),
(4,1002,'sample1'),
(5,1002,'sample2')
--My query
select * from #temp1
union
select * from #temp2 where CID in (select CID from #temp1)
order by _row_ord,CID
drop table #temp1,#temp2
So my current output is:
I want to group the details of every client together for which I am unable to use 'where' clause across Union condition.
My desired output:
Any help?! Order by is also not helping me.
I can imagine you want all of the rows for a CID sorted by _row_ord from the first table before the ones from the second table. And the CID should be the outermost sort criteria.
If that's right, you can select literals from your tables. Let the literal for the first table be less than that of the second table. Then first sort by CID, then that literal and finally by _row_ord.
SELECT cid,
_data
FROM (SELECT 1 s,
_row_ord,
cid,
_data
FROM #temp1
UNION ALL
SELECT 2 s,
_row_ord,
cid,
_data
FROM #temp2) x
ORDER BY cid,
s,
_row_ord;
db<>fiddle
If I correctly understand your need, you need the output to be sorted the way that #temp1 rows appear before #temp2 rows for each cid value.
What you could do is generate additional column ordnum assigning values for each table, just for sorting purposes, and then get rid of it in the outer select statement.
select cid, _data
from (
select 1 as ordnum, *
from #temp1
union all
select 2 as ordnum, *
from #temp2 t2
where exists (
select 1
from #temp1 t1
where t1.cid = t2.cid
)
) q
order by cid, ordnum
I have also rewritten your where condition for an equivalent which should work faster using exists operator.
Live DEMO - click me!
Output
cid _data
1001 text1
1001 text2
1001 sample1
1001 sample2
1002 text1
1002 text2
1002 sample1
1002 sample2
Use With. here is my first try with your sql
create table #temp1(_row_ord int,CID int,_data varchar(10))
insert #temp1
values
(1,1001,'text1'),
(2,1001,'text2'),
(4,1002,'text1'),
(5,1002,'text2')
create table #temp2(_row_ord int,CID int,_data varchar(10))
insert #temp2
values
(1,1001,'sample1'),
(2,1001,'sample2'),
(4,1002,'sample1'),
(5,1002,'sample2');
WITH result( _row_ord, CID,_data) AS
(
--My query
select * from #temp1
union
select * from #temp2 where CID in (select CID from #temp1)
)
select * from tmp order by CID ,_data
drop table #temp1,#temp2
result
_row_ord CID _data
1 1001 sample1
2 1001 sample2
1 1001 text1
2 1001 text2
4 1002 sample1
5 1002 sample2
4 1002 text1
5 1002 text2
Union is placed between two result set blocks and forms a single result set block. If you want a where clause on a particular block you can put it:
select a from a where a = 1
union
select z from z
select a from a
union
select z from z where z = 1
select a from a where a = 1
union
select z from z where z = 1
The first query in a union defines column names in the output. You can wrap an output in brackets, alias it and do a where on the whole lot:
select * from
(
select a as newname from a where a = 1
union
select z from z where z = 2
) o
where o.newname = 3
It is important to note that a.a and z.z will combine into a new column, o.newname. As a result, saying where o.newname will filter on all rows from both a and z (the rows from z are also stacked into the newname column). The outer query knows only about o.newname, it knows nothing of a or z
Side note, the query above produces nothing because we know that only rows where a.a is 1 and z.z is 2 are output by the union as o.newname. This o.newname is then filtered to only output rows that are 3, but no rows are 3
select * from
(
select a as newname from a
union
select z from z
) o
where o.newname = 3
This query will pick up any rows in a or z where a.a is 3 or z.z is 3, thanks to the filtering of the resulting union

get the missing numbers between list of numbers using sql

in my database I have 10 users numbers some of them have been deleted, and when I select the column at shows like this:
missing_user_number:
1,
2,
5,
8,
10,
and I need to know if there is a script that can get the missing numbers like this, I don't want the deleted data back, I just want the missing numbers as an integrs data:
missing_user_number:
3,
4,
6,
7,
9,
In most versions of SQL, it is actually easier to get ranges of missing values, rather than each missing value:
select user_number + 1 as missing_range_start, next_user_number - 1 as missing_range_end
from (select t.*,
lead(user_number) over (order by user_number) as next_user_number
from t
) t
where user_number <> user_number + 1;
Note: This only finds internal missing numbers, as in the example in your question.
You can create an in-line numbers table that contains all 10 user numbers. Then LEFT JOIN your table to it in order to get the missing numbers:
SELECT t1.n AS missing_user_number
FROM (
SELECT 1 AS n UNION ALL SELECT 2 ... SELECT 10
) AS t1
LEFT JOIN mytable AS t2 ON t1.n = t2.user_number
WHERE t2.user_number IS NULL

How to save the information contains by all the polygons after a st_union (POSTGIS)

I have the following case, i have implemented a small query that merge the adjacent polygons of the same "type". But of course I lose all the information, except the geometry and the "type".
The following image sumarize the first step:
But I want to concatenate the information of the two old blue polygon in the new one.
I try to implement a query where differents fields are based on differents group by.
Something that look like:
SELECT ST_DUMP(ST_Union(geom)).geom as geom,string_agg(param1,',') as param1, string_agg(param2,',') as param2, type
FROM t1
GROUP BY type (for the st_union function)
GROUP BY geom (for the string_agg function)
But i'm unable to understand how to manage this part !
A bit late to the party but what you need to do is to first merge the polygons and then find which of the old polygons intersect with the merged ones. You basically need 2 new tables. One table with the new, grouped polygons and one join table to connect the new merged polygons to your original polygons (containing only the grouped_polygon_ids and the original_polygon_ids).
This can be done with something like this:
CREATE TABLE grouped_polygons AS
SELECT uuid_generate_v4() as id,
ST_DUMP(ST_Union(geom)).geom as geom,
string_agg(param1,',') as param1,
string_agg(param2,',') as param2,
type
FROM t1
GROUP BY type (for the st_union function)
GROUP BY geom (for the string_agg function);
CREATE TABLE join_table AS
SELECT t1.id as original_polygon_id,
grouped_polygons.id as grouped_polygon_id
FROM t1
JOIN grouped_polygons
ON st_intersects(t1.geom, grouped_polygons.geom);
Then you can query the results like this:
SELECT gb.id, gp.geom, t1.*
FROM grouped_polygons gp
JOIN join_table jt
ON gp.id = jt.grouped_polygon_id
JOIN t1
ON t1.id = jt.original_polygon_id
ORDER BY gp.id;
This way you can have all the attributes of t1 connected to the new polygons. You can choose which aggregation function to use to group them together if you want.
I tested this script in a simple environment:
select geom, my_type ,
case when the_path is not null then values1[the_path] else values1[1] end as value1,
case when the_path is not null then values2[the_path] else values2[1] end as value2
from (
select
st_asewkt( (st_dump(st_union(geom))).geom ) as geom,
(st_dump(st_union(geom))).path[1] as the_path ,
my_type,
array_agg(value1) as values1,
array_agg(value2) as values2
from t1
group by my_type
) tx
I hope it could help you to figure out this problem.
This is the script for the simple environment:
drop table t1;
create table t1(
value1 text,
value2 text,
my_type text,
geom geometry(Polygon)
);
insert into t1 (value1,value2,my_type,geom) values ('1-one','2-one','red',ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'));
insert into t1 (value1,value2,my_type,geom) values ('1-two','2-two','red',ST_GeomFromText('POLYGON((1 0, 1 1, 2 1, 2 0, 1 0))'));
insert into t1 (value1,value2,my_type,geom) values ('1-three','2-three','blue',ST_GeomFromText('POLYGON((4 0, 4 1, 5 1, 5 0, 4 0))'));
insert into t1 (value1,value2,my_type,geom) values ('1-four','2-four','blue',ST_GeomFromText('POLYGON((7 0, 7 1, 8 1, 8 0, 7 0))'));
and the result

MSSQL ORDER BY Passed List

I am using Lucene to perform queries on a subset of SQL data which returns me a scored list of RecordIDs, e.g. 11,4,5,25,30 .
I want to use this list to retrieve a set of results from the full SQL Table by RecordIDs.
So SELECT * FROM MyFullRecord
where RecordID in (11,5,3,25,30)
I would like the retrieved list to maintain the scored order.
I can do it by using an Order by like so;
ORDER BY (CASE WHEN RecordID = 11 THEN 0
WHEN RecordID = 5 THEN 1
WHEN RecordID = 3 THEN 2
WHEN RecordID = 25 THEN 3
WHEN RecordID = 30 THEN 4
END)
I am concerned with the loading of the server loading especially if I am passing long lists of RecordIDs. Does anyone have experience of this or how can I determine an optimum list length.
Are there any other ways to achieve this functionality in MSSQL?
Roger
You can record your list into a table or table variable with sorting priorities.
And then join your table with this sorting one.
DECLARE TABLE #tSortOrder (RecordID INT, SortOrder INT)
INSERT INTO #tSortOrder (RecordID, SortOrder)
SELECT 11, 1 UNION ALL
SELECT 5, 2 UNION ALL
SELECT 3, 3 UNION ALL
SELECT 25, 4 UNION ALL
SELECT 30, 5
SELECT *
FROM yourTable T
LEFT JOIN #tSortOrder S ON T.RecordID = S.RecordID
ORDER BY S.SortOrder
Instead of creating a searched order by statement, you could create an in memory table to join. It's easier on the eyes and definitely scales better.
SQL Statement
SELECT mfr.*
FROM MyFullRecord mfr
INNER JOIN (
SELECT *
FROM (VALUES (1, 11),
(2, 5),
(3, 3),
(4, 25),
(5, 30)
) q(ID, RecordID)
) q ON q.RecordID = mfr.RecordID
ORDER BY
q.ID
Look here for a fiddle
Something like:
SELECT * FROM MyFullRecord where RecordID in (11,5,3,25,30)
ORDER BY
CHARINDEX(','+CAST(RecordID AS varchar)+',',
','+'11,5,3,25,30'+',')
SQLFiddle demo

Returning rows that had no matches

I've read and read and read but I haven't found a solution to my problem.
I'm doing something like:
SELECT a
FROM t1
WHERE t1.b IN (<external list of values>)
There are other conditions of course but this is the jist of it.
My question is: is there a way to show which in the manually entered list of values didn't find a match? I've looked but I can't find and I'm going in circles.
Create a temp table with the external list of values, then you can do:
select item
from tmptable t
where t.item not in ( select b from t1 )
If the list is short enough, you can do something like:
with t as (
select case when t.b1='FIRSTITEM' then 1 else 0 end firstfound
case when t.b1='2NDITEM' then 1 else 0 end secondfound
case when t.b1='3RDITEM' then 1 else 0 end thirdfound
...
from t1 wher t1.b in 'LIST...'
)
select sum(firstfound), sum(secondfound), sum(thirdfound), ...
from t
But with proper rights, I would use Nicholas' answer.
To display which values in the list of values haven't found a match, as one of the approaches, you could create a nested table SQL(schema object) data type:
-- assuming that the values in the list
-- are of number datatype
create type T_NumList as table of number;
and use it as follows:
-- sample of data. generates numbers from 1 to 11
SQL> with t1(col) as(
2 select level
3 from dual
4 connect by level <= 11
5 )
6 select s.column_value as without_match
7 from table(t_NumList(1, 2, 15, 50, 23)) s -- here goes your list of values
8 left join t1 t
9 on (s.column_value = t.col)
10 where t.col is null
11 ;
Result:
WITHOUT_MATCH
-------------
15
50
23
SQLFiddle Demo
There is no easy way to convert "a externally provided" list into a table that can be used to do the comparison. One way is to use one of the (undocumented) system types to generate a table on the fly based on the values supplied:
with value_list (id) as (
select column_value
from table(sys.odcinumberlist (1, 2, 3)) -- this is the list of values
)
select l.id as missing_id
from value_list l
left join t1 on t1.id = l.id
where t1.id is null;
There are ways to get what you have described, but they have requirements which exceed the statement of the problem. From the minimal description provided, there's no way to have the SQL return the list of the manually-entered values that did not match.
For example, if it's possible to insert the manually-entered values into a separate table - let's call it matchtbl, with the column named b - then the following should do the job:
SELECT matchtbl.b
FROM matchtbl
WHERE matchtbl.b NOT IN (SELECT distinct b
FROM t1)
Of course, if the data is being processed by a programming language, it should be relatively easy to keep track of the set of values returned by the original query, by adding the b column to the output, and then perform the set difference.
Putting the list in an in clause makes this hard. If you can put the list in a table, then the following works:
with list as (
select val1 as value from dual union all
select val2 from dual union all
. . .
select valn
)
select list.value, count(t1.b)
from list left outer join
t1
on t1.b = list.value
group by list.value;