Create a stored procedure to aggregate rows - sql-server

Having a transaction table with the following rows:
Id UserId PlatformId TransactionTypeId
-------------------------------------------------
0 1 3 1
1 1 1 2
2 2 3 2
3 3 2 1
4 2 3 1
How do I write a stored procedure that can aggregate the rows into a new table with the following format?
Id UserId Platforms TransactionTypeId
-------------------------------------------------
0 1 {"p3":1,"p1":1} {"t1":1,"t2":1}
1 2 {"p3":2} {"t2":1,"t1":1}
3 3 {"p2":1} {"t1":1}
So the rows are gouped by User, count each platform/transactionType and store as key/value json string.
Ref: My previous related question

You could use GROUP BY and FOR JSON:
SELECT MIN(ID) AS ID, UserId, MIN(sub.x) AS Platforms, MIN(sub2.x) AS Transactions
FROM tab t
OUTER APPLY (SELECT CONCAT('p', platformId) AS platform, cnt
FROM (SELECT PlatformId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY PlatformId) s
FOR JSON AUTO) sub(x)
OUTER APPLY (SELECT CONCAT('t', TransactiontypeId) AS Transactions, cnt
FROM (SELECT TransactiontypeId, COUNT(*) AS cnt
FROM tab t2 WHERE t2.UserId = t.UserId
GROUP BY TransactiontypeId) s
FOR JSON AUTO) sub2(x)
GROUP BY UserId;
DBFiddle Demo
Result is a bit different(array of key-value) but please treat it as starting point.

Your sample JSON is not really a json, but since you want it that way:
SELECT u.UserId, plt.pValue, ttyp.ttValue
FROM Users AS [u]
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+pn.pName+'":'+LTRIM(STR(pn.pCount))
FROM (SELECT p.Name AS pName, COUNT(*) AS pCount
FROM transactions t
left JOIN Platforms p ON p.PlatformID = t.PlatformId
WHERE t.UserId = u.UserId
GROUP BY p.PlatformId, p.Name
) pn
FOR XML PATH('')),1,1,'')+'}'
) plt(pValue)
CROSS APPLY (
SELECT '{'+STUFF( (SELECT ',"'+tty.ttName+'":'+LTRIM(STR(tty.ttCount))
FROM (SELECT tt.Name AS ttName, COUNT(*) AS ttCount
FROM transactions t
left JOIN dbo.TransactionType tt ON tt.TransactionTypeId = t.TransactionTypeID
WHERE t.UserId = u.UserId
GROUP BY tt.TransactionTypeId, tt.Name
) tty
FOR XML PATH('')),1,1,'')+'}'
) ttyp(ttValue)
WHERE EXISTS (SELECT * FROM transactions t WHERE u.UserId = t.UserId)
ORDER BY UserId;
DBFiddle Sample

Related

how to select only rows with maximum VID in a table with multiple VIDs and other fields?

I have a view in SQL server that gives me records of some games.it returns multiple records that belong to a game and they have same Gid but different version have different Vids like below
vid gid otherData(Platform)
2 1 PC
2 1 PC ...
3 1 X1
3 1 X2....
2 5 PC
2 5 PC ...
3 5 X1
3 5 X2....
how to use group by and select or something else to select these record only?
3 1 X1
3 1 X2....
3 5 X1
3 5 X2....
You want dense_rank() :
select top (1) with ties t.*
from table t
order by dense_rank() over (partition by gid order by vid desc);
EDIT :
select t.*
from (select t.*,
dense_rank() over (partition by gid order by vid desc) as seq
from table t
) t
where seq = 1;
Solutions using GROUP BY:
With CTE:
;WITH HighestVIDByGID AS
(
SELECT
V.gid,
MaxVid = MAX(V.vid)
FROM
vYourView AS V
GROUP BY
V.gid
)
SELECT
V.*
FROM
vYourView AS V
INNER JOIN HighestVIDByGID AS M ON
V.gid = M.gid AND
V.vid = M.MaxVid
Using INNER JOIN with subquery:
SELECT
T.*
FROM
vYourView AS T
INNER JOIN (
SELECT
V.gid,
MaxVid = MAX(V.vid)
FROM
vYourView AS V
GROUP BY
V.gid
) AS M ON
T.gid = M.gid AND
T.vid = M.MaxVid
Using EXISTS:
SELECT
T.*
FROM
vYourView AS T
WHERE
EXISTS (
SELECT
'max vid version'
FROM
vYourView AS V
GROUP BY
V.gid
HAVING
T.gid = V.gid AND
T.vid = MAX(V.vid))

How to use CTE to get a query repeated for multiple inputs?

