Moving an an entire row from one position to another - sql

I have 5 rows from my query results and i want to know if its even possible to move an entire row from one position to another. For example the third row to the first row.
Below is my sql code:
DECLARE #ClientID int = 1041
DECLARE #ProfileID int =2520
DECLARE #PageType tinyint = 2
BEGIN
WITH SortedList
AS (
SELECT PageID, PageName, PageTitle, PageUrl, ParentID,
CAST((PageName) AS VARCHAR(1000)) AS "Path"
FROM Pagelist p
WHERE p.ParentId IS NULL and p.PageType=#PageType
UNION ALL
SELECT p.PageID, p.PageName, p.PageTitle, p.PageUrl, p.ParentID,
CAST((a.path + '/' + p.PageName) AS VARCHAR(1000)) AS "Path"
FROM Pagelist AS p
JOIN SortedList AS a
ON p.ParentID = a.PageID
WHERE p.PageType=#PageType
)
SELECT a.PageID,a.PageTitle,a.ParentID,ua.Access,a.Path,a.PageName
FROM SortedList as a,ProfilePageAccess ua,UserProfile up
WHERE ua.ClientID=#ClientID and ua.PageID=a.PageID
and up.ProfileID=ua.ProfileID and up.ProfileID=#ProfileID
ORDER BY a.Path
END
I want to be able to move the third row to first row.
This is my current results
PAGEID PAGETITLE PARENTID ACCESS PATH PAGENAME
001R Administration 801 2 HRAdmin HrAdmin
002R Performance 802 2 HRAdmin/AdminPer AdminPer
003R Overall Ratings 803 2 HRAdmin/AdminPerformance Perform
004R Score Ratings 804 2 HRAdmin/AdminPerformance Perform
005R Template Setup 805 2 HRAdmin/AdminPerformance Perform

In your ORDER BY you have: ORDER BY a.Path
This is ordering by a.Path in ascending (alphabetical) order.
If you want to order by specific values of some other column, you can use an ORDER BY with a CASE Statement.
ex.
...
SELECT a.PageID,a.PageTitle,a.ParentID,ua.Access,a.Path,a.PageName
FROM SortedList as a,ProfilePageAccess ua,UserProfile up
WHERE ua.ClientID=#ClientID and ua.PageID=a.PageID
and up.ProfileID=ua.ProfileID and up.ProfileID=#ProfileID
ORDER BY CASE a.PageTitle
WHEN 'Overall Ratings' THEN 1
WHEN 'Score Ratings' THEN 2
ELSE 3
END

One way is to change your ORDER BY to something more complex. For example, in Postgres you could change your query from
SELECT a.PageID,a.PageTitle,a.ParentID,ua.Access,a.Path,a.PageName
to
SELECT a.PageID,a.PageTitle,a.ParentID,ua.Access,a.Path,a.PageName, a.PageID == '003R' as priority_column
and change
ORDER BY a.Path
to
ORDER BY priority_column DESC, a.Path
Then it will sort results such that all rows where priority_column is true are above all rows where priority_column is false.
EDIT: Zorkolot's answer is cleaner than this; probably go with that one.

Related

Improving recursive SQL looping

