Low speed because of left join - tsql

Some times I know the performance problem of a query is because of left join , what can I do ? (replacing a sub query is suggested? )
Imagine we have tables A and B and low speed is because of the left join , what's yours recommended solution ?
Any kind of help is appreciated
CREATE TABLE A ( a1 VARCHAR(10),
a2 INT,
a3 INT )
CREATE TABLE B ( b1 VARCHAR(10),
b2 INT,
ab3 INT )
INSERT INTO A
VALUES ( 'a1', 11, 91 ),
( 'a2', 12, 92 ),
( 'a3', 13, 93 ),
( 'a4', 14, 94 )
INSERT INTO B
VALUES ( 'b1', 21, 91 ),
( 'b2', 22, 92 ),
( 'b3', 23, 93 )
-------------------------
SELECT *
FROM A
LEFT JOIN B ON a3 = ab3

Based upon my experience i will suggest you to consider following things
- Remove unnecessary left joins
- Remove where clause if it can be used along with Inner join condition
- May create indexes on your columns
- Select desired columns, avoid using * for all columns
- Avoid giving large lengths for your columns

Related

How to change "65→67→69" to "J7,G2,P9" in SQL/PostgreSQL/MySQL? Or use split fields/value mapper in Pentaho Data Integration (Spoon) to realize it?

How to change "65→67→69" to "J7,G2,P9" in SQL/PostgreSQL/MySQL? Or use split fields/value mapper in Pentaho Data Integration (Spoon) to realize it?
I use KETTLE(Pentaho Data Integration/Spoon) to insert data to PostgreSQL from other databases, I have a field with below data
value
-----------
65→67→69
15→19→17
25→23→45
19→28→98
ID value
--------
65 J7
67 G2
69 P9
15 A8
19 b9
17 C1
25 b12
23 e12
45 A23
28 C17
98 F18
And how to change the above value to the below value? Is there any SQL way or KETTLE way to realize it?
new_value
-----------
J7,G2,P9
A8,b9,C1
b12,e12,A23
b9,C17,B18
Thanks so much for any advice.
Assuming these tables:
create table table1 (value text);
insert into table1 (value)
values
('65→67→69'),
('15→19→17'),
('25→23→45'),
('19→28→98')
;
create table table2 (id int, value text);
insert into table2 (id, value)
values
(65, 'J7'),
(67, 'G2'),
(69, 'P9'),
(15, 'A8'),
(19, 'b9'),
(17, 'C1'),
(25, 'b12'),
(23, 'e12'),
(45, 'A23'),
(28, 'C17'),
(98, 'F18')
;
In Postgres you can use a scalar subselect:
select t1.value,
(select string_agg(t2.value, ',' order by t.idx)
from table_2 t2
join lateral unnest(string_to_array(t1.value,'→')) with ordinality as t(val,idx) on t2.id::text = t.val
) as new_value
from table_1 t1;
Online example

PostgreSQL: generate all possible strings of arbitrary length from a set of characters

I'm trying to generate all possible strings of length x using a fixed set of characters in PostgreSQL. For the simple case of x = 2 I can use the query below, but I cannot figure out how to do it for an arbitrary length (I'm assuming this will involve recursion):
with characters (c) as (
select unnest(array['a', 'b', 'c', 'd'])
)
select concat(c1.c, c2.c)
from characters c1
cross join characters c2
This generates aa, ab, ac, ad, ba, bb, bc, bd, etc.
Using recursive CTE:
with recursive characters (c) as (
select unnest(array['a', 'b', 'c', 'd'])
), param(val) AS (
VALUES (4) -- here goes param value
), cte AS (
select concat(c1.c, c2.c) AS c, 2 AS l
from characters c1
cross join characters c2
UNION ALL
SELECT CONCAT(c1.c, c2.c), l + 1
FROM cte c1
CROSS JOIN characters c2
WHERE l <= (SELECT val FROM param)
)
SELECT c
FROM cte
WHERE l = (SELECT val FROM param)
ORDER BY c;
db<>fiddle demo

