T-SQL: create results in a comma seperated combined single row - tsql

Using this code:
SELECT *
FROM
(SELECT
EQUITY_DIVISION_25_NAME Broker,
COUNT(tbl_user_details.USER_ID) new,
DENSE_RANK() OVER (ORDER BY COUNT(tbl_user_details.USER_ID) DESC) Rank
FROM
TBL_FEES_MASTER RIGHT
OUTER JOIN
TBL_USER_CREATE ON TBL_FEES_MASTER.FEE_ID = TBL_USER_CREATE.FEE_ID
LEFT OUTER JOIN
TBL_USER_DETAILS ON TBL_USER_CREATE.USER_ID = TBL_USER_DETAILS.USER_ID
WHERE
Joined_dt >= (SELECT MAX(last_report_run)
FROM new_lance_table
WHERE last_report_run < (SELECT MAX(last_report_run)
FROM new_lance_table))
AND (joined_dt <= GETDATE())
GROUP BY
EQUITY_DIVISION_25_NAME) a
I get table results:
Broker new Rank
-----------------------------
Todd Schuster 7 1
Tony Ketterling 7 1
Al Palmonari 4 2
Randall Wall 4 2
Edmund Sperry 3 3
Eric Lee 3 3
Steve Stringham 3 3
Timothy Gulla 2 4
Troy Peterson 2 4
Tuiono Malakai 2 4
Nancy Umbreit 2 4
Steve Goodsell 2 4
Sandy Dunkley 1 5
Gary Rosine 1 5
Ian Chait 1 5
Nancy Pearce-Harris 1 5
William Hochstedler 1 5
Troy C Peterson 1 5
How can I query to make it so the results show up like this:
Rank 1: Tie! Todd Schuster (7), Tony Ketterling (7)
Rank 2: Tie! Al Palmonari (4), Randall Wall (4)
Rank 3: Tie! Edmund Sperry (3), Eric Lee (3), Steve Stringham (3)

You can try it like this:
DECLARE #dummyTbl TABLE(
Broker VARCHAR(29)
,new VARCHAR(13)
,Rank VARCHAR(8)
);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Todd Schuster',7,1);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Tony Ketterling',7,1);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Al Palmonari',4,2);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Randall Wall',4,2);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Edmund Sperry',3,3);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Eric Lee',3,NULL);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Steve Stringham',3,3);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Timothy Gulla',2,4);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Troy Peterson',2,4);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Tuiono Malakai',2,4);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Nancy Umbreit',2,4);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Steve Goodsell',2,4);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Sandy Dunkley',1,5);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Gary Rosine',1,5);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Ian Chait',1,5);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Nancy Pearce-Harris',1,5);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('William Hochstedler',1,5);
INSERT INTO #dummyTbl(Broker,new,Rank) VALUES ('Troy C Peterson',1,5);
--I use the declared table variable and a simple SELECT * FROM #dummyTbl to mock-up you query
WITH CTE AS
(
/*Place your own query here*/
SELECT * FROM #dummyTbl
)
SELECT 'Rank ' + CAST(t.Rank AS VARCHAR(10)) + ': Tie! '
+STUFF(
(
SELECT ', ' + x.Broker + ' (' + CAST(x.new AS VARCHAR(10)) + ')'
FROM CTE AS x
WHERE x.Rank=t.Rank
FOR XML PATH('')
),1,2,'')
FROM CTE AS t
GROUP BY t.Rank
The result
NULL
Rank 1: Tie! Todd Schuster (7), Tony Ketterling (7)
Rank 2: Tie! Al Palmonari (4), Randall Wall (4)
Rank 3: Tie! Edmund Sperry (3), Steve Stringham (3)
Rank 4: Tie! Timothy Gulla (2), Troy Peterson (2), Tuiono Malakai (2), Nancy Umbreit (2), Steve Goodsell (2)
Rank 5: Tie! Sandy Dunkley (1), Gary Rosine (1), Ian Chait (1), Nancy Pearce-Harris (1), William Hochstedler (1), Troy C Peterson (1)
Short explanation:
A simple SELECT Rank FROM CTE GROUP BY Rank will return with a distinct list of Ranks. This numbers are extended by some constant characters. But you would not be allowed to access other columns than Rank, unless you'd add them to your GROUP BY...
But: You can use a sub-select, in which t.Rank is the only column in use. The other columns are taken from a second call to the same table, but aliased as x.
The rest (STUFF() in combination with FOR XML to concat values) is rather easy. There are tons of questions and answers around this technique.

