SQLAlchemy: कैस्केड हटाएं


116

मुझे SQLAlchemy के कैस्केड विकल्पों के साथ कुछ तुच्छ याद आ रही होगी क्योंकि मुझे सही तरीके से काम करने के लिए एक साधारण कैस्केड डिलीट नहीं मिल सकता है - यदि कोई मूल तत्व हटा दिया गया है, तो बच्चे nullविदेशी कुंजी के साथ बने रहते हैं ।

मैंने यहां एक संक्षिप्त परीक्षण मामला रखा है:

from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key = True)

class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key = True)
    parentid = Column(Integer, ForeignKey(Parent.id))
    parent = relationship(Parent, cascade = "all,delete", backref = "children")

engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)

session = Session()

parent = Parent()
parent.children.append(Child())
parent.children.append(Child())
parent.children.append(Child())

session.add(parent)
session.commit()

print "Before delete, children = {0}".format(session.query(Child).count())
print "Before delete, parent = {0}".format(session.query(Parent).count())

session.delete(parent)
session.commit()

print "After delete, children = {0}".format(session.query(Child).count())
print "After delete parent = {0}".format(session.query(Parent).count())

session.close()

आउटपुट:

Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0

माता-पिता और बच्चे के बीच एक सरल, एक से कई संबंध हैं। स्क्रिप्ट एक अभिभावक बनाता है, 3 बच्चों को जोड़ता है, फिर कमिट करता है। अगला, यह माता-पिता को हटा देता है, लेकिन बच्चे बने रहते हैं। क्यों? मैं बच्चों को कैस्केड डिलीट कैसे करूँ?


डॉक्स में यह खंड (मूल पोस्ट के बाद कम से कम अब, 3 साल बाद) इस पर काफी मददगार लगता है: डॉक्स.सकलवचेमी.org
rel_0_9

जवाबों:


184

समस्या यह है कि sqlalchemy Childमाता-पिता के रूप में मानता है , क्योंकि यह वह जगह है जहां आपने अपने रिश्ते को परिभाषित किया है (यह ध्यान नहीं देता है कि आपने इसे "बच्चा" कहा है)।

यदि आप Parentइसके बजाय कक्षा पर संबंध परिभाषित करते हैं , तो यह काम करेगा:

children = relationship("Child", cascade="all,delete", backref="parent")

( "Child"एक स्ट्रिंग के रूप में नोट करें : यह घोषणा शैली का उपयोग करते समय अनुमति दी जाती है, ताकि आप एक ऐसे वर्ग का उल्लेख कर सकें जो अभी तक जुड़ा नहीं है)

आप और भी जोड़ना चाहते हैं delete-orphan( deleteमाता-पिता द्वारा हटाए जाने पर बच्चों को हटा दिया जाता है, साथ delete-orphanही माता-पिता से "हटाए गए" किसी भी बच्चे को हटा देता है, भले ही माता-पिता को हटा दिया गया हो)

संपादित करें: बस पता चला है: यदि आप वास्तव में Childवर्ग पर संबंध को परिभाषित करना चाहते हैं , तो आप ऐसा कर सकते हैं, लेकिन आपको बैकस्क पर कैस्केड को परिभाषित करना होगा (स्पष्ट रूप से बैकफ्रेड बनाकर), इस तरह:

parent = relationship(Parent, backref=backref("children", cascade="all,delete"))

(आसन्न from sqlalchemy.orm import backref)


6
अहा, यह बात है। काश दस्तावेजीकरण इस बारे में अधिक स्पष्ट होता!
कार्ल

15
ऐ। बहुत मददगार। मैं हमेशा SQLAlchemy के प्रलेखन के साथ मुद्दों था।
अयज


1
@ लाइमैन ज़र्गा: ओपी के उदाहरण में: यदि आप किसी Childऑब्जेक्ट को हटाते हैं parent.children, तो क्या उस ऑब्जेक्ट को डेटाबेस से हटा दिया जाना चाहिए, या केवल यह माता-पिता के संदर्भ को हटा दिया जाना चाहिए (यानी parentidपंक्ति को हटाने के बजाय अशक्त करने के लिए स्तंभ सेट करें)
स्टीवन