SQL Query to conditionally display values in DB2

I need to conditionally display values by joining 3 different tables, below is the sample data,
TABLE A
LOCATION SITE LOCATIONID
100 DEMO 1234
10 DEMO 1050
TABLE B
LOCATION PARENT SITE
100 10 DEMO
TABLE C
LDKEY LDTEXT LDOWNERTABLE
1234 Hello A
1050 Welcome A
OR
TABLE C
LDKEY LDTEXT LDOWNERTABLE
1050 Welcome A
When Select query is executed on Table A then it should bring output as 100 if its locationid (1234) has a record in Table C .
Output 1
LOCATION SITE LDTEXT
100 DEMO Hello
If there is no record corresponding to locationid(1234) in table C then it should bring in its parent's LDTEXT
OUTPUT 2
LOCATION SITE LDTEXT
100 DEMO Welcome
Below is the query that I had tried and it brought me both the records 100 and 10 ideally it should bring me only 100 with LDTEXT as HELLO
select * from A where (location = '100' and site = 'DEMO'
and (locationsid in (select ldkey from C where ldownertable='A' )) or
location in ( select parent from B where location = '100' and site = 'DEMO' and
locationsid in (select ldkey from C where ldownertable='A' )))
Try this as is. You may uncomment the commented out line to check the difference.
WITH
A (LOCATION, SITE, LOCATIONID) AS
(
VALUES
(100, 'DEMO', 1234)
, ( 10, 'DEMO', 1050)
)
, B (LOCATION, PARENT, SITE) AS
(
VALUES
(100, 10, 'DEMO')
)
, C (LDKEY, LDTEXT, LDOWNERTABLE) AS
(
VALUES
(1050, 'Welcome', 'A')
--, (1234, 'Hello', 'A')
)
SELECT A.LOCATION, A.SITE, COALESCE(C1.LDTEXT, C2.LDTEXT) LDTEXT
FROM A
LEFT JOIN
(
B
JOIN A A2 ON A2.LOCATION = B.PARENT
) ON B.LOCATION = A.LOCATION
LEFT JOIN C C1 ON C1.LDKEY = A.LOCATIONID
LEFT JOIN C C2 ON C2.LDKEY = A2.LOCATIONID
WHERE A.LOCATION = 100
;

select all rows where column values are unique

There's a table T with columns n00, n01, n01, ..., n99, all integers.
I need to select all rows from this table where n00...n99 values are unique within each row.
Example for smaller number of columns:
columns: n0, n1, n2
row 1: 10, 20, 30
row 2: 34, 45, 56
row 3: 12, 13, 12
row 4: 31, 65, 90
I need the select statement to return rows 1, 2 and 4 but not 3 (row 3 contains non-unique value of 12 so filter it out).
Effectively I need to implement this:
select *
from t
where
n00 <> n01 and n00 <> n02 and ... and n00 <> n99
and n01 <> n02 and n01 <> n03 and ... and n01 <> n99
and n02 <> n03 and n02 <> n04 and ... and n02 <> n99
...
and n97 <> n98 and n97 <> n99
and n98 <> n99
... but with "smarter" WHERE block.
Any hints welcome.
You can use UNPIVOT as well:
DECLARE #t TABLE(n0 int, n1 int, n2 int);
INSERT INTO #t VALUES (10, 20, 30), (34, 45, 56), (12, 13, 12), (31, 65, 90);
WITH cteRows AS(
SELECT ROW_NUMBER() OVER (ORDER BY n0, n1, n2) rn, *
FROM #t
),
cteUP AS(
SELECT rn, rn_val
FROM cteRows
UNPIVOT(
rn_val FOR rn_vals IN(n0, n1, n2)
) up
),
cteFilter AS(
SELECT rn, rn_val, count(*) anz
FROM cteUP
GROUP BY rn, rn_val
HAVING count(*) > 1
)
SELECT *
FROM cteRows
WHERE rn NOT IN (SELECT rn FROM cteFilter)
A more dynamic approach using CROSS APPLY and a little XML. I should add UNPIVOT would be more performant, but the performance of this approach is very respectable, and you don't have identify all the fields.
You'll notice I added an ID field. Can be removed from the CROSS APPLY C if it does not exist. I included the ID to demonstrate that additional fields may be excluded from the logic.
Declare #YourTable table (id int,n0 int, n1 int, n2 int)
Insert Into #YourTable values
(1,10, 20, 30),
(2,34, 45, 56),
(3,12, 13, 12),
(4,31, 65, 90)
Select A.*
From #YourTable A
Cross Apply (Select XMLData=cast((Select A.* For XML Raw) as xml)) B
Cross Apply (
Select Cnt=count(*),Uniq=count(Distinct Value)
From (
Select ID = r.value('#id','int') -- case sensitive
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('id','excludeotherfields') -- case sensitive
) U
) C
Where Cnt=Uniq
Returns
id n0 n1 n2
1 10 20 30
2 34 45 56
4 31 65 90
If it helps with the visualization, the XML portion generates the following

