There are many reasons for almost every table to have a primary key.  This articles UDF locates any user tables that don’t have primary keys. Here’s the CREATE FUNCTION script:

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
CREATE FUNCTION dbo.udf_Tbl_PKeyMissingTAB () RETURNS TABLE
/*
* Returns names of tables that do not have a primary key.
*
* Example:
select * from udf_Tbl_PKeyMissingTAB()
*
* © Copyright 2004 Andrew Novick https://novicksoftware.com
* You may use this function in any of your SQL Server databases
* including databases that you sell, so long as they contain
* other unrelated database objects. You may not publish this
* UDF either in print or electronically.
* Published as T-SQL UDF of Week Newsletter Vol 2 #39 9/28/04
https://novicksoftware.com/UDFofWeek/UDFofWeek.htm
****************************************************************/
AS RETURN

SELECT [Name]
FROM SysObjects
WHERE xtype = ‘U’
AND 0=OBJECTPROPERTY(id, ‘IsMsShipped’)
AND [Name] NOT IN (SELECT DISTINCT o.[Name]
From [SysObjects] o
inner join SysIndexes i
on o.[ID] = i.[ID]
inner join [SysColumns] c
on o.[ID] = c.[ID]
Where (i.Status & 0x800) = 0x800
)

GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

GRANT SELECT ON dbo.udf_Tbl_PKeyMissingTAB to PUBLIC
GO

The select statement is pretty straightforward. It looks for entries in sysobjects that are tables and thus has xtype = ‘U’. It also requires that they’re not part of SQL Server by testing that the OBJECTPROPERTY IsMsShipped is 0, that is, it didn’t come from Microsoft. Finally, it checks that there isn’t a primary key.

Here’s the results from:

select * from udf_Tbl_PKeyMissingTAB()
go

Results:

Name
A Table to Show the Length of MS_Description
AddrCountryCD
ADHOC_SS2K_RawKeywordList
ADHOC_XE_com list of ISO 4217 currency codes
AppAdHocTable
AppMetadataColumn
AppMetadataDomain
AppMetadataStoredProcedure
AppMetadataTable
AppMetadataView
Broker
CharacterNames
DUAL
Example_Road_Analysis_Section
ExampleDataTypes
ExampleFloatData
ExampleNumberString
ExampleRoadLength
ExampleSales
ExampleTableShowingOwnershipConflict
ExampleTableShowingOwnershipConflict
ExampleTableWithKeywordColumnNames
ExampleTableWithNoIndex
ExampleTableWithOneColumn
ExampleTableWithoutDescriptions
ExampleTableWithoutDescriptions
ExchangeMembership
ISO3166Codes
ISO3166CodesWith3Char
NewTestTable
PhoneRawAreaCodeMaster
PhoneRawInternationalcodes
qsvsales_sum$
SQLKeywordList
SQLPermissionTester
SQLTableWithAllPermissions
SQLTableWithDeletePermission
SQLTableWithDRIPermission
SQLTableWithInsertPermission
SQLTableWithNoPermissions
SQLTableWithSelectPermission
SQLTableWithUpdatePermission
SystemStatsStartTime
tblRplayerCountry
TEST_IDENTITY
TESTTRACE
TimeZone
TSQLFunctionGlossary
ViewCounts

The results are from the database that I used when writing Transact-SQL User-Defined Functions. It contains all sorts of experimental tables that don’t warrant primary keys but in real application databases don’t allow things to get so out of hand. I’d either give the table a primary key or, if it wasn’t used get rid of it.