筆記:如何使用postgresql做順序扣減庫存

如何使用postgresql做順序扣減庫存

Ⅰ.廢話在前面

  首先這篇筆記源自於最近的一次需求,這個臨時性需求是根據兩份數據(庫存數據以及出庫數據) 算出實際庫存給到業務,至於庫存爲什麼不等於剩餘庫存,這個一兩句話也說不清(主要是我不懂。。。😓),算出來的實際庫存是以產品&批次爲主展示實際庫存(庫存按日期分批次不求總),所以給的出庫數據(需要扣減的)一個按產品代碼彙總的數據,順帶一提的是兩張表是以產品代碼連接的 ; 最終,算出來的實際庫存除了會有庫存表日期和數量外還得有 扣減數量列 以及 扣減後數量(實際庫存),扣減順序是按照批次的日期升序扣減,批次日期爲空的首先扣減(需考慮到排序);還有就是:沒有任何扣減數量(沒有出庫的)的產品 最終的 扣減後數量(批次庫存數量-出庫數量) 爲庫存數量,扣減數爲零 ~
好了,我先給出測試的表數據以及最終結果的樣子,各位思考思考哈~😄

Ⅱ.表數據及實際庫存(結果)

  • 庫存數據
    select * from t_product_inventory;

    id type 產品代碼 日期 數量
    7 in 99999279 2018-11-01 24480
    8 in 99999279 2018-11-03 20832
    9 in 99999279 2018-12-02 21360
    10 in 99999279 2019-06-14 18768
    11 in 99999279 2019-06-16 9552
    12 in 99999279 2019-07-12 2304
    13 in 99999279 2019-09-05 3696
    14 in 99999279 2019-09-06 16
    15 in 99999279 2019-10-22 48
    16 in 99999279 2019-11-03 14112
    17 in 99999279 2019-12-02 2160
    18 in 99999279 2019-12-04 720
    19 in 99999279 2019-12-12 12960
    20 in 99999290 2019-12-23 6336
    21 in 99999290 2019-12-26 50
    29 in 99999777 2021-04-08 10011
  • 出庫數據
    select * from t_product_out;

    id type 產品代碼 數量
    1 out 99999279 77777
    2 out 99999290 10000
  • 實際庫存(結果)

    id-- --產品代碼-- --日期(庫存批次日期)-- --數量(庫存)-- --數量_出-- --出_入差異(出-庫存)
    7 99999279 24480 24480 0
    8 99999279 2018-11-03 20832 20832 0
    9 99999279 2018-12-02 21360 21360 0
    10 99999279 2019-06-14 18768 11105 7663
    11 99999279 2019-06-16 9552 0 9552
    12 99999279 2019-07-12 2304 0 2304
    13 99999279 2019-09-05 3696 0 3696
    14 99999279 2019-09-06 16 0 16
    15 99999279 2019-10-22 48 0 48
    16 99999279 2019-11-03 14112 0 14112
    17 99999279 2019-12-02 2160 0 2160
    18 99999279 2019-12-04 720 0 720
    19 99999279 2019-12-12 12960 0 12960
    20 99999290 2019-12-23 6336 6336 0
    21 99999290 2019-12-26 50 50 3614
    29 99999777 2021-04-08 10011 0 10011

Ⅲ.思考及實現😂

   首先要說sql的思考過程還是比較複雜滴(當然可以確定是我自跟兒寫的),而整個過程幾乎就是走一步看一步的解決問題的過程🤣,掉了多少頭髮可想而知了。。。

First.我們確定在sql中處理,那首先想到的是得有個連表吧,另外排序也會是最easy的吧😅,let me try ~

  SELECT 
  	i.id,
  	i.type,
  	i."產品代碼",
  	i."日期",
  	i."數量",
  	o."出_彙總"
  FROM t_product_inventory i
   LEFT JOIN ( 
   SELECT t_product_out."產品代碼",
  	sum(t_product_out."數量") AS "出_彙總"
  	FROM t_product_out
  	GROUP BY t_product_out."產品代碼"
  ) o ON i."產品代碼" = o."產品代碼"
  ORDER BY i."產品代碼", i."日期" NULLS FIRST;
id type 產品代碼 日期 數量 出_彙總
7 in 99999279 24480 77777
8 in 99999279 2018-11-03 20832 77777
9 in 99999279 2018-12-02 21360 77777
10 in 99999279 2019-06-14 18768 77777
11 in 99999279 2019-06-16 9552 77777
12 in 99999279 2019-07-12 2304 77777
13 in 99999279 2019-09-05 3696 77777
14 in 99999279 2019-09-06 16 77777
15 in 99999279 2019-10-22 48 77777
16 in 99999279 2019-11-03 14112 77777
17 in 99999279 2019-12-02 2160 77777
18 in 99999279 2019-12-04 720 77777
19 in 99999279 2019-12-12 12960 77777
20 in 99999290 2019-12-23 6336 6386
21 in 99999290 2019-12-26 50 6386
29 in 99999777 2021-04-08 10011

