How to select the intersect between 2 columns from 2 tables? - tsql

I want to select the FROM TO range that available in both table
E.g: at VAL = 3000 TABLE_A RANGING FROM 3000-9999, TABLE_B RANGING FROM 0-4000
SO THE INTERCEPT BETWEEN WILL BE 3000 - 4000.
How can I complete this task?
CREATE TABLE TABLE_A(
A_ID NVARCHAR,
B_ID SMALLINT,
A_FROM SMALLINT,
A_TO SMALLINT)
CREATE TABLE TABLE_B(
B_ID SMALLINT,
VAL SMALLINT,
B_FROM SMALLINT,
B_TO SMALLINT)
INSERT INTO TABLE_A(A_ID,B_ID,A_FROM,A_TO)
VALUES (N'A',1,0,3000),
(N'A',2,3000,9999);
INSERT INTO TABLE_B(B_ID,VAL,B_FROM,B_TO)
VALUES (1,1000,0,2000),
(1,2000,2000,9999),
(2,3000,0,4000),
(2,4000,4000,9999)
SELECT A_ID,
A.B_ID,
VAL,
A_FROM AS 'FROM',
A_TO AS 'TO'
FROM TABLE_A AS A
INNER JOIN TABLE_B AS B
ON A.B_ID = B.B_ID;

Something like this
SELECT A_ID,
A.B_ID,
VAL,
A_FROM AS 'FROM',
A_TO AS 'TO' ,
CASE WHEN A.A_FROM >= B.B_FROM THEN A.A_FROM ELSE B.B_FROM END AS start ,
CASE WHEN A.A_TO >= B.B_TO THEN B.B_TO ELSE A.A_TO END AS finish
FROM #TABLE_A AS A
INNER JOIN #TABLE_B AS B
ON A.B_ID = B.B_ID;

Related

Is it possible to find duplicating records in two columns simultaneously in PostgreSQL?

