INTERSECT ALL not working on PostgreSQL 11 - sql

The PostgreSQL operation INTERSECT ALL does not seem to work. What am I missing?
The following query returns just one row containing the value two, but I am expecting two with the value as I am using intersect all.
(
(select 1 as z)
union all
(select 2 as z)
union all
(select 2 as z)
)
intersect all
(select 2 as z)
Does anyone have a guess?

There is only one row (with a value of 2 for the column z) in the second operand to INTERSECT ALL so only that one can be used to find a matching partner in the other operand.
Add a second row to the second operand of the INTERSECT ALL and you'll have two rows in the result.
(SELECT 1 z
UNION ALL
SELECT 2 z
UNION ALL
SELECT 2 z)
INTERSECT ALL
(SELECT 2 z
UNION ALL
SELECT 2 z);
Or you might instead want a join.
SELECT *
FROM (SELECT 1 z
UNION ALL
SELECT 2 z
UNION ALL
SELECT 2 z) x1
INNER JOIN (SELECT 2 z) x2
ON x2.z = x1.z;

This is how it works in all version, not just 11.
The single value of '2' on the right side of the INTERSECT ALL is consumed upon matching, and can't match multiple times.
Do you really want WHERE EXISTS (..) instead?

Related

Case statement with four columns, i.e. attributes

I have a table with values "1", "0" or "". The table has four columns: p, q, r and s.
I need help creating a case statement that returns values when the attribute is equal to 1.
For ID 5 the case statement should return "p s".
For ID 14 the case statement should return "s".
For ID 33 the case statement should return 'p r s". And so on.
Do I need to come with a case statement that has every possible combination? Or is there a simpler way. Below is what I have come up with thus far.
case
when p = 1 and q =1 then "p q"
when p = 1 and r =1 then "p r"
when p = 1 and s =1 then "p s"
when r = 1 then r
when q = 1 then q
when r = 1 then r
when s = 1 then s
else ''
end
One solution could be this which uses a case for each attribute to return the correct value, surrounded by a trim to remove the trailing space.
with tbl(id, p, q, r, s) as (
select 5,1,0,0,1 from dual union all
select 14,0,0,0,1 from dual
)
select id,
trim(regexp_replace(case p when 1 then 'p' end ||
case q when 1 then 'q' end ||
case r when 1 then 'r' end ||
case s when 1 then 's' end, '(.)', '\1 '))
from tbl;
The real solution would be to fix the database design. This design technically violates Boyce-Codd 4th normal form in that it contains more than 1 independent attribute. The fact an ID "has" or "is part of" attribute p or q, etc should be split out. This design should be 3 tables, the main table with the ID, the lookup table containing info about attributes that the main ID could have (p, q, r or s) and the associative table that joins the two where appropriate (assuming an ID row could have more than one attribute and an attribute could belong to more than one ID), which is how to model a many-to-many relationship.
main_tbl main_attr attribute_lookup
ID col1 col2 main_id attr_id attr_id attr_desc
5 5 1 1 p
14 5 4 2 q
14 4 3 r
4 s
Then it would be simple to query this model to build your list, easy to maintain if an attribute description changes (only 1 place to change it), etc.
Select from it like this:
select m.ID, m.col1, listagg(al.attr_desc, ' ') within group (order by al.attr_desc) as attr_desc
from main_tbl m
join main_attr ma
on m.ID = ma.main_id
join attribute_lookup al
on ma.attr_id = al.attr_id
group by m.id, m.col1;
You can use concatenations with decode() functions
select id, decode(p,1,'p','')||decode(q,1,'q','')
||decode(r,1,'r','')||decode(s,1,'s','') as "String"
from t;
Demo
If you need spaces between letters, consider using :
with t(id,p,q,r,s) as
(
select 5,1,0,0,1 from dual union all
select 14,0,0,0,1 from dual union all
select 31,null,0,null,1 from dual union all
select 33,1,0,1,1 from dual
), t2 as
(
select id, decode(p,1,'p','')||decode(q,1,'q','')
||decode(r,1,'r','')||decode(s,1,'s','') as str
from t
), t3 as
(
select id, substr(str,level,1) as str, level as lvl
from t2
connect by level <= length(str)
and prior id = id
and prior sys_guid() is not null
)
select id, listagg(str,' ') within group (order by lvl) as "String"
from t3
group by id;
Demo
in my opinion, its a bad practice to use columns for relationships.
you should have two tables, one that's called arts and another that is called mapping art looks like this:
ID - ART
1 - p
2 - q
3 - r
4 - 2
...
and mapping maps your base-'ID's to your art-ids and looks like this
MYID - ARTID
5 - 1
5 - 4
afterwards, you should make use of oracles pivot operator. its more dynamically

