An Alternative to OFFSET... FETCH NEXT in SQL Server 2008 - sql-server

I have a posts table in SQL Server and I need to select (say) the first 10 rows ordered by the count of their upvotes, here is the DB script:
create database someforum
go
use someforum
go
create table users (
user_id int identity primary key,
username varchar(80) unique not null
);
create table posts (
post_id int identity primary key,
post_time datetime,
post_title nvarchar(32),
post_body nvarchar(255),
post_user int foreign key references users(user_id)
);
create table votes (
vote_id int identity primary key,
user_id int foreign key references users(user_id),
vote_type bit, --upvote=true downvote=false
post_id int foreign key references posts(post_id)
);
insert into users values ('foo'),('bar')
insert into posts values
(getdate(),N'a post by foo',N'hey',1),
(getdate(),N'a post by bar',N'hey!',2)
insert into votes values (1,0,1),(2,0,1),(1,1,2),(2,1,2) --first post downvoted by its poster (foo) and bar, second post was upvoted by both users
I need an efficient query to select the next top 10 rows from Posts based on count of upvotes. How can I achieve this in SQL Server 2008?
Important edit: I stupidly forgot to mention that I was using SQL Server 2008 R2 to which OFFSET... FETCH NEXT wasn't introduced yet. I also edited out what is currently irrelevant to my needs.

Here's what I wanted (without using the score column):
select top 10 p.post_title,sum(case when vote_type=1 then 1 else -1 end) as score
from posts p join votes v on p.post_id = v.post_id
group by p.post_title
And as to the alternative to OFFSET… FETCH NEXT, I found a great solution in DBA

There is no "best"; but a working command might involve select top 10 ... order by Score desc. I realize that your posts tables doesn't have a Score column (that aggregates and denormalizes the votes), but: you can change that
the OFFSET / FETCH clause

You could use a gridView object to display the results: this will allow users to sort on various columns with the minimum of code on your part and also allow pagination, the inclusion of numbered links at the bottom of the gridView allowing users to move through the list of results.
Using a gridView with 10 rows will allow the display of your top 10 and users will also have the option of moving through the rest of the sorted list.

1) You can calcucalte and filter by this query
SELECT * FROM (
SELECT *, COUNT(*) as upvotes FROM posts AS p INNER JOIN votes AS v ON (p.post_id = v.post_id) WHERE v.type = true
) as v_post OFFSET 10 ROWS
2) You can shift post by step count (10 at now) in the end of query: FETCH NEXT 10, FETCH NEXT 20 etc.

Related

Autoincrement column based on query

I'm trying a really novice MS Access form (linked to) with a SQL Server in the backend.
So I have a simple table for invoices created with
create table Invoice
(
ID INT not null primary key,
IssuerID int not null,
InvoiceID int not null,
CustomerID int not null
)
Maybe I even do not need an ID, since I can uniquely identify entries by pairs (IssuerID, InvoiceID).
All in all, I want to add invoice by IssuerID to some CustomerID and InvoiceID should naturally increment by 1. I can do something like
SELECT ISNULL(MAX(InvoiceID), 0) + 1 AS InvoiceID
FROM Invoice
WHERE IssuerID = #sp;
where #sp is some value.
In the UI logic, I have a combobox to pick IssuerID (ACCESS) and here my problem begin.
I've used a textbox for InvoiceID. I want it to update the table by the next ID (i.e. if IssuerID = 5, had 3 issued invoices, next invoice should be numbered 4).
I was not able to make direct SQL work, nor by using VBA in after_update on IssuerID combo.
So I've created a view (in SQL Server and linked to it) with all possible IssuerIds and their appropriate nextInvoiceIds.
SELECT
IssuerID, NextInvoiceID
FROM
(SELECT
Issuers.ID AS IssuerID,
ISNULL(MAX(Invoice.InvoiceID), 0) + 1 AS NextInvoiceID
FROM
Issuers
FULL OUTER JOIN
Invoice ON Issuers.ID = Invoice.IssuerID
GROUP BY
Issuers.ID) AS UNDATA
Next, I was able to pick nextInvoiceID, but now I need it to also update the actual column back.
Is it possible from the same textbox?
Should I instead fallback to update this (InvoiceID) with some trigger (or are there other auto-increment options available in SQL Server)?

