ตรวจสอบเร็วที่สุดว่ามีแถวอยู่ใน PostgreSQL


177

ฉันมีแถวจำนวนมากที่ฉันต้องการแทรกลงในตาราง แต่เม็ดมีดเหล่านี้มักจะทำเป็นแบตช์ ดังนั้นฉันต้องการตรวจสอบว่าแถวเดียวจากชุดมีอยู่ในตารางเพราะฉันรู้ว่าพวกเขาทั้งหมดถูกแทรก

ดังนั้นจึงไม่ใช่การตรวจสอบคีย์หลัก แต่ไม่ควรสำคัญมากเกินไป ฉันต้องการตรวจสอบแถวเดียวเท่านั้นจึงcount(*)อาจไม่ดีดังนั้นสิ่งที่existsฉันเดา

แต่เนื่องจากฉันค่อนข้างใหม่กับ PostgreSQL ฉันควรถามคนที่รู้

ชุดของฉันมีแถวที่มีโครงสร้างดังต่อไปนี้:

userid | rightid | remaining_count

ดังนั้นหากตารางมีแถวใด ๆ ที่มีให้useridนั่นหมายความว่าพวกมันทั้งหมดอยู่ที่นั่น


คุณต้องการดูว่าตารางมีแถวใดหรือแถวใดจากแบตช์ของคุณหรือไม่
JNK

แถวใด ๆ จากชุดของฉันใช่ พวกเขาทุกคนมีส่วนร่วมในสาขาเดียวกันไม่สามารถแก้ไขได้เพียงเล็กน้อย
Valentin Kuzub

โปรดอธิบายคำถามของคุณ คุณต้องการเพิ่มชุดของระเบียนทั้งหมดหรือไม่? มีอะไรพิเศษเกี่ยวกับการนับไหม? (BTW คำที่สงวนไว้ใช้ไม่ได้กับชื่อคอลัมน์)
wildplasser

โอเคฉันพยายามทำให้สถานการณ์จริงง่ายขึ้นเล็กน้อย แต่เรากำลังเข้าใกล้และนำไปใช้จริงมากขึ้น เมื่อแทรกแถวเหล่านั้นแล้ว (มีอีกฟิลด์หนึ่ง for_date) ฉันจะเริ่มลดสิทธิ์สำหรับผู้ใช้ที่ระบุเนื่องจากใช้สิทธิ์เฉพาะเมื่อสิทธิ์กลายเป็น 0 พวกเขาจะไม่สามารถดำเนินการเหล่านั้นได้อีกในวันนั้น นั่นคือเรื่องจริง
Valentin Kuzub

1
เพียงแสดง (ส่วนที่เกี่ยวข้องของ) นิยามตารางและบอกสิ่งที่คุณตั้งใจจะทำ
wildplasser

คำตอบ:


345

ใช้คำสำคัญ EXISTS สำหรับการคืนค่า TRUE / FALSE:

select exists(select 1 from contact where id=12)

21
ส่วนขยายนี้คุณสามารถตั้งชื่อคอลัมน์ที่ส่งคืนเพื่อการอ้างอิงได้ง่าย ตัวอย่างselect exists(select 1 from contact where id=12) AS "exists"
Rowan

3
สิ่งนี้ดีกว่าเพราะจะส่งคืนค่า (จริงหรือเท็จ) แทนบางครั้งไม่มี (ขึ้นอยู่กับภาษาการเขียนโปรแกรมของคุณ) ซึ่งอาจไม่ขยายตามที่คุณคาดหวัง
isaaclw

1
ฉันมี Seq Scan โดยใช้วิธีนี้ ฉันทำอะไรผิดหรือเปล่า?
FiftiN

2
@ Michael.MI มีตาราง DB ที่มี 30 ล้านแถวและเมื่อฉันใช้existsหรือlimit 1ฉันมีประสิทธิภาพลดลงอย่างมากเนื่องจาก Postgres ใช้ Seq Scan แทนการสแกนดัชนี และanalyzeไม่ช่วย
FiftiN

2
@maciek โปรดเข้าใจว่า 'id' เป็นคีย์หลักดังนั้น "LIMIT 1" จะไม่มีจุดหมายเนื่องจากมีเพียงหนึ่งระเบียนที่มี ID นั้น
StartupGuy

34

ง่าย ๆ เพียงแค่:

select 1 from tbl where userid = 123 limit 1;

ที่123เป็นหมายเลขผู้ใช้ของชุดที่คุณกำลังจะแทรก

แบบสอบถามด้านบนจะส่งกลับชุดว่างหรือแถวเดียวขึ้นอยู่กับว่ามีระเบียนที่มีหมายเลขผู้ใช้ที่กำหนด

หากสิ่งนี้ช้าเกินไปคุณสามารถสร้างดัชนีtbl.useridได้

