SQL Pagination including duplicate rows - sql

I'm having some trouble solving an issue with pagination in SQL.
I'm stuck trying to fill a #PageSize variable in my stored procedure that comes from some ODATA, However the value from ODATA doesn't get me what I'm after necessarily. My query you see returns results like this.
+----+----------+
| ID | PersonID |
+----+----------+
| 1 | 1 |
+----+----------+
| 1 | 2 |
+----+----------+
| 2 | 1 |
+----+----------+
| 2 | 2 |
+----+----------+
| 2 | 3 |
+----+----------+
| 3 | 4 |
+----+----------+
| 3 | 4 |
+----+----------+
Obviously if I got a #PageResult = 5 from OData, it would just return 5 rows, but I want it to return x occurrences of ID.
To demonstrate what I basically want, is that if my #PageSize is 1, my sproc return this.
+----+----------+
| ID | PersonID |
+----+----------+
| 1 | 1 |
+----+----------+
| 1 | 2 |
+----+----------+
If it is 2, I return this.
+----+----------+
| ID | PersonID |
+----+----------+
| 1 | 1 |
+----+----------+
| 1 | 2 |
+----+----------+
| 2 | 1 |
+----+----------+
| 2 | 2 |
+----+----------+
| 2 | 3 |
+----+----------+
And so on. I'm having no end of trouble trying to get it to return data this way, I've tried doing things like distinct top(#pagesize) ID but it always seems to get the order wrong so it misses ID's and dense_ranks don't appear to do the job either. I imagine this is causing me so much hassle because there is no default order in SQL so the solution is not so obvious. Can any of you suggest how I might achieve this?
The closest I've gotten is with this
SET #PageSize = (select COUNT(personId) from #temptable WHERE ID IN (SELECT DISTINCT TOP(#PageSize) ID From #temptable))

Try something like this:
declare #t table(ID int, PersonID int)
insert into #t(ID,PersonID) values
(1,1),(1,2),(2,1),(2,2),(2,3),(3,3),(3,4);
with q as
(
select id, row_number() over (order by ID) rn
from #t
group by id
)
select *
from #t
where id in
(
select id
from q
where rn between 1 and 2
)
order by ID, PersonID
which outputs
ID PersonID
----------- -----------
1 1
1 2
2 1
2 2
2 3
(5 rows affected)

Related

How to get a hierarchy table in Sql Server

I would like to create a table that shows the hierarchy of another SQL Server table.
I have a table with the following structure
+-----------+----------+
| AccountID | ParentID |
+-----------+----------+
| 1 | |
+-----------+----------+
| 2 | 1 |
+-----------+----------+
| 3 | 1 |
+-----------+----------+
| 4 | 2 |
+-----------+----------+
| 5 | 3 |
+-----------+----------+
| 6 | 5 |
+-----------+----------+
and would like to get another table with the following structure
+-----------+------+
| AccountID | Path |
+-----------+------+
| 1 | 1 |
+-----------+------+
| 2 | 1 |
+-----------+------+
| 2 | 2 |
+-----------+------+
| 3 | 1 |
+-----------+------+
| 3 | 3 |
+-----------+------+
| 4 | 1 |
+-----------+------+
| 4 | 2 |
+-----------+------+
| 4 | 4 |
+-----------+------+
| 5 | 1 |
+-----------+------+
| 5 | 3 |
+-----------+------+
| 5 | 5 |
+-----------+------+
| 6 | 1 |
+-----------+------+
| 6 | 3 |
+-----------+------+
| 6 | 5 |
+-----------+------+
| 6 | 6 |
+-----------+------+
Note: In the Parents ID field you must always include your own ID, i.e., 1-1, 2-2, etc.
If you see in the first table, for AccountID 1, there is no ParentID, because it is the highest hierarchical level. But in the table I need to extract, you see that for AccountID 1 the value 1 appears in the Path column. The same happens for the rest of the values, that is, for AccountID 2, in the result table AccountID 1 appears (its superior hierarchical value), but it is also necessary that it includes the value 2. And so for the rest of the values in the AccountID column.
Setup sample data:
create table Account
(
AccountID INT,
ParentID INT NULL
)
INSERT INTO Account(AccountID, ParentID)
VALUES
(1, NULL),
(2,1),
(3,1),
(4,2),
(5,3),
(6,5)
I'm not able to get this results. Could you help me?
Thanks in advance
As mentioned, the easiest way to achieve this is with a rCTE, and the recurse down each level of the hierarchy until you get to the bottom:
--Sample Data
WITH YourTable AS(
SELECT V.AccountID,
V.[Path]
FROM (VALUES(1,NULL),
(2,1),
(3,1),
(4,2),
(5,3),
(6,5))V(AccountID,[Path])),
--Solution
rCTe AS(
SELECT YT.AccountID AS RootID,
YT.AccountID,
YT.[Path]
FROM YourTable YT
UNION ALL
SELECT r.RootID,
YT.AccountID,
YT.[Path]
FROM rCTe r
JOIN YourTable YT ON r.[Path] = YT.AccountID)
SELECT r.RootID AS AccountID,
r.AccountID AS [Path]
FROM rCTe r
ORDER BY AccountId,
[Path];
DB<>Fiddle
I tried with this sentence, based on your sentence,
WITH rCTe AS (
SELECT YT.Accountid AS RootID,
YT.Accountid,
YT.Parentaccountid
FROM PBI_OrganizacionJerarquica YT
UNION ALL
SELECT r.RootID,
YT.Accountid,
YT.Parentaccountid
FROM rCTe r
JOIN PBI_OrganizacionJerarquica YT ON r.Parentaccountid = YT.Accountid)
SELECT r.RootID AS AccountID,
r.Accountid AS [Path]
FROM rCTe r
ORDER BY AccountId,
[Path];
and I get this error
Msg 319, Level 15, State 1, Line 3
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.

getting concatenated desc from a table of 3 different columns of another table

I am trying to get a single description column from a reference table in PostgreSQL using 3 id columns as a concatenated value.
I have a id Table as below:
+-----+-----+-----+
| id1 | id2 | id3 |
+-----+-----+-----+
| 1 | 2 | 3 |
| 4 | 6 | 5 |
+-----+-----+-----+
and Reference Table;
+----+----------+
| id | desc |
+----+----------+
| 1 | apple |
| 2 | boy |
| 3 | cat |
| 4 | dog |
| 5 | elephant |
| 6 | Flight |
+----+----------+
The Desired expected output is as below
I just have to concat a "/M" in the end additionally.
I don't have to add /M if id2 and id3 both are null
+-----------------------+
| desc |
+-----------------------+
| apple+boy+cat/M |
| dog+Flight+Elephant/M |
+-----------------------+
You can use string_agg() to concatenate all rows with a single expression. Something like:
select (select string_agg(r.descr, '+' order by r.id)||
case when count(r.descr) > 1 then '/M' else '' end
from ref r
where id in (i.id1, i.id2, id3)) as descr
from id_table i;
Online example: https://rextester.com/KVCGLD44632
The above sorts the descriptions by the ID value. If you need to preserve the order of the columns in the "id table", you could use something like this:
select (select string_agg(r.descr, '+' order by t.idx)||
case when count(r.descr) > 1 then '/M' else '' end
from ref r
join (values (i.id1, 1), (i.id2, 2), (i.id3, 3)) as t(id, idx)
on t.id = r.id
) as descr
from id_table i;
Note that desc is a reserved keyword, you should not use it as a column name. That's why I used descr in my example.

Update an ordinal column based on the alphabetic ordering of another column

I have a table representing a system of folders and sub-folders with an ordinal m_order column.
Sometimes sub-folders are sorted alphanumerically, others are sorted by date or by importance.
I recently had to delete some sub-folders of a particular parent folder and add a few new ones. I also had to switch the ordering scheme to alphanumeric. This needed to be reflected in the m_order column.
Here's an example of the table:
+-----+-----------+-----------+------------+
| ID | parent | title | m_order |
+-----+-----------+-----------+------------+
| 100 | 1 | docs | 3 |
| 101 | 1 | reports | 2 |
| 102 | 1 | travel | 1 |
| 103 | 1 | weekly | 4 |
| 104 | 1 | briefings | 5 |
| ... | ... | ... | ... |
+-----+-----------+-----------+------------+
And here is what I want:
+-----+-----------+-----------+------------+
| ID | parent | title | m_order |
+-----+-----------+-----------+------------+
| 100 | 1 | docs | 3 |
| 101 | 1 | reports | 4 |
| 102 | 1 | travel | 5 |
| 200 | 1 | contacts | 2 |
| 201 | 1 | admin | 1 |
| ... | ... | ... | ... |
+-----+-----------+-----------+------------+
I would do this with a simple update:
with toupdate as (
select m.*, row_number() over (partition by parent order by title) as seqnum
from menu m
)
update toupdate
set m_order = toupdate.seqnum;
This restarts the ordering for each parent. If you have a particular parent in mind, use a WHERE clause:
where parentid = #parentid and m_order <> toupdate.seqnum
After deleting the old folders and inserting the new records, I accomplished the reordering by using MERGE INTO and ROW_NUMBER():
DECLARE #parentID INT
...
MERGE INTO menu
USING (
SELECT ROW_NUMBER() OVER (ORDER BY title) AS rowNumber, ID
FROM menu
WHERE parent = #parentID
) AS reordered
ON menu.ID = reordered.ID
WHEN MATCHED THEN
UPDATE
SET menu.m_order = reordered.rowNumber
I needed both t-sql and Oracle versions of this. To save future readers the struggle with the subtle differences in the ORA UPDATE syntax, here it is, shamelessly ripped off Gordon Linoff's answer:
update (
with toupdate as (
select
m.primarykey,
row_number() over(partition by parent order by title) as seqnum
from menu m
)
select m.primarykey, t.seqnum from menu m inner join toupdate t on t.primarykey=m.primarykey
)
set m_order = t.seqnum;

Do I need a recursive CTE to update a table that relies on itself?

I need to apologize for the title. I put a lot of thought into it but didn't get too far.
I have a table that looks like this:
+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------+--------+
| accountid | pricexxxxxid | accountid | pricelevelid | counts |
+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------+--------+
| 36B077D4-E765-4C70-BE18-2ECA871420D3 | 00000000-0000-0000-0000-000000000000 | 36B077D4-E765-4C70-BE18-2ECA871420D3 | F43C47CE-28C6-42E2-8399-92C58ED4BA9D | 1 |
| EBC18CBC-2D2E-44CB-B36A-0ADE9E2BDE9F | 00000000-0000-0000-0000-000000000000 | EBC18CBC-2D2E-44CB-B36A-0ADE9E2BDE9F | 3BEEA9D3-F26B-47E4-88FA-A2AA366980ED | 1 |
| 8DC8D0FC-3138-425A-A922-2F0CAC57E887 | 00000000-0000-0000-0000-000000000000 | 8DC8D0FC-3138-425A-A922-2F0CAC57E887 | F1B8AD5D-B008-4C3F-94A0-AD3F90C777D7 | 1 |
| 8F908A92-1327-4655-BAE4-C890D971A554 | 00000000-0000-0000-0000-000000000000 | 8F908A92-1327-4655-BAE4-C890D971A554 | 2E0EC67E-5F8F-4305-932E-BBF8DF83DBEC | 1 |
| 37221AAC-B885-4002-B7D9-591F8C14D019 | 00000000-0000-0000-0000-000000000000 | 37221AAC-B885-4002-B7D9-591F8C14D019 | F4A2A0CA-FDFF-4C21-AE92-D4583DC18DED | 1 |
| 66F406B4-0D9B-40B8-9A23-119EE74B00B7 | 00000000-0000-0000-0000-000000000000 | 66F406B4-0D9B-40B8-9A23-119EE74B00B7 | 204B8570-CEBA-4C72-9B72-8B9B14AF625E | 2 |
| D0168CE3-479E-439E-967C-4FF0D701291A | 00000000-0000-0000-0000-000000000000 | D0168CE3-479E-439E-967C-4FF0D701291A | 204B8570-CEBA-4C72-9B72-8B9B14AF625E | 2 |
| 57E5F6E5-0A8A-4E54-B793-2F6493DC1EA3 | 00000000-0000-0000-0000-000000000000 | 57E5F6E5-0A8A-4E54-B793-2F6493DC1EA3 | 893F9FD2-43C9-4355-AEFC-08A62BF2B066 | 3 |
+--------------------------------------+--------------------------------------+--------------------------------------+--------------------------------------+--------+
It is sorted by ascending counts.
I would like to update the pricexxxxids that are all 00000000-0000-0000-0000-000000000000 with their corresponding pricelevelid.
For example for accountid = 36B077D4-E765-4C70-BE18-2ECA871420D3 I would like the pricexxxxid to be F43C47CE-28C6-42E2-8399-92C58ED4BA9D.
After that is done, I would like all the records FOLLOWING this one where accountid = 36B077D4-E765-4C70-BE18-2ECA871420D3 to be deleted.
Another words in result I will end up with a distinct list of accountids with pricexxxxid to be assigned with the corresponding value from pricelevelid.
Thank you so much for your guidance.
for your first case do !
update table
set pricexxxxids=pricelevelid.
if i understand your second case correctly :(delete duplicates/select distinct)?
delete from
(
select *,rn=row_number()over(partition by accountid order by accountid) from table
)x
where rn>1
--select distinct * from table
edited
select * from
(
select *,rn=row_number()over(partition by accountid order by accountid) from table
)x
where x.rn=1
updated
SELECT accountid,pricelevelid FROM
(
(SELECT *,
Row_number() OVER ( partition BY accountid ORDER BY counts, pricelevelid ) AS Recency
FROM table
)x
WHERE x.Recency = 1

Select rows appearing after a row with a given ID when sorted by criteria unrelated to the ID

Given the data in the table "people":
+----+-------+
| id | name |
+----+-------+
| 1 | Jane |
| 2 | Joe |
| 4 | John |
| 5 | Alice |
| 6 | Bob |
+----+-------+
And the order:
SELECT * FROM people ORDER BY name
... which would return:
+----+-------+
| id | name |
+----+-------+
| 5 | Alice |
| 6 | Bob |
| 1 | Jane |
| 2 | Joe |
| 4 | John |
+----+-------+
How could one write a query--including the order above--which would return only rows after the one with a given id, e.g., if given an id of 1, it would return:
+----+-------+
| id | name |
+----+-------+
| 2 | Joe |
| 4 | John |
+----+-------+
To be clear, the id is variable and not known before hand.
An approach using commonly supported SQL would be great, but I'm using PostgreSQL 9.2 and ActiveRecord 3.2 if they have anything additional of use, e.g., OVER() and ROW_NUMBER().
[Edit] I'd previously showed the wrong desired result set, including the row with the given id. But, the result set, as described in the question, should only include rows after the given ID.
select *
from people
where
name >= (
select name
from people
where id = 1
)
and id != 1
order by name
So far the simplest approach I've found for a situation where precision is needed, e.g., no missing or duplicate results across multiple calls with varying values for ID is to combine window functions and CTEs, as in:
WITH ordered_people AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY name) AS n
FROM people
ORDER BY name
)
SELECT *
FROM ordered_people
WHERE n > (SELECT n FROM ordered_people WHERE id = 1)
ORDER BY name
;