SQL Server FullText Search with Weighted Columns from Previous One Column

In the database on which I am attempting to create a FullText Search I need to construct a table with its column names coming from one column in a previous table. In my current implementation attempt the FullText indexing is completed on the first table Data and the search for the phrase is done there, then the second table with the search results is made.
The schema for the database is
**Players**
Id
PlayerName
Blacklisted
...
**Details**
Id
Name -> FirstName, LastName, Team, Substitute, ...
...
**Data**
Id
DetailId
PlayerId
Content
DetailId in the table Data relates to Id in Details, and PlayerId relates to Id in Players. If there are 1k rows in Players and 20 rows in Details, then there are 20k rows in Data.
WITH RankedPlayers AS
(
SELECT PlayerID, SUM(KT.[RANK]) AS Rnk
FROM Data c
INNER JOIN FREETEXTTABLE(dbo.Data, Content, '"Some phrase like team name and player name"')
AS KT ON c. DataID = KT.[KEY]
GROUP BY c.PlayerID
)
…
Then a table is made by selecting the rows in one column. Similar to a pivot.
…
SELECT rc.Rnk,
c.PlayerID,
PlayerName,
TeamID,
…
(SELECT Content FROM dbo.Data data WHERE DetailID = 1 AND data.PlayerID = c.PlayerID) AS [TeamName],
…
FROM dbo.Players c
JOIN RankedPlayers rc ON c. PlayerID = rc. PlayerID
ORDER BY rc.Rnk DESC
I can return a ranked table with this implementation, the aim however is to be able to produce results from weighted columns, so say the column Playername contributes to the rank more than say TeamName.
I have tried making a schema bound view with a pivot, but then I cannot index it because of the pivot. I have tried making a view of that view, but it seems the metadata is inherited, plus that feels like a clunky method.
I then tried to do it as a straight query using sub queries in the select statement, but cannot due to indexing not liking sub queries.
I then tried to join multiple times, again the index on the view doesn't like self-referencing joins.
How to do this?
I have come across this article http://developmentnow.com/2006/08/07/weighted-columns-in-sql-server-2005-full-text-search/ , and other articles here on weighted columns, however nothing as far as I can find addresses weighting columns when the columns were initially row data.
A simple solution that works really well. Put weight on the rows containing the required IDs in another table, left join that table to the table to which the full text search had been applied, and multiply the rank by the weight. Continue as previously implemented.
In code that comes out as
DECLARE #Weight TABLE
(
DetailID INT,
[Weight] FLOAT
);
INSERT INTO #Weight VALUES
(1, 0.80),
(2, 0.80),
(3, 0.50);
WITH RankedPlayers AS
(
SELECT PlayerID, SUM(KT.[RANK] * ISNULL(cw.[Weight], 0.10)) AS Rnk
FROM Data c
INNER JOIN FREETEXTTABLE(dbo.Data, Content, 'Karl Kognition C404') AS KT ON c.DataID = KT.[KEY]
LEFT JOIN #Weight cw ON c.DetailID = cw.DetailID
GROUP BY c.PlayerID
)
SELECT rc.Rnk,
...
I'm using a temporary table here for evidence of concept. I am considering adding a column Weights to the table Details to avoid an unnecessary table and left join.

Computed column expression