หากแม้แต่แถวเดียวจากแบทช์มีอยู่ในตารางในกรณีนั้นฉันไม่จำเป็นต้องแทรกแถวของฉันเพราะฉันรู้ว่าพวกเขาทั้งหมดถูกแทรก

เพื่อให้สิ่งนี้ยังคงเป็นจริงแม้ว่าโปรแกรมของคุณจะถูกขัดจังหวะมิดแบตช์ฉันขอแนะนำให้คุณตรวจสอบให้แน่ใจว่าคุณได้จัดการธุรกรรมฐานข้อมูลอย่างเหมาะสม


11
บางครั้งการเขียนโปรแกรมอาจจะง่ายกว่า "เลือกจำนวน (*) จาก (เลือก 1 ... จำกัด 1)" เนื่องจากรับประกันว่าจะส่งคืนแถวที่มีค่านับ (*) เท่ากับ 0 หรือ 1 เสมอ
David Aldridge

@DavidAldridge ยังคงนับ (*) หมายความว่าทุกแถวจะต้องอ่านในขณะที่ จำกัด 1 หยุดที่บันทึกแรกและส่งกลับ
Imraan

3
@Imraan ฉันคิดว่าคุณตีความข้อความค้นหาผิด การCOUNTกระทำที่ซ้อนกันSELECTที่มีมากที่สุด 1 แถว (เพราะLIMITอยู่ในแบบสอบถามย่อย)
jpmc26

9
INSERT INTO target( userid, rightid, count )
  SELECT userid, rightid, count 
  FROM batch
  WHERE NOT EXISTS (
    SELECT * FROM target t2, batch b2
    WHERE t2.userid = b2.userid
    -- ... other keyfields ...
    )       
    ;

BTW: ถ้าคุณต้องการให้แบตช์ทั้งหมดล้มเหลวในกรณีที่มีการซ้ำซ้อน (จากข้อ จำกัด ของคีย์หลัก)

INSERT INTO target( userid, rightid, count )
SELECT userid, rightid, count 
FROM batch
    ;

จะทำสิ่งที่คุณต้องการอย่างใดอย่างหนึ่งไม่ว่าจะสำเร็จหรือล้มเหลว


นี้จะตรวจสอบแต่ละแถว เขาต้องการตรวจเช็คครั้งเดียว
JNK

1
ไม่มันเป็นการตรวจสอบครั้งเดียว แบบสอบถามย่อยไม่เกี่ยวข้องกัน มันจะประกันตัวเมื่อพบคู่ที่ตรงกันหนึ่งคู่
wildplasser

ใช่แล้วฉันคิดว่ามันหมายถึงการสืบค้นภายนอก +1 ถึงคุณ
JNK

BTW: เนื่องจากการสืบค้นอยู่ภายในธุรกรรมไม่มีอะไรจะเกิดขึ้นหากมีการแทรกรหัสซ้ำดังนั้นจึงไม่สามารถทำการสืบค้นย่อยได้
wildplasser

อืมฉันไม่แน่ใจว่าฉันเข้าใจ หลังจากแทรกสิทธิ์ฉันจะเริ่มนับจำนวนคอลัมน์ที่ลดลง (มีเพียงรายละเอียดบางอย่างสำหรับรูปภาพ) หากมีแถวอยู่แล้วและละเว้นแบบสอบถามย่อยฉันคิดว่าจะเกิดข้อผิดพลาดกับรหัสซ้ำที่ไม่ซ้ำกันหรือ (รูปแบบผู้ใช้และรหัสที่ไม่ซ้ำกันที่สำคัญ)
Valentin Kuzub

1
select true from tablename where condition limit 1;

ฉันเชื่อว่านี่เป็นแบบสอบถามที่ postgres ใช้สำหรับตรวจสอบคีย์ต่างประเทศ

ในกรณีของคุณคุณสามารถทำได้ในครั้งเดียวด้วย:

insert into yourtable select $userid, $rightid, $count where not (select true from yourtable where userid = $userid limit 1);

1

เป็น @MikeM ชี้ให้เห็น

select exists(select 1 from contact where id=12)

ด้วยดัชนีที่ติดต่อมักจะสามารถลดค่าใช้จ่ายเวลา 1 มิลลิวินาที

CREATE INDEX index_contact on contact(id);

0
SELECT 1 FROM user_right where userid = ? LIMIT 1

หากชุดผลลัพธ์ของคุณมีแถวคุณไม่จำเป็นต้องแทรก มิฉะนั้นใส่บันทึกของคุณ


ถ้าพวงมี 100 แถวมันจะส่งคืน 100 แถวคุณคิดว่าดีหรือไม่
Valentin Kuzub