[注意:因爲所給的出庫數據是沒有重複的,以上是可以略去sum聚合這個操作的,因爲兩張表是按產品代碼做關聯的(很顯然),另外就是日期是可以降序排列的,但是在日期有null值的情況下null所在的記錄默認是降序排在最後的,所以要 order by 要指定 NULLS FIRST 這樣才能爲後面null批次的做優先扣減 🤗]

Second. 我們已經通過連表做好出庫的數據列,排序也做好了,現在。。。讓我想想

覺得還是先回顧下需求吧,我們的需求是每個產品下每一個批次順序扣減的最終結果(還有批次扣減的數),其中扣減數量應該就是=當前批次(庫存)數量-出庫數量,公式是確定的,看起來似乎簡單,然而難點是如何算出這個”扣減數量(出庫數量)“呢???😂😂 。。。 想想,我們用當前產品出庫總數按批次往下減,這樣會出現一個問題是批次剩餘數量=出庫總數-當前批次數量,而且這個批次剩餘數量並不能累加,只能用出庫數量依次遞減纔是,。。。好了,這又是一個難點,繼續思考下,目前我們是不是沒法做(至少是沒法簡單的)獲取到 庫存數量-出庫數量;幸運的是。。。如果將產品庫存數量依次遞減,這樣不就可以算出庫存差異了(事實上這樣也有各種各樣的問題)。。。讓我們試試看吧

SELECT 
	i.id,
	i.type,
	i."產品代碼",
	i."日期",
	i."數量",
	o."出_彙總",
	sum(i."數量") OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS "入_遞增"
FROM (t_product_inventory i
 LEFT JOIN ( SELECT t_product_out."產品代碼",
		sum(t_product_out."數量") AS "出_彙總"
		FROM t_product_out
		GROUP BY t_product_out."產品代碼") o 
 ON (((i."產品代碼")::text = (o."產品代碼")::text)))
ORDER BY i."產品代碼", i."日期" NULLS FIRST;
id type 產品代碼 日期 數量 出_彙總 入_遞增
7 in 99999279 24480 77777 24480
8 in 99999279 2018-11-03 20832 77777 45312
9 in 99999279 2018-12-02 21360 77777 66672
10 in 99999279 2019-06-14 18768 77777 85440
11 in 99999279 2019-06-16 9552 77777 94992
12 in 99999279 2019-07-12 2304 77777 97296
13 in 99999279 2019-09-05 3696 77777 100992
14 in 99999279 2019-09-06 16 77777 101008
15 in 99999279 2019-10-22 48 77777 101056
16 in 99999279 2019-11-03 14112 77777 115168
17 in 99999279 2019-12-02 2160 77777 117328
18 in 99999279 2019-12-04 720 77777 118048
19 in 99999279 2019-12-12 12960 77777 131008
20 in 99999290 2019-12-23 6336 10000 6336
21 in 99999290 2019-12-26 50 10000 6386
29 in 99999777 2021-04-08 10011 10011

[看,我們將各個產品庫存數量按照批次的順序依次遞增累加了(入_遞增這一列),注意窗口函數內需要排序!]

Third. 好了,讓我們趁熱將差異也算出來吧

SELECT t1.id,
  t1.type,
  t1."產品代碼",
  t1."日期",
  t1."數量",
  t1."出_彙總",
  t1."入_遞增",
      CASE
          WHEN (((t1."出_彙總" - t1."入_遞增") > (0)::numeric) /*AND (t1.rk <> t1.rk_ct)*/) THEN (0)::numeric
          ELSE (t1."入_遞增" - t1."出_彙總")
      END AS "出_入差異"
 FROM ( 
	SELECT i.id,
          i.type,
          i."產品代碼",
          i."日期",
          i."數量",
          o."出_彙總",
          sum(i."數量") OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS "入_遞增"
         FROM (t_product_inventory i
           LEFT JOIN ( SELECT t_product_out."產品代碼",
                  sum(t_product_out."數量") AS "出_彙總"
                 FROM t_product_out
                GROUP BY t_product_out."產品代碼") o ON (((i."產品代碼")::text = (o."產品代碼")::text)))
        ORDER BY i."產品代碼", i."日期" NULLS FIRST
) t1