1
रुको, relationshipअभिभावक-बच्चे की स्थापना को निर्धारित नहीं करता है। ForeignKeyएक मेज पर उपयोग करना वह है जो इसे बच्चे के रूप में स्थापित करता है। इससे कोई फर्क नहीं पड़ता relationshipकि माता-पिता या बच्चे पर है।
d512

110

@ स्टीवन का asnwer अच्छा है जब आप हटा रहे हैं session.delete()जिसके माध्यम से मेरे मामले में कभी नहीं होता है। मैंने देखा कि अधिकांश समय मैं session.query().filter().delete()(जो कि मेमोरी में तत्वों को नहीं डालता है और db से सीधे हटाता है) को हटाता है। इस पद्धति का उपयोग करना sqlalchemy का cascade='all, delete'काम नहीं करता है। यद्यपि एक समाधान है: ON DELETE CASCADEdb के माध्यम से (नोट: सभी डेटाबेस इसका समर्थन नहीं करते हैं)।

class Child(Base):
    __tablename__ = "children"

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))

class Parent(Base):
    __tablename__ = "parents"

    id = Column(Integer, primary_key=True)
    child = relationship(Child, backref="parent", passive_deletes=True)

3
इस अंतर को स्पष्ट करने के लिए धन्यवाद - मैं session.query().filter().delete()इस मुद्दे को खोजने के लिए उपयोग करने और संघर्ष करने की कोशिश कर रहा था
रात का ing४45४४

4
passive_deletes='all'माता-पिता के डिलीट होने पर मुझे डेटाबेस कैस्केड द्वारा बच्चों को हटाने के लिए सेट करना था । साथ passive_deletes=True, बच्चों वस्तुओं की संबद्धता समाप्त कर रहे थे हो रही (शून्य पर माता पिता के सेट) से पहले माता-पिता को नष्ट कर दिया गया है, ताकि डेटाबेस झरना कुछ भी नहीं कर रहा था।
मिलोराद पॉप-टॉसिक

@ MiloradPop-Tosic मैंने 3 साल से अधिक समय तक SQLAlchemy का उपयोग नहीं किया है, लेकिन डॉक्टर को पढ़ना passive_deletes की तरह दिखता है = यह अभी भी सही बात है।
एलेक्स ओक्रुस्को 3

2
मैं इस बात की पुष्टि कर सकता हूं कि passive_deletes=Trueइस परिदृश्य में सही ढंग से काम होता है।
d512

मुझे स्वत: उत्पन्न होने वाले संशोधन से परेशानी हो रही थी, जिसमें डिलीट पर कैस्केड शामिल था - यह उत्तर था।
JNW

105

बहुत पुरानी पोस्ट, लेकिन मैंने इस पर सिर्फ एक या दो घंटे बिताए, इसलिए मैं अपनी खोज को साझा करना चाहता था, खासकर जब से सूचीबद्ध कुछ अन्य टिप्पणियां काफी सही नहीं हैं।

टी एल; डॉ

बच्चे की मेज को एक विदेशी दें या मौजूदा जोड़ को संशोधित करें ondelete='CASCADE':

parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))

और एक निम्नलिखित संबंधों का:

क) यह मूल तालिका पर है:

children = db.relationship('Child', backref='parent', passive_deletes=True)

बी) या बच्चे की मेज पर:

parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))

विवरण

सबसे पहले, स्वीकार किए गए उत्तर के बावजूद, माता-पिता / बच्चे के संबंध का उपयोग करके स्थापित नहीं किया गया है relationship, इसका उपयोग करके स्थापित किया गया है ForeignKey। आप relationshipया तो माता-पिता या बच्चे की मेज पर रख सकते हैं और यह ठीक काम करेगा। हालांकि, स्पष्ट रूप से बच्चे की तालिकाओं पर, आपको backrefकीवर्ड तर्क के अतिरिक्त फ़ंक्शन का उपयोग करना होगा ।

