How to overcome SQLite View limitation - sql

Given that in SQLite, Views:
are read-only
cannot be UPDATEd,
the following is the situation:
There are 4 tables A, B, C and D and a View has to be created with data from all the four tables conditionally. Here's the pseudo-construct:
CREATE VIEW AS E SELECT A.A1, A.A2, A.A3, A.A4, B.B1, C.C1, C.C2, D.D1, D.D2 FROM A, B, C, D
WHERE A.X = 'SOME STRING' AND
A.FK = C.PK AND
A.Y = B.Z AND
D.G = A.PK AND
D.H = 'SOME STRING'
The requirement is that, irrespective of no matches in D, the remaining matches should get populated, (with 0 values in the view E for the columns from D). Needless to say, the above construct works if there are matching D rows, but obviously returns an empty view if there are no D matches.
How can the CASE statement or SELECT sub-queries (or an altogether different construct, like an INSTEAD OF trigger) deliver this requirement?
Greatly appreciate if the database experts could publish the exact construct(s). Many, many thanks in advance!

First, use explicit joins:
SELECT A.A1, A.A2, A.A3, A.A4, B.B1, C.C1, C.C2, D.D1, D.D2
FROM A
JOIN B ON A.Y = B.Z
JOIN C ON A.FK = C.PK
JOIN D ON D.G = A.PK
WHERE A.X = 'SOME STRING'
AND D.H = 'SOME STRING';
Then you can use an outer join when you want to keep rows without a match:
FROM A
JOIN B ON A.Y = B.Z
JOIN C ON A.FK = C.PK
LEFT JOIN D ON D.G = A.PK AND D.H = 'SOME STRING'
WHERE A.X = 'SOME STRING';
(The D.H comparison must be part of the join condition because D.H is NULL for missing rows, and the comparison would fail in the WHERE clause.)

Related

sql join scenario

I have 2 tables name like A and B. A have columns say X, Y and Z and Table B have coulmns say P, Q and R. here in my case table have blank data for few rows in all the columns.
I need to join these 2 tables such that If A.X<>'' and B.X<>'' then It should join the table. If A.X='' and B.X='' then
it should check the next columns A.Y<>'' and B.Y<>''. If this is also blank it should join the table on next condition A.Z<>'' and B.Z<>''. If all these 3 condition have blanks It should not join for that row.
How can we achieve this using sql join?
Thanks in advance
You can go for conditional JOINS as given below:
SELECT *
FROM A
LEFT OUTER JOIN B as b1
ON A.X = b1.X AND B1.X <> '' -- JOIN only rows WHERE x is not blank
LEFT OUTER JOIN B as b2
ON A.Y = b2.Y AND b2.Y <> '' -- JOIN only rows WHERE y is not blank
LEFT OUTER JOIN B AS b3
ON A.Z = b3.Z AND b3.Z <> '' -- JOIN only rows WHERE z is not blank
WHERE
b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL
Ramu's answer is close (I upvoted it) but it needs to be refined. The important part of the answer that is correct -- the equality conditions in the JOINs make the query easier to optimize.
However, it is better written as:
SELECT a.*,
COALESCE(b1.P, b2.P, b3.P) as P,
COALESCE(b1.Q, b2.Q, b3.Q) as Q,
COALESCE(b1.R, b2.R, b3.R) as R
FROM A LEFT JOIN
B b1
ON A.X = b1.X LEFT JOIN
B b2
ON A.Y = b2.Y AND
b1.X IS NULL LEFT JOIN -- no previous match
B b3
ON A.Z = b3.Z AND
b2.Y IS NULL AND
b1.X IS NULL -- no previous match
WHERE b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL ;
The two key changes are:
The LEFT JOIN conditions check that previous columns did not match.
The SELECT uses COALESCE() to fetch columns.
Also, I don't think the condition on empty strings is needed. There will be no match if there are no empty string values in B for that column. If both tables have empty strings, then you apparently do want a match -- and an empty string matches an empty string in SQL Server.
You can also express this using APPLY:
SELECT a.*, b.*
FROM A CROSS APPLY
(SELECT TOP (1) WITH TIES B.*
FROM B
WHERE A.X = b.X OR
A.Y = b.Y OR
A.Z = b.Z
ORDER BY (CASE WHEN A.X = B.X THEN 1
WHEN A.Y = B.Y THEN 2
WHEN A.Z = B.Z THEN 3
)
) B;
However, I would expect the previous version to have much better performance.
Use a CASE expression in the ON clause:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND B.P <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND B.Q <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND B.R <> '' AND A.Z = B.R THEN 1
END
or:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND A.Z = B.R THEN 1
END
There is no need for further joins.
The CASE expression makes sure that each condition will be checked in the order that you want it to be checked.
So if the 1st condition is satisfied then the rows of the 2 tables will be joined, if not then the 2nd condition will be checked and so on.

Alternative to <> in SQL Developer