You can select your current result into a temp table, and then loop from 1 to max(rank) and do string combination.

Related

Get day name based on custome ID of bigint[] datatype

I want to display Day name based on ID which is in bigint[] in the table as shown below:
Table:
create table tbl_days
(
day_ids bigint[]
);
Records:
insert into tbl_days values('{1,2}');
insert into tbl_days values('{1,2,3}');
insert into tbl_days values('{1,4}');
insert into tbl_days values('{4,7}');
insert into tbl_days values('{1,2,3,4,5,6,7}');
insert into tbl_days values('{2,4,7}');
Would like to display day name for:
1 for Monday
2 for Tuesday
.
..
7 for Sunday.
Query 1: Using replace(), which is taking 3 more seconds to get the main query result.
select replace(replace(replace(replace(replace(replace(replace(day_ids::varchar,'1','Monday'),'2','Tuesday'),'3','Wednesday'),'4','Thursday'),'5','Friday'),'6','Saturday'),'7','Sunday')
from tbl_days;
Query 2: Using string_agg(), here problem with an order.
Step 1: Add days into temp table
create temp table temp_days
(
id int,
days varchar
);
insert into temp_days values(1,'Monday'),(2,'Tuesday'),(3,'Wednesday'),(4,'Thursday'),(5,'Friday'),(6,'Saturday'),(7,'Sunday');
Step 2: Join with main table
select d.day_ids,string_agg(distinct t.days,',')
from tbl_days d
inner join temp_days t on t.id = any(d.day_ids)
group by d.day_ids
step-by-step demo:db<>fiddle
SELECT
id,
string_agg( -- 4
CASE day -- 3
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
WHEN 7 THEN 'Sunday'
END,
','
ORDER BY index_in_array -- 4
)
FROM (
SELECT
*,
row_number() OVER () as id -- 1
FROM tbl_days
) s,
unnest(day_ids) WITH ORDINALITY as t(day, index_in_array) -- 2
GROUP BY id
For my approach you need an id column. This creates one. If you already have one, you can ignore this step
unnest() expands the array into one row per element. The WITH ORDINALITY clause adds an index to the records which saves the position of the element in the original array
Replace the numbers with the related string using a CASE clause
Reaggregate the weekdays by their original ids. The order can be ensured by using the in (2) created index, which can be using in the ORDER BY clause of the aggregate

How to use a declare statement to update a table

I have this Declare Statement
declare #ReferralLevelData table([Type of Contact] varchar(10));
insert into #ReferralLevelData values ('f2f'),('nf2f'),('Travel'),('f2f'),('nf2f'),('Travel'),('f2f'),('nf2f'),('Travel');
select (row_number() over (order by [Type of Contact]) % 3) +1 as [Referral ID]
,[Type of Contact]
from #ReferralLevelData
order by [Referral ID]
,[Type of Contact];
It does not insert into the table so i feel this is not working as expect, i.e it doesn't modify the table.
If it did work I was hoping to modify the statement to make it update.
At the moment the table just prints this result
1 f2f
1 nf2f
1 Travel
2 f2f
2 nf2f
2 Travel
3 f2f
3 nf2f
3 Travel
EDIT:
I want TO Update the table to enter recurring data in groups of three.
I have a table of data, it is duplicated twice in the same table to make three sets.
Its "ReferenceID" is the primary key, i want to in a way group the 3 same ReferenceID's and inject these three values "f2f" "NF2F" "Travel" into the row called "Type" in any order but ensure that each ReferenceID only has one of those values.
Do you mean the following?
declare #ReferralLevelData table(
[Referral ID] int,
[Type of Contact] varchar(10)
);
insert into #ReferralLevelData([Referral ID],[Type of Contact])
select
(row_number() over (order by [Type of Contact]) % 3) +1 as [Referral ID]
,[Type of Contact]
from
(
values ('f2f'),('nf2f'),('Travel'),('f2f'),('nf2f'),('Travel'),('f2f'),('nf2f'),('Travel')
) v([Type of Contact]);
If it suits you then you also can use the next query to generate data:
select r.[Referral ID],ct.[Type of Contact]
from
(
values ('f2f'),('nf2f'),('Travel')
) ct([Type of Contact])
cross join
(
values (1),(2),(3)
) r([Referral ID]);