I am trying to solve a performance issue on an inherited system that appears when we have a significant amount of data.
We have a table that contains the two fields "ItemID" and "ParentItemID".
The "ParentItemID" field relates to another row in the same talbe where the "ItemID" field matches this row's "ParentItemID" field.
This relationship can be many, many rows deep in places.
The following query is being run and looks like it could be another cause of slowdown:
WHILE 1=1
BEGIN
SELECT #ParentID = ParentItemID FROM Items WHERE ItemID = #LastParentID
IF #parentID IS NULL
BEGIN
break
END
ELSE
BEGIN
SET #LastParentID = #ParentID
END
END
Is there a better way of doing this sort of recursive search?
note: we are NOT allowed to make table changes at this point, so adding a "RootItemID" column is not possible (I've already asked, as this would solve the problem outright!)
You could use a common table expression for this:
WITH Antecedents (ITemID, ParentItemID, Level)
AS
(
-- Anchor member definition
SELECT ItemID, ParentItemID, 0 AS Level FROM Items WHERE ItemID = #StartingID
UNION ALL
SELECT ItemID, ParentItemID, Antecedents.Level + 1 AS Level
FROM Items
INNER JOIN Antecedents
ON Antecedents.ParentItemID = Items.ItemID
)
SELECT TOP 1 #LastParentID = ItemID
FROM Antecedents
ORDER BY Level DESC
More info on recursive CTE's here:
http://msdn.microsoft.com/en-us/library/ms186243.aspx
you can do it by a Common Table Expression like :
;WITH cte_hierarchy
AS (SELECT *
FROM item
WHERE ItemID = #ParentID
UNION ALL
SELECT i.*
FROM item i
JOIN cte_hierarchy h
ON i.ItemID = h.ParentItemID)
SELECT *
FROM cte_hierarchy
WHERE .....

Incrementing Row_Number based on change in value of a column

I have a sql view which contains data from 3 linked entities (Title > Edition > SKU). The data in this view is used to search on ANY field within the 3 entities. For example, if you specify a condition title.regionid = '14' the view returns 4,000 unique rows (1 per SKU), which belong to 765 unique Editions, which belong to 456 unique Titles.
What I need is to enable paging based on Titles using Row_Number(). So
SELECT * FROM myview WHERE title.regionid = '14' AND Row BETWEEN 0 AND 35
The problem is that my Row column needs to count the rows by Title, not by SKU, so from a result set of 4,000 rows, if the first title contains 12 editions and 65 SKUs, the row number for all 65 rows should be 1, because they belong to the same Title.
I cannot use GroupBy because my view contains 40+ columns all of which can be searched on via the WHERE clause.
Here's the query:
SELECT *
FROM (
SELECT row_number() OVER (ORDER BY a.TitleSort ASC) AS Row, a.*
FROM (SELECT * FROM v_AdvancedSearch
WHERE
istitledeleted = 0
--AND ISBN = '1-4157-5842-5'
--AND etc
) AS a
) d
WHERE
Row BETWEEN 0 AND 35
In the first page there are 35 rows which only belong to 4 titles, but the Row column counts by row so it stops there, whereas if it counted by Title I would get 387 rows for page 1... How can I accomplish paging in this situation?
WITH Titles AS
(
SELECT *
FROM (
SELECT row_number() OVER (ORDER BY a.TitleSort ASC) AS Row, a.*
FROM (SELECT DISTINCT TitleSORT, TitleId FROM v_AdvancedSearch
WHERE
istitledeleted = 0
--AND ISBN = '1-4157-5842-5'
--AND PictureFormat = 'Widescreen'
--AND UPC = '0-9736-14381-6-0'
--AND Edition = 'Standard'
) AS a
) d
WHERE
Row BETWEEN 0 AND 35
)
SELECT * FROM v_AdvancedSearch V
INNER JOIN Titles ON Titles.TitleId = V.TitleId
WHERE istitledeleted = 0
--CONDITIONS NEED TO BE REPEATED HERE
--AND ISBN = '1-4157-5842-5'
ORDER BY V.TitleSort ASC
This form works best for me
WITH
[cte] AS (
SELECT
DENSE_RANK ( ) OVER ( ORDER BY [v].[TitleSort], [v].[TitleId] ) AS [ordinal],
[v].[TitleSort],
[v].[TitleId]
--,
--field list,
--etc
FROM [v_AdvancedSearch] AS [v]
WHERE
[v].[istitledeleted] = 0
--AND
--additional conditions AND
--etc
)
SELECT
[v].[ordinal],
[v].[TitleSort],
[v].[TitleId]
--,
--field list,
--etc
FROM [cte] AS [v]
WHERE
[v].[ordinal] >= 0 AND
[v].[ordinal] <= 35
ORDER BY [v].[ordinal];
No need for DISTINCT or GROUP BY
No need to repeat the criteria conditions
Just gotta have that explicit field list
https://learn.microsoft.com/en-us/sql/t-sql/functions/dense-rank-transact-sql
https://www.google.com/search?q=%22sql+server%22+%22select+star%22

MySQL get row position in ORDER BY

With the following MySQL table:
+-----------------------------+
+ id INT UNSIGNED +
+ name VARCHAR(100) +
+-----------------------------+
How can I select a single row AND its position amongst the other rows in the table, when sorted by name ASC. So if the table data looks like this, when sorted by name:
+-----------------------------+
+ id | name +
+-----------------------------+
+ 5 | Alpha +
+ 7 | Beta +
+ 3 | Delta +
+ ..... +
+ 1 | Zed +
+-----------------------------+
How could I select the Beta row getting the current position of that row? The result set I'm looking for would be something like this:
+-----------------------------+
+ id | position | name +
+-----------------------------+
+ 7 | 2 | Beta +
+-----------------------------+
I can do a simple SELECT * FROM tbl ORDER BY name ASC then enumerate the rows in PHP, but it seems wasteful to load a potentially large resultset just for a single row.
Use this:
SELECT x.id,
x.position,
x.name
FROM (SELECT t.id,
t.name,
#rownum := #rownum + 1 AS position
FROM TABLE t
JOIN (SELECT #rownum := 0) r
ORDER BY t.name) x
WHERE x.name = 'Beta'
...to get a unique position value. This:
SELECT t.id,
(SELECT COUNT(*)
FROM TABLE x
WHERE x.name <= t.name) AS position,
t.name
FROM TABLE t
WHERE t.name = 'Beta'
...will give ties the same value. IE: If there are two values at second place, they'll both have a position of 2 when the first query will give a position of 2 to one of them, and 3 to the other...
This is the only way that I can think of:
SELECT `id`,
(SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
`name`
FROM `table`
WHERE `name` = 'Beta'
If the query is simple and the size of returned result set is potentially large, then you may try to split it into two queries.
The first query with a narrow-down filtering criteria just to retrieve data of that row, and the second query uses COUNT with WHERE clause to calculate the position.
For example in your case
Query 1:
SELECT * FROM tbl WHERE name = 'Beta'
Query 2:
SELECT COUNT(1) FROM tbl WHERE name >= 'Beta'
We use this approach in a table with 2M record and this is way more scalable than OMG Ponies's approach.
The other answers seem too complicated for me.
Here comes an easy example, let's say you have a table with columns:
userid | points
and you want to sort the userids by points and get the row position (the "ranking" of the user), then you use:
SET #row_number = 0;
SELECT
(#row_number:=#row_number + 1) AS num, userid, points
FROM
ourtable
ORDER BY points DESC
num gives you the row postion (ranking).
If you have MySQL 8.0+ then you might want to use ROW_NUMBER()
The position of a row in the table represents how many rows are "better" than the targeted row.
So, you must count those rows.
SELECT COUNT(*)+1 FROM table WHERE name<'Beta'
In case of a tie, the highest position is returned.
If you add another row with same name of "Beta" after the existing "Beta" row, then the position returned would be still 2, as they would share same place in the classification.
Hope this helps people that will search for something similar in the future, as I believe that the question owner already solved his issue.
I've got a very very similar issue, that's why I won't ask the same question, but I will share here what did I do, I had to use also a group by, and order by AVG.
There are students, with signatures and socore, and I had to rank them (in other words, I first calc the AVG, then order them in DESC, and then finally I needed to add the position (rank for me), So I did something Very similar as the best answer here, with a little changes that adjust to my problem):
I put finally the position (rank for me) column in the external SELECT
SET #rank=0;
SELECT #rank := #rank + 1 AS ranking, t.avg, t.name
FROM(SELECT avg(students_signatures.score) as avg, students.name as name
FROM alumnos_materia
JOIN (SELECT #rownum := 0) r
left JOIN students ON students.id=students_signatures.id_student
GROUP BY students.name order by avg DESC) t
I was going through the accepted answer and it seemed bit complicated so here is the simplified version of it.
SELECT t,COUNT(*) AS position FROM t
WHERE name <= 'search string' ORDER BY name
I have similar types of problem where I require rank(Index) of table order by votes desc. The following works fine with for me.
Select *, ROW_NUMBER() OVER(ORDER BY votes DESC) as "rank"
From "category_model"
where ("model_type" = ? and "category_id" = ?)
may be what you need is with add syntax
LIMIT
so use
SELECT * FROM tbl ORDER BY name ASC LIMIT 1
if you just need one row..

How to select a row for certain (or give preference in the selection) in mysql?

Need your help guys in forming a query.
Example.
Company - Car Rental
Table - Cars
ID NAME STATUS
1 Mercedes Showroom
2 Mercedes On-Road
Now, how do I select only one entry from this table which satisfies the below conditions?
If Mercedes is available in Showroom, then fetch only that row. (i.e. row 1 in above example)
But If none of the Mercedes are available in the showroom, then fetch any one of the rows. (i.e. row 1 or row 2) - (This is just to say that all the mercedes are on-road)
Using distinct ain't helping here as the ID's are also fetched in the select statement
Thanks!
Here's a common way of solving that problem:
SELECT *,
CASE STATUS
WHEN 'Showroom' THEN 0
ELSE 1
END AS InShowRoom
FROM Cars
WHERE NAME = 'Mercedes'
ORDER BY InShowRoom
LIMIT 1
Here's how to get all the cars, which also shows another way to solve the problem:
SELECT ID, NAME, IFNULL(c2.STATUS, c1.STATUS)
FROM Cars c1
LEFT OUTER JOIN Cars c2
ON c2.NAME = c1.NAME AND c2.STATUS = 'Showroom'
GROUP BY NAME
ORDER BY NAME
You would want to use the FIND_IN_SET() function to do that.
SELECT *
FROM Cars
WHERE NAME = 'Mercedes'
ORDER BY FIND_IN_SET(`STATUS`,'Showroom') DESC
LIMIT 1
If you have a preferred order of other statuses, just add them to the second parameter.
ORDER BY FIND_IN_SET(`STATUS`,'On-Road,Showroom' ) DESC
To fetch 'best' status for all cars you can simply do this:
SELECT *
FROM Cars
GROUP BY NAME
ORDER BY FIND_IN_SET(`STATUS`,'Showroom') DESC
SELECT * FROM cars
WHERE name = 'Mercedes'
AND status = 'Showroom'
UNION SELECT * FROM cars
WHERE name = 'Mercedes'
LIMIT 1;
EDIT Removed the ALL on the UNION since we only want distinct rows anyway.
MySQL doesn't have ranking/analytic/windowing functions, but you can use a variable to simulate ROW_NUMBER functionality (when you see "--", it's a comment):
SELECT x.id, x.name, x.status
FROM (SELECT t.id,
t.name,
t.status,
CASE
WHEN #car_name != t.name THEN #rownum := 1 -- reset on diff name
ELSE #rownum := #rownum + 1
END AS rank,
#car_name := t.name -- necessary to set #car_name for the comparison
FROM CARS t
JOIN (SELECT #rownum := NULL, #car_name := '') r
ORDER BY t.name, t.status DESC) x --ORDER BY is necessary for rank value
WHERE x.rank = 1
Ordering by status DESC means that "Showroom" will be at the top of the list, so it'll be ranked as 1. If the car name doesn't have a "Showroom" status, the row ranked as 1 will be whatever status comes after "Showroom". The WHERE clause will only return the first row for each car in the table.
The status being a text based data type tells me your data is not normalized - I could add records with "Showroom", "SHOWroom", and "showROOM". They'd be valid, but you're looking at using functions like LOWER & UPPER when you are grouping things for counting, sum, etc. The use of functions would also render an index on the column useless... You'll want to consider making a CAR_STATUS_TYPE_CODE table, and use a foreign key relationship to make sure bad data doesn't get into your table:
DROP TABLE IF EXISTS `example`.`car_status_type_code`;
CREATE TABLE `example`.`car_status_type_code` (
`car_status_type_code_id` int(10) unsigned NOT NULL auto_increment,
`description` varchar(45) NOT NULL default '',
PRIMARY KEY (`car_status_type_code_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Get last item in a table - SQL

I have a History Table in SQL Server that basically tracks an item through a process. The item has some fixed fields that don't change throughout the process, but has a few other fields including status and Id which increment as the steps of the process increase.
Basically I want to retrieve the last step for each item given a Batch Reference. So if I do a
Select * from HistoryTable where BatchRef = #BatchRef
It will return all the steps for all the items in the batch - eg
Id Status BatchRef ItemCount
1 1 Batch001 100
1 2 Batch001 110
2 1 Batch001 60
2 2 Batch001 100
But what I really want is:
Id Status BatchRef ItemCount
1 2 Batch001 110
2 2 Batch001 100
Edit: Appologies - can't seem to get the TABLE tags to work with Markdown - followed the help to the letter, and looks fine in the preview
Assuming you have an identity column in the table...
select
top 1 <fields>
from
HistoryTable
where
BatchRef = #BatchRef
order by
<IdentityColumn> DESC
It's kind of hard to make sense of your table design - I think SO ate your delimiters.
The basic way of handling this is to GROUP BY your fixed fields, and select a MAX (or MIN) for some unqiue value (a datetime usually works well). In your case, I think that the GROUP BY would be BatchRef and ItemCount, and Id will be your unique column.
Then, join back to the table to get all columns. Something like:
SELECT *
FROM HistoryTable
JOIN (
SELECT
MAX(Id) as Id.
BatchRef,
ItemCount
FROM HsitoryTable
WHERE
BacthRef = #batchRef
GROUP BY
BatchRef,
ItemCount
) as Latest ON
HistoryTable.Id = Latest.Id
Assuming the Item Ids are incrementally numbered:
--Declare a temp table to hold the last step for each item id
DECLARE #LastStepForEach TABLE (
Id int,
Status int,
BatchRef char(10),
ItemCount int)
--Loop counter
DECLARE #count INT;
SET #count = 0;
--Loop through all of the items
WHILE (#count < (SELECT MAX(Id) FROM HistoryTable WHERE BatchRef = #BatchRef))
BEGIN
SET #count = #count + 1;
INSERT INTO #LastStepForEach (Id, Status, BatchRef, ItemCount)
SELECT Id, Status, BatchRef, ItemCount
FROM HistoryTable
WHERE BatchRef = #BatchRef
AND Id = #count
AND Status =
(
SELECT MAX(Status)
FROM HistoryTable
WHERE BatchRef = #BatchRef
AND Id = #count
)
END
SELECT *
FROM #LastStepForEach
SELECT id, status, BatchRef, MAX(itemcount) AS maxItemcount
FROM HistoryTable GROUP BY id, status, BatchRef
HAVING status > 1
It's a bit hard to decypher your data the way WMD has formatted it, but you can pull of the sort of trick you need with common table expressions on SQL 2005:
with LastBatches as (
select Batch, max(Id)
from HistoryTable
group by Batch
)
select *
from HistoryTable h
join LastBatches b on b.Batch = h.Batch and b.Id = h.Id
Or a subquery (assuming the group by in the subquery works - off the top of my head I don't recall):
select *
from HistoryTable h
join (
select Batch, max(Id)
from HistoryTable
group by Batch
) b on b.Batch = h.Batch and b.Id = h.Id
Edit: I was assuming you wanted the last item for every batch. If you just need it for the one batch then the other answers (doing a top 1 and ordering descending) are the way to go.
As already suggested you probably want to reorder your query to sort it in the other direction so you actually fetch the first row. Then you'd probably want to use something like
SELECT TOP 1 ...
if you're using MSSQL 2k or earlier, or the SQL compliant variant
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY key ASC) AS rownumber,
columns
FROM tablename
) AS foo
WHERE rownumber = n
for any other version (or for other database systems that support the standard notation), or
SELECT ... LIMIT 1 OFFSET 0
for some other variants without the standard SQL support.
See also this question for some additional discussion around selecting rows. Using the aggregate function max() might or might not be faster depending on whether calculating the value requires a table scan.