SQL query with subqueries based on base values - sql

Good morning; I have two tables that I am trying to work with to develop a single query with subquery and having issues if someone could help.
First Table - [MPA_Desc] - There is more data I parsed for example
MPAID Color Model Side
1 085 x1 R
2 777 x1 R
3 085 x1 L
4 777 x1 L
Second Table - [Paintsched] - There is more data I parsed for example
Lot MPAID Amount
1 1 100
2 2 250
3 4 100
4 2 100
I am trying to get this as my query result:
Color R L
085 100 0
777 350 100
This is the query and subquery I am using, It's incomplete and doesn't have the "LH" calculations in it because I want to get the RH right first:
Select distinct(mp.Color), rh.RH
from MPA_Desc MP
right join (Select MPA_Desc.MPAID, MPA_Desc.Color, nullif(sum(qty),0) as RH from PaintSched inner join MPA_Desc on PaintSched.MPAID = MPA_Desc.MPAID
where side = 'r' group by MPA_Desc.MPAID,MPA_Desc.Color) RH
ON mp.MPAID = rh.MPAID
where Model = 'x1'
But this however gives me the following result:
Color R
085 100
085 NULL
777 350
777 Null
I know it's from my full join but I need to show the Null's as "0" and I thought nullif would work but apparently not in a subquery. Secondly I need it to just show the List of color codes by model in the original query and seperate out the sums based on RH or LH parts. Any help would be great, thank you all!