I have a specific need for a computed column called ProductCode
ProductId | SellerId | ProductCode
1 1 000001
2 1 000002
3 2 000001
4 1 000003
ProductId is identity, increments by 1.
SellerId is a foreign key.
So my computed column ProductCode must look how many products does Seller have and be in format 000000. The problem here is how to know which Sellers products to look for?
I've written have a TSQL which doesn't look how many products does a seller have
ALTER TABLE dbo.Product
ADD ProductCode AS RIGHT('000000' + CAST(ProductId AS VARCHAR(6)) , 6) PERSISTED
You cannot have a computed column based on data outside of the current row that is being updated. The best you can do to make this automatic is to create an after-trigger that queries the entire table to find the next value for the product code. But in order to make this work you'd have to use an exclusive table lock, which will utterly destroy concurrency, so it's not a good idea.
I also don't recommend using a view because it would have to calculate the ProductCode every time you read the table. This would be a huge performance-killer as well. By not saving the value in the database never to be touched again, your product codes would be subject to spurious changes (as in the case of perhaps deleting an erroneously-entered and never-used product).
Here's what I recommend instead. Create a new table:
dbo.SellerProductCode
SellerID LastProductCode
-------- ---------------
1 3
2 1
This table reliably records the last-used product code for each seller. On INSERT to your Product table, a trigger will update the LastProductCode in this table appropriately for all affected SellerIDs, and then update all the newly-inserted rows in the Product table with appropriate values. It might look something like the below.
See this trigger working in a Sql Fiddle
CREATE TRIGGER TR_Product_I ON dbo.Product FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE #LastProductCode TABLE (
SellerID int NOT NULL PRIMARY KEY CLUSTERED,
LastProductCode int NOT NULL
);
WITH ItemCounts AS (
SELECT
I.SellerID,
ItemCount = Count(*)
FROM
Inserted I
GROUP BY
I.SellerID
)
MERGE dbo.SellerProductCode C
USING ItemCounts I
ON C.SellerID = I.SellerID
WHEN NOT MATCHED BY TARGET THEN
INSERT (SellerID, LastProductCode)
VALUES (I.SellerID, I.ItemCount)
WHEN MATCHED THEN
UPDATE SET C.LastProductCode = C.LastProductCode + I.ItemCount
OUTPUT
Inserted.SellerID,
Inserted.LastProductCode
INTO #LastProductCode;
WITH P AS (
SELECT
NewProductCode =
L.LastProductCode + 1
- Row_Number() OVER (PARTITION BY I.SellerID ORDER BY P.ProductID DESC),
P.*
FROM
Inserted I
INNER JOIN dbo.Product P
ON I.ProductID = P.ProductID
INNER JOIN #LastProductCode L
ON P.SellerID = L.SellerID
)
UPDATE P
SET P.ProductCode = Right('00000' + Convert(varchar(6), P.NewProductCode), 6);
Note that this trigger works even if multiple rows are inserted. There is no need to preload the SellerProductCode table, either--new sellers will automatically be added. This will handle concurrency with few problems. If concurrency problems are encountered, proper locking hints can be added without deleterious effect as the table will remain very small and ROWLOCK can be used (except for the INSERT which will require a range lock).
Please do see the Sql Fiddle for working, tested code demonstrating the technique. Now you have real product codes that have no reason to ever change and will be reliable.
I would normally recommend using a view to do this type of calculation. The view could even be indexed if select performance is the most important factor (I see you're using persisted).
You cannot have a subquery in a computed column, which essentially means that you can only access the data in the current row. The only ways to get this count would be to use a user-defined function in your computed column, or triggers to update a non-computed column.
A view might look like the following:
create view ProductCodes as
select p.ProductId, p.SellerId,
(
select right('000000' + cast(count(*) as varchar(6)), 6)
from Product
where SellerID = p.SellerID
and ProductID <= p.ProductID
) as ProductCode
from Product p
One big caveat to your product numbering scheme, and a downfall for both the view and UDF options, is that we're relying upon a count of rows with a lower ProductId. This means that if a Product is inserted in the middle of the sequence, it would actually change the ProductCodes of existing Products with a higher ProductId. At that point, you must either:
Guarantee the sequencing of ProductId (identity alone does not do this)
Rely upon a different column that has a guaranteed sequence (still dubious, but maybe CreateDate?)
Use a trigger to get a count at insert which is then never changed.

Creating a partitioned view of detail tables when the CHECK is on the header tables

I've been reading documentation and looking at FAQs and haven't found an answer for this one which probably means it can't be done. My actual situation is a little more complex, but I'll try to simplify it for this question. For each of the past years, I have a header/detail tables with a foreign key linking them. The year datum is in the header records! I want to be able to query all tables concatenated across years.
I have set up views that follows a 'SELECT + UNION ALL' format. I've also put check constraints on the header tables to restrict their values to their respective year. This allows the SQL server query optimizer to only query specific tables when running a query that is restricted with a WHERE clause. Awesome. Up to this point, this information can be found anywhere and everywhere by searching for Partitioned Views.
I want to do the same sort of query optimization with the detail tables but can't figure it out. There is nothing in the detail record that indicates what year it belongs to without joining with the header record; Meaning, the foreign key constraint is the only constraint I have to go off of.
The only solution I've thought of is adding a 'year' column to the detail tables and then adding another where sub clause to the queries. Is there any thing I can do to create a partitioned view of the detail tables using the existing foreign key constraint?
Here is some DDL for reference:
CREATE TABLE header2008 (
hid INT PRIMARY KEY,
dt DATE CHECK ('2008-01-01' <= dt AND dt < '2009-01-01')
)
CREATE TABLE header2009 (
hid INT PRIMARY KEY,
dt DATE CHECK ('2009-01-01' <= dt AND dt < '2010-01-01')
)
CREATE TABLE detail2008 (
did INT PRIMARY KEY,
hid INT FOREIGN KEY REFERENCES header2008(hid),
value INT
)
CREATE TABLE detail2009 (
did INT PRIMARY KEY,
hid INT FOREIGN KEY REFERENCES header2009(hid),
value INT
)
GO
CREATE VIEW headerAll AS
SELECT * FROM header2008 UNION ALL
SELECT * FROM header2009
GO
CREATE VIEW detailAll AS
SELECT * FROM detail2008 UNION ALL
SELECT * FROM detail2009
GO
--This only hits the header2008 table (GOOD)
SELECT *
FROM headerAll h
WHERE dt = '2008-04-04'
--This hits the header2008, detail2008, and detail 2009 tables. (BAD)
SELECT *
FROM headerAll h
INNER JOIN detailAll d ON h.hid = d.hid
WHERE dt = '2008-04-04'
Since you're not going for partitioned tables, I'm assuming you can't target 2005+ Enterprise Edition or higher.
Here is an alternative to adding a new physical column to your tables:
CREATE VIEW detailAll AS
SELECT 2008 AS Year, * FROM detail2008
UNION ALL
SELECT 2009, * FROM detail2009
then,
SELECT *
FROM headerAll h
INNER JOIN detailAll d ON h.hid = d.hid
WHERE dt = '2008-04-04' AND d.Year = 2008
Before you run off and implement this, there is a catch; well, two catches actually.
This solution, like the headerAll view as it's written, cannot accommodate parameters on the partitioning column and still do partition elimination. Using a search predicate of WHERE dt = #date AND d.Year = YEAR(#date) causes table scans across all tables in both views because the query optimizer assumes #date is an arbitrary value (and there's no way to fix that). This is a recipe for a performance disaster if the view is exposed publicly in your database API: there is no restriction on parameterization in queries, and most query authors and ORMs tend to use parameterized queries wherever possible (it's almost always a good thing!).
To get the views to do partition elimination in a real application, you will have to resort to dynamic string execution. How you accomplish this will depend on your business requirements, data requirements, and application architecture. It will be a bit trickier if you're grabbing data from multiple years.
Note also that using dynamic string execution would allow you to write queries directly against the base tables instead of introducing a UNIONed view for each "table". I don't think there's anything wrong with the latter, but this is an option you may not have considered.

Some basic questions on database design and how to insert accordingly with LINQ to Entities

Ok, I am total newbie so bear with me.
Trying to implement an ordering system and wish
to save the orders to the database with LINQ to Entities. I can do it now
but for each new object that is saved to the orders table
a new row is inserted, with new OrderNo for each ProductID where as I obviously
should be able to have multiple ProductID's for each OrderNo.
Everything is very simplified as I am just testing.
I have an orders table with columns as such:
OrderNo PK, Identity specification
Line int PL
ProductID int
and a products table
ProductID int PK
An order entity object is instantiated and its properties
are populated with data from a form which is posted to an action method.
It is then saved to the orders table with the following code:
(DropDownList1Value) has value of an existing ProductID and "DropDownList1Value" is the id of the DropDownList element in view.
[HttpPost]
public ActionResult OrderProcessor()
{
int productId = int.Parse(Request.Form["DropDownList1Value"]);
var order = new Order();
order.ProductID = productId;
context.Orders.AddObject(order);
context.SaveChanges();
return View(order);
}
So the records that are inserted look as such:
Sorry, couldn't line up the values under their respective column name in this editor.
OrderNo Line ProductID
101 0 3
102 0 5
103 0 2
Where as I want something like this:
OrderNo Line ProductID
101 1 3
101 2 5
101 3 2
102 1 2
So I wish to know how can I modify the orders table so it
can have multiple records with same "OrderNo" and just increment for "Line" for diff ProductID's and how do I go about inserting such records with LinQ to Entities where
I will obviously have many ProductId from multiple DropDowLists
and they will all be for the one order.
Currently I have foreign key dependency on ProductID in Products table,
so no record in the Orders table can have ProductID which does not exists in the Products table.
I need to make the table depend on the whole key that is OrderNo + Line
and have the "Line" auto increment.
Or is there a much better way of implementing of what I am after here?
Thanks.
Let me first explain briefly what I understood.
There is an invoice, which contains several products for one order number.
and this is how your invoice looks like:
Order Number: 101
------------------
Sl. Products
1 3
2 5
3 2
Before answering I want to point out that you are taking OrderId from a form (That is from client side) This is a wrong and INSECURE approach. Let the order id be AutoGenerated by database.
I would suggest to tweak your database design a little.
Here is a solution that will work.
Note: I am consedering your database support Auto-Increment, for MS SQL replace it with IDENTITY, for Oracle you need to create a sequence.
Product (
id INT PK AUTO-INCREMENT
);
Order (
id INT PK AUTO-INCREMENT
user-id INT FK # user who purchased
### and other common details Like date of purchase etc.
);
Order-Detail (
id INT PK AUTO-INCREMENT
order-id INT FK # Common order id
pdt-id INT FK # product which was purchased.
);
When you make a purchase:
1. Insert a row in order table
2. Fetch the last inserted id
3. Insert order-id from last step and products which are purchased in Order-Detail table,
Fetch all the orders made by a user:
1. Read from order table.
Fetch all products purchased for an order:
1. Fetch details from Order-Detail
Note: You will get List of products purchased, Use Order-detail.id as "Line"
EDIT:
Thanx to HLGEM's comment
If you think price of a product may change then instead of updating the price add a new row to the table (and flag the old table so that it wont be visible, you can also have a column in new table pointing to old table), thus old purchase will point to old product and new orders will point to updated (new) row.
There is one more approach this problem:
store the current cost of product in order-detail table.
If you are facing difficulty understanding above solution here is another and simpler one.
In Order table, Make a composite primary key including OrderNo and Line.
Whenever inserting into database you will need to generate line number in your code, which you can do by runnign a loop over array of propduct being purchased.
I think it would be better to split your current Order table into two separate tables:
Order table
(PK, Identity specification) OrderId
Perhaps other fields like Invoice address, Delivery address, etc.
OrderLine table
(PK, Identity specification) OrderLineId
(FK to Order table) OrderId
(FK to Product table) ProductId
For both tables you have an Entity in your class model: class Order and class OrderLine and a one-to-many relationship between them, so Order has a collection of OrderLines.
Creating an order with all order lines would then look like this:
var order = new Order();
foreach (var item in collection)
{
var orderLine = new OrderLine()
// Get productId from your DropDownLists
orderLine.ProductId = productId;
order.OrderLines.Add(orderLine);
}
context.Orders.AddObject(order);
context.SaveChanges();
Edit
The MVC MusicStore Tutorial might also help for the first steps to create an order processing system with ASP.NET MVC and Entity Framework. It contains classes for orders and order details (among others) and explains their relationships.

Resources