คุณสามารถ จำกัด ไว้ที่ 1 แถว ควรทำงานได้ดีขึ้น ดูคำตอบที่แก้ไขจาก @aix เพื่อสิ่งนั้น
เฟเบียนบาร์นีย์

0

หากคุณคิดเกี่ยวกับการแสดงคุณอาจใช้ "PERFORM" ในฟังก์ชั่นแบบนี้ได้:

 PERFORM 1 FROM skytf.test_2 WHERE id=i LIMIT 1;
  IF FOUND THEN
      RAISE NOTICE ' found record id=%', i;  
  ELSE
      RAISE NOTICE ' not found record id=%', i;  
 END IF;

ใช้งานไม่ได้กับฉัน: ฉันได้รับข้อผิดพลาดทางไวยากรณ์ใกล้ประสิทธิภาพ
Simon

1
ว่า PL / pgsql ไม่ SQL จึงไวยากรณ์ผิดพลาดสำหรับ "ดำเนินการ" ถ้าพยายามที่จะใช้มันเป็น SQL
มาร์ค K แวนส์

-1

ฉันต้องการเสนอความคิดอื่นเพื่อระบุประโยคของคุณโดยเฉพาะ: "ดังนั้นฉันต้องการตรวจสอบว่าแถวเดียวจากชุดมีอยู่ในตารางเพราะแล้วฉันรู้ว่าพวกเขาทั้งหมดถูกแทรก "

คุณกำลังทำสิ่งต่าง ๆ ให้มีประสิทธิภาพโดยการใส่ใน "แบทช์" แต่จากนั้นทำการตรวจสอบการมีอยู่ของแต่ละระเบียนหรือไม่ ดูเหมือนว่ามันจะง่ายสำหรับฉัน ดังนั้นเมื่อคุณพูดว่า " ส่วนแทรกจะทำในแบตช์เสมอ " ฉันถือว่าคุณหมายถึงคุณกำลังแทรกหลายระเบียนด้วยคำสั่งแทรกเดียว คุณต้องตระหนักว่า Postgres นั้นเป็นไปตามข้อกำหนดของกรด หากคุณกำลังแทรกหลายระเบียน (ชุดข้อมูล) ที่มีคำสั่งแทรกเดียวไม่จำเป็นต้องตรวจสอบว่ามีการแทรกบางส่วนหรือไม่ คำสั่งผ่านหรือไม่ก็จะล้มเหลว บันทึกทั้งหมดจะถูกแทรกหรือไม่มี

ในทางกลับกันถ้ารหัส C # ของคุณกำลังทำคำสั่งแทรก "set" แยกต่างหากตัวอย่างเช่นในลูปและในใจของคุณนี่คือ "แบทช์" .. ดังนั้นคุณไม่ควรอธิบายว่าเป็น " เม็ดมีดจะถูกทำเป็นแบต " ความจริงที่ว่าคุณคาดหวังว่าส่วนหนึ่งของสิ่งที่คุณเรียกว่า "แบทช์" อาจไม่ได้แทรกและจริง ๆ แล้วรู้สึกว่าจำเป็นต้องมีการตรวจสอบอย่างยิ่งแสดงให้เห็นอย่างชัดเจนว่าเป็นกรณีนี้ในกรณีนี้ คุณต้องเปลี่ยนกระบวนทัศน์ของคุณเพื่อแทรกหลาย ๆ ระเบียนด้วยการแทรกครั้งเดียวและนำหน้าการตรวจสอบว่าแต่ละระเบียนสร้างขึ้นหรือไม่

ลองพิจารณาตัวอย่างนี้:

CREATE TABLE temp_test (
    id SERIAL PRIMARY KEY,
    sometext TEXT,
    userid INT,
    somethingtomakeitfail INT unique
)
-- insert a batch of 3 rows
;;
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 1, 1),
('bar', 2, 2),
('baz', 3, 3)
;;
-- inspect the data of what we inserted
SELECT * FROM temp_test
;;
-- this entire statement will fail .. no need to check which one made it
INSERT INTO temp_test (sometext, userid, somethingtomakeitfail) VALUES
('foo', 2, 4),
('bar', 2, 5),
('baz', 3, 3)  -- <<--(deliberately simulate a failure)
;;
-- check it ... everything is the same from the last successful insert ..
-- no need to check which records from the 2nd insert may have made it in
SELECT * FROM temp_test

นี่คือกระบวนทัศน์จริงสำหรับฐานข้อมูลที่สอดคล้องกับกรดใด ๆ .. ไม่ใช่แค่ Postgresql ในคำอื่น ๆ คุณจะดีกว่าถ้าคุณแก้ไขแนวคิด "แบทช์" ของคุณและหลีกเลี่ยงการตรวจสอบแถวโดยแถวในสถานที่แรก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.