I have a database with a list of game related trades in it, and I'm trying to identify the trades that match. Let me explain better.
I have the following records:
UserID GameID PlatformID RecivingGameID RecivingGamePlatformID PostID
1111 18 167 41 43 2312451124
2222 41 43 18 167 1276949826
3333 41 43 18 21 6798639876
4444 41 43 90 167 4587938698
In this table there is only 1 match, between post 2312451124 and 1276949826. This is because both users have the game that the other user wants for a specific platform.
So if I am the user 1111, the only match I have to see is with the user 2222 and vice versa. Users 3333 and 4444 have no matches in the database.
How can I identify the trades that match?
Here's what I tried:
SELECT * FROM Posts WHERE UserID != 1111 AND (RecivingGameID IN (
SELECT DISTINCT GameID FROM Posts) AND GameID IN (
SELECT DISTINCT RecivingGameID FROM Posts)) AND (RecivingGamePlatformID IN (
SELECT DISTINCT PlatformID FROM Posts) AND PlatformID IN (
SELECT DISTINCT RecivingGamePlatformID FROM Posts))
SELECT * FROM Posts WHERE UserID != 1111 AND RecivingGameID IN (
SELECT DISTINCT GameID FROM Posts) AND GameID IN (
SELECT DISTINCT RecivingGameID FROM Posts) AND RecivingGamePlatformID IN (
SELECT DISTINCT PlatformID FROM Posts) AND PlatformID IN (
SELECT DISTINCT RecivingGamePlatformID FROM Posts)
Use a self join:
SELECT p1.*
FROM Posts p1 INNER JOIN Posts p2
ON p2.UserID <> p1.UserID
AND (p1.GameID, p1.PlatformID) = (p2.RecivingGameID, p2.RecivingGamePlatformID)
AND (p2.GameID, p2.PlatformID) = (p1.RecivingGameID, p1.RecivingGamePlatformID)
WHERE p2.UserID = 1111;
See the demo.
WITH
Posts(UserID, GameID, PlatformID, RecivingGameID, RecivingGamePlatformID, PostID) AS (
VALUES
(1111, 18, 167, 41, 43, 2312451124),
(2222, 41, 43, 18, 167, 1276949826),
(3333, 41, 43, 18, 21, 6798639876),
(4444, 41, 43, 90, 167, 4587938698)
),
matches AS (
SELECT DISTINCT
src.UserID AS UserID, src.PostID AS PostID,
dst.UserID AS RecivingUserID, dst.PostID AS RecivingPostID
FROM Posts AS src, Posts AS dst
WHERE src.GameID = dst.RecivingGameID
AND dst.GameID = src.RecivingGameID
AND src.PlatformID = dst.RecivingGamePlatformID
AND dst.PlatformID = src.RecivingGamePlatformID
AND src.UserID < dst.UserID
)
SELECT * FROM matches;
Related
id
category_id
product_id
status
13
93
2137
1
14
94
2137
1
15
93
2138
2
16
94
2138
2
17
87
2128
1
18
94
2128
1
19
87
2139
2
20
94
2139
2
21
88
2132
1
22
93
2132
1
23
88
2140
2
24
93
2140
2
25
87
2137
1
26
87
2141
2
27
93
2136
1
28
93
2137
1
29
88
2134
1
30
88
2143
2
I have this kind of data presented to me. For my query I'm given a list of category ids.
Let's say I'm given three lists with
1. {93, 94}
2. {88, 87, 86}
3. {93}
Now I would need a query, which would give me product ids, which appear at least once in ALL of those lists and for which the status is 1. So for the example query the result should be:
product_id
2137
The first step in any solution is to normalize the selection criteria data into a table of the form { category_group_id, category_id } with only one category_id for row. There are several ways to do this but I've used the relatively new STRING_SPLIT function here (same as Luis LL). This normalized criteria may be loaded into a temp table or included as a Common Table Expression (CTE) as is done below.
Once the criteria is normalized, the real problem can be solved by (1) filtering the input data by status, (2) joining it with the normalized selection criteria from above, (3) grouping by product ID, and then (4) counting the number of distinct category group IDs matched. If that count matches the total number of category group IDs (three for the sample data), we have a match.
;WITH NormalizedCategoryIds AS (
SELECT C.category_group_id, CAST(S.Value AS INT) AS category_id
FROM CategoryIds C
CROSS APPLY STRING_SPLIT(
REPLACE(REPLACE(category_id_list, '{', ''), '}', ''),
',') S
)
SELECT D.product_id
FROM SampleData D
JOIN NormalizedCategoryIds C on C.category_id = D.category_id
WHERE D.status = 1
GROUP BY D.product_id
HAVING COUNT(DISTINCT C.category_group_id) = (SELECT COUNT(*) FROM CategoryIds)
If we started with criteria that was already normalized, the HAVING clause could be changed to:
HAVING COUNT(DISTINCT C.category_group_id)
= (SELECT COUNT(DISTINCT C2.category_group_id) FROM NormalizedCategoryIds C2)
That value could also be calculated ahead of the query.
Sample results:
product_id
2132
2137
Even though not in the original posted results, 2132 is also included here, because it matches all three category groups. The 93 row matches category groups 1 and 3 and the 88 record matches category group 2.
See this db<>fiddle for a working demo including some extra test data.
This should work for SQL Server 2016 and above.
CREATE TABLE table1
(
id INT,
category_id INT,
product_id INT,
status INT
)
INSERT INTO table1
(id, category_id, product_id, status)
VALUES
( 13, 93, 2137, 1)
,( 14, 94, 2137, 1)
,( 15, 93, 2138, 2)
,( 16, 94, 2138, 2)
,( 17, 87, 2128, 1)
,( 18, 94, 2128, 1)
,( 19, 87, 2139, 2)
,( 20, 94, 2139, 2)
,( 21, 88, 2132, 1)
,( 22, 93, 2132, 1)
,( 23, 88, 2140, 2)
,( 24, 93, 2140, 2)
,( 25, 87, 2137, 1)
,( 26, 87, 2141, 2)
,( 27, 93, 2136, 1)
,( 28, 93, 2137, 1)
,( 29, 88, 2134, 1)
,( 30, 88, 2143, 2)
CREATE TABLE Input
(IdLst varchar(100))
INSERT INTO Input (IdLst)
VALUES
('{93, 94}')
,('{88, 87, 86}')
,('{93}')
;WITH Categories AS (
SELECT CONVERT(INT, Value ) category_id
FROM Input
CROSS APPLY STRING_SPLIT(REPLACE(REPLACE( IdLst, '{', ''), '}', ''), ',')
)
SELECT product_id
FROM Categories
INNER JOIN table1 ON table1.category_id = Categories.category_id
GROUP BY product_id
HAVING COUNT(1) = (SELECT COUNT(1) cntCategories FROM Categories )
I have a table of article's logs. I need to get all the articles which have only one log, or in case there are amount of logs more than 1: if an article has any log in status = 103, it's need to fetch only rows after this log, in other case all the logs. So from the following dataset I want to get only rows with Id 1383 and 284653.
Id
Article
Version
StatusId
AddedDate
1383
1481703
0
42
2011-11-25 09:23:42.000
284645
435545
1
41
2021-11-02 18:29:42.000
284650
435545
2
41
2021-11-02 18:34:58.000
284651
435545
2
103
2021-11-02 18:34:58.000
284653
435545
3
41
2021-11-02 18:38:33.000
Any ideas how to handle it properly ? Thanks in advance
You can use window functions here. A combination of a running COUNT and a windowed COUNT will do the trick
The benefit of using window functions rather than self-joins is that you only scan the base table once.
SELECT
Id,
Article,
Version,
StatusId,
AddedDate
FROM (
SELECT *,
HasPrev103 = COUNT(CASE WHEN StatusId = 103 THEN 1 END) OVER
(PARTITION BY Article ORDER BY AddedDate ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),
Has103 = COUNT(CASE WHEN StatusId = 103 THEN 1 END) OVER (PARTITION BY Article),
Count = COUNT(*) OVER (PARTITION BY Article)
FROM YourTable t
) t
WHERE (Has103 > 0 AND HasPrev103 > 0) OR Count = 1;
db<>fiddle
CREATE TABLE #Article (
Id int NOT NULL PRIMARY KEY,
Article int NOT NULL,
Version int NOT NULL,
StatusId int NOT NULL,
DateAdded datetime NOT NULL
)
INSERT INTO #Article (Id, Article, Version, StatusId, DateAdded)
VALUES
(1383, 1481703, 0, 42, '2011-11-25 09:23:42.000'),
(284645, 435545, 1, 41 , '2021-11-02 18:29:42.000'),
(284650, 435545, 2, 41 , '2021-11-02 18:34:58.000'),
(284651, 435545, 2, 103, '2021-11-02 18:34:58.000'),
(284653, 435545, 3, 41 , '2021-11-02 18:38:33.000')
SELECT *
FROM #Article a
LEFT JOIN (
-- Get articles that appear only once.
SELECT Article
FROM #Article
GROUP BY Article
HAVING COUNT(*) = 1
) AS o
ON a.Article = o.Article
LEFT JOIN (
-- Get the 103s and their corresponding date.
SELECT Article, DateAdded
FROM #Article
WHERE StatusId = 103
) AS s
ON a.Article = s.Article AND s.DateAdded < a.DateAdded
WHERE o.Article IS NOT NULL OR (s.Article IS NOT NULL AND a.DateAdded > s.DateAdded)
DROP TABLE #Article
Consider a table having data as shown. I want to find the top 3 marks and combine the rest of the values of column marks as a single value 0.
name age marks height
-----------------------------
anil 25 67 5
ashish 23 75 6
ritu 22 0 4
manish 25 0 6
kirti 23 97 5
Output
name age marks height
-----------------------------
kirti 23 97 5
ashish 23 75 6
anil 25 67 5
OTHERS 0 0 0
With TOP 3 and UNION ALL for the last row:
select t.* from (
select top 3 * from tablename
order by marks desc
) t
union all
select 'OTHERS', 0, 0, 0
See the demo.
Results:
> name | age | marks | height
> :----- | --: | ----: | -----:
> kirti | 23 | 97 | 5
> ashish | 23 | 75 | 6
> anil | 25 | 67 | 5
> OTHERS | 0 | 0 | 0
I would use a CTE (Common Table Expression) and the ROW_NUMBER() function:
;WITH cte AS (SELECT
[Name],
Age,
Marks,
Height,
ROW_NUMBER() OVER (ORDER BY Marks DESC) AS [Rank]
FROM
Test
)
SELECT
[Name],
Age,
Marks,
Height
FROM
cte
WHERE
[Rank] <= 3
UNION ALL SELECT 'OTHERS', 0, 0, 0
You can use select top 3 or row_number()
You can use row_number() as follows
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5),
('other',0,0,0);
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height from cte where row#<4 or name='other'
order by row#
Another way, using union without inserting ('other',0,0,0) to the table, you can the same result
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5)
--,('other',0,0,0)
;
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height,row# from cte where row#<4
union select 'others',0,0,0,4
order by row#
I have three tables. Table Cust has a custID field, plus various other values (name, address etc)
Table List has a single column ID. Each ID is a custID in the Cust table
Edit: the purpose of this is to filter the records, restricting thge results to ones where the CustID appears in the list table.
All three tables are indexed.
Table Trans has a TransactionID field, a Cust field that holds a customer ID, And other transaction fields
Edit: I should have mentioned that in some cases there will be no transaction record. In this case I want one row of Customer info with the transaction fields null or blank.
I want a query to return cust and transaction ID for each ID in the list table. If there is more than one matching row in the transaction table, I want each included along 3with the matching cust info. So if the tables look like This:
Cust
ID Name
01 John
02 Mary
03 Mike
04 Jane
05 Sue
06 Frank
List
ID
01
03
05
06
Transact
TransID CustId Msg
21 01 There
22 01 is
23 02 a
24 03 tide
25 04 in
26 04 the
27 05 affairs
28 05 of
29 05 men
I want the result set to be:
CustID Name TransID Msg
01 John 21 There
01 John 22 is
03 Mike 24 tide
05 Sue 27 affairs
05 Sue 28 of
05 Sue 29 men
06 Frank -- --
(Where -- represents NULL or BLANK)
Obviously the actual tables are much larger (millions of rows), but that shows the pattern, one row for every item in table Transactions that matches any of the items in the List table, with matching fields from the Cust table. if there is no matching Transaction, one row of customer info from each ID in the List table. CustID is unique in the Cust and List tables, but not in the transaction table.
This needs to work on any version of SQL server from 2005 onward, if that matters.
Any suggestions?
Unless I'm missing something, this is all you need to do:
Select T.CustID, C.Name, T.TransID, T.Msg
From Transact T
Join Cust C On C.Id = T.CustId
Join List L On L.Id = C.Id
Order By T.CustID, T.TransID
;with cust (id, name) as
(
select 1, 'John' union all
select 2, 'Mary' union all
select 3, 'Mike' union all
select 4, 'Jane' union all
select 5, 'Sue'
), list (id) as
(
select 1 union all
select 3 union all
select 5
), transact (TransId, CustId, Msg) as
(
select 21, 1, 'There '
union all select 22, 1, 'is'
union all select 23, 2, 'a'
union all select 24, 3, 'tide'
union all select 25, 4, 'in'
union all select 26, 4, 'the'
union all select 27, 5, 'affairs'
union all select 28, 5, 'of'
union all select 29, 5, 'men'
)
select
CustId = c.id,
Name = c.Name,
TransId = t.TransId,
Msg = t.Msg
from cust c
inner join list l
on c.id = l.id
inner join transact t
on l.id = t.custid
yields:
CustId Name TransId Msg
----------- ---- ----------- -------
1 John 21 There
1 John 22 is
3 Mike 24 tide
5 Sue 27 affairs
5 Sue 28 of
5 Sue 29 men
I have an existing database where some logic is made by the front end application.
Now I have to make reports from that database and I'm facing to a proble of missing records which are covered on a record basis in the frontend but have issues in the report
Given the following tables:
create table #T (id int, id1 int, label varchar(50))
create table #T1 (id int, T_id1 int, A int, B int, C int)
With the values:
insert into #T values (10, 1, 'label1'), (10, 2, 'label2'), (10, 3, 'label3'), (10, 15, 'label15'), (10, 16, 'label16'), (20, 100, 'label100'), (20, 101, 'label101')
insert into #T1 values (10, 1, 100, 200, 300), (10, 15, 150, 250, 350), (20, 100, 151, 251, 351), (20, 101, 151, 251, 351)
if I make a report we can see some missing records:
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
result:
id id1 A B C
10 1 100 200 300
10 2 NULL NULL NULL
10 3 NULL NULL NULL
10 15 150 250 350
10 16 NULL NULL NULL
20 100 151 251 351
20 101 151 251 351
Expected result would be:
id id1 A B B
10 1 100 200 300
10 2 100 200 300
10 3 100 200 300
10 15 150 250 350
10 16 150 250 350
20 100 151 251 351
20 101 151 251 351
As you can see here the missing data is filled out of the the first (in id, id1 order) previous existing record for a given id. For a given id there can be any number of "missing" records and for the given id there can be any number of existing records after a not existing ones.
I can do this with a cursor but I'm looking for a solution without cursor
You can use subquery (to find groups with same values) + window function
WITH Grouped AS (
SELECT #T.id, #T.id1, #T1.A, #T1.B, #T1.C,
GroupN = SUM(CASE WHEN #T1.A IS NULL THEN 0 ELSE 1 END) OVER(/* PARTITION BY id ? */ ORDER BY id1 ROWS UNBOUNDED PRECEDING)
FROM #T
LEFT JOIN #T1 ON #T.id1 = #T1.T_id1
)
SELECT Grouped.id, Grouped.id1,
A = MAX(A) OVER(PARTITION BY GroupN),
B = MAX(B) OVER(PARTITION BY GroupN),
C = MAX(C) OVER(PARTITION BY GroupN)
FROM Grouped
You can use below sql for thedesired output:
with cte (id, id1, A, B, C)
as
(
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
)
select cte.id, cte.id1,
coalesce(cte.A,TT.A) A,
coalesce(cte.B,TT.B) B,
coalesce(cte.C,TT.C) C
from cte
left join
(
select p.id1,max(q.id1) id1_max
from cte p
inner join cte q on p.id1 > q.id1 and p.a is null and q.a is not null
group by p.id1
) T on cte.id1 = T.id1
left join cte TT on T.id1_max = TT.id1