i will try and be as clear as possible on this one, as i have no idea what to do next and would love a kick in the right direction.
Im trying to compare the values within 2 tables. The tables look like this:
Table1:
Table2
INSERT INTO #table1 ([elementName], [elementValue])
VALUES
('t1','Project'),
('p1','test1'),
('n1','value1'),
('t2','Project'),
('p2','test2'),
('n2','value2'),
('t3','Project'),
('p3','test3'),
('n3','value3'),
('t4',''),
('p4',''),
('n4',''),
('t5',''),
('p5',''),
('n5','')
INSERT INTO #table2 ([elementName], [elementValue])
VALUES
('t1','Project'),
('p1',''),
('n1',''),
('t2','Project'),
('p2','test3'),
('n2','value123'),
('t3','Project'),
('p3',''),
('n3',''),
('t4','Package'),
('p4',''),
('n4',''),
('t5','Project'),
('p5','Testtest'),
('n5','valuevalue')
I used this code to fill the testtables. Normally this is an automated process, and the tables are filled from an XML string.
Furthermore, the numbers in the element name are considered "groups" meaning T1 P1 and N1 are together.
I would like to compare T1 and P1 etc from Table1 to any combination of T and P from table2
If they match, i would like to overwrite the value of Table 1 N1 with the value of the matched N on table 2. (in the example, table1 N3 would be replaced with table2 N2
Besides that i also want to keep every group in table 1 that is not in table 2
but also add every group that is in table 2 but not in table 1 on one of the blank spots.
Last but not least, if the T value is filled, but P value is empty, it does not have to overwrite/change anything in table1.
The expected result would be this:
Table1:
i made the changes bold.
I dont really have an idea on where to start on this. Ive tried functions as except and intersect, but did not get even close to what i would like to see.
with t1 as (
select * from (values
('t1','Project'),
('p1','test1'),
('n1','value1'),
('t2','Project'),
('p2','test2'),
('n2','value2'),
('t3','Project'),
('p3','test3'),
('n3','value3'),
('t4',''),
('p4',''),
('n4',''),
('t5',''),
('p5',''),
('n5','')
) v([elementName], [elementValue])
),
t2 as (
select * from (values
('t1','Project'),
('p1',''),
('n1',''),
('t2','Project'),
('p2','test3'),
('n2','value123'),
('t3','Project'),
('p3',''),
('n3',''),
('t4','Package'),
('p4',''),
('n4',''),
('t5','Project'),
('p5','Testtest'),
('n5','valuevalue')
) v([elementName], [elementValue])
),
pivoted_t1 as (
select *
from
(select left([elementName], 1) letter, right([elementName], len([elementName]) - 1) number, [elementValue] as value from t1) t1
pivot(min(value) for letter in ([t], [p], [n])) pvt1
),
pivoted_t2 as (
select *
from
(select left([elementName], 1) letter, right([elementName], len([elementName]) - 1) number, [elementValue] as value from t2) t2
pivot(min(value) for letter in ([t], [p], [n])) pvt2
),
amended_values as (
select
pvt1.number,
coalesce(pvt2.t, pvt1.t) as t,
coalesce(pvt2.p, pvt1.p) as p,
coalesce(pvt2.n, pvt1.n) as n,
count(case when pvt1.t = '' and pvt1.p = '' then 1 end) over(order by pvt1.number rows between unbounded preceding and current row) as empty_row_number
from
pivoted_t1 pvt1
left join pivoted_t2 pvt2 on pvt1.t = pvt2.t and pvt1.p = pvt2.p and pvt1.t <> '' and pvt1.p <> ''
),
added_new_values as (
select
a.number,
coalesce(n.t, a.t) as t,
coalesce(n.p, a.p) as p,
coalesce(n.n, a.n) as n
from
amended_values a
left join (
select number, t, p, n, row_number() over (order by number) as row_number
from pivoted_t2 t2
where
t2.t <> ''
and t2.p <> ''
and not exists (select * from pivoted_t1 t1 where t1.t = t2.t and t1.p = t2.p)
) n on n.row_number = a.empty_row_number
)
select
concat([elementName], number) as [elementName],
[elementValue]
from
added_new_values
unpivot ([elementValue] for [elementName] in ([t], [p], [n])) upvt
;
Related
I'm trying to get some individual stats from a score keeping system. In essence, teams are scheduled into matches
Match
---------
Matchid (uniqueidentifier)
SessionId (int)
WeekNum (int)
Those matches are broken into sets, where two particular players from a team play each other
MatchSet
-----------
SetId (int)
Matchid (uniqueidentifier)
HomePlayer (int)
AwayPlayer (int)
WinningPlayer (int)
LosingPlayer (int)
WinningPoints (int)
LosingPoints (int)
MatchEndTime (datetime)
In order to allow for player absences, players are allowed to play twice per Match. The points from each set will count for their team totals, but for the individual awards, only the first time that a player plays should be counted.
I had been trying to make use of a CTE to number the rows
;WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY MatchId ORDER BY MatchEndTime) AS rn
FROM
(SELECT
SetId, MS.MatchId, WinningPlayer, LosingPlayer,
HomePlayer, AwayPlayer, WinningPoints, LosingPoints, MatchEndTime
FROM
MatchSet MS
INNER JOIN
[Match] M ON M.MatchId = MS.MatchId AND M.[Session] = #SessionId
)
but I'm struggling as the player could be either the home player or away player in a given set (also, could either be the winner or the loser)
Ideally, this result could then be joined based on either WinningPlayer or LosingPlayer back to the players table, which would let me get a list of individual standings
I think the first step is to write a couple CTEs that get the data into a structure where you can evaluate player points regardless of win/loss. Here's a possible start:
;with PlayersPoints as
(
select m.MatchId
,m.SessionId
,m.WeekNum
,ms.SetId
,ms.WinningPlayer as PlayerId
,ms.WinningPoints as Points
,'W' as Outcome
,ms.MatchEndTime
from MatchSet ms
join Match m on on ms.MatchId = m.MatchId
and m.SessionId = #SessionId
union all
select m.MatchId
,m.SessionId
,m.WeekNum
,ms.SetId
,ms.LosingPlayer as PlayerId
,ms.LosingPoints as Points
,'L' as Outcome
,ms.MatchEndTime
from MatchSet ms
join Match m on on ms.MatchId = m.MatchId
and m.SessionId = #SessionId
)
, PlayerMatch as
(
select SetId
,WeekNum
,MatchId
,PlayerId
,row_number() over (partition by PlayerId, WeekNum order by MatchEndTime) as PlayerMatchSequence
from PlayerPoints
)
....
The first CTE pulls out the points for each player, and the second CTE identifies which match it is. So for calculating individual points, you'd look for PlayerMatchSequence = 1.
Perhaps you could virtualize a normalized view of your data and key off of it instead of the MatchSet table.
;WITH TeamPlayerMatch AS
(
SELECT TeamID,PlayerID=WinnningPlayer,MatchID,Points = MS.WinningPoints, IsWinner=1 FROM MatchSet MS INNER JOIN TeamPlayer T ON T.PlayerID=HomePlayer
UNION ALL
SELECT TeamID,PlayerID=LosingPlayer,MatchID,Points = MS.LosingPoints, IsWinner=0 FROM MatchSet MS INNER JOIN TeamPlayer T ON T.PlayerID=AwayPlayer
)
,cte AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY MatchId ORDER BY MatchEndTime) AS rn
FROM
(SELECT
SetId, MS.MatchId, PlayerID, TeamID, Points, MatchEndTime, IsWinner
FROM
TeamPlayerMatch MS
INNER JOIN
[Match] M ON M.MatchId = MS.MatchId AND M.[Session] = #SessionId
WHERE
IsWinner=1
)
Is it possible to do a 1 on 1 element array concatenation if I have a query like this:
EDIT: Arrays not always have the same number of elements.
could be that array1 has sometimes 4 elements ans array2 8 elements.
drop table if exists a;
drop table if exists b;
create temporary table a as (select 1 as id,array['a','b','c'] as array1);
create temporary table b as (select 1 as id,array['x','y','z'] as array2);
select
a.id,
a.array1,
b.array2,
array_concat--This has to be a 1 to 1 ordered concatenation (see
--example below)
from a
left join b on b.id=a.id
What I would like to obtain here is a paired concatenation of both arrays 1 and 2, like this:
id array11 array2 array_concat
1 ['a','b','c'] ['d','e','f'] ['a-d','b-e','c-f']
2 ['x','y','z'] ['i','j','k'] ['x-i','y-j','z-k']
3 ...
I tried using unnest but i can't make it work:
select
a.id,
a.array1,
b.array2,
array_concat
from table a
left join b on b.id=a.id
left join (select a.array1,b.array2, array_agg(a1||b2)
FROM unnest(a.array1, b.array2)
ab (a1, b2)
) ag on ag.array1=a.array1 and ag.array2=b.array2
;
EDIT:
This works for only one table:
SELECT array_agg(el1||el2)
FROM unnest(ARRAY['a','b','c'], ARRAY['d','e','f']) el (el1, el2);
++Thanks to https://stackoverflow.com/users/1463595/%D0%9D%D0%9B%D0%9E
EDIT:
I came to a very close solution but it mixes up some of the intermediate values once the concatenation between arrays is done, never the less I still need a perfect solution...
The approach I am now using is:
1) Creating one table based on the 2 separate ones
2) aggregating using Lateral:
create temporary table new_table as
SELECT
id,
a.a,
b.b
FROM a a
LEFT JOIN b b on a.id=b.id;
SELECT id,
ab_unified
FROM pair_sources_mediums_campaigns,
LATERAL (SELECT ARRAY_AGG(a||'[-]'||b order by grp1) as ab_unified
FROM (SELECT DISTINCT case when a null
then 'not tracked'
else a
end as a
,case when b is null
then 'none'
else b
end as b
,rn - ROW_NUMBER() OVER(PARTITION BY a,b ORDER BY rn) AS grp1
FROM unnest(a,b) with ordinality as el (a,b,rn)
) AS sub
) AS lat1
order by 1;
Something like this.
with a_elements (id, element, idx) as (
select a.id,
u.element,
u.idx
from a
cross join lateral unnest(a.array1) with ordinality as u(element, idx)
), b_elements (id, element, idx) as (
select b.id,
u.element,
u.idx
from b
cross join lateral unnest(b.array2) with ordinality as u(element, idx)
)
select id,
array_agg(concat_ws('-', a.element, b.element) order by idx) as elements
from a_elements a
full outer join b_elements b using (id, idx)
group by coalesce(a.id, b.id);
The join operator using (..) will automatically take the non-null value from the joined tables. This removes the need to use e.g. coalesce(a.id, b.id).n
It's not pretty and definitely not efficient for large tables, but seems to do all you want.
For arrays that do not have the same amount of elements, the result will only have the element from one of the arrays.
For this dataset:
insert into a
(id, array1)
values
(1, array['a','b','c','d']),
(2, array['d','e','f']);
insert into b
(id, array2)
values
(1, array['x','y','z']),
(2, array['m','n','o','p']);
It returns this result:
id | elements
---+----------------
1 | {a-x,b-y,c-z,d}
2 | {d-m,e-n,f-o,p}
I think you were thinking too far, try this (SQLFiddle):
select
a.id,
a.array1,
b.array2,
array[a.array1[1] || '-' || b.array2[1],
a.array1[2] || '-' || b.array2[2],
a.array1[3] || '-' || b.array2[3]] array_concat
from
a inner join
b on b.id = a.id
;
I have 2 tables:
Deparment table (save data of Deparment):
AccDocSales table (save data of Order made by which Department):
Now, I want to select these Departments, which is not showing in AccDocSales table per MONTH (This mean which Department does't have DeptCode in AccDocSales table per MONTH).
Ex (for this case):
I used this query:
SELECT distinct MONTH(DocDate) as THANG, B20Dept.Code, B20Dept.Name
FROM B20Dept, B30AccDocSales S1
WHERE YEAR(S1.DocDate) = 2014 AND B20Dept.Code NOT IN
(
SELECT S2.DeptCode
FROM B30AccDocSales S2, B20Dept
WHERE YEAR(S2.DocDate) = 2014 AND S2.DeptCode = B20Dept.Code
AND MONTH(S1.DocDate) = MONTH(S2.DocDate)
)
ORDER BY MONTH(DocDate)
It working but my teacher said "NOT IN" in this query is NOT acceptable. He asked me find another way to do this without "IN", "NOT IN".
PS: I find one more problem with this query. That is which month have all Department in "DeptCode" and which month have no row, they all do not show up any result when run that query.
Please help.
ALTERNATIVES OF NOT IN-
Example Code –
IF OBJECT_ID('Tempdb..#SampleTable1') IS NOT NULL
DROP TABLE #SampleTable1;
IF OBJECT_ID('Tempdb..#SampleTable2') IS NOT NULL
DROP TABLE #SampleTable2;
SET NOCOUNT ON;
CREATE TABLE #SampleTable1
( NUM INT NOT NULL );
GO
INSERT INTO #SampleTable1 (NUM)
VALUES (1),(2),(3),(4),(5) ;
GO
CREATE TABLE #SampleTable2
( NUM INT NOT NULL );
GO
INSERT INTO #SampleTable2 (NUM)
VALUES (4),(5),(6),(7),(8) ;
GO
SELECT NUM AS [#SampleTable1] FROM #SampleTable1;
GO
SELECT NUM AS [#SampleTable2] FROM #SampleTable2;
GO
1. NOT EXISTS
--METHOD 1 (Using NOT EXISTS)
SELECT S1.NUM AS [NOT EXISTS]
FROM #SampleTable1 S1
WHERE NOT EXISTS (SELECT NUM FROM #SampleTable2 S2 WHERE S2.NUM=S1.NUM);
GO
2. EXCEPT
--METHOD 2 (Using EXCEPT)
SELECT NUM AS [EXCEPT]
FROM #SampleTable1
EXCEPT
SELECT NUM FROM #SampleTable2;
GO
3. ANY
--METHOD 3 (Using = ANY)
SELECT S1.NUM AS [= ANY]
FROM #SampleTable1 S1
WHERE NOT (S1.NUM = ANY
(
SELECT S2.NUM
FROM #SampleTable2 S2
) );
4. OUTER APPLY
--METHOD 4 (Using OUTER APPLY and avoiding JOIN CONDITION)
SELECT S1.NUM AS [OUTER APPLY]
FROM #SampleTable1 S1
OUTER APPLY (
SELECT NUM FROM #SampleTable2 S2 WHERE S2.NUM=S1.NUM
) T
WHERE T.NUM IS NULL;
GO
5. LEFT JOIN / IS NULL
--METHOD 5 (Using LEFT JOIN/IS NULL)
SELECT S1.NUM AS [LEFT JOIN]
FROM #SampleTable1 S1
LEFT JOIN #SampleTable2 S2 ON S1.NUM=S2.NUM
WHERE S2.NUM IS NULL;
GO
6. CORRELATED SUBQUERY
--METHOD 6 (Using CORRELATED SUBQUERY)
SELECT NUM AS [CORRELATED SUBQUERY]
FROM #SampleTable1 AS S1
WHERE (SELECT COUNT(*) FROM #SampleTable2 S2
WHERE S2.NUM = S1.NUM) = 0;
GO
7. ALL
--METHOD 7 (Using <> ALL)
SELECT NUM AS [<> ALL]
FROM #SampleTable1
WHERE NUM <>ALL
(
SELECT NUM
FROM #SampleTable2
);
GO
8. CROSS APPLY
--METHOD 8 (CROSS APPLY)
SELECT T1.NUM AS [CROSS APPLY]
FROM #SampleTable1 AS S1
CROSS APPLY
(
SELECT S1.NUM
EXCEPT
SELECT NUM
FROM #SampleTable2
) T1;
GO
9. FULL OUTER JOIN
--METHOD 9 (Using FULL OUTER JOIN/IS NULL)
SELECT S1.NUM AS [FULL OUTER JOIN]
FROM #SampleTable1 S1
FULL OUTER JOIN #SampleTable2 S2 ON S1.NUM=S2.NUM
WHERE S2.NUM IS NULL;
GO
NOT IN
--METHOD 10 (Using NOT IN)
SELECT NUM AS [NOT IN]
FROM #SampleTable1
WHERE NUM NOT IN
(SELECT NUM FROM #SampleTable2);
GO
For Small Number of Data, NOT EXISTS is best choice irrespective of With/Without Index,Null or Non-Null Values. In this case, LEFT JOIN/IS NULL is least efficient while comparing to the performance of other Alternatives because of its behavior to not skipping the
already matched values with the right table and returning all the results and filtering them out at final steps using IS NULL filter.
But For Large set of Data, Sub query method is not recommended.
Try this solution:
select s.*, d.*
from B20Dept d
cross apply (select distinct YEAR(s.DocDate) Y, MONTH(s.DocDate) THANG from B30AccDocSales s) s
left join (
select YEAR(s.DocDate) Y, MONTH(s.DocDate) THANG, s.DeptCode
from B30AccDocSales s
group by YEAR(s.DocDate), MONTH(s.DocDate), s.DeptCode) m on m.Y = s.Y and m.THANG = s.THANG and m.DeptCode = d.Code
where m.DeptCode is null
order by s.Y, s.THANG
EDIT:
In the query below, You can find a solution for the problem, that is in You PS:
declare #Year int = 2014
select s.*, d.*
from B20Dept d
cross apply (values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) s (THANG)
left join (
select MONTH(s.DocDate) THANG, s.DeptCode
from B30AccDocSales s
where YEAR(s.DocDate) = #Year
group by MONTH(s.DocDate), s.DeptCode) m on m.THANG = s.THANG and m.DeptCode = d.Code
where m.DeptCode is null
union
select MONTH(s.DocDate) THANG, '', ''
from B30AccDocSales s
where YEAR(s.DocDate) = #Year
group by MONTH(s.DocDate)
having COUNT(distinct s.DeptCode) = (select count(1) from B20Dept)
order by s.THANG
One simple solution, most of the time faster than not in is :
Select * from A where 0 = ( select count(*) from B where A.id = B.id)
I have one table vwuser. I want join this table with the table valued function fnuserrank(userID). So I need to cross apply with table valued function:
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
For each userID it generates multiple records. I only want the last record for each empid that does not have a Rank of Term(inated). How can I do this?
Data:
HistoryID empid Rank MonitorDate
1 A1 E1 2012-8-9
2 A1 E2 2012-9-12
3 A1 Term 2012-10-13
4 A2 E3 2011-10-09
5 A2 TERM 2012-11-9
From this 2nd record and 4th record must be selected.
In SQL Server 2005+ you can use this Common Table Expression (CTE) to determine the latest record by MonitorDate that doesn't have a Rank of 'Term':
WITH EmployeeData AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY empId, ORDER BY MonitorDate DESC) AS RowNumber
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
WHERE Rank != 'Term'
)
SELECT *
FROM EmployeeData AS ed
WHERE ed.RowNumber = 1;
Note: The statement before this CTE will need to end in a semi-colon. Because of this, I have seen many people write them like ;WITH EmployeeData AS...
You'll have to play with this. Having trouble mocking your schema on sqlfiddle.
Select bar.*
from
(
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
where rank != 'TERM'
) foo
left join
(
SELECT *
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empId = bar.empId
and foo.MonitorDate > bar.MonitorDate
where bar.empid is null
I always need to test out left outers on dates being higher. The way it works is you do a left outer. Every row EXCEPT one per user has row(s) with a higher monitor date. That one row is the one you want. I usually use an example from my code, but i'm on the wrong laptop. to get it working you can select foo., bar. and look at the results and spot the row you want and make the condition correct.
You could also do this, which is easier to remember
SELECT *
FROM vwuser AS a
CROSS APPLY fnuserrank(a.userid)
) foo
join
(
select empid, max(monitordate) maxdate
FROM vwuser AS b
CROSS APPLY fnuserrank(b.userid)
where rank != 'TERM'
) bar
on foo.empid = bar.empid
and foo.monitordate = bar.maxdate
I usually prefer to use set based logic over aggregate functions, but whatever works. You can tweak it also by caching the results of your TVF join into a table variable.
EDIT:
http://www.sqlfiddle.com/#!3/613e4/17 - I mocked up your TVF here. Apparently sqlfiddle didn't like "go".
select foo.*, bar.*
from
(
SELECT f.*
FROM vwuser AS a
join fnuserrank f
on a.empid = f.empid
where rank != 'TERM'
) foo
left join
(
SELECT f1.empid [barempid], f1.monitordate [barmonitordate]
FROM vwuser AS b
join fnuserrank f1
on b.empid = f1.empid
where rank != 'TERM'
) bar
on foo.empId = bar.barempid
and foo.MonitorDate > bar.barmonitordate
where bar.barempid is null
I have a scenario where i have to check a variable for it's default value, and if it has i have to check EXISTS part conditionally with Table2 and if it does not have the default value, i have to check EXISTS part conditionally with Table3.
Below is a sample code:-
SELECT * FROM tbl1 WHERE EXISTS (SELECT CASE WHEN #boolVar = 0 THEN (SELECT 'X' FROM tbl2 WHERE tbl1.col1 = tbl2.col1) ELSE (SELECT 'X' FROM tbl3 where tbl1.col1 = tbl3.col1) END)
Demo query with constants for testing purpose: -
SELECT 1 WHERE EXISTS (SELECT CASE WHEN 1 = 0 THEN (SELECT 'X' WHERE 1=0)
ELSE (SELECT 'X' WHERE 1 = 2) END)
Note: - The above query always returning 1, even not a single condition is satisfying.
I know we can use OR operator for the same and any how we can achieve it, but i really want to know that in case both the tables have no rows satisfying their particular where clause, even it's returning all the rows from Table1.
I tried to explain the same with the demo query with constant values.
Please help.
When your query doesn't find any matching records, it will basically do:
SELECT 1 WHERE EXISTS (SELECT NULL)
As a row containing a null value is still a row, the EXISTS command returns true.
You can add a condition to filter out the null row:
SELECT * FROM tbl1 WHERE EXISTS (
SELECT 1 FROM (
SELECT
CASE WHEN #boolVar = 0 THEN (SELECT 'X' FROM tbl2 WHERE tbl1.col1 = tbl2.col1)
ELSE (SELECT 'X' FROM tbl3 where tbl1.col1 = tbl3.col1)
END AS Y
) Z
WHERE Y IS NOT NULL
)
Here's an alternative, just in case:
SELECT *
FROM Table1
WHERE EXISTS (
SELECT 1
FROM Table2
WHERE #var = #defValue
AND ... /* other conditions as necessary */
UNION ALL
SELECT 1
FROM Table3
WHERE #var <> #defValue
AND ... /* other conditions as necessary */
);