需求
查詢用戶最後一次登錄的信息,每個用戶只返回一條最後登錄的記錄
數據庫
-- Create table
create table B_MAC_USB_KEY
(
ID VARCHAR2(64) not null,
REMARK VARCHAR2(2000),
CREATE_BY VARCHAR2(64),
CREATE_DATE DATE default sysdate,
UPDATE_BY VARCHAR2(64),
UPDATE_DATE DATE,
STATUS CHAR(1) default '0',
SNO VARCHAR2(20),
MAC VARCHAR2(100),
BIND_NUM NUMBER,
LOGIN_DATE_MAC DATE,
LOGIN_DATE_SNO DATE,
NO VARCHAR2(20)
)
tablespace YXDW
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64
next 8
minextents 1
maxextents unlimited
);
-- Add comments to the table
comment on table B_MAC_USB_KEY
is 'MAC和加密狗綁定關係表';
-- Add comments to the columns
comment on column B_MAC_USB_KEY.REMARK
is '備註';
comment on column B_MAC_USB_KEY.CREATE_BY
is '創建人';
comment on column B_MAC_USB_KEY.CREATE_DATE
is '創建時間';
comment on column B_MAC_USB_KEY.UPDATE_BY
is '修改人';
comment on column B_MAC_USB_KEY.UPDATE_DATE
is '修改時間';
comment on column B_MAC_USB_KEY.STATUS
is '0:正常1:刪除';
comment on column B_MAC_USB_KEY.SNO
is '加密狗編號(usb外表的號碼)';
comment on column B_MAC_USB_KEY.MAC
is '機器碼';
comment on column B_MAC_USB_KEY.BIND_NUM
is '加密狗已綁定的MAC數量';
comment on column B_MAC_USB_KEY.LOGIN_DATE_MAC
is 'mac登錄時間';
comment on column B_MAC_USB_KEY.LOGIN_DATE_SNO
is 'sno登錄時間';
comment on column B_MAC_USB_KEY.NO
is 'SN,加密狗內部序列號';
SQL語句
參考:https://ask.csdn.net/questions/744504
SELECT * FROM (
SELECT user_id , login_time ,row_number() over (partition by user_id order by login_time desc) MM from xxx)
WHERE mm = 1
正式語句
<sql id="userUsbKeyColumns">
a.ID AS "id",
a.BRAND AS "brand", <!-- 終端品牌 -->
a.TYPE AS "type", <!-- 終端類型 -->
a.OWNER_ID AS "ownerId", <!-- 公司ID -->
(
select b.owner_name
from B_OWNER b
WHERE a.OWNER_ID = b.OWNER_ID
) AS "ownerName", <!-- 公司名 -->
a.USER_ID AS "userId", <!-- 使用人id -->
(select b.u_cname from sys_user b where b.USERID = a.USER_ID) AS "userName", <!-- 使用人姓名 -->
a.REMARK AS "remark",
a.CREATE_BY AS "createBy",
a.CREATE_DATE AS "createDate",
a.UPDATE_BY AS "updateBy",
a.UPDATE_DATE AS "updateDate",
a.STATUS AS "status", <!-- 0正常1停用 -->
a.SNO AS "sno", <!-- 加密狗設備編號(usb外表的號碼) -->
a.NO AS "no", <!-- SN,加密狗內部序列號 -->
a.LOGIN_DATE_SNO AS "loginDateSno" <!-- 加密狗登錄時間 -->
</sql>
SELECT
*
FROM
(
SELECT
<include refid="userUsbKeyColumns"/>
,ROW_NUMBER () OVER (
PARTITION BY a.user_id
ORDER BY a.LOGIN_DATE_SNO DESC
) RNO
FROM B_USER_USB_KEY a
<where>
a.STATUS = #{status}
<if test="ownerId != null and ownerId != '' ">
and a.OWNER_ID in (select owner_id from b_owner start with owner_id = #{ownerId} connect by prior owner_id=owner_pid)
</if>
<if test="sno != null and sno != '' ">
and a.sno = #{sno,jdbcType=VARCHAR}
</if>
<if test="userId != null and userId != '' ">
and a.USER_ID = #{userId}
</if>
</where>
)
WHERE
RNO= 1
注意:ROW_NUMBER () OVER前要加“,”。
分析
Partition By a.user_id是按照a.user_id對查詢結果分割(Partition的意思就是分割),a.user_id相同的在一起
Order By ORDER BY a.LOGIN_DATE_SNO DESC是對分段的結果進行排序
對語句語句拆分分析
爲了便於分析,現只查詢上述語句的部分列
內層查詢
SELECT
A . ID AS "id",
(
SELECT
b.owner_name
FROM
B_OWNER b
WHERE
A .OWNER_ID = b.OWNER_ID
) AS "ownerName",-- 公司名 -->
A .USER_ID AS userId, -- 用戶id -->
(
SELECT
b.u_cname
FROM
sys_user b
WHERE
b.USERID = A .USER_ID
) AS "userName", -- 使用人姓名 -->
A .STATUS AS "status", -- 0正常1停用 -->
A .SNO AS "sno", -- 加密狗設備編號(usb外表的號碼) -->
A . NO AS "no", -- SN,加密狗內部序列號 -->
A .LOGIN_DATE_SNO AS "loginDateSno" -- 加密狗登錄時間 -->
,
ROW_NUMBER () OVER (
PARTITION BY A .user_id
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
FROM
B_USER_USB_KEY A
WHERE
A .STATUS = '0'
結果
通過 ROW_NUMBER () OVER (
PARTITION BY A .user_id
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
將user_id相同的放到一起,然後用LOGIN_DATE_SNO DESC進行排序,其中RNO表示user_id相同的記錄有幾個。每個user_id相同的截取片段都有一個第1名
如果不進行分割,即
ROW_NUMBER () OVER (
ORDER BY
A .LOGIN_DATE_SNO DESC
) RNO
中沒有PARTITION BY A .user_id
則查詢結果爲
查詢結果按登錄時間倒序,只有1個第1名
外層語句
SELECT
*
FROM
(
內層語句
)
WHERE
RNO = 1
RNO=1表示只顯示內層語句中user_id相同的數據塊的第一條記錄
結果
這部分可以參考:https://www.sohu.com/a/280125372_120045344
ORACLE的PARTITION BY 用法
是什麼
Parttion by 關鍵字是Oracle中分析性函數over的一部分,Oracle從8.1.6開始提供分析函數,它和聚合函數不同的地方在於它能夠返回一個分組中的多條記錄,而聚合函數一般只有一條反映統計值的結果。
幹什麼
分析函數用於計算基於組的某種聚合值,常用於大型項目中的統計分析及相關功能
有那些
--row_number() 順序排序
row_number() over(partition by ... order by ...)
--rank() (跳躍排序,如果有兩個第一級別時,接下來是第三級別)
rank() over(partition by ... order by ...)
--dense_rank()(連續排序,如果有兩個第一級別時,接下來是第二級)
dense_rank() over(partition by ... order by ...)
--求分組後的總數。
count() over(partition by ... order by ...)
--求分組後的最大值。
max() over(partition by ... order by ...)
--求分組後的最小值。
min() over(partition by ... order by ...)
sum() over(partition by ... order by ...)
--求分組後的平均值。
avg() over(partition by ... order by ...)
--第一個
first_value() over(partition by ... order by ...)
--最後一個
last_value() over(partition by ... order by ...)
--取出前n行數據。
lag() over(partition by ... order by ...)
--取出後n行數據。
lead() over(partition by ... order by ...)
--Ratio_to_report() 括號中就是分子,over() 括號中就是分母。
ratio_to_report() over(partition by ... order by ...)
percent_rank() over(partition by ... order by ...)
實例1
查詢出每個部門工資最低的員工編號【每個部門可能有兩個最低的工資員工】
create table TSALER
(
userid NUMBER(10),
salary NUMBER(10),
deptid NUMBER(10)
)
-- Add comments to the columns
comment on column TSALER.userid
is '員工ID';
comment on column TSALER.salary
is '工資';
comment on column TSALER.deptid
is '部門ID';
insert into TSALER (工號, 工資, 部門編號)
values (1, 200, 1);
insert into TSALER (工號, 工資, 部門編號)
values (2, 2000, 1);
insert into TSALER (工號, 工資, 部門編號)
values (3, 200, 1);
insert into TSALER (工號, 工資, 部門編號)
values (4, 1000, 2);
insert into TSALER (工號, 工資, 部門編號)
values (5, 1000, 2);
insert into TSALER (工號, 工資, 部門編號)
values (6, 3000, 2);
查詢結果:
方法一
SELECT
tsaler.*
FROM
tsaler
INNER JOIN (
SELECT
MIN (salary) AS salary,
deptid
FROM
tsaler
GROUP BY
deptid
) c ON tsaler.salary = c.salary
AND tsaler.deptid = c.deptid
方法二
SELECT
*
FROM
tsaler
INNER JOIN (
SELECT
MIN (salary) AS salary,
deptid
FROM
tsaler
GROUP BY
deptid
) c USING (salary, deptid)
方法三
--row_number() 順序排序
SELECT
ROW_NUMBER () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
--rank() (跳躍排序,如果有兩個第一級別時,接下來是第三級別)
SELECT
RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
--dense_rank()(連續排序,如果有兩個第一級別時,接下來是第二級)
SELECT
DENSE_RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler;
-------方案3解決方案
SELECT
*
FROM
(
SELECT
RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler
)
WHERE
my_rank = 1;
SELECT
*
FROM
(
SELECT
DENSE_RANK () OVER (
PARTITION BY deptid
ORDER BY
salary
) my_rank,
deptid,
USERID,
salary
FROM
tsaler
)
WHERE
my_rank = 1;
參考:https://www.cnblogs.com/jak-black/p/4210653.html
實例2
需求:對每一個類型進行求和並且求該類型所佔的比例
SELECT
T .CHANNEL AS PATTERN,
COUNT (T .TRANSACTIONKEY) AS T_COUNT,
SUM (T .AMT) AS T_AMT,
ROUND (
100 * SUM (T .AMT) / SUM (SUM(T .AMT)) OVER (PARTITION BY 1),
2
) AS AMT_PERCENT,
ROUND (
100 * COUNT (T .TRANSACTIONKEY) / SUM (COUNT(T .TRANSACTIONKEY)) OVER (PARTITION BY 1),
2
) AS COUNT_PERCENT
FROM
XX (表名) T
WHERE
T .PARTY_ID = '100579050'
GROUP BY
T .CHANNEL
實驗數據
create table T2_TEMP(
NAME varchar2(10) primary key,
CLASS varchar2(10),
SROCE NUMBER
)
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('cfe', '2', 74);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('dss', '1', 95);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('ffd', '1', 95);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('fda', '1', 80);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('gds', '2', 92);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('gf', '3', 99);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('ddd', '3', 99);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('adf', '3', 45);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('asdf', '3', 55);
insert into T2_TEMP (NAME, CLASS, SROCE)
values ('3dd', '3', 78);
1、over函數的寫法:
over(partition by class order by sroce) 按照sroce排序進行累計,order by是個默認的開窗函數,按照class分區。
2、開窗的窗口範圍:
over(order by sroce range between 5 preceding and 5 following):窗口範圍爲當前行數據幅度減5加5後的範圍內的。
over(order by sroce rows between 5 preceding and 5 following):窗口範圍爲當前行前後各移動5行。
3、與over()函數結合的函數的介紹
(1)、查詢每個班的第一名的成績:如下
SELECT
*
FROM
(
SELECT
T . NAME,
T . CLASS,
T .sroce,
RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T
)
WHERE
mm = 1;
結果爲:
dss 1 95 1
ffd 1 95 1
gds 2 92 1
gf 3 99 1
ddd 3 99 1
注意:在求第一名成績的時候,不能用row_number(),因爲如果同班有兩個並列第一,row_number()只返回一個結果。
SELECT
*
FROM
(
SELECT
T . NAME,
T . CLASS,
T .sroce,
ROW_NUMBER () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T
)
WHERE
mm = 1;
結果爲:
dss 1 95 1
gfs 2 92 1
ddd 3 99 1
可以看出,本來第一名是兩個人的並列,結果只顯示了一個。
(2)、rank()和dense_rank()可以將所有的都查找出來,rank可以將並列第一名的都查找出來;rank()和dense_rank()區別:rank()是跳躍排序,有兩個第二名時接下來就是第四名。
求班級成績排名:
SELECT
T . NAME,
T . CLASS,
T .sroce,
RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
查詢結果:
dss 1 95 1
ffd 1 95 1
fda 1 80 3
gds 2 92 1
cfe 2 74 2
gf 3 99 1
ddd 3 99 1
3dd 3 78 3
asdf 3 55 4
adf 3 45 5
dense_rank()是連續排序,有兩個第二名時仍然跟着第三名
SELECT
T . NAME,
T . CLASS,
T .sroce,
DENSE_RANK () OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
查詢結果:
dss 1 95 1
ffd 1 95 1
fda 1 80 2
gds 2 92 1
cfe 2 74 2
gf 3 99 1
ddd 3 99 1
3dd 3 78 2
asdf 3 55 3
adf 3 45 4
3、sum()over()的使用
根據班級進行分數求和
SELECT
T . NAME,
T . CLASS,
T .sroce,
SUM (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
dss 1 95 190 --由於兩個95都是第一名,所以累加時是兩個第一名的相加
ffd 1 95 190
fda 1 80 270 --第一名加上第二名的
gds 2 92 92
cfe 2 74 166
gf 3 99 198
ddd 3 99 198
3dd 3 78 276
asdf 3 55 331
adf 3 45 376
4、first_value() over()和last_value() over()的使用
SELECT
T . NAME,
T . CLASS,
T .sroce,
FIRST_VALUE (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
SELECT
T . NAME,
T . CLASS,
T .sroce,
LAST_VALUE (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
分別求出第一個和最後一個成績。
5、sum() over()的使用
SELECT
T . NAME,
T . CLASS,
T .sroce,
SUM (T .sroce) OVER (
PARTITION BY T . CLASS
ORDER BY
T .sroce DESC
) mm
FROM
T2_TEMP T;
求出班級的總分。
6、over partition by與group by的區別:
group by是對檢索結果的保留行進行單純分組,一般和聚合函數一起使用例如max、min、sum、avg、count等一塊用。partition by雖然也具有分組功能,但同時也具有其他的高級功能。