I have the following query:
SELECT **top 1** account, date, result
FROM table_1 as t1
JOIN table_2 at t2 ON t1.accountId = t2.frn_accountId
WHERE accountID = 1
ORDER BY date
This query returns the result that I want however I want that result for multiple accountID. They query should return the top 1 value for each accountID.
The query that produce the list of the accountID-s is:
SELECT accountID from lskin WHERE refname LIKE '%BHA%' and isactive = 1
How can I write this query so it can produce the desired result? I have been playing around with CTE but haven't been able to make it correct. It doesn't have to be with CTE, I just thought it can be easier using CTE...
Here is CTE solution.
SELECT *
FROM (SELECT account
, date
, result
, ROW_NUMBER() OVER (PARTITION BY t1.accountId ORDER BY date DESC) AS Rownum
FROM table_1 AS t1
INNER JOIN table_2 AS t2
ON t1.accountId = t2.frn_accountId
INNER JOIN lskin AS l
ON l.accountID = t1.accountID
WHERE l.refname LIKE '%BHA%'
) a
WHERE a.Rownum = 1;
Use max on your date and group by the account, or what ever columns are appropriate.
SELECT
account,
DT = max(date),
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
WHERE l.refname like '%BHA%'
GROUP BY
account
,result
If the grouping isn't correct, just join to a sub-query to limit it with max date. Just change the table names as necessary.
SELECT
account,
date,
result
FROM table_1 as t1
JOIN table_2 as t2 ON t1.accountId = t2.frn_accountId
JOIN lskin as l on l.accountID = t1.accountID
INNER JOIN (select max(date) dt, accountID from table_1 group by accountID) tt on tt.dt = t1.accountId and tt.accountId = t1.accountId
WHERE l.refname like '%BHA%'
Ignore the CTE at the top. That's just test data.
/* CTE Test Data */
; WITH table_1 AS (
SELECT 1 AS accountID, 'acc1' AS account UNION ALL
SELECT 2 AS accountID, 'acc2' AS account UNION ALL
SELECT 3 AS accountID, 'acc3' AS account
)
, table_2 AS (
SELECT 1 AS frn_accountID, 'new1' AS result, GETDATE() AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'mid1' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 1 AS frn_accountID, 'old1' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'new2' AS result, GETDATE() AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'mid2' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 2 AS frn_accountID, 'old2' AS result, GETDATE()-2 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'new3' AS result, GETDATE() AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'mid3' AS result, GETDATE()-1 AS [date] UNION ALL
SELECT 3 AS frn_accountID, 'old3' AS result, GETDATE()-2 AS [date]
)
, lskin AS (
SELECT 1 AS accountID, 'purple' AS refName, 1 AS isActive UNION ALL
SELECT 2 AS accountID, 'blue' AS refName, 1 AS isActive UNION ALL
SELECT 3 AS accountID, 'orange' AS refName, 0 AS isActive UNION ALL
SELECT 4 AS accountID, 'blue' AS refName, 1 AS isActive
)
,
/* Just use the below and remove comment markers around WITH to build Orders CTE. */
/* ; WITH */
theCTE AS (
SELECT s1.accountID, s1.account, s1.result, s1.[date]
FROM (
SELECT t1.accountid, t1.account, t2.result, t2.[date], ROW_NUMBER() OVER (PARTITION BY t1.account ORDER BY t2.[date]) AS rn
FROM table_1 t1
INNER JOIN table_2 t2 ON t1.accountID = t2.frn_accountID
) s1
WHERE s1.rn = 1
)
SELECT lskin.accountID
FROM lskin
INNER JOIN theCTE ON theCTE.accountid = lskin.accountID
WHERE lskin.refName LIKE '%blue%'
AND lskin.isActive = 1
;
EDITED:
I'm still making a lot of assumptions about your data structure. And again, make sure you're querying what you need. CTEs are awesome, but you don't want to accidentally filter out expected results.

Select elements from two tables with JOIN and UNION excluding IDs in a column

