Get records for last 10 dates

孤街浪徒 提交于 2019-12-22 13:52:50

问题


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 table book. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!