Divide table raw into chunks in Postgres with st_dwithin limit

I got a table with linestrings that I want to divide into chunks that have a list of id not higher than provided number for each and store only lines that are within certain distance.
For example, I got a table with 14 rows
create table lines ( id integer primary key, geom geometry(linestring) );
insert into lines (id, geom) values ( 1, 'LINESTRING(0 0, 0 1)');
insert into lines (id, geom) values ( 2, 'LINESTRING(0 1, 1 1)');
insert into lines (id, geom) values ( 3, 'LINESTRING(1 1, 1 2)');
insert into lines (id, geom) values ( 4, 'LINESTRING(1 2, 2 2)');
insert into lines (id, geom) values ( 11, 'LINESTRING(2 2, 2 3)');
insert into lines (id, geom) values ( 12, 'LINESTRING(2 3, 3 3)');
insert into lines (id, geom) values ( 13, 'LINESTRING(3 3, 3 4)');
insert into lines (id, geom) values ( 14, 'LINESTRING(3 4, 4 4)');
create index lines_gix on lines using gist(geom);
I want to split it into chunks with 3 ids for each chunk with lines that are within 2 meters from each other or the first one.
The result I am trying to get from this example is:
| Chunk No.| Id chunk list |
|----------|----------------|
| 1 | 1, 2, 3 |
| 2 | 4, 5, 6 |
| 3 | 7, 8, 9 |
| 4 | 10, 11, 12 |
| 5 | 13, 14 |
I tried to use st_clusterwithin but when lines are close to each other it will return all of them not split into chunks.
I also tried to use some with recursive magic like the one from the answer provided by Paul Ramsey here. But I don't know how to modify the query to return limited grouped id list.
I am not sure if it is the best possible answer so if anyone has a better method or know how to improve provided answer feel free to update it. With a little modification of Paul answer, I've managed to create following queries that are doing what I asked for.
-- Create function for easier interaction
CREATE OR REPLACE FUNCTION find_connected(integer, double precision, integer, integer[])
returns integer[] AS
$$
WITH RECURSIVE lines_r AS -- Recursive allow to use the same query on the output - is like continues append to result and use it inside a query
(SELECT ARRAY[id] AS idlist,
geom, id
FROM lines
WHERE id = $1
UNION ALL
SELECT array_append(lines_r.idlist, lines.id) AS idlist, -- append id list to array
lines.geom AS geom, -- keep geometry
lines.id AS id -- keep source table id
FROM (SELECT * FROM lines WHERE NOT $4 #> array[id]) lines, lines_r -- from source table and recursive table
WHERE ST_DWITHIN(lines.geom, lines_r.geom, $2) -- where lines are within 2 meters
AND NOT lines_r.idlist #> ARRAY[lines.id] -- recursive id list array not contain lines array
AND array_length(idlist, 1) <= $3
)
SELECT idlist
FROM lines_r WHERE array_length(idlist, 1) <= $3 ORDER BY array_length(idlist, 1) DESC LIMIT 1;
$$
LANGUAGE 'sql';
-- Create id chunks
WITH RECURSIVE groups_r AS (
(SELECT find_connected(id, 2, 3, ARRAY[id]) AS idlist, find_connected(id, 2, 3, ARRAY[id]) AS grouplist, id
FROM lines WHERE id = 1)
UNION ALL
(SELECT array_cat(groups_r.idlist, find_connected(lines.id, 2, 3, groups_r.idlist)) AS idlist,
find_connected(lines.id, 2, 3, groups_r.idlist) AS grouplist,
lines.id
FROM lines,
groups_r
WHERE NOT groups_r.idlist #> ARRAY[lines.id]
LIMIT 1))
SELECT
-- (SELECT array_agg(DISTINCT x) FROM unnest(idlist) t (x)) idlist, -- left for better understanding what is happening
row_number() OVER () chunk_id,
(SELECT array_agg(DISTINCT x) FROM unnest(grouplist) t (x)) grouplist,
id input_line_id
FROM groups_r;
The only problem is that performance is quite pure when the number of ids in the chunk increase. For a table with 300 rows and 20 ids per chunk, execution time is around 15 min, even with indexes on geometry and id columns.

T-SQL query, multiple values in a field

I have two tables in a database. The first table tblTracker contains many columns, but the column of particular interest is called siteAdmin and each row in that column can contain multiple loginIDs of 5 digits like 21457, 21456 or just one like 21444. The next table users contains columns like LoginID, fname, and lname.
What I would like to be able to do is take the loginIDs contained in tblTracker.siteAdmin and return fname + lname from users. I can successfully do this when there is only one loginID in the row such as 21444 but I cannot figure out how to do this when there is more than one like 21457, 21456.
Here is the SQL statement I use for when there is one loginID in that column
SELECT b.FName + '' '' + b.LName AS siteAdminName,
FROM tblTracker a
LEFT OUTER JOIN users b ON a.siteAdmin= b.Login_Id
However this doesn't work when it tries to join a siteAdmin with more than one LoginID in it
Thanks!
I prefer the number table approach to split a string in TSQL
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
ListValue
-----------------------
1
2
3
4
5
6777
(6 row(s) affected)
Your can now use a CROSS APPLY to split every row in your table like:
DECLARE #users table (LoginID int, fname varchar(5), lname varchar(5))
INSERT INTO #users VALUES (1, 'Sam', 'Jones')
INSERT INTO #users VALUES (2, 'Don', 'Smith')
INSERT INTO #users VALUES (3, 'Joe', 'Doe')
INSERT INTO #users VALUES (4, 'Tim', 'White')
INSERT INTO #users VALUES (5, 'Matt', 'Davis')
INSERT INTO #users VALUES (15,'Sue', 'Me')
DECLARE #tblTracker table (RowID int, siteAdmin varchar(50))
INSERT INTO #tblTracker VALUES (1,'1,2,3')
INSERT INTO #tblTracker VALUES (2,'2,3,4')
INSERT INTO #tblTracker VALUES (3,'1,5')
INSERT INTO #tblTracker VALUES (4,'1')
INSERT INTO #tblTracker VALUES (5,'5')
INSERT INTO #tblTracker VALUES (6,'')
INSERT INTO #tblTracker VALUES (7,'8,9,10')
INSERT INTO #tblTracker VALUES (8,'1,15,3,4,5')
SELECT
t.RowID, u.LoginID, u.fname+' '+u.lname AS YourAdmin
FROM #tblTracker t
CROSS APPLY dbo.FN_ListToTable(',',t.siteAdmin) st
LEFT OUTER JOIN #users u ON st.ListValue=u.LoginID --to get all rows even if missing siteAdmin
--INNER JOIN #users u ON st.ListValue=u.LoginID --to remove rows without any siteAdmin
ORDER BY t.RowID,u.fname,u.lname
OUTPUT:
RowID LoginID YourAdmin
----------- ----------- -----------
1 2 Don Smith
1 3 Joe Doe
1 1 Sam Jones
2 2 Don Smith
2 3 Joe Doe
2 4 Tim White
3 5 Matt Davis
3 1 Sam Jones
4 1 Sam Jones
5 5 Matt Davis
7 NULL NULL
7 NULL NULL
7 NULL NULL
8 3 Joe Doe
8 5 Matt Davis
8 1 Sam Jones
8 15 Sue Me
8 4 Tim White
(18 row(s) affected)

Find duplicate row "details" in table

OrderId OrderCode Description
-------------------------------
1 Z123 Stuff
2 ABC999 Things
3 Z123 Stuff
I have duplicates in a table like the above. I'm trying to get a report of which Orders are duplicates, and what Order they are duplicates of, so I can figure out how they got into the database.
So ideally I'd like to get an output something like;
OrderId IsDuplicatedBy
-------------------------
1 3
3 1
I can't work out how to code this in SQL.
You can use the same table twice in one query and join on the fields you need to check against. T1.OrderID <> T2.OrderID is needed to not find a duplicate for the same row.
declare #T table (OrderID int, OrderCode varchar(10), Description varchar(50))
insert into #T values
(1, 'Z123', 'Stuff'),
(2, 'ABC999', 'Things'),
(3, 'Z123', 'Stuff')
select
T1.OrderID,
T2.OrderID as IsDuplicatedBy
from #T as T1
inner join #T as T2
on T1.OrderCode = T2.OrderCode and
T1.Description = T2.Description and
T1.OrderID <> T2.OrderID
Result:
OrderID IsDuplicatedBy
1 3
3 1