id type 產品代碼 日期 數量 出_彙總 入_遞增 出_入差異
7 in 99999279 24480 77777 24480 0
8 in 99999279 2018-11-03 20832 77777 45312 0
9 in 99999279 2018-12-02 21360 77777 66672 0
10 in 99999279 2019-06-14 18768 77777 85440 7663
11 in 99999279 2019-06-16 9552 77777 94992 17215
12 in 99999279 2019-07-12 2304 77777 97296 19519
13 in 99999279 2019-09-05 3696 77777 100992 23215
14 in 99999279 2019-09-06 16 77777 101008 23231
15 in 99999279 2019-10-22 48 77777 101056 23279
16 in 99999279 2019-11-03 14112 77777 115168 37391
17 in 99999279 2019-12-02 2160 77777 117328 39551
18 in 99999279 2019-12-04 720 77777 118048 40271
19 in 99999279 2019-12-12 12960 77777 131008 53231
20 in 99999290 2019-12-23 6336 10000 6336 0
21 in 99999290 2019-12-26 50 10000 6386 0
29 in 99999777 2021-04-08 10011 10011

[看似一切都沒有問題,所以中間我特意將 99999290 這款產品臨時改爲10000,這樣你就會看到2019-12-26這個 出_入差異 值爲零,零,怎麼可能爲零呢。。。不要計較了一定是sql有缺陷😆]

Third+. 對於以上sql出現的缺陷我準備做個Plus版以修復它~😜

**首先要確定的是 99999290 -> 2019-12-26 這個批次的差異應該是3614,造成這樣的原因無非就是(最後一個批次的)出庫數大於庫存數~,看出問題了就不能無視缺陷的存在😎,所以對於最後一個批次如果出庫數量仍然大於當前批次的數量,他的差異(出_入差異)應該就是負數;等等,那我如何確定每個產品的最後一個批次呢,讓我們試着用sql找找看😂 **

  SELECT t1.id,
			t1.type,
			t1."產品代碼",
			t1."日期",
			t1."數量",
			t1."出_彙總",
			t1.rk,
			t1.rk_ct,
			t1."入_遞增",
			CASE
			  WHEN (((t1."出_彙總" - t1."入_遞增") > (0)::numeric) AND (t1.rk <> t1.rk_ct)) THEN (0)::numeric
			  ELSE (t1."入_遞增" - t1."出_彙總")
			END AS "出_入差異"
		 FROM ( 
		 SELECT i.id,
				i.type,
				i."產品代碼",
				i."日期",
				i."數量",
				o."出_彙總",
				row_number() OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS rk,
				count(1) OVER (PARTITION BY i."產品代碼") AS rk_ct,
				sum(i."數量") OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS "入_遞增"
				FROM (t_product_inventory i
				 LEFT JOIN ( SELECT t_product_out."產品代碼",
					sum(t_product_out."數量") AS "出_彙總"
					FROM t_product_out
					GROUP BY t_product_out."產品代碼") o ON (((i."產品代碼")::text = (o."產品代碼")::text)))
				ORDER BY i."產品代碼", i."日期" NULLS FIRST
) t1;
id type 產品代碼 日期 數量 出_彙總 rk rk_ct 入_遞增 出_入差異
7 in 99999279 24480 77777 1 13 24480 0
8 in 99999279 2018-11-03 20832 77777 2 13 45312 0
9 in 99999279 2018-12-02 21360 77777 3 13 66672 0
10 in 99999279 2019-06-14 18768 77777 4 13 85440 7663
11 in 99999279 2019-06-16 9552 77777 5 13 94992 17215
12 in 99999279 2019-07-12 2304 77777 6 13 97296 19519
13 in 99999279 2019-09-05 3696 77777 7 13 100992 23215
14 in 99999279 2019-09-06 16 77777 8 13 101008 23231
15 in 99999279 2019-10-22 48 77777 9 13 101056 23279
16 in 99999279 2019-11-03 14112 77777 10 13 115168 37391
17 in 99999279 2019-12-02 2160 77777 11 13 117328 39551
18 in 99999279 2019-12-04 720 77777 12 13 118048 40271
19 in 99999279 2019-12-12 12960 77777 13 13 131008 53231
20 in 99999290 2019-12-23 6336 10000 1 2 6336 0
21 in 99999290 2019-12-26 50 10000 2 2 6386 -3614
29 in 99999777 2021-04-08 10011 1 1 10011

[看,以上處理方式是不是贊👍,前面的缺陷完美滴解決,總結重點就是:通過窗口函數算出最後一列,這一列通過rk以及rk_ct比較得來的,想想看是不是很妙 😉]

Next. oh ~ 糟糕