विकल्प 1 (पसंदीदा)

दूसरा, SqlAlchemy कैस्केडिंग के दो अलग-अलग प्रकारों का समर्थन करता है। पहला, और जो मैं सुझाता हूं, वह आपके डेटाबेस में बनाया गया है और आमतौर पर विदेशी कुंजी घोषणा पर एक बाधा का रूप लेता है। PostgreSQL में यह इस तरह दिखता है:

CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id)
REFERENCES parent_table(id) MATCH SIMPLE
ON DELETE CASCADE

इसका मतलब है कि जब आप किसी रिकॉर्ड को हटाते हैं parent_table, तो child_tableडेटाबेस में आपके लिए सभी संबंधित पंक्तियों को हटा दिया जाएगा। यह तेज़ और विश्वसनीय है और शायद आपका सबसे अच्छा दांव है। आप इसे इस ForeignKeyतरह (चाइल्ड टेबल परिभाषा का हिस्सा) के माध्यम से SqlAlchemy में सेट करते हैं :

parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))

ondelete='CASCADE'बात यह है कि बनाता है ON DELETE CASCADEमेज पर।

पकड़ लिया!

यहाँ एक महत्वपूर्ण चेतावनी है। ध्यान दें कि मेरे पास कैसे relationshipनिर्दिष्ट है passive_deletes=True? यदि आपके पास ऐसा नहीं है, तो पूरी बात नहीं चलेगी। ऐसा इसलिए होता है क्योंकि जब आप मूल रिकॉर्ड हटाते हैं तो SqlAlchemy वास्तव में कुछ अजीब करता है। यह सभी चाइल्ड रो की विदेशी कुंजियों को सेट करता है NULL। इसलिए यदि आप एक पंक्ति को parent_tableजहां id= 5 से हटाते हैं , तो वह मूल रूप से निष्पादित होगी

UPDATE child_table SET parent_id = NULL WHERE parent_id = 5

आप यह क्यों चाहते हैं, मुझे कुछ पता नहीं है। मुझे आश्चर्य होगा कि कई डेटाबेस इंजनों ने भी आपको NULLएक अनाथ बनाने के लिए एक वैध विदेशी कुंजी सेट करने की अनुमति दी । एक बुरे विचार की तरह लगता है, लेकिन शायद एक उपयोग मामला है। वैसे भी, यदि आप SqlAlchemy को ऐसा करने देते हैं, तो आप डेटाबेस को अपने द्वारा उपयोग किए जा रहे बच्चों को साफ करने में सक्षम होने से रोकेंगे ON DELETE CASCADE। ऐसा इसलिए है क्योंकि यह उन विदेशी कुंजियों पर निर्भर करता है जो यह जानने के लिए कि कौन सी चाइल्ड रो को हटाना है। एक बार SqlAlchemy ने उन सभी को सेट कर दिया NULL, डेटाबेस उन्हें हटा नहीं सकता। स्थापना passive_deletes=Trueकरने से रोकता है SQLAlchemy NULLविदेशी कुंजी बाहर ing।

आप SqlAlchemy डॉक्स में निष्क्रिय डिलीट के बारे में अधिक पढ़ सकते हैं ।

विकल्प 2

दूसरा तरीका यह है कि आप SqlAlchemy को अपने लिए करने दें। यह के cascadeतर्क का उपयोग करके स्थापित किया गया है relationship। यदि आपके पास मूल तालिका पर निर्धारित संबंध है, तो यह इस तरह दिखता है:

children = relationship('Child', cascade='all,delete', backref='parent')

यदि रिश्ता बच्चे पर है, तो आप इसे इस तरह करते हैं:

parent = relationship('Parent', backref=backref('children', cascade='all,delete'))

फिर, यह बच्चा है इसलिए आपको एक विधि को कॉल करना होगा backrefऔर कैस्केड डेटा को वहां डाल देना होगा।