create implicit data without having to create temp/volatile/working table

This is possible:
SELECT 'Bla' AS X
why is this not possible in TeraData:
SELECT 'Bla' AS X
UNION
SELECT 'DiBla' AS X
Is there a way to achieve the above without having to create a temp/volatile/working table in TeraData?
PS:
The error is: A select for a union, intersect or minus must reference a table
If you want two columns on one row, then use:
SELECT 'Bla' AS X, 'DiBla' AS Y
If you want:
X
Bla
DiBla
Then you just do:
select 'Bla' as X
union all
select 'DiBlah' as X;
If you want:
X Y
Bla NULL
NULL DiBla
Then:
SELECT 'Bla' as X, NULL as Y
UNION ALL
SELECT NULL as X, 'DiBla' as Y
You have a attribute name mishmash in your UNION. You can not perform UNION between relations having different structure. Therefore, use
SELECT cast('Bla' as varchar(6)) AS X FROM (SELECT 1 a) t
UNION
SELECT cast('DiBla' as varchar(6)) AS X FROM (SELECT 1 a) t
the explicit casting make sure that the data types are equivalent as well as the attribute names. Another solution could be
SELECT * FROM
(
SELECT cast('Bla' as varchar(6)) AS X
) t
UNION
SELECT * FROM
(
SELECT cast('DiBla' as varchar(6)) AS X
) t

SQL - Return all rows for ID where one row meets condition A,B,or C