Rank result set according to condition

I have a table which has 3 columns: Product, Date, Status
I want to rank in this manner:
for each product order by Date, and Rank if Status = FALSE then 0, if it's TRUE then start ranking by 1, continue ranking by the same value if previous Status is TRUE.
In this ordered set if FALSE comes assign to it 0, and for the next coming TRUE status for same product assign x+1 (x here is previous rank value for status TRUE).
I hope picture makes it more clear
This code uses SS2008R2 features which do not include LEAD/LAG. A better solution is certainly possible with more modern versions of SQL Server.
-- Sample data.
declare #Samples as Table ( Product VarChar(10), ProductDate Date,
ProductStatus Bit, DesiredRank Int );
insert into #Samples values
( 'a', '20160525', 0, 0 ), ( 'a', '20160526', 1, 1 ), ( 'a', '20160529', 1, 1 ),
( 'a', '20160601', 1, 1 ), ( 'a', '20160603', 0, 0 ), ( 'a', '20160604', 0, 0 ),
( 'a', '20160611', 1, 2 ), ( 'a', '20160612', 0, 0 ), ( 'a', '20160613', 1, 3 ),
( 'b', '20160521', 1, 1 ), ( 'b', '20160522', 0, 0 ), ( 'b', '20160525', 1, 2 );
select * from #Samples;
-- Query to rank data as requested.
with WithRN as (
select Product, ProductDate, ProductStatus, DesiredRank,
Row_Number() over ( partition by Product order by ProductDate ) as RN
from #Samples
),
RCTE as (
select *, Cast( ProductStatus as Int ) as C
from WithRN
where RN = 1
union all
select WRN.*, C + Cast( 1 - R.ProductStatus as Int ) * Cast( WRN.ProductStatus as Int )
from RCTE as R inner join
WithRN as WRN on WRN.Product = R.Product and WRN.RN = R.RN + 1 )
select Product, ProductDate, ProductStatus, DesiredRank,
C * ProductStatus as CalculatedRank
from RCTE
order by Product, ProductDate;
Note that the sample data was extracted from an image using a Mark I Eyeball. Had the OP taken heed of advice here it would have been somewhat easier.
Tip: Using column names that don't happen to match data types and keywords makes life somewhat simpler.
Try this query,
SELECT a.Product ,
a.Date ,
a.Status ,
CASE WHEN a.Status = 'FALSE' THEN 0
ELSE 1
END [Rank]
FROM ( SELECT Product ,
Date ,
Status ,
ROW_NUMBER() OVER ( PARTITION BY Product ORDER BY DATE, Status ) RNK
FROM TableProduct
) a
ORDER BY Product, a.RNK