I did some searching on this site and couldn't find exactly what I'm looking for, so I hope this isn't a duplicate. I have an issue where a query in a view is taking about 39 seconds to run, which is dragging down a report query that joins to this view multiple times.
To keep this simple I'm going to keep the code simple, but keep the structure exactly as it is on the view. Here is the SELECT statement:
SELECT ....
FROM A a
JOIN B b on a.x = b.x
JOIN C c ON c.s = 'P' AND c.y = b.y
JOIN B AS b2 ON b2.y = c.y AND b2.x <> a.x
JOIN B b3 ON b3.x = b2.x
The x's and y's are the same column names in all join predicates.
The issue I am having comes with the line AND b2.x <> a.x. Without this, it runs in about 1 second, but with it its always taking over 30 seconds. I've tried rewriting this predicate multiple times:
b2.x IN (select b2.x FROM B b2 join A a on b2.x <> a.x)
b2.x NOT IN (select b2.x FROM b b2 JOIN A a on b2.x <> a.x)
NOT b2.x = a.x
OR even removing it and putting in a where clause after the joins, with all of the above varieties and also :
WHERE b2.x NOT IN (SELECT x FROM a)
WHERE b2.x (NOT IN SELECT DISTINCT x FROM a)
Im running out of ideas and need to figure out a way to optimize this. Any suggestions or hints at what else I can look at? Just running
SELECT b2.x from B b2 JOIN A a ON b2.x <> a.x
runs very quickly, so I don't think the underlying tables are the issues.
If the query runs really fast without the condition, but poorly with it, then I might suggest a materialized CTE:
WITH abc as (
SELECT /*+ materialize */...., b2.x as b2x, a.x as ax
FROM A a JOIN
B b
ON a.x = b.x JOIN
C c
ON c.s = 'P' AND c.y = b.y JOIN
B b2
ON b2.y = c.y AND b2.x <> a.x JOIN
B b3
ON b3.x = b2.x
)
SELECT abc.*
FROM ABC
WHERE b2x <> ax;

JOIN on column only if NOT NULL

I'm in the process of re-writing an old SQL query and have troubles making sense out of it. It contains several conditions of the form
SELECT ...
FROM a, b, c
WHERE
c.id = ...
AND (
a.x_id IS NULL
OR a.x_id = c.x_id
)
AND b.id = a.b_id (+)
Can this query be rewritten using proper JOIN syntax? Is it equivalent to the following or will it produce different results under certain circumstances?
SELECT ...
FROM b
LEFT JOIN a
ON b.id = a.b_id
LEFT JOIN c
ON a.x_id = c.x_id
WHERE c.id = ...
The original query is 100 lines long and spans 5 tables, plus several joins over "virtual tables" (i.e. where conditions of the form x.z_id = y.z_id), which makes it hard to break down into more manageable bits or debug.
if you want same result as you have in first query - you must make left join only with table a, like this :
SELECT ...
FROM b, c
LEFT JOIN a
ON b.id = a.b_id and b.id = a.b_id
WHERE
c.id = ... b.c_id
or if you want the same style with all tables, you can use inner join with table b, like this :
SELECT ...
FROM c
INNER JOIN b
on b.c_id = c.id
LEFT JOIN a
ON b.id = a.b_id
WHERE
c.id = ...
in my both query we select data from table b where column is not null

How to find if LEFT JOIN joined an actual row, or placeholder NULL values?

Suppose I issue a query like this:
SELECT a.x, b.y FROM a LEFT JOIN b ON b.id = a.id
I also want to know if a row from b is actually joined or there are just placeholder NULL values supplied by LEFTJOIN. I guess I can determine it comparing values of a.id and b.id in the result, but is there a way to do this in the query itself?
I.e. I'd want something like
SELECT a.x, b.y, b_is_actually_joined FROM a LEFT JOIN b ON b.id = a.id
where values in the column b_is_actually_joined are 1 or 0 (for example).
Just check for NULL b.id:
SELECT a.x, b.y, b.id IS NOT NULL AS b_is_actually_joined
FROM a
LEFT JOIN b ON b.id = a.id
For Oracle SQL you can use NVL2 function:
SELECT a.id, b.*, NVL2(b.id, 1, 0) AS b_is_actually_joined
FROM a
LEFT JOIN b ON b.id = a.id
SQL Fiddle
This should work in Ms Sql Server:
select CAST((coalesce(b.id, 0)) as bit) as b_is_actually_joined FROM a
LEFT JOIN b ON b.id = a.id
I am unaware of a standard SQL solution for this

Locally symmetric difference in sql

I have a problem similar to this StackOverflow question, except that I need to exclude certain fields from the comparison but still include it in the result set.
I'm penning the problem as locally symmetric difference.
For example Table A and B have columns X,Y,Z and I want to compare only Y,Z for differences but I still want the result set to include X.
Sounds like this is basically what you want. Match rows between two tables on columns Y and Z, find the unmatched rows, and output the values of columns X, Y, and Z.
SELECT a.x, a.y, a.z, b.x, b.y, b.z
FROM a FULL OUTER JOIN b ON a.y = b.y AND a.z = b.z
WHERE a.y IS NULL OR b.y IS NULL
Old style SQL for a full join - A concatenated with B, excluding rows in B also in A (the middle):
-- all rows in A with or without matching B
select a.x, a.y, a.z
from a
left join b
on a.x = b.x
and a.y = b.y
union all
-- all rows in B with no match in A to "exclude the middle"
select b.x, b.y, null as z
from b
where not exists (select null
from a
where b.x = a.x
and b.y = a.y)
ANSI Style:
select coalesce(a.x, b.x) as x,
coalesce(a.y, b.y) as y,
a.z
from a
full outer join b
on a.x = b.x
and a.y = b.y
The coalesce's are there for safety; I've never actually had cause to write a full outer join in the real world.
If what you really want to find out if two table are identical, here's how:
SELECT COUNT(*)
FROM (SELECT list_of_columns
FROM one_of_the_tables
MINUS
SELECT list_of_columns
FROM the_other_table
UNION ALL
SELECT list_of_columns
FROM the_other_table
MINUS
SELECT list_of_columns
FROM one_of_the_tables)
If that returns a non-zero result, then there is a difference. It doesn't tell you which table it's in, but it's a start.