इसके स्थान पर, जब आप मूल पंक्ति हटाते हैं, तो SqlAlchemy वास्तव में आपके लिए चाइल्ड रो को साफ करने के लिए डिलीट स्टेटमेंट चलाएगा। यह संभवत: इस डेटाबेस को संभालने के रूप में कुशल नहीं होगा यदि आप के लिए है तो मैं इसकी सिफारिश नहीं करता हूं।

यहाँ कास्केलेमी डॉक्स का समर्थन करता है जिन पर कैस्केडिंग सुविधाएँ हैं।


समझाने के लिए धन्यवाद। यह अब समझ में आता है।
ओडिन

1
Columnबच्चे की तालिका में ForeignKey('parent.id', ondelete='cascade', onupdate='cascade')काम नहीं करने की घोषणा क्यों करता है ? मुझे उम्मीद थी कि जब उनके अभिभावक टेबल रो भी डिलीट हो जाएंगे तो वे बच्चों को डिलीट कर देंगे। इसके बजाय, SQLA या तो बच्चों को सेट करता है parent.id=NULLया उन्हें "जैसा है" छोड़ देता है, लेकिन हटाता नहीं है। यही कारण है कि मूल रूप से परिभाषित करने के बाद relationshipके रूप में माता-पिता में children = relationship('Parent', backref='parent')या relationship('Parent', backref=backref('parent', passive_deletes=True)); DB cascadeDDL (SQLite3-based proof-of-concept) में नियम दिखाता है। विचार?
23 अप्रैल को code_dredd

1
इसके अलावा, मुझे यह ध्यान देना चाहिए कि जब मैं उपयोग करता backref=backref('parent', passive_deletes=True)हूं तो मुझे निम्नलिखित चेतावनी मिलती है: SAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % selfयह सुझाव देते हुए कि passive_deletes=Trueयह (स्पष्ट) एक से कई माता-पिता-बच्चे के रिश्ते को किसी कारण से उपयोग करना पसंद नहीं है।
code_dredd

महान व्याख्या। एक सवाल - deleteबेमानी है cascade='all,delete'?
ज़गगी

1
@zaggi deleteआईएस निरर्थक है cascade='all,delete', क्योंकि SQLAlchemy के डॉक्स के अनुसार , allइसका एक पर्याय है:save-update, merge, refresh-expire, expunge, delete
pmsoltani

7

स्टीवन सही है कि आपको स्पष्ट रूप से बैकएफ़आर बनाने की आवश्यकता है, इसका परिणाम माता-पिता पर लागू होने वाले कैस्केड के रूप में होता है (जैसा कि इसके विपरीत किया जा रहा है कि बच्चे को परीक्षा परिदृश्य में लागू किया जा रहा है)।

हालाँकि, चाइल्ड पर संबंध को परिभाषित करना चक्रीय कीमिया को चाइल्ड को माता-पिता नहीं मानता है। इससे कोई फर्क नहीं पड़ता कि संबंध कहां परिभाषित किया गया है (बच्चे या माता-पिता), इसकी विदेशी कुंजी जो दो तालिकाओं को जोड़ती है जो यह निर्धारित करती है कि कौन सा माता-पिता है और कौन सा बच्चा है।

यह समझ में आता है कि हालांकि एक सम्मेलन के लिए छड़ी है, और स्टीवन की प्रतिक्रिया के आधार पर, मैं अपने सभी बाल संबंधों को माता-पिता पर परिभाषित कर रहा हूं।


6

मैंने दस्तावेज के साथ भी संघर्ष किया, लेकिन पाया कि डॉकस्ट्रिंग्स खुद को मैनुअल से आसान बनाते हैं। उदाहरण के लिए, यदि आप sqlalchemy.orm से संबंध आयात करते हैं और मदद (संबंध) करते हैं, तो यह आपको सभी विकल्प देगा जो आप कैस्केड के लिए निर्दिष्ट कर सकते हैं। बुलेट के लिए delete-orphanकहता है:

यदि किसी माता-पिता के साथ बच्चे के प्रकार का कोई आइटम नहीं मिला है, तो उसे हटाने के लिए चिह्नित करें।
ध्यान दें कि यह विकल्प बच्चे के वर्ग के एक लंबित आइटम को बिना माता-पिता के मौजूद रहने से रोकता है।

मुझे पता है कि जिस तरह से माता-पिता-बच्चे के संबंधों को परिभाषित करने के लिए प्रलेखन था, उससे आपका मुद्दा अधिक था। लेकिन ऐसा लग रहा था कि आपको झरना विकल्पों के साथ भी समस्या हो सकती है, क्योंकि "all"इसमें शामिल हैं "delete""delete-orphan"एकमात्र विकल्प है जो इसमें शामिल नहीं है "all"


वस्तुओं help(..)पर उपयोग करने से sqlalchemyबहुत मदद मिलती है! धन्यवाद :-))) ! PyCharm संदर्भ में कुछ भी नहीं दिखाता है, और स्पष्ट रूप से जांच करना भूल गया help। आपका बहुत बहुत धन्यवाद!
dmitry_romanov

5

स्टीवन का जवाब ठोस है। मैं एक अतिरिक्त निहितार्थ बताना चाहता हूँ।

उपयोग करके relationship, आप एप्लिकेशन परत (फ्लास्क) को संदर्भात्मक अखंडता के लिए जिम्मेदार बना रहे हैं। इसका मतलब है कि अन्य प्रक्रियाएं जो डेटाबेस तक पहुंच प्राप्त करती हैं, फ्लास्क के माध्यम से नहीं, जैसे डेटाबेस उपयोगिता या सीधे डेटाबेस से जुड़ने वाला व्यक्ति, उन बाधाओं का अनुभव नहीं करेगा और आपके डेटा को इस तरह से बदल सकता है कि आपके द्वारा डिजाइन किए गए तार्किक डेटा मॉडल को तोड़ देता है ।

जब भी संभव हो, ForeignKeyd512 और एलेक्स द्वारा वर्णित दृष्टिकोण का उपयोग करें । DB इंजन सही मायने में बाधाओं (एक अपरिहार्य तरीके से) को लागू करने में बहुत अच्छा है, इसलिए यह डेटा अखंडता बनाए रखने के लिए अब तक की सबसे अच्छी रणनीति है। डेटा की अखंडता को संभालने के लिए आपको किसी ऐप पर भरोसा करने की आवश्यकता केवल उसी समय होती है जब डेटाबेस उन्हें संभाल नहीं सकता है, जैसे कि SQLite के संस्करण जो विदेशी कुंजी का समर्थन नहीं करते हैं।

यदि आपको माता-पिता-बाल वस्तु संबंधों को नेविगेट करने जैसे एप्लिकेशन व्यवहार को सक्षम करने के लिए संस्थाओं के बीच और अधिक संबंध बनाने की आवश्यकता है, तो इसके backrefसाथ संयोजन के रूप में उपयोग करें ForeignKey


2

स्टेवन द्वारा उत्तर एकदम सही है। लेकिन अगर आपको अभी भी त्रुटि हो रही है। शीर्ष पर अन्य संभव प्रयास होगा -

http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/

लिंक से कॉपी किया गया-

यदि आप अपने मॉडलों में कैस्केड हटाने को निर्दिष्ट करते हैं तो भी त्वरित टिप यदि आप एक विदेशी कुंजी निर्भरता के साथ परेशानी में पड़ते हैं।

SQLAlchemy का उपयोग करते हुए, एक कैस्केड हटाने को निर्दिष्ट करने के लिए आपको cascade='all, delete'अपनी मूल तालिका पर होना चाहिए । ठीक है, लेकिन तब जब आप कुछ ऐसा निष्पादित करते हैं:

session.query(models.yourmodule.YourParentTable).filter(conditions).delete()

यह वास्तव में आपके बच्चों की तालिकाओं में प्रयुक्त एक विदेशी कुंजी के बारे में एक त्रुटि को ट्रिगर करता है।

