Reputation: 21513
We have been stumped with this all morning.
I have an MSSQL stored proc which processes some records. Each record generates an email with dynamic content, and this is populated from another stored proc.
So the first stored proc has a cursor, and each row is processed resulting in a call to another stored proc, which itself has a cursor to loop through. The first stored proc has no output parameters or returned values, etc, while the second uses output parameters to return fields to the first proc.
This works fine from Datagrip.
Calling this from php using PDO (or using other drivers) it fails to run fully. It would produce a small batch of records then stop (tends to be either 5, 9, 13 or 45 - has changed as we have experimented with difference solutions).
We have managed to get it to run now using PDOStatement::nextRowset . We use a query of the first stored proc and then using while ( $stmt->nextRowset() ) ;
to loop through the (non existent / unwanted) row sets.
This works. But as the first stored proc isn't returning anything (just that pdo seems to want to process some kind of internal result set) this seems very dirty and inefficient.
Is there an alternative? Possibly a parameter to pass to pdo, or a setting within the stored proc?
Below there is some simplified code to show how things interact.
PHP calling script.
<?php
$emailRepository = new EmailRepository(hostname, port, dbname, username, password);
$ret = $emailRepository->sendRenewalEmails();
class EmailRepository
{
public function __construct($hostname, $port, $dbname, $username, $password)
{
$this->hostname = $hostname;
$this->port = $port;
$this->dbname = $dbname;
$this->username = $username;
$this->password = $password;
$this->connect();
}
private function connect()
{
try {
$this->db = new PDO ("sqlsrv:Server=$this->hostname,$this->port;Database=$this->dbname", $this->username, $this->password);
$this->db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
} catch (PDOException $e) {
var_dump("Failed to get DB handle: " . $e->getMessage());
}
}
public function sendRenewalEmails()
{
try {
$stmt = $this->db->prepare("EXEC membership_queue_renewal_emails");
$stmt->execute();
do {
echo '>';
} while($stmt->nextRowset());
return true;
} catch (Exception $e) {
echo $e->getMessage();
}
}
}
First stored proc (heavily cut down)
CREATE PROCEDURE [dbo].[queue_renewal_emails]
AS
BEGIN
DECLARE @curr_member_cursor CURSOR;
DECLARE @curr_club_cursor CURSOR;
DECLARE @g_personid INT;
DECLARE @g_emailTemplateId INT;
DECLARE @g_email_subject VARCHAR(200);
DECLARE @g_email_html VARCHAR(max);
DECLARE @g_email_plain VARCHAR(max);
DECLARE @g_personEmail VARCHAR(128);
SET @curr_club_cursor = CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT DISTINCT
bgemailTemplate.bgemte_name,
bgemailTemplate.bgemte_emailtemplateid,
vpersonpe.pers_emailaddress,
vpersonpe.pers_personId,
FROM company WITH(NOLOCK)
INNER JOIN complink WITH(NOLOCK) ON complink.clli_companyid = complink.Comp_CompanyId
AND complink.clli_Deleted is null
INNER JOIN vpersonpe WITH(NOLOCK) ON vpersonpe.pers_personId = complink.clli_personId
INNER JOIN bgemailTemplate WITH(NOLOCK) ON bgemailTemplate.bgemte_Deleted IS NULL
WHERE vPersonPE.pers_deleted IS NULL
AND company.comp_deleted IS NULL
AND vPersonPE.pers_parentid IS NULL
AND vpersonpe.pers_status NOT IN ('Cancelled','Expired','Suspended','Awaiting Approval','Declined','On hold','Revoked','Expelled');
-- loop through each course
OPEN @curr_club_cursor;
FETCH NEXT FROM @curr_club_cursor INTO @g_email_subject, @g_emailTemplateId, @g_personEmail, @g_personid;
WHILE @@fetch_status = 0
BEGIN
EXEC dbo.populateEmail @g_emailtemplateid /* Email template id */,
@g_email_plain OUTPUT /* Plain text email to have the placeholders replaced */,
@g_email_subject OUTPUT,
@g_personid ;
FETCH NEXT FROM @curr_club_cursor INTO @g_email_subject, @g_emailTemplateId, @g_personEmail, @g_personid;
END
CLOSE @curr_club_cursor
DEALLOCATE @curr_club_cursor
END
go
Second stored proc (heavily cut down).
CREATE PROCEDURE [dbo].[populateEmail]
@p_emailtemplateid INT,
@p_email_text VARCHAR(max) OUTPUT,
@p_email_subject VARCHAR(200) OUTPUT,
@p_person_id INT
AS
BEGIN
SET NOCOUNT ON;
-- CURSORs
DECLARE @curr_field_cursor CURSOR;
DECLARE @g_email_plain VARCHAR(MAX) = '';
DECLARE @g_email_subject VARCHAR(200) = '';
DECLARE @g_emte_emailtemplateid INT;
DECLARE @g_EmailPlaceholderId INT;
DECLARE @g_place_holder VARCHAR(128);
DECLARE @g_source_column VARCHAR(128);
DECLARE @g_prev_source_query_num INT;
DECLARE @g_source_query_num INT;
-- Variables to read results into from each query
DECLARE @g_q11_comp_name VARCHAR(180);
DECLARE @g_q11_comp_website VARCHAR(300);
DECLARE @g_q11_comp_pers_salutation VARCHAR(30);
DECLARE @g_q11_comp_pers_firstname VARCHAR(90);
DECLARE @g_q11_comp_pers_lastname VARCHAR(120);
-- Start processing
SET @p_email_text = '';
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET @curr_field_cursor = CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT a.emte_emailtemplateid,
b.emtl_EmailPlaceholderId,
c.empl_PlaceHolder,
c.empl_SourceQueryNum,
c.empl_SourceColumn,
a.emte_plaintextemail,
a.emte_subject
FROM EmailTemplate a with (nolock)
LEFT OUTER JOIN EmailTemplateLink b with (nolock)
ON a.emte_emailtemplateid = b.emtl_EmailTemplateId
LEFT OUTER JOIN EmailPlaceholder c with (nolock)
ON b.emtl_EmailPlaceholderId = c.empl_EmailPlaceholderID
WHERE a.emte_emailtemplateid = @p_emailtemplateid
ORDER BY c.empl_SourceQueryNum;
-- Loop through each required place holder for the passed email template.
SET @g_prev_source_query_num = 0;
OPEN @curr_field_cursor
FETCH NEXT FROM @curr_field_cursor INTO @g_emte_emailtemplateid, @g_EmailPlaceholderId, @g_place_holder, @g_source_query_num, @g_source_column, @g_email_plain, @g_email_subject;
WHILE @@fetch_status = 0
BEGIN
IF @g_prev_source_query_num = 0
BEGIN
SET @p_email_text = @g_email_plain;
SET @p_email_subject = @g_email_subject;
END;
IF @g_source_query_num = 11
BEGIN
IF @g_prev_source_query_num != @g_source_query_num
BEGIN
SELECT @g_q11_comp_name = comp_name,
@g_q11_comp_website = comp_website,
@g_q11_comp_pers_salutation = Pers_Salutation,
@g_q11_comp_pers_firstname = pers_firstname,
@g_q11_comp_pers_lastname = pers_lastname
FROM company with (nolock)
LEFT OUTER JOIN vPerson with (nolock) ON company.Comp_PrimaryPersonId = vPerson.Pers_PersonId
LEFT OUTER JOIN address with (nolock) ON company.Comp_PrimaryAddressId = address.Addr_AddressId
WHERE company.Comp_CompanyId = @p_person_id;
END;
IF @g_source_column = 'comp_name'
BEGIN
SET @p_email_text = REPLACE(@p_email_text, @g_place_holder, COALESCE(@g_q11_comp_name, ''));
SET @p_email_subject = REPLACE(@p_email_subject, @g_place_holder, COALESCE(@g_q11_comp_name, ''));
END;
ELSE IF @g_source_column = 'comp_website'
BEGIN
SET @p_email_text = REPLACE(@p_email_text, @g_place_holder, COALESCE(@g_q11_comp_website, ''));
SET @p_email_subject = REPLACE(@p_email_subject, @g_place_holder, COALESCE(@g_q11_comp_website, ''));
END;
ELSE IF @g_source_column = 'comp_primary_person_firstname'
BEGIN
SET @p_email_text = REPLACE(@p_email_text, @g_place_holder, COALESCE(@g_q11_comp_pers_salutation, ''));
SET @p_email_subject = REPLACE(@p_email_subject, @g_place_holder, COALESCE(@g_q11_comp_pers_salutation, ''));
END;
ELSE IF @g_source_column = 'comp_primary_person_salutation'
BEGIN
SET @p_email_text = REPLACE(@p_email_text, @g_place_holder, COALESCE(@g_q11_comp_pers_firstname, ''));
SET @p_email_subject = REPLACE(@p_email_subject, @g_place_holder, COALESCE(@g_q11_comp_pers_firstname, ''));
END;
ELSE IF @g_source_column = 'comp_primary_person_lastname'
BEGIN
SET @p_email_text = REPLACE(@p_email_text, @g_place_holder, COALESCE(@g_q11_comp_pers_lastname, ''));
SET @p_email_subject = REPLACE(@p_email_subject, @g_place_holder, COALESCE(@g_q11_comp_pers_lastname, ''));
END;
END;
SET @g_prev_source_query_num = @g_source_query_num;
FETCH NEXT FROM @curr_field_cursor INTO @g_emte_emailtemplateid, @g_EmailPlaceholderId, @g_place_holder, @g_source_query_num, @g_source_column, @g_email_plain, @g_email_subject;
END;
CLOSE @curr_field_cursor
DEALLOCATE @curr_field_cursor
END
go
Upvotes: 3
Views: 61
Reputation: 89091
as the first stored proc isn't returning anything ... is there an alternative to
while ( $stmt->nextRowset() ) ;
I'ts probably the rowcount messages causing the client to see empty rowsets. Add SET NOCOUNT ON
as the first line of each stored procedure.
Upvotes: 4