I'm trying to return all rows for a particular IDs where a condition is met in any one of the rows tied to those IDs. Pardon me being a newbie to SQL... Example below:
ID * Line * # *
12 * 1 * A *
12 * 2 * B *
12 * 3 * X *
12 * 4 * Y *
15 * 1 * A *
15 * 2 * B *
15 * 3 * C *
Not sure what the code would be other than my select and condition = (X, Y, or Z) to return:
ID * Line * # *
12 * 1 * A * <-- doesn't include X, Y, or Z but is part of the ID which
12 * 2 * B * <-- has X in another row of that ID
12 * 3 * X *
12 * 4 * Y *
I'm wanting to pull all row records despite not meeting the condition as long as they're part of the ID that has a row that meets the condition.
Thanks for the help!
* Edit: Including code attempted*
SELECT ID
,LINE
,#
WHERE ID,
IN (
SELECT ID
WHERE # IN ('X','Y','Z'))
Results:
ID LINE #
12 3 X
12 4 Y
What I need:
ID LINE #
12 1 A
12 2 B
12 3 X
12 4 Y
I almost feel like I need to create a temp table of ID & LINE using my condition of IN('X','Y','Z') and then inner join on ID for all LINE(s) not X,Y,Z. I think that may work, but I haven't learned how to use temp tables yet. I'm a little troubled because I'm using a query, which I've simplified a ton here, where I'm selecting 18 fields that join in 7 other tables. I think this is just complicating my understanding of the subquery, not so much the subquery being affected by that.
Thanks all for the help and answers so far!
You can use a subquery and IN for this.
Select *
From YourTable
where ID in (select ID from YourTable where # in ('X','Y','Z'))
Just a note, there is no 12 * 4 * C * in your data but I think it's just a type-o in your results and should be 12 * 4 * Y *
Besides the subquery approach you might also try an OLAP-function (Depending on the actual data this might be better or worse, of course)
In Teradata you can apply QUALIFY:
Select *
From YourTable
qualify -- check if any row with the same ID has X/Y/Z
max(case when ID in ('X','Y','Z') then 1 else 0 end)
over (partition by ID) = 1
In SQL Server you have to use a Derived Table/CTE:
Select *
From
( Select *,
max(case when ID in ('X','Y','Z') then 1 else 0 end)
over (partition by ID) as flag
from YourTable
) as dt
where flag = 1

Double IN Statements in SQL

Just curious about the IN statement in SQL.
I know I can search multiple columns with one value by doing
'val1' IN (col1,col2)
And can search a column for multiple values
col1 IN ('val1','val2')
But is there a way to do both of these simultaneously, without restorting to an repeating AND / OR in the SQl? I am looking to do this in the most scalable way, so independent of how many vals / cols i need to search in.
So essentially:
('val1','val2') IN (col1,col2)
but valid.
You could do something like this (which I've also put on SQLFiddle):
-- Test data:
WITH t(col1, col2) AS (
SELECT 'val1', 'valX' UNION ALL
SELECT 'valY', 'valZ'
)
-- Solution:
SELECT *
FROM t
WHERE EXISTS (
SELECT 1
-- Join all columns with all values to see if any column matches any value
FROM (VALUES(t.col1),(t.col2)) t1(col)
JOIN (VALUES('val1'),('val2')) t2(val)
ON col = val
)
Of course, one could argue, which version is more concise.
Yes, for example you can do this in Oracle:
select x, y from (select 1 as x, 2 as y from dual)
where (x,y) in (select 1 as p, 2 as q from dual)

Using IN with convert in sql

I would like to use the IN clause, but with the convert function.
Basically, I have a table (A) with the column of type int.
But in the other table (B) I Have values which are of type varchar.
Essentially, what I am looking for something like this
select *
from B
where myB_Column IN (select myA_Columng from A)
However, I am not sure if the int from table A, would map / convert / evaluate properly for the varchar in B.
I am using SQL Server 2008.
You can use CASE statement in where clause like this and CAST only if its Integer.
else 0 or NULL depending on your requirements.
SELECT *
FROM B
WHERE CASE ISNUMERIC(myB_Column) WHEN 1 THEN CAST(myB_Column AS INT) ELSE 0 END
IN (SELECT myA_Columng FROM A)
ISNUMERIC will be 1 (true) for Decimal values as-well so ideally you should implement your own IsInteger UDF .To do that look at this question
T-sql - determine if value is integer
Option #1
Select * from B where myB_Column IN
(
Select Cast(myA_Columng As Int) from A Where ISNUMERIC(myA_Columng) = 1
)
Option #2
Select B.* from B
Inner Join
(
Select Cast(myA_Columng As Int) As myA_Columng from A
Where ISNUMERIC(myA_Columng) = 1
) T
On T.myA_Columng = B.myB_Column
Option #3
Select B.* from B
Left Join
(
Select Cast(myA_Columng As Int) As myA_Columng from A
Where ISNUMERIC(myA_Columng) = 1
) T
On T.myA_Columng = B.myB_Column
I will opt third one. Reason is below mentioned.
Disadvantages of IN Predicate
Suppose I have two list objects.
List 1 List 2
1 12
2 7
3 8
4 98
5 9
6 10
7 6
Using Contains, it will search for each List-1 item in List-2 that means iteration will happen 49 times !!!
You can also use exists caluse,
select *
from B
where EXISTS (select 1 from A WHERE CAST(myA_Column AS VARCHAR) = myB_Column)
You can use below query :
select B.*
from B
inner join (Select distinct MyA_Columng from A) AS X ON B.MyB_Column = CAST(x.MyA_Columng as NVARCHAR(50))
Try it by using CAST()
SELECT *
FROM B
WHERE CAST(myB_Column AS INT(11)) IN (
SELECT myA_Columng
FROM A
)