I have 3 SQL tables like these:
tbl_items
id ... name ... value ... active
1 color red 1
2 style modern 1
3 age old 1
4 size small 1
tbl_adv_items
id ... name ... value ... active
1 texture suave 0
2 material plastic 1
tbl_items_classes
id ... item_id
1 1
2 3
I want to select all IDs from tbl_items and tbl_adv_items with a condition: exclude items from tbl_items if their ID exists in the item_id column from tbl_items_classes
Right now I have this query:
SELECT tbl_items.id FROM tbl_items
JOIN tbl_items_classes ON tbl_items_classes.item_id <> tbl_items.id
WHERE tbl_items.active = 1
UNION ALL SELECT id FROM tbl_adv_items WHERE active = 1
which gives me all the items, including those with their ID in item_id. ID 1 and 3 from tbl_items should not be returned. I guess I need a JOIN here but I can't make it work
Use EXCEPT:
SELECT id FROM tbl_items WHERE tbl_items.active = 1
UNION ALL
SELECT id FROM tbl_adv_items WHERE active = 1
EXCEPT
SELECT item_id FROM tbl_items_classes
This will have the effect of excluding all item_id values contained in table tbl_items_classes.
Demo here
EDIT:
If you also want to know the origin of the id returned by the query (something not clearly stated in the OP), then you can use the following query:
(SELECT id, 0 AS origin_table FROM tbl_items WHERE tbl_items.active = 1
UNION ALL
SELECT id, 1 AS origin_table FROM tbl_adv_items WHERE active = 1)
EXCEPT
(SELECT item_id, 0 AS origin_table FROM tbl_items_classes
UNION ALL
SELECT item_id, 1 AS origin_table FROM tbl_items_classes)
For performance reason use classic LEFT JOIN:
DEMO
WITH cte AS (
SELECT id , 'tbl_items' AS origin
FROM tbl_items
WHERE active = 1
UNION ALL
SELECT id, 'tbl_adv_items' AS origin
FROM tbl_adv_items
WHERE active = 1
)
SELECT c.id, c.origin
FROM cte c
LEFT JOIN tbl_items_classes cl
ON c.id = cl.item_id
WHERE cl.item_id IS NULL;
Left second case.
Source: http://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins
You can simply make joins between tables and put a condition to filter records. Try this:
select * from tbl_items tblA
inner join tbl_adv_items tblB on tblA.id = tblB.Id
left outer join tbl_items_classes tblC on tblA.id = tblC.item_Id
where tblC.Id is null -- this check will exclude desired items

MSSql only group those with count greater than 3 and return the rest records

I want to group the key with count greater than 3, and the query will return the rest of the records also. I don't want to use Union All, is there any other way to do it?
ID
1
1
1
2
3
3
4
4
4
4
Return
1
1
1
2
3
3
4
You can use ranking- and aggregate functions:
WITH CTE AS
(
SELECT ID,
CNT = COUNT(*) OVER (PARTITION BY ID),
RN = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY ID)
FROM dbo.TableName
)
SELECT ID
FROM CTE
WHERE CNT <= 3 OR RN = 1
Demo
I'd do it like this
SELECT
GroupedData.ID
FROM
(SELECT ID, CNT = COUNT(*)
FROM dbo.TableName
GROUP BY ID) GroupedData AS g
LEFT JOIN dbo.TableName AS t
ON t.id = g.id and g.CNT<=3
This also allows you to add further columns which report details for the group or individual record as appropriate
SELECT
g.ID,
ISNULL(t.RecordName,'Grouped Records') as RecordName,
ISNULL(t.NumericField,g.NumericField) as NumericField
FROM
(
SELECT ID, CNT = COUNT(*), SUM(NumericField) as NumericField
FROM dbo.TableName
GROUP BY ID
) GroupedData AS g
LEFT JOIN dbo.TableName AS t
ON t.id = g.id and g.CNT<=3

Concatenate column values against another column group

May be this is odd but i need to concatenate values of ActionId to corresponding group of roleId and Order by ActionID is must., some thing like
ActionID RoleId
"1357" 1
"2468" 2
Here is what i have currently, I am looking for GROUP_CONCAT equivalent in MS SQL.
select av.ActionId, ra.RoleId from RoleAction ra join ActionValue av
on ra.ActionId = av.ActionId order by av.ActionId
ActionID RoleId
1 1
3 1
5 1
7 1
4 2
2 2
6 2
8 2
Is there way to do that? Thanks in advance.
You can make it work using FOR XML PATH('') and an inner query:
SELECT DISTINCT T1.RoleID,
(SELECT '' + ActionID
FROM RoleAction T2
WHERE T1.RoleID = T2.RoleID
ORDER BY ActionID
FOR XML PATH(''))
FROM RoleAction T1
This should work:
WITH CTE_A AS
(
select av.ActionId, ra.RoleId from RoleAction ra join ActionValue av
on ra.ActionId = av.ActionId
)
SELECT DISTINCT A.RoleId,
(SELECT '' +
CAST(B.ActionId AS varchar(10))
FROM CTE_A B
WHERE B.RoleID = A.RoleID
FOR XML PATH('')) AS ActionID
FROM CTE_A A
GROUP BY A.RoleID

Resources