问题
I have scenario where I have to get last 10 dates when books are sold. Consider below example
Store Book
---------- --------------------
Id Name Id Name Sid Count Date
1 ABC 1 XYZ 1 20 11/11/2015
2 DEF 2 JHG 1 10 11/11/2015
3 UYH 1 10 15/11/2015
4 TRE 1 50 17/11/2015
I have get result based on stored.Id. When I pass the ID, the report should be generated with bookname sold date and count of sold copies.
Output:
BookName 11/11/2015 15/11/2015 17/11/2015
XYZ 20 -- --
JHG 10 -- --
UYH -- 10 --
TRE -- -- 50
Hope my question is understandable.
回答1:
This looks unsuspicious, but it's a hell of a question.
Assumptions
- Your counts are
integer. - All columns in table book are defined
NOT NULL. (name, sid, date)is defined unique in tablebook. Preferably (for performance) with columns in this order:UNIQUE(sid, date, name)Details:
Multicolumn index and performance
- Is a composite index also good for queries on the first field?
crosstab() queries
To get top performance and short query strings (especially if you run this query often) I suggest the additional module tablefunc providing various crosstab() functions. Basic instructions:
- PostgreSQL Crosstab Query
Basic queries
You need to get these right first.
The last 10 days:
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY 1 DESC
LIMIT 10;
Numbers for last 10 days:
SELECT name, date, count
FROM book
WHERE sid = 1
AND date >= (
SELECT DISTINCT date
FROM book
ORDER BY 1 DESC
OFFSET 9
LIMIT 1
)
ORDER BY 1,2;
Column names for output columns (for full solution):
SELECT 'bookname, "' || string_agg(to_char(date, 'DD/MM/YYYY'), '", "') || '"'
FROM (
SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY 1 DESC
LIMIT 10
) sub;
SQL Fiddle demonstrating SQL parts. (The additional module tablefunc is not installed there.)
Simple result with static column names
This may be good enough for you (but you don't see actual dates in the result):
SELECT * FROM crosstab(
'SELECT name, date, count
FROM book
WHERE sid = 1
AND date >= (
SELECT DISTINCT date FROM book
ORDER BY 1 DESC
OFFSET 9 LIMIT 1
)
ORDER BY 1,2'
, 'SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY 1 DESC
LIMIT 10'
) AS (bookname text
, date1 int, date2 int, date3 int, date4 int, date5 int
, date6 int, date7 int, date8 int, date9 int, date10 int);
For repeated use I suggest you create this (very fast) generic C function for 10 integer columns once, to simplify things a bit:
CREATE OR REPLACE FUNCTION crosstab_int10(text, text)
RETURNS TABLE (bookname
, date1, date2, date3, date4, date5
, date6, date7, date8, date9, date10) AS
'$libdir/tablefunc','crosstab_hash' LANGUAGE C STABLE STRICT;
Details in this related answer:
- Dynamically generate columns for crosstab in PostgreSQL
Then your call becomes:
SELECT * FROM crosstab_int10(
'SELECT name, date, count
FROM book
WHERE sid = 1
AND date >= (
SELECT DISTINCT date FROM book
ORDER BY 1 DESC
OFFSET 9 LIMIT 1
)
ORDER BY 1,2'
, 'SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY 1 DESC
LIMIT 10'
); -- no column definition list required!
Full solution with dynamic column names
Your actual question is more complicated, you also want dynamic column names.
For a given table, the resulting query could look like this then:
SELECT * FROM crosstab_int10(
'SELECT name, date, count
FROM book
WHERE sid = 1
AND date >= (
SELECT DISTINCT date FROM book
ORDER BY 1 DESC
OFFSET 9 LIMIT 1
)
ORDER BY 1,2'
, 'SELECT DISTINCT date
FROM book
WHERE sid = 1
ORDER BY 1 DESC
LIMIT 10'
) AS t(bookname
, "17/11/2015", "15/11/2015", "11/11/2015", "10/11/2015"
, "09/11/2015", "08/11/2015", "07/11/2015", "06/11/2015"
, "05/11/2015", "04/11/2015");
The difficulty is to distill dynamic column names. Either assemble the query string by hand, or (much rather) let this function do it for you:
CREATE OR REPLACE FUNCTION f_generate_date10_sql(_sid int = 1)
RETURNS text AS
$func$
SELECT format(
$$SELECT * FROM crosstab_int10(
'SELECT name, date, count
FROM book
WHERE sid = %1$s
AND date >= (
SELECT DISTINCT date FROM book
ORDER BY 1 DESC
OFFSET 9 LIMIT 1
)
ORDER BY 1,2'
, 'SELECT DISTINCT date
FROM book
WHERE sid = %1$s
ORDER BY 1 DESC
LIMIT 10'
) AS ct(bookname, "$$
|| string_agg(to_char(date, 'DD/MM/YYYY'), '", "') || '")'
, _sid)
FROM (
SELECT DISTINCT date
FROM book
ORDER BY 1 DESC
LIMIT 10
) sub
$func$ LANGUAGE sql;
Call:
SELECT f_generate_date10_sql(1);
This generates the desired query, which you execute in turn.
来源:https://stackoverflow.com/questions/32905410/get-records-for-last-10-dates