I have the following database schema (oversimplified):
create sequence partners_partner_id_seq;
create table partners
(
partner_id integer default nextval('partners_partner_id_seq'::regclass) not null primary key,
name varchar(255) default NULL::character varying,
company_id varchar(20) default NULL::character varying,
vat_id varchar(50) default NULL::character varying,
is_deleted boolean default false not null
);
INSERT INTO partners(name, company_id, vat_id) VALUES('test1','1010109191191', 'BG1010109191192');
INSERT INTO partners(name, company_id, vat_id) VALUES('test2','1010109191191', 'BG1010109191192');
INSERT INTO partners(name, company_id, vat_id) VALUES('test3','3214567890102', 'BG1010109191192');
INSERT INTO partners(name, company_id, vat_id) VALUES('test4','9999999999999', 'GE9999999999999');
I am trying to figure out how to return test1, test2 (because the company_id column value duplicates vertically) and test3 (because the vat_id column value duplicates vertically as well).
To put it in other words - I need to find duplicating company_id and vat_id records and group them together, so that test1, test2 and test3 would be together, because they duplicate by company_id and vat_id.
So far I have the following query:
SELECT *
FROM (
SELECT *, LEAD(row, 1) OVER () AS nextrow
FROM (
SELECT *, ROW_NUMBER() OVER (w) AS row
FROM partners
WHERE is_deleted = false
AND ((company_id != '' AND company_id IS NOT null) OR (vat_id != '' AND vat_id IS NOT NULL))
WINDOW w AS (PARTITION BY company_id, vat_id ORDER BY partner_id DESC)
) x
) y
WHERE (row > 1 OR nextrow > 1)
AND is_deleted = false
This successfully shows all company_id duplicates, but does not appear to show vat_id ones - test3 row is missing. Is this possible to be done within one query?
Here is a db-fiddle with the schema, data and predefined query reproducing my result.
You can do this with recursion, but depending on the size of your data you may want to iterate, instead.
The trick is to make the name just another match key instead of treating it differently than the company_id and vat_id:
create table partners (
partner_id integer generated always as identity primary key,
name text,
company_id text,
vat_id text,
is_deleted boolean not null default false
);
insert into partners (name, company_id, vat_id) values
('test1','1010109191191', 'BG1010109191192'),
('test2','1010109191191', 'BG1010109191192'),
('test3','3214567890102', 'BG1010109191192'),
('test4','9999999999999', 'GE9999999999999'),
('test5','3214567890102', 'BG8888888888888'),
('test6','2983489023408', 'BG8888888888888')
;
I added a couple of test cases and left in the lone partner.
with recursive keys as (
select partner_id,
array['n_'||name, 'c_'||company_id, 'v_'||vat_id] as matcher,
array[partner_id] as matchlist,
1 as size
from partners
), matchers as (
select *
from keys
union all
select p.partner_id, c.matcher,
p.matchlist||c.partner_id as matchlist,
p.size + 1
from matchers p
join keys c
on c.matcher && p.matcher
and not p.matchlist #> array[c.partner_id]
), largest as (
select distinct sort(matchlist) as matchlist
from matchers m
where not exists (select 1
from matchers
where matchlist #> m.matchlist
and size > m.size)
-- and size > 1
)
select *
from largest
;
matchlist
{1,2,3,5,6}
{4}
fiddle
EDIT UPDATE
Since recursion did not perform, here is an iterative example in plpgsql that uses a temporary table:
create temporary table match1 (
partner_id int not null,
group_id int not null,
matchkey uuid not null
);
create index on match1 (matchkey);
create index on match1 (group_id);
insert into match1
select partner_id, partner_id, md5('n_'||name)::uuid from partners
union all
select partner_id, partner_id, md5('c_'||company_id)::uuid from partners
union all
select partner_id, partner_id, md5('v_'||vat_id)::uuid from partners;
do $$
declare _cnt bigint;
begin
loop
with consolidate as (
select group_id,
min(group_id) over (partition by matchkey) as new_group_id
from match1
), minimize as (
select group_id, min(new_group_id) as new_group_id
from consolidate
group by group_id
), doupdate as (
update match1
set group_id = m.new_group_id
from minimize m
where m.group_id = match1.group_id
and m.new_group_id != match1.group_id
returning *
)
select count(*) into _cnt from doupdate;
if _cnt = 0 then
exit;
end if;
end loop;
end;
$$;
updated fiddle

aggregate multiple columns over dynamic pivot in sql