You can use PIVOT
DECLARE #MPA_Desc TABLE (MPAID INT, Color VARCHAR(5), Model VARCHAR(5), Side VARCHAR(5))
INSERT INTO #MPA_Desc VALUES
(1 , '085', 'x1', 'R'),
(2 , '777', 'x1', 'R'),
(3 , '085', 'x1', 'L'),
(4 , '777', 'x1', 'L')
DECLARE #Paintsched TABLE (Lot INT, MPAID INT, Amount INT)
INSERT INTO #Paintsched VALUES
(1 ,1 , 100),
(2 ,2 , 250),
(3 ,4 , 100),
(4 ,2 , 100)
SELECT Color, ISNULL(R,0) R, ISNULL( L,0) L FROM
( SELECT Color, Side, Amount FROM #MPA_Desc D
INNER JOIN #Paintsched P ON D.MPAID = P.MPAID ) SRC
PIVOT (SUM(Amount) FOR Side IN ([R],[L])) PVT
Result:
Color R L
----- ----------- -----------
085 100 0
777 350 100

You need conditional aggregation :
select md.Color,
sum(case when Side = 'R' then ps.amount else 0 end),
sum(case when Side = 'L' then ps.amount else 0 end)
from MPA_Desc md inner join
Paintsched ps
on ps.MPAID = md.MPAID
group by md.Color;

Try the following (Its Dynamic) -:
Declare #sql varchar(MAX),#query nvarchar(MAX)
select #sql=stuff((select distinct ', sum(case when side='''+side+''' then amount else 0 end) as '+side from [MPA_Desc] FOR XML PATH ('')), 1, 1, '' )
set #query='select color,'+#sql+' from [MPA_Desc] m join [Paintsched] n on m.MPAID=n.MPAID group by color'
EXEC sp_sqlexec #query
SQL Server 2014

Related

Common point in multiple polygons in SQL

i have two tables that contains a list geometry data
Ex. (0xE6100000010CFB24190B88E44A40AADDAB69817F3740)
i did the intersection of shapes between the two tables , now i'm trying to find a common point in all the intersected shapes
i tried to find the STCentroid() of each shape , but i can't find out how to find the common point in all of them
select p1.shape_data.STIntersection(p2.shape_data).STCentroid() as inter_geometry
from map_shapes p1
inner join areas_map_shapes p2 on p2.shape_data.STIntersects(p1.shape_data) = 1
where p2.shape_data.STIntersects(p1.shape_data) = 1
and p2.shape_id = 206
i tried also to aggregate all the intersected shapes
SELECT
geometry::UnionAggregate(ss.shape_data),
geometry::STGeomFromText( geometry::UnionAggregate(ss.shape_data).STCentroid().ToString(), 0).STY as lat,
geometry::STGeomFromText( geometry::UnionAggregate(ss.shape_data).STCentroid().ToString(), 0).STX as lon
FROM areas_map_shapes T
inner join map_shapes SS on SS.shape_data.STIntersects(T.shape_data) = 1
WHERE SS.shape_data.STIntersects(T.shape_data) = 1
AND T.shape_id = 206
and T.status = 1
and SS.status = 1
and T.country_id = 4
my problem is that i need to find the only one common point in all the shapes that intersects
adding image to represent what i got so far , this shows all the shapes the intersects with the main shape , i need to find a common point in all of them
Its hard to tell from your example because (as #nbk pointed out) its difficult to reproduce what you're asking for. That said, it looks like you're looking for the STIntersection function.
DECLARE #GeometryTable TABLE(
ID INT,
geom GEOMETRY
)
INSERT INTO #GeometryTable (ID, Geom) VALUES (1, GEOMETRY::STGeomFromText('POLYGON((0 0, 0 2, 2 2, 2 0, 0 0))', 0))
INSERT INTO #GeometryTable (ID, Geom) VALUES (2, GEOMETRY::STGeomFromText('POLYGON((1 1, 1 3, 3 3, 3 1, 1 1))', 0))
INSERT INTO #GeometryTable (ID, Geom) VALUES (3, GEOMETRY::STGeomFromText('POLYGON((0 1, 0 3, 2 3, 2 1, 0 1))', 0))
SELECT
G1.geom.STIntersection(G2.geom).STIntersection(G3.geom)
FROM
#GeometryTable G1
INNER JOIN
#GeometryTable G2
ON
G1.geom.STIntersects(G2.geom) = 1
INNER JOIN
#GeometryTable G3
ON
G1.geom.STIntersects(G3.geom) = 1
AND G2.geom.STIntersects(G3.geom) = 1
WHERE
G1.ID = 1
AND G2.ID = 2
AND G3.ID = 3
Not sure there's an easy/fast way to do it. One idea is to use STIntersection to create a intersection polygon of all your areas in a recursive CTE:
drop table #t_geoms
create table #t_geoms (geom geometry, row_id int identity)
-- create some random data
insert into #t_geoms
select top 30 GEOMETRY::Point(ROW_NUMBER() OVER(ORDER BY object_id) * 0.01 + 10,ROW_NUMBER() OVER(ORDER BY object_id) * 0.01 + 10, 4326).STBuffer(3) x
from sys.objects
;with cte as (
select geom, row_id
from #t_geoms
where row_id = 1
union all
select g.geom.STIntersection(c.geom), g.row_id
from cte c
inner join #t_geoms g
ON g.row_id = c.row_id + 1
)
select top 1 geom, geom.STCentroid() AS centerPointOfIntersection
from cte
order by row_id desc
option(MAXRECURSION 0)
Note that if not all polygons actually intersect, you get an emptry geom

How to calculate the degree of agreement by row comparisons in SQL Server?

For a minimal, reproducible example (reprex) let's assume I have a database object (dbo) in a Microsoft SQL Server and I want to query things in T-SQL.
My dbo looks like this:
Animal-ID Marker-ID Allele1 Allele2
--------------------------------------------
1 OAR1 A G
1 OAR2 C C
1 OAR3 T G
2 OAR1 A C
2 OAR2 C C
2 OAR3 A C
What I would like to do is calculate an allele match percentage per Marker-ID across all Animal-IDs.
Given the dbo example from above the desired result looks like this:
Animal-ID-pair Marker-ID Match-percentage
--------------------------------------------
1-2 OAR1 50
1-2 OAR2 100
1-2 OAR3 0
So far, I tried the following approaches:
First I thought selecting individual rows is sufficient.
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY Animal-ID ASC) AS rownumber,
Animal-ID, Marker-ID,
Allele1, Allele2
FROM
dbo) AS foo
WHERE
rownumber BETWEEN 1 AND 3;
and then compare that to the range between 4 and 6.
The problem here is that, in my real and way lager data set, not all animal-ID pairs have the same number of rows, i.e. not the same number of markers.
That is why I thought grouping might be helpful:
SELECT
Animal-ID, Marker-ID,
Allele1, Allele2
FROM
dbo
WHERE
Animal-ID IN (SELECT Animal-ID FROM dbo
GROUP BY Animal-ID
HAVING COUNT(*) > 1);
but that does not allow me to do comparisons and/or calculations across groups.
Thus I would like to ask how to calculate the degree of agreement in the comparison of row pairs.
Sample data
create table genomes
(
AnimalId int,
MarkerId nvarchar(10),
Allele1 nvarchar(1),
Allele2 nvarchar(2)
)
insert into genomes (AnimalId, MarkerId, Allele1, Allele2) values
(1, 'OAR1', 'A', 'G'),
(1, 'OAR2', 'C', 'C'),
(1, 'OAR3', 'T', 'G'),
(2, 'OAR1', 'A', 'C'),
(2, 'OAR2', 'C', 'C'),
(2, 'OAR3', 'A', 'C'),
(3, 'OAR1', 'A', 'G'), --new sample Animal with less data (no OAR3)
(3, 'OAR2', 'C', 'G');
Solution
Select all unique animals cte_AllAnimals.
Select all unique markers cte_AllMarkers.
Combine every animal with every animal behind it a2.AnimalId > a1.AnimalId. This will give you all unique animal combinations.
Combine every pair with every marker cross join cte_AllMarkers.
This gives me:
with cte_AllMarkers as
(
select g.MarkerId
from genomes g
group by g.MarkerId
),
cte_AllAnimals as
(
select g.AnimalId
from genomes g
group by g.AnimalId
)
select convert(nvarchar(10), a1.AnimalId) + '-' +
convert(nvarchar(10), a2.AnimalId) as AnimalIdPair,
m.MarkerId,
case g1.Allele1 when g2.Allele1 then 50 else 0 end +
case g1.Allele2 when g2.Allele2 then 50 else 0 end as MatchPercentage
from cte_AllAnimals a1
join cte_AllAnimals a2
on a2.AnimalId > a1.AnimalId
cross join cte_AllMarkers m
left join genomes g1
on g1.AnimalId = a1.AnimalId
and g1.MarkerId = m.MarkerId
left join genomes g2
on g2.AnimalId = a2.AnimalId
and g2.MarkerId = m.MarkerId
order by a1.AnimalId,
a2.AnimalId,
m.MarkerId;
Result
AnimalIdPair MarkerId MatchPercentage
------------ -------- ---------------
1-2 OAR1 50
1-2 OAR2 100
1-2 OAR3 0
1-3 OAR1 100
1-3 OAR2 50
1-3 OAR3 0
2-3 OAR1 50
2-3 OAR2 50
2-3 OAR3 0
Fiddle to see it in action.
By Using SUBQUERY & STUFF
DECLARE #T TABLE(Animal_ID INT, Marker_ID CHAR(10) , Allele1 CHAR, Allele2 CHAR)
INSERT INTO #T VALUES
(1,'OAR1','A','G'),
(1,'OAR2','C','C'),
(1,'OAR3','T','G'),
(2,'OAR1','A','C'),
(2,'OAR2','C','C'),
(2,'OAR3','A','C')
SELECT * FROM #T
SELECT S.*,(ISNULL(S1.C,0)+ISNULL(S2.C,0))*100/LEN(Allele_Pair) AS Percentage
FROM (
SELECT STUFF((SELECT CONCAT('-' , Animal_ID ) FROM #T t1
WHERE t1.Marker_ID = t2.Marker_ID FOR XML PATH ('')), 1, 1, '') AS Animal_ID_Pair
,Marker_ID,
STUFF((SELECT CONCAT(Allele1,Allele2) FROM #T t1
WHERE t1.Marker_ID = t2.Marker_ID FOR XML PATH ('')), 1, 0, '') AS Allele_Pair
FROM #T t2
GROUP BY Marker_ID) S
LEFT JOIN (SELECT Marker_ID,Allele2,COUNT(Allele2) AS C FROm #T GROUP BY Allele2,Marker_ID HAVING COUNT(Allele2)>1) S1 ON S1.Marker_ID=S.Marker_ID
LEFT JOIN (SELECT Marker_ID,Allele1,COUNT(Allele1) AS C FROm #T GROUP BY Allele1,Marker_ID HAVING COUNT(Allele1)>1) S2 ON S2.Marker_ID=S.Marker_ID
Output:
Animal_ID_Pair Marker_ID Allele_Pair Percentage
1-2 OAR1 AGAC 50
1-2 OAR2 CCCC 100
1-2 OAR3 TGAC 0
A self-join does what you want -- with some arithmetic:
select t1.animal_id, t2.animal_id,
( case when t1.allele1 = t2.allele1 then 1.0 else 0 end +
case when t1.allele2 = t2.allele2 then 1.0 else 0 end +
) / 2.0 as match_percentage
from t t1 join
t t2
on t1.marker_id = t2.marker_id and
t1.animal_id < t2.animal_id;
Although it is easy enough to add new alleles into this. You can also express this as by unpivoting the alleles and aggregating:
with ta as (
select t.*,, v.*
from t cross apply
(values (1, allele1), (2, allele2)) v(allele, val)
)
select ta1.animal_id, ta2.animal_id, mta1.marker,
avg(case when ta1.val = ta2.val then 1.0 else 0 end) as match_percentage
from ta ta1 join
ta ta2
on ta1.marker_id = ta2.marker_id and
ta1.animal_id < ta2.animal_id
group by ta1.animal_id, ta2.animal_id;

How to get the result in below format in SQL Server?

I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n

How to add and subtract value from previous rows based on condition

I have a table with values
Slno Type Amount
1 P 40
2 C 20
3 P 45
4 P 20
5 C 10
I want to get values for RESULT column.
Type Amount RESULT
P 40 40
C 20 20
P 45 65
P 20 85
C 10 75
If Type is C then value gets subtracted from previous value,
else if Type is P then value gets added to previous values.
This is what i've tried:
;WITH FINALMIDRESULT
AS (SELECT Type,
Value1,
Row_number()
OVER(
ORDER BY Slno ASC) rownum
FROM #midRes)
SELECT C1.Type,
C1.Value1,
CASE
WHEN C1.Type = 'C' THEN (SELECT Sum(Amount)
FROM FINALMIDRESULT c2
WHERE c2.rownum <= C1.rownum)
ELSE (SELECT Sum(Amount) - Sum(Amount)
FROM FINALMIDRESULT c2
WHERE c2.rownum <= C1.rownum)
END AS RESULT
FROM FINALMIDRESULT C1
This is the Result that i have got
Type Amount RESULT
P 40 0
C 20 60
P 45 0
P 20 0
C 10 135
You need to implement a seft INNER JOIN to sum all values with Slno less than the current value, like below:
;WITH OriginalData AS
( SELECT *
FROM
( VALUES
(1, 'P', 40),
(2, 'C', 20),
(3, 'P', 45),
(4, 'P', 20),
(5, 'C', 10)
) AS Temp(Slno, Type, Amount)
)
SELECT [Current].Type, [Current].Amount,
ISNULL(SUM(
CASE WHEN [Previous].Type = 'P'
THEN +[Previous].Amount
ELSE -[Previous].Amount
END),0) +
CASE WHEN [Current].Type = 'P'
THEN +[Current].Amount
ELSE -[Current].Amount
END Result
FROM OriginalData [Current]
LEFT JOIN OriginalData [Previous]
ON [Previous].Slno < [Current].Slno
GROUP BY [Current].Slno, [Current].Type, [Current].Amount
ORDER BY [Current].Slno
I think the biggest change you can make is to shift your mindset. When you think "previous values" you chose a procedural path which can be solved my any major programming language, but rapidly evolve to a cursor approach in SQL -- what isn't appropriate in this case.
When comes to SQL, you need to think in "sets", so you can drive your efforts to identify those data sets and combine them.
SELECT SlNo, Type, Amount,
Sum((Case when Type='C' then -1 else 1 END)*Amount) Over(Order by SlNo) Result
FROM TableName

Pivoting , Dynamic columns in SQL Server

I have tables like these
LoanPrograms
Id Name
------------
1 LP1
2 LP2
3 LP3
Channels
Id Name
----------
4 Channel1
5 Channel2
6 Channel3
LoanProgramsChannels
LoanProgramId Channelid
----------------------
1 4
1 5
2 4
I wanted to get data like these
LoanProgarmNames channel1 channel2 channel3
---------------- -------- -------- --------
LP1 y y N
LP2 y N N
LP3 N N N
I am quite new to SQL, I know I have to use PIVOT to achieve these, but not sure how can I achieve in these scenario. Can anybody help on this ?
Because you need to "fill in the gaps" the best way is to create a cartesian product using a CROSS JOIN:
;WITH CTE AS (
SELECT A.NAME AS LOANPROGRAMNAME
, B.NAME AS CHANNELNAME
, CASE WHEN C.CHANNELID IS NULL THEN 'N' ELSE 'Y' END AS LOANPROGRAMCHANNELS
FROM LOANPROGRAMS AS A
CROSS JOIN CHANNELS AS B
LEFT JOIN LOANPROGRAMSCHANNELS AS C
ON CAST(A.ID AS VARCHAR)+CAST(B.ID AS VARCHAR) =
CAST(C.LOANPROGRAMID AS VARCHAR)+CAST(C.CHANNELID AS VARCHAR))
SELECT LOANPROGRAMNAME, [CHANNEL1], [CHANNEL2], [CHANNEL3]
FROM CTE
PIVOT(MAX(LOANPROGRAMCHANNELS) FOR CHANNELNAME IN ([CHANNEL1], [CHANNEL2], [CHANNEL3])) PIV
Once you've got the condition (Y/N) you can pivot the required columns, as done here.
There might be better ways to code this, but I believe the following will provide what you want:
declare #tbLoanPrograms table (
ID int, Name varchar(12)
)
insert into #tbLoanPrograms (
ID , Name
)
values (1, 'LP1')
, (2, 'LP2')
, (3, 'LP3')
declare #tbChannels table (
ID int, Name varchar(12)
)
insert into #tbChannels (
ID , Name
)
values (4, 'Channel1')
, (5, 'Channel2')
, (6, 'Channel3')
declare #tbLoanProgramsChannels table (
LoanProgramId int, Channelid int
)
insert into #tbLoanProgramsChannels (
LoanProgramId , Channelid
)
values (1, 4)
, (1, 5)
, (2, 4)
select
t.Name
, Channel1 = max(Channel1)
, Channel2 = max(Channel2)
, Channel3 = max(Channel3)
from (
select
lp.Name
, Channel1 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 4
then 'y'
else 'N'
end
, Channel2 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 5
then 'y'
else 'N'
end
, Channel3 =
case
when lpc.LoanProgramId is not null and lpc.Channelid = 6
then 'y'
else 'N'
end
from
#tbLoanPrograms lp
left join #tbLoanProgramsChannels lpc on lpc.LoanProgramId = lp.ID
left join #tbChannels c on c.ID = lpc.Channelid
) t
group by t.Name