इसका उपयोग मैंने ऑब्जेक्ट को क्वेरी करने और फिर उसे हटाने के लिए किया:

session = models.DBSession()
your_db_object = session.query(models.yourmodule.YourParentTable).filter(conditions).first()
if your_db_object is not None:
    session.delete(your_db_object)

यह आपके माता-पिता के रिकॉर्ड और इससे जुड़े सभी बच्चों को हटा देना चाहिए।


1
क्या कॉल .first()करना आवश्यक है? क्या फ़िल्टर स्थितियाँ वस्तुओं की सूची लौटाती हैं, और सब कुछ हटाना पड़ता है? क्या कॉलिंग .first()केवल पहली वस्तु नहीं है? @Prashant
राजू एस

2

एलेक्स ओक्रुस्को जवाब ने लगभग मेरे लिए सबसे अच्छा काम किया। ऑनडेलीट = 'कैस्केड' और पैसिव_डेलेट्स का उपयोग किया गया = सही संयुक्त। लेकिन मुझे साइक्लाइट के लिए काम करने के लिए कुछ अतिरिक्त करना पड़ा।

Base = declarative_base()
ROOM_TABLE = "roomdata"
FURNITURE_TABLE = "furnituredata"

class DBFurniture(Base):
    __tablename__ = FURNITURE_TABLE
    id = Column(Integer, primary_key=True)
    room_id = Column(Integer, ForeignKey('roomdata.id', ondelete='CASCADE'))


class DBRoom(Base):
    __tablename__ = ROOM_TABLE
    id = Column(Integer, primary_key=True)
    furniture = relationship("DBFurniture", backref="room", passive_deletes=True)

यह सुनिश्चित करने के लिए इस कोड को जोड़ना सुनिश्चित करें कि यह sqlite के लिए काम करता है।

from sqlalchemy import event
from sqlalchemy.engine import Engine
from sqlite3 import Connection as SQLite3Connection

@event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
    if isinstance(dbapi_connection, SQLite3Connection):
        cursor = dbapi_connection.cursor()
        cursor.execute("PRAGMA foreign_keys=ON;")
        cursor.close()

यहां से चोरी: SQLAlchemy अभिव्यक्ति भाषा और SQLite के डिलीट कैस्केड पर


0

TLDR: यदि उपरोक्त समाधान काम नहीं करते हैं, तो अपने कॉलम में अशक्त = गलत जोड़ने का प्रयास करें।

मैं कुछ लोगों के लिए यहां एक छोटा बिंदु जोड़ना चाहूंगा, जिन्हें मौजूदा समाधानों (जो बहुत अच्छे हैं) के साथ काम करने के लिए झरना फ़ंक्शन नहीं मिल सकता है। मेरे काम और उदाहरण के बीच मुख्य अंतर यह था कि मैंने ऑटोमैप का इस्तेमाल किया। मुझे नहीं पता कि कैस्केड के सेटअप में हस्तक्षेप कैसे हो सकता है, लेकिन मैं यह नोट करना चाहता हूं कि मैंने इसका इस्तेमाल किया। मैं एक SQLite डेटाबेस के साथ भी काम कर रहा हूं।

मैंने यहां बताए गए हर समाधान की कोशिश की, लेकिन पैरेंट पंक्ति को हटाए जाने पर मेरी चाइल्ड टेबल में पंक्तियों को उनके विदेशी कुंजी सेट को शून्य करने के लिए जारी रखा गया। मैं कोई फायदा नहीं हुआ यहाँ सभी समाधान की कोशिश की। हालांकि, झरना ने एक बार काम किया जब मैंने बच्चे की कॉलम को विदेशी कुंजी के साथ अशक्त करने के लिए सेट किया था = गलत।

बच्चे की मेज पर, मैंने जोड़ा:

Column('parent_id', Integer(), ForeignKey('parent.id', ondelete="CASCADE"), nullable=False)
Child.parent = relationship("parent", backref=backref("children", passive_deletes=True)

इस सेटअप के साथ, झरना अपेक्षित रूप से कार्य करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.