[_雖然我們可能注意到了出庫數超出的情況,但是你可能忽略了最後一個問題,如果某個產品最近根本就沒有出庫呢...不妨看看 99999777 這款產品 是不是...是不是。。😥 ,當然對於出庫數不存在的解決辦法就相當easy了,當然如果你認真揣度過上面的sql的話。。。應該就不存在困難,如果不看以下sql,試試看~(相信你可以喲😎) _]

 SELECT 
    tt1.id,
    tt1.type,
    tt1."產品代碼",
    tt1."日期",
    tt1."數量",
    tt1."出_彙總",
    tt1.rk,
    tt1.rk_ct,
    tt1."入_遞增",
    tt1."出_入差異",
    case when  tt1."出_彙總" is null  then 0 else 
    ((tt1."數量")::numeric - COALESCE((tt1."出_入差異" - lag(tt1."出_入差異", 1, (0)::numeric) OVER (PARTITION BY tt1."產品代碼" ORDER BY tt1."日期" NULLS FIRST, tt1."數量")),0)) end AS "數量_出",
    case when  tt1."出_彙總" is null  then tt1."數量" else 
    (COALESCE(tt1."出_入差異",0) - lag(tt1."出_入差異", 1, (0)::numeric) OVER (PARTITION BY tt1."產品代碼" ORDER BY tt1."日期" NULLS FIRST, tt1."數量")) end AS "出_入差異_result"
   FROM ( 
	 SELECT t1.id,
            t1.type,
            t1."產品代碼",
            t1."日期",
            t1."數量",
            t1."出_彙總",
            t1.rk,
            t1.rk_ct,
            t1."入_遞增",
                CASE
                    WHEN (((t1."出_彙總" - t1."入_遞增") > (0)::numeric) AND (t1.rk <> t1.rk_ct)) THEN (0)::numeric
                    ELSE (t1."入_遞增" - t1."出_彙總")
                END AS "出_入差異"
           FROM ( 
	       SELECT 
       		i.id,
                    i.type,
                    i."產品代碼",
                    i."日期",
                    i."數量",
                    o."出_彙總",
                    row_number() OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS rk,
                    count(1) OVER (PARTITION BY i."產品代碼") AS rk_ct,
                    sum(i."數量") OVER (PARTITION BY i."產品代碼" ORDER BY i."日期" NULLS FIRST, i."數量") AS "入_遞增"
                   FROM (t_product_inventory i
                     LEFT JOIN ( SELECT t_product_out."產品代碼",
                            sum(t_product_out."數量") AS "出_彙總"
                           FROM t_product_out
                          GROUP BY t_product_out."產品代碼") o ON (((i."產品代碼")::text = (o."產品代碼")::text)))
                  ORDER BY i."產品代碼", i."日期" NULLS FIRST
									
              ) t1
) tt1
  ORDER BY tt1."產品代碼", tt1."日期" NULLS FIRST;
id type 產品代碼 日期 數量 出_彙總 rk rk_ct 入_遞增 出_入差異 數量_出 出_入差異_result
7 in 99999279 24480 77777 1 13 24480 0 24480 0
8 in 99999279 2018-11-03 20832 77777 2 13 45312 0 20832 0
9 in 99999279 2018-12-02 21360 77777 3 13 66672 0 21360 0
10 in 99999279 2019-06-14 18768 77777 4 13 85440 7663 11105 7663
11 in 99999279 2019-06-16 9552 77777 5 13 94992 17215 0 9552
12 in 99999279 2019-07-12 2304 77777 6 13 97296 19519 0 2304
13 in 99999279 2019-09-05 3696 77777 7 13 100992 23215 0 3696
14 in 99999279 2019-09-06 16 77777 8 13 101008 23231 0 16
15 in 99999279 2019-10-22 48 77777 9 13 101056 23279 0 48
16 in 99999279 2019-11-03 14112 77777 10 13 115168 37391 0 14112
17 in 99999279 2019-12-02 2160 77777 11 13 117328 39551 0 2160
18 in 99999279 2019-12-04 720 77777 12 13 118048 40271 0 720
19 in 99999279 2019-12-12 12960 77777 13 13 131008 53231 0 12960
20 in 99999290 2019-12-23 6336 10000 1 2 6336 0 6336 0
21 in 99999290 2019-12-26 50 10000 2 2 6386 -3614 3664 -3614
29 in 99999777 2021-04-08 10011 1 1 10011 0 10011

[注意: 以上 出_入差異_result 這一列即爲最終求解哈,爲了這一列費老多力了😄]

最後

** 很多時候我們以爲的似乎並不是那麼難,只是你很少去思考而已,當然吶,以上只是個人拙見,解決方法肯定還有很多,各位不妨試試看囖~ 😘 **

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章