I'm creating a stored procedure that would allow the user to retrieve data from 2 tables by providing the PersonID number as a parameter.
I thought of using the pivot function to pivot the Data table dynamically by non-aggregating over multiple columns and retrieving data from ONE column in a different table. The 2 tables below are just sample data as I have over 100 columns for the data table, hence the dynamic part. The 2 tables doesn't have a common ID column but just a common column_name.
Here are the 2 tables:
Mapping Table:
CREATE table #table (
ID varchar(10) NOT NULL,
Column_Name varchar (255) NOT NULL,
Page_Num varchar(10) NOT NULL,
Line_Num varchar(10) NOT NULL,
Element_Num varchar(10) NOT NULL
)
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('1','Name', 'DT-01', '200','20')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('2','SSN', 'DT-02', '220','10')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('3','City', 'DT-03', '300','11')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('4','StreetName', 'DT-04', '350','33')
INSERT INTO #table (ID,Column_Name,Page_Num,Line_Num,Element_Num) VALUES ('5','Sex', 'DT-05', '310','51')
Creates:
ID Column_Name Page_Num Line_Num Element_Num
_________________________________________________________________
1 Name DT-01 200 20
2 SSN DT-02 220 10
3 City DT-03 300 11
4 StreetName DT-04 350 33
5 Sex DT-05 310 51
Data table:
CREATE table #temp (
PersonID varchar (100) NOT NULL,
Name varchar(100) NOT NULL,
SSN varchar (255) NOT NULL,
City varchar(100) NOT NULL,
StreetName varchar(100) NOT NULL,
Sex varchar(100) NOT NULL
)
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('112','Joe','945890189', 'Lookesville', 'Broad st','Male')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('140','Santana','514819926', 'Falls Church', 'Gane Rd', 'Female')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('481','Wyatt','014523548','Gainesville', 'Westfield blvd', 'Male')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('724','Brittany','551489230','Aldi', 'Ostrich rd', 'Female')
INSERT INTO #temp (PersonID,Name,SSN,City,StreetName,Sex) VALUES ('100','Giovanni','774451362','Paige', 'Company ln', 'Male')
Creates:
PersonID Name SSN City StreetName Sex
_______________________________________________________________________
112 Joe 945890189 Lookesville Broad st Male
140 Santana 514819926 Falls Church Gane Rd Female
481 Wyatt 014523548 Gainesville Westfield rd Male
724 Brittany 551489230 Aldi Ostrich rd Female
100 Giovanni 774451362 Paige Company ln Male
The end result should be:
Example: User enters parameter PersonID = 140
Column_name Page_Num Line_Num Element_Num Data
_____________________________________________________________________________
Name DT-01 200 20 Santana
SSN DT-02 220 10 514819926
City DT-03 300 11 Falls Church
StreetName DT-04 350 33 Gane Rd
Sex DT-05 310 51 Female
... ... ... ... ...
and so on..
The following will dynamically unpivot a data row, and then perform a join on the field name with the def data.
If you want to run this query without a filter, I would suggest adding A.PersonID to the top SELECT and remove the WHERE
I should add, UNPIVOT would be more performant, but with this approach, there is no need to define and/or recast values. That said, the performance is still very respectable.
Example
Select D.*
,Data=C.Value
From #Temp A
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as X(r)
Cross Apply X.r.nodes('./#*') AS N(attr)
) C
Join #Table D on (C.Item=D.Column_Name)
Where PersonID=140
Returns
If it Helps with the Visualization, the CROSS APPLY C generates the following:
EDIT - As a Stored Procedure
CREATE PROCEDURE [dbo].[YourProcedureName](#PersonID int)
As
Begin
Set NoCount On;
Select D.*
,Data=C.Value
From YourPersonTableName A
Cross Apply (Select XMLData = cast((Select A.* For XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as X(r)
Cross Apply X.r.nodes('./#*') AS N(attr)
) C
Join YourObjectTableName D on (C.Item=D.Column_Name)
Where PersonID=#PersonID
End

Can I Choose Different Table for inner join operation?

This is my T-SQL
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join [tableA or tableB] on xxxxxxxx
I want join tableA when Profile = Soft and join tableB when Profile = Hard, how can I do just only using T-SQL in one batch?
Thanks
You can't directly do it, but could achieve the same effect with outer joins
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
left outer join tableA ON tableA.x = p1.x AND p1.Profile = 'Soft'
left outer join tableB ON tableB.x = p1.x AND p1.Profile = 'Hard'
where
where
(tableA.x IS NOT NULL and p1.Profile = 'Soft')
or (tableB.x IS NOT NULL and p1.Profile = 'Hard')
Of course, you can choose different tables for inner join operation, but it must be based on some condition or variable.
For Example:
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join tableA A
on Profile='Soft'
AND <any other Condition>
UNION
select Id,Profile,Type ,
case Profile
when 'Soft' then 'SID'
when 'Hard' then 'HID'
end as [Profile]
from ProductDetail p1
inner join tableB B
on Profile='Hard'
AND <any other Condition>
You can do this in a single statement with the same or similar case statement in your join. Below is sample code using temp tables that joins to 2 different reference tables merged into a single result set using a UNION
DECLARE #ProductDetail TABLE (Id INT, sProfile VARCHAR(100), StID INT, HdID INT)
DECLARE #TableA TABLE (StId INT, Field1 VARCHAR(100))
DECLARE #TableB TABLE (HdId INT, Field1 VARCHAR(100))
INSERT INTO #ProductDetail (Id, sProfile, StID , HdID ) VALUES (1,'Soft',1,1)
INSERT INTO #ProductDetail (Id, sProfile, StID , HdID ) VALUES (2,'Hard',2,2)
INSERT INTO #TableA (StId,Field1) VALUES (1,'Soft 1')
INSERT INTO #TableA (StId,Field1) VALUES (2,'Soft 2')
INSERT INTO #TableB (HdId,Field1) VALUES (1,'Hard 1')
INSERT INTO #TableB (HdId,Field1) VALUES (2,'Hard 2')
SELECT
p1.Id,p1.sProfile,
CASE
WHEN p1.sProfile = 'Soft' THEN StID
WHEN p1.sProfile = 'Hard' THEN HdId
END AS [Profile]
,ReferenceTable.FieldName
FROM
#ProductDetail p1
INNER JOIN
(
SELECT StID AS id, 'Soft' AS sProfile, Field1 AS FieldName
FROM #TableA AS tableA
UNION ALL
SELECT HdID AS id, 'Hard' AS sProfile, Field1 AS FieldName
FROM #TableB AS tableB
)
AS ReferenceTable
ON
CASE
WHEN p1.sProfile = 'Soft' THEN StID
WHEN p1.sProfile = 'Hard' THEN HdID
END = ReferenceTable.Id
AND p1.sProfile = ReferenceTable.sProfile
This will return the following result set:
Id sProfile Profile FieldName
1 Soft 1 Soft 1
2 Hard 2 Hard 2

PostgreSQL grouping

I would like to group values according to values in over columns.
This is an example:
I would like to get the output:
{{-30,-50,20},{-20,30,60},{-30,NULL or other value, 20}}
I managed to arrive to:
SELECT array_agg("val")
FROM my_table
WHERE "t_id" = 1
GROUP BY "m_id";
{{-30,-50,20},{-20,30,60},{-30,20}}
What would be the best approach?
create table my_table (
t_id int,
m_id int,
s_id int,
val int
);
insert into my_table (t_id, m_id, s_id, val) values
(1,1,1,-30),
(1,1,2,-50),
(1,1,3,20),
(1,2,1,-20),
(1,2,2,30),
(1,2,3,60),
(1,3,1,-30),
(1,3,3,20);
select array_agg(val order by s_id)
from
my_table t
right join
(
(
select distinct t_id, m_id
from my_table
) a
cross join
(
select distinct s_id
from my_table
) b
) s using (t_id, m_id, s_id)
where t_id = 1
group by m_id
order by m_id
;
array_agg
---------------
{-30,-50,20}
{-20,30,60}
{-30,NULL,20}

Join two tables with count from first table

I know there is an obvious answer to this question, but I'm like a noob trying to remember how to write queries. I have the following table structure in Postgresql:
CREATE TABLE public.table1 (
accountid BIGINT NOT NULL,
rpt_start DATE NOT NULL,
rpt_end DATE NOT NULL,
CONSTRAINT table1_pkey PRIMARY KEY(accountid, rpt_start, rpt_end)
)
WITH (oids = false);
CREATE TABLE public.table2 (
customer_id BIGINT NOT NULL,
read VARCHAR(255),
CONSTRAINT table2 PRIMARY KEY(customer_id)
)
WITH (oids = false);
The objective of the query is to display a result set of accountid's, count of accountid's in table1 and read from table2. The join is on table1.accountid = table2.customer_id.
The result set should appear as follows:
accountid count read
1234 2 100
1235 9 110
1236 1 91
The count column reflect the number of rows in table1 for each accountid. The read column is a value from table2 associated with the same accountid.
select accountid, "count", read
from
(
select accountid, count(*) "count"
from table1
group by accountid
) t1
inner join
table2 t2 on t1.accountid = t2.customer_id
order by accountid
SELECT table2.customer_id, COUNT(*), table2.read
FROM table2
LEFT JOIN table1 ON (table2.customer_id = table1.accountid)
GROUP BY table2.customer_id, table2.read
SELECT t2.customer_id, t2.read, COUNT(*) AS the_count
FROM table2 t2
JOIN table1 t1 ON t1.accountid = t2.customer_id
GROUP BY t2.customer_id, t2.read
;