Author Archive
SQL Server: random records avoiding CTE reevaluation
Comments enabled. I *really* need your comment
From Stack Overflow:
I need to get 5 random records from a table plus a further record based on data from the users preferences.
How can I combine the two statements whilst ensuring that no results are repeated (i. e. the favourite is not present in the 5 random records)?
We should select the 6th record so that it would satisfy two conditions: first, it should be preferred by user, second, it should not be among the 5 random records selected earlier.
The problem with random records it that they are, um, random. We usually cannot reproduce 5 random records to apply further conditions on them.
That's where the CTEs would come handy, but there's a little problem.
A CTE, as Microsoft understands them, is just an alias for an online view.
Therefore, each time a CTE is used in a query, it may (or may not) be reevaluated.
It's not a problem for a plain CTEs which are built out of static columns.
However, this is totally a problem when we try to UNION
two queries using an undeterministic CTE (like, ordered by NEWID()
), as we would do in this case.
Let's create a sample table and see what happens:
Dynamic pivot
Comments enabled. I *really* need your comment
From Stack Overflow:
I have a table and want to transpose its rows to columns, similar to a pivot table but without summarising.
For example I have the following tables:
Question (QuestionID, QuestionText) Response (ResponseID, ResponseText, QuestionID)Basically I want to be able to create a dynamic table something like:
Question 1 Question 2 Question 3 Response 1.1 Response 1.2 Response 1.3 Response 2.1 Response 2.2 Response 2.3 Response 3.1 Response 3.2 Response 3.3 Response 4.1 Response 4.2 Response 4.3 The main requirement would be I don't know at design time what the question text will be.
First, let's put it straight: SQL is not a right tool to do this.
SQL operates on sets. An SQL query is a functions that returns a set. And the column layout is an implicit property of a set. It should be defined in design time. This is the way SQL works.
However, there are several approaches that can simplify the task. One of them is using SQL Server's XML abilities.
To arrange the answers in a table, we need to make the following:
- Select all questions. This rowset will define the columns of our table.
- Select the row numbers for all answers for the question that has the most answers. This rowset will define the rows of our table. The number of rows wil be exactly as the number of answers to the most answered question.
- Assign the ordinal number to each answer on any question. This is done using
ROW_NUMBER()
. This value will uniquely define the position of the answer in a table. - For each row (taken from the rowset made on step 2), take all the questions and
LEFT JOIN
the answers that need to be put into this row, using the ordinal number calculated on the step 3 as aLEFT JOIN
condition. This will put either the answer or an empty string into the corresponding cell. - Transform the rowset retrieved on step above into an XML string made out of
<td>
's and enclosed into a<tr>
- Transform the resulting rowset into an XML made out of the XML's from the previous step and enclosed into a
<table>
Let's create a sample table and see how can we do this:
MySQL: selecting rows before and after filtered one
Comments enabled. I *really* need your comment
From Stack Overflow:
I have a basic MySQL table,
terms
, comprised of anid
andterm
field.I want to create an alphabetically sorted dictionary index (in the literal sense), that would list ten 10 terms above the selected term, and 20 below it
An example of this could be found on Urban Dictionary where on the left column you see the current term highlighted, and a number of terms above it, and some below, all sorted alphabetically.
As we all know, MySQL doesn't support a
ROW_NUMBER()
or a similar function so we end up resorting to user variables and sub-selects.I also cannot create a view with user defined variables because MySQL doesn't allow that.
MySQL indeed doesn't support ROW_NUMBER()
and this function indeed can be emulated using session variables:
However, it will be an overkill in this case, since a more simple and more efficient set-based solution using LIMIT
can be used instead.
Let's create a sample table and see how:
MySQL: grouping continuous ranges
From Stack Overflow:
Given the following table:
ID State Date 12 1 2009-07-16 10:00 45 2 2009-07-16 13:00 67 2 2009-07-16 14:40 77 1 2009-07-16 15:00 89 1 2009-07-16 15:30 99 1 2009-07-16 16:00 , how can I group by the field
state
, while still maintaining the borders between the state changes?I need
MIN(id)
,MIN(date)
andCOUNT(*)
for each group, and this is expected:
ID State Date Count 12 1 2009-07-16 10:00 1 45 2 2009-07-16 13:00 2 77 1 2009-07-16 15:00 3
Though a pure set-based solution exists, in MySQL, this can be easily done using session variables, which is more efficient.
Let's create a sample table:
PostgreSQL: last 12 weeks of a year
Comments enabled. I *really* need your comment
From Stack Overflow:
I have a query like this:
SELECT EXTRACT(WEEK FROM j.updated_at) as "week", count(j.id) FROM jobs WHERE EXTRACT(YEAR FROM j.updated_at)=2009 GROUP BY EXTRACT(WEEK FROM j.updated_at) ORDER BY week, which works fine, but I only want to show the last 12 weeks.
LIMIT 12
works, but only gives me the first twelve and I need the order to be in sequential week order (i. e. not reversed) for charting purposes.
To select last 12 rows in ascending order it's enough to select first 12 rows in descending order and resort them in a subquery:
SELECT * FROM ( SELECT EXTRACT(week FROM updated_at) AS week, COUNT(*) AS cnt FROM jobs WHERE EXTRACT(year FROM updated_at) = 2008 GROUP BY week ORDER BY week DESC LIMIT 12 ) q ORDER BY week
However, this is rather inefficient. This query selects and aggregate all the year data just to fetch 12 last weeks.
More than that, EXTRACT(year FROM updated)
is not a sargable predicate, and all table rows (or index rows) need to be examined.
On a sample table of 1,000,000 rows, this query runs for more that 3.5 seconds:
SELECT * FROM ( SELECT EXTRACT(week FROM updated) AS week, COUNT(*) AS cnt FROM t_week WHERE EXTRACT(year FROM updated) = 2008 GROUP BY week ORDER BY week DESC LIMIT 12 ) q ORDER BY week
week | cnt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
41 | 3584 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
42 | 3467 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
43 | 3499 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
44 | 3535 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
45 | 3511 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
46 | 3621 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
47 | 3529 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
48 | 3500 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
49 | 3415 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
50 | 3536 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
51 | 3504 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
52 | 3479 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
12 rows fetched in 0.0004s (3.5843s) |
Sort (cost=58517.44..58517.47 rows=11 width=16) Sort Key: (date_part('week'::text, t_week.updated)) -> Limit (cost=58517.11..58517.14 rows=11 width=8) -> Sort (cost=58517.11..58517.14 rows=11 width=8) Sort Key: (date_part('week'::text, t_week.updated)) -> HashAggregate (cost=58516.75..58516.92 rows=11 width=8) -> Seq Scan on t_week (cost=0.00..58491.75 rows=5000 width=8) Filter: (date_part('year'::text, updated) = 2008::double precision)
However, this query can be easily improved. For each year, we can easily calculate the beginning and the end of each of last 12 weeks, and use these values in a more index-friendly query.
Let's create a sample table and see how to do this:
Hierarchial queries in MySQL: identifying trees
Comments enabled. I *really* need your comment
Continuing on the theme of hierarchical queries in MySQL:
- Hierarchical queries in MySQL
- Hierarchical queries in MySQL: adding level
- Hierarchical queries in MySQL: adding ancestry chains.
- Hierarchical queries in MySQL: finding leaves
- Hierarchical queries in MySQL: finding loops
- Hierarchical data in MySQL: parents and children in one query
Assume we have a table with hierarchical structure like this:
treeitem | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
9 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
10 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
11 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
12 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
13 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
14 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
5 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
15 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
16 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
17 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
18 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
7 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
8 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
18 rows fetched in 0.0003s (0.0137s) |
We have two trees here: one starting from 1, another one starting from 2.
The problem is: given any item, we should identify the whole tree this item belongs to, and return the whole tree in the hierarchical order.
This also can be easily done using hierarchical queries in MySQL.
In this article: Hierarchical queries in MySQL I shown how to implement a function that returnes tree items in correct order, being called sequentially.
This function is reentrable and keeps its state in session variables, one of which, @start_with
, defines the parent element for the tree we want to build.
We have two problems here:
- Given an item, define a root of the tree it belongs to
- Build a whole tree, starting from the root
The first problem can be solved by iterating the linked list backwards, starting from the variable given as an input.
This article:
describes how to do it in great detail, that's why I'll just put a query here.
Let's create the table described above:
PostgreSQL: round-robin order
Comments enabled. I *really* need your comment
From Stack Overflow:
I have a bunch of URLs stored in a table waiting to be scraped by a script.
However, many of those URLs are from the same site. I would like to return those URLs in a
site-friendlyorder (that is, try to avoid two URLs from the same site in a row) so I won't be accidentally blocked by making too many HTTP requests in a short time.The database layout is something like this:
create table urls ( site varchar, -- holds e.g. www.example.com or stockoverflow.com url varchar unique );Example result:
SELECT url FROM urls ORDER BY mysterious_round_robin_function(site)
url http://www.example.com/some/file http://stackoverflow.com/questions/ask http://use.perl.org/ http://www.example.com/some/other/file http://stackoverflow.com/tags
To solve this task, we just need to assign a number to an URL within each site, and order by this number first, then by site, like this:
number | site | url |
---|---|---|
1 | example.com | http://www.example.com/some/file |
1 | stackoverflow.com | http://stackoverflow.com/questions/ask |
1 | use.perl.org | http://use.perl.org/ |
2 | example.com | http://www.example.com/some/other/file |
2 | stackoverflow.com | http://stackoverflow.com/tags |
Good news: in new PostgreSQL 8.4, we just can use a window function to do this:
SELECT url FROM urls ORDER BY ROW_NUMBER() OVER (PARTITION BY site ORDER BY url), site
url | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a.example.com/url104 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
b.example.com/url1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
c.example.com/url106 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
y.example.com/url102 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
z.example.com/url103 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
a.example.com/url130 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
b.example.com/url105 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
c.example.com/url132 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
l.example.com/url999 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
m.example.com/url974 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1000 rows fetched in 0.0169s (0.0178s) |
Yes, that simple.
Bad news: in PostgreSQL 8.3 and below, we still need to hack.
Let's create a sample table and see how:
Hierarchical data in MySQL: parents and children in one query
Answering questions asked on the site.
Michael asks:
I was wondering how to implement a hierarchical query in MySQL (using the ancestry chains version) for a single row, such that it picks up the parents (if any) and any children (if any).
The idea is, I want to be able to jump in at any point, provide an Id of some sort, and be able to draw out the entire hierarchy for that Id, both upwards and downwards.
We need to combine two queries here:
- Original hierarchical query that returns all descendants of a given
id
(a descendancy chain) - A query that would return all ancestors of a given
id
(an ancestry chain)
An id
can have only one parent
, that's why we can employ a linked list technique to build an ancestry chain, like shown in this article:
Here's the query to to this (no functions required):
SELECT @r AS _id, ( SELECT @r := parent FROM t_hierarchy WHERE id = _id ) AS parent, @l := @l + 1 AS lvl FROM ( SELECT @r := 1218, @l := 0, @cl := 0 ) vars, t_hierarchy h WHERE @r <> 0
To combine two queries, we can employ a simple UNION ALL
.
The only problem that is left to preserve the correct level
, since the ancestry chain query conts level
backwards, and the hierarchical query will count it starting from zero.
Let's create a sample table and see what we get:
Read the rest of this entry »
PostgreSQL 8.4: sampling random rows
On Jul 1, 2009, PostgreSQL 8.4 was released.
In this series of articles, I'd like to show how to reimplement some tasks I wrote about in the previous blog posts using new PostgreSQL features.
Other articles on new features of PostgreSQL 8.4:
Today, I'll show a way to sample random rows from a PRIMARY KEY preserved table.
Usually, if we need, say, 10 random rows from a table, we issue this query:
SELECT * FROM t_random ORDER BY RANDOM() LIMIT 10
PostgreSQL heavily optimizes this query, since it sees a LIMIT
condition and does not sort all rows. Instead, it just keeps a running buffer which contains at most 10 rows with the least values or RANDOM
calculated so far, and when a row small enough is met, it sorts only this buffer, not the whole set.
This is quite efficient, but still requires a full table scan.
This can be a problem, since the queries like that are often run frequently on heavily loaded sites (like for showing 10 random pages on social bookmarking systems), and full table scans will hamper performance significantly.
With new PosgreSQL 8.4 abilities to run recursive queries, this can be improved.
We can sample random values of the row id
s and use an array to record previously selected values.
Let's create a sample table and see how can we imrove this query:
Read the rest of this entry »
PostgreSQL 8.4: preserving order for hierarchical query
On Jul 1, 2009, PostgreSQL 8.4 was released.
In this series of articles, I'd like to show how to reimplement some tasks I wrote about in the previous blog posts using new PostgreSQL features.
Previously in the series:
Now, let's see how we can implement the hierarchical queries using the new features of PostgreSQL 8.4.
In PostgreSQL 8.3, we had to create a recursive function to do that. If you are still bound to 8.3 or an earlier version, you can read this article to see how to do it:
In 8.4, we have recursive CTE's (common table expressions).
Let's create a sample hierarchical table and see how can we query